mirror of
https://github.com/tooot-app/app
synced 2025-05-20 21:44:20 +02:00
Merge branch 'main' into candidate
This commit is contained in:
commit
9f70cef94f
7
.github/workflows/build.yml
vendored
7
.github/workflows/build.yml
vendored
@ -39,6 +39,7 @@ jobs:
|
|||||||
APP_STORE_CONNECT_API_KEY_KEY_ID: ${{ secrets.APP_STORE_CONNECT_API_KEY_KEY_ID }}
|
APP_STORE_CONNECT_API_KEY_KEY_ID: ${{ secrets.APP_STORE_CONNECT_API_KEY_KEY_ID }}
|
||||||
APP_STORE_CONNECT_API_KEY_ISSUER_ID: ${{ secrets.APP_STORE_CONNECT_API_KEY_ISSUER_ID }}
|
APP_STORE_CONNECT_API_KEY_ISSUER_ID: ${{ secrets.APP_STORE_CONNECT_API_KEY_ISSUER_ID }}
|
||||||
APP_STORE_CONNECT_API_KEY_KEY: ${{ secrets.APP_STORE_CONNECT_API_KEY_KEY }}
|
APP_STORE_CONNECT_API_KEY_KEY: ${{ secrets.APP_STORE_CONNECT_API_KEY_KEY }}
|
||||||
|
GH_PAT_GET_RELEASE: ${{ secrets.GITHUB_TOKEN }}
|
||||||
run: yarn app:build ios
|
run: yarn app:build ios
|
||||||
|
|
||||||
build-android:
|
build-android:
|
||||||
@ -73,6 +74,7 @@ jobs:
|
|||||||
ANDROID_KEYSTORE_ALIAS: ${{ secrets.ANDROID_KEYSTORE_ALIAS }}
|
ANDROID_KEYSTORE_ALIAS: ${{ secrets.ANDROID_KEYSTORE_ALIAS }}
|
||||||
ANDROID_KEYSTORE_KEY_PASSWORD: ${{ secrets.ANDROID_KEYSTORE_KEY_PASSWORD }}
|
ANDROID_KEYSTORE_KEY_PASSWORD: ${{ secrets.ANDROID_KEYSTORE_KEY_PASSWORD }}
|
||||||
SUPPLY_JSON_KEY_DATA: ${{ secrets.SUPPLY_JSON_KEY_DATA }}
|
SUPPLY_JSON_KEY_DATA: ${{ secrets.SUPPLY_JSON_KEY_DATA }}
|
||||||
|
GH_PAT_GET_RELEASE: ${{ secrets.GITHUB_TOKEN }}
|
||||||
run: yarn app:build android
|
run: yarn app:build android
|
||||||
|
|
||||||
create-release:
|
create-release:
|
||||||
@ -108,10 +110,13 @@ jobs:
|
|||||||
ENVIRONMENT: ${{ steps.branch.outputs.branch }}
|
ENVIRONMENT: ${{ steps.branch.outputs.branch }}
|
||||||
LC_ALL: en_US.UTF-8
|
LC_ALL: en_US.UTF-8
|
||||||
LANG: en_US.UTF-8
|
LANG: en_US.UTF-8
|
||||||
|
SENTRY_ORGANIZATION: ${{ secrets.SENTRY_ORGANIZATION }}
|
||||||
|
SENTRY_PROJECT: ${{ secrets.SENTRY_PROJECT }}
|
||||||
|
SENTRY_AUTH_TOKEN: ${{ secrets.SENTRY_AUTH_TOKEN }}
|
||||||
ANDROID_KEYSTORE: ${{ secrets.ANDROID_KEYSTORE }}
|
ANDROID_KEYSTORE: ${{ secrets.ANDROID_KEYSTORE }}
|
||||||
ANDROID_KEYSTORE_PASSWORD: ${{ secrets.ANDROID_KEYSTORE_PASSWORD }}
|
ANDROID_KEYSTORE_PASSWORD: ${{ secrets.ANDROID_KEYSTORE_PASSWORD }}
|
||||||
ANDROID_KEYSTORE_ALIAS: ${{ secrets.ANDROID_KEYSTORE_ALIAS }}
|
ANDROID_KEYSTORE_ALIAS: ${{ secrets.ANDROID_KEYSTORE_ALIAS }}
|
||||||
ANDROID_KEYSTORE_KEY_PASSWORD: ${{ secrets.ANDROID_KEYSTORE_KEY_PASSWORD }}
|
ANDROID_KEYSTORE_KEY_PASSWORD: ${{ secrets.ANDROID_KEYSTORE_KEY_PASSWORD }}
|
||||||
GH_PAT_GET_RELEASE: ${{ secrets.GH_PAT_GET_RELEASE }}
|
GH_PAT_GET_RELEASE: ${{ secrets.GITHUB_TOKEN }}
|
||||||
FL_GITHUB_RELEASE_API_BEARER: ${{ secrets.GITHUB_TOKEN }}
|
FL_GITHUB_RELEASE_API_BEARER: ${{ secrets.GITHUB_TOKEN }}
|
||||||
run: yarn app:build release
|
run: yarn app:build release
|
||||||
|
1
.gitignore
vendored
1
.gitignore
vendored
@ -31,6 +31,7 @@ DerivedData
|
|||||||
*.ipa
|
*.ipa
|
||||||
*.xcuserstate
|
*.xcuserstate
|
||||||
project.xcworkspace
|
project.xcworkspace
|
||||||
|
ios/.xcode.env.local
|
||||||
|
|
||||||
# Android/IntelliJ
|
# Android/IntelliJ
|
||||||
#
|
#
|
||||||
|
3
.vscode/settings.json
vendored
Normal file
3
.vscode/settings.json
vendored
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
{
|
||||||
|
"javascript.inlayHints.functionLikeReturnTypes.enabled": false
|
||||||
|
}
|
@ -149,7 +149,7 @@ private_lane :build_android_apk do
|
|||||||
end
|
end
|
||||||
|
|
||||||
lane :ios do
|
lane :ios do
|
||||||
releaseExists = get_github_release(url: GITHUB_REPO, version: "v#{VERSION}", api_token: ENV['GH_PAT_GET_RELEASE'])
|
releaseExists = get_github_release(url: GITHUB_REPO, version: "v#{VERSION}", api_bearer: ENV['GH_PAT_GET_RELEASE'])
|
||||||
if releaseExists
|
if releaseExists
|
||||||
puts("Release #{GITHUB_RELEASE} exists. Continue with building React Native only.")
|
puts("Release #{GITHUB_RELEASE} exists. Continue with building React Native only.")
|
||||||
else
|
else
|
||||||
@ -161,7 +161,7 @@ lane :ios do
|
|||||||
end
|
end
|
||||||
|
|
||||||
lane :android do
|
lane :android do
|
||||||
releaseExists = get_github_release(url: GITHUB_REPO, version: "v#{VERSION}", api_token: ENV['GH_PAT_GET_RELEASE'])
|
releaseExists = get_github_release(url: GITHUB_REPO, version: "v#{VERSION}", api_bearer: ENV['GH_PAT_GET_RELEASE'])
|
||||||
if releaseExists
|
if releaseExists
|
||||||
puts("Release #{GITHUB_RELEASE} exists. Continue with building React Native only.")
|
puts("Release #{GITHUB_RELEASE} exists. Continue with building React Native only.")
|
||||||
else
|
else
|
||||||
@ -172,7 +172,7 @@ lane :android do
|
|||||||
end
|
end
|
||||||
|
|
||||||
lane :release do
|
lane :release do
|
||||||
releaseExists = get_github_release(url: GITHUB_REPO, version: "v#{VERSION}", api_token: ENV['GH_PAT_GET_RELEASE'])
|
releaseExists = get_github_release(url: GITHUB_REPO, version: "v#{VERSION}", api_bearer: ENV['GH_PAT_GET_RELEASE'])
|
||||||
if !releaseExists
|
if !releaseExists
|
||||||
build_android_apk
|
build_android_apk
|
||||||
set_github_release(
|
set_github_release(
|
||||||
|
10
ios/.xcode.env
Normal file
10
ios/.xcode.env
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
# This `.xcode.env` file is versioned and is used to source the environment
|
||||||
|
# used when running script phases inside Xcode.
|
||||||
|
# To customize your local environment, you can create an `.xcode.env.local`
|
||||||
|
# file that is not versioned.
|
||||||
|
# NODE_BINARY variable contains the PATH to the node executable.
|
||||||
|
#
|
||||||
|
# Customize the NODE_BINARY variable here.
|
||||||
|
# For example, to use nvm with brew, add the following line
|
||||||
|
# . "$(brew --prefix nvm)/nvm.sh" --no-use
|
||||||
|
export NODE_BINARY=$(command -v node)
|
32
ios/Podfile
32
ios/Podfile
@ -2,22 +2,16 @@ require File.join(File.dirname(`node --print "require.resolve('expo/package.json
|
|||||||
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/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")
|
require File.join(File.dirname(`node --print "require.resolve('@react-native-community/cli-platform-ios/package.json')"`), "native_modules")
|
||||||
|
|
||||||
platform :ios, '12.0'
|
platform :ios, '12.4'
|
||||||
install! 'cocoapods', :deterministic_uuids => false
|
install! 'cocoapods', :deterministic_uuids => false
|
||||||
|
|
||||||
|
production = ENV["PRODUCTION"] == "1"
|
||||||
|
|
||||||
require 'json'
|
require 'json'
|
||||||
podfile_properties = JSON.parse(File.read('./Podfile.properties.json')) rescue {}
|
podfile_properties = JSON.parse(File.read('./Podfile.properties.json')) rescue {}
|
||||||
|
|
||||||
target 'tooot' do
|
target 'tooot' do
|
||||||
use_expo_modules!
|
use_expo_modules!
|
||||||
post_integrate do |installer|
|
|
||||||
begin
|
|
||||||
expo_patch_react_imports!(installer)
|
|
||||||
rescue => e
|
|
||||||
Pod::UI.warn e
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
config = use_native_modules!
|
config = use_native_modules!
|
||||||
|
|
||||||
# Flags change depending on the env values.
|
# Flags change depending on the env values.
|
||||||
@ -25,27 +19,33 @@ target 'tooot' do
|
|||||||
|
|
||||||
use_react_native!(
|
use_react_native!(
|
||||||
:path => config[:reactNativePath],
|
:path => config[:reactNativePath],
|
||||||
|
:production => production,
|
||||||
:hermes_enabled => podfile_properties['expo.jsEngine'] == 'hermes',
|
:hermes_enabled => podfile_properties['expo.jsEngine'] == 'hermes',
|
||||||
:fabric_enabled => flags[:fabric_enabled],
|
:fabric_enabled => flags[:fabric_enabled],
|
||||||
|
:flipper_configuration => FlipperConfiguration.enabled,
|
||||||
# An absolute path to your application root.
|
# An absolute path to your application root.
|
||||||
:app_path => "#{Pod::Config.instance.installation_root}/.."
|
:app_path => "#{Pod::Config.instance.installation_root}/.."
|
||||||
)
|
)
|
||||||
|
|
||||||
# Enables Flipper.
|
|
||||||
#
|
|
||||||
# Note that if you have use_frameworks! enabled, Flipper will not work and
|
|
||||||
# you should disable the next line.
|
|
||||||
# use_flipper!()
|
|
||||||
|
|
||||||
post_install do |installer|
|
post_install do |installer|
|
||||||
react_native_post_install(installer)
|
react_native_post_install(installer)
|
||||||
|
__apply_Xcode_12_5_M1_post_install_workaround(installer)
|
||||||
|
|
||||||
|
# For share extension
|
||||||
installer.pods_project.targets.each do |target|
|
installer.pods_project.targets.each do |target|
|
||||||
target.build_configurations.each do |config|
|
target.build_configurations.each do |config|
|
||||||
config.build_settings['APPLICATION_EXTENSION_API_ONLY'] = 'NO'
|
config.build_settings['APPLICATION_EXTENSION_API_ONLY'] = 'No'
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
post_integrate do |installer|
|
||||||
|
begin
|
||||||
|
expo_patch_react_imports!(installer)
|
||||||
|
rescue => e
|
||||||
|
Pod::UI.warn e
|
||||||
|
end
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
target 'ShareExtension' do
|
target 'ShareExtension' do
|
||||||
|
880
ios/Podfile.lock
880
ios/Podfile.lock
File diff suppressed because it is too large
Load Diff
@ -72,7 +72,7 @@
|
|||||||
AA286B85B6C04FC6940260E9 /* SplashScreen.storyboard */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.storyboard; name = SplashScreen.storyboard; path = tooot/SplashScreen.storyboard; sourceTree = "<group>"; };
|
AA286B85B6C04FC6940260E9 /* SplashScreen.storyboard */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.storyboard; name = SplashScreen.storyboard; path = tooot/SplashScreen.storyboard; sourceTree = "<group>"; };
|
||||||
B96B72E5384D44A7B240B27E /* GoogleService-Info.plist */ = {isa = PBXFileReference; explicitFileType = undefined; fileEncoding = 4; includeInIndex = 0; lastKnownFileType = text.plist.xml; name = "GoogleService-Info.plist"; path = "tooot/GoogleService-Info.plist"; sourceTree = "<group>"; };
|
B96B72E5384D44A7B240B27E /* GoogleService-Info.plist */ = {isa = PBXFileReference; explicitFileType = undefined; fileEncoding = 4; includeInIndex = 0; lastKnownFileType = text.plist.xml; name = "GoogleService-Info.plist"; path = "tooot/GoogleService-Info.plist"; sourceTree = "<group>"; };
|
||||||
BB2F792C24A3F905000567C9 /* Expo.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = Expo.plist; sourceTree = "<group>"; };
|
BB2F792C24A3F905000567C9 /* Expo.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = Expo.plist; sourceTree = "<group>"; };
|
||||||
DF8133F098604A10B0D94952 /* boop.mp3 */ = {isa = PBXFileReference; explicitFileType = undefined; fileEncoding = undefined; includeInIndex = 0; lastKnownFileType = unknown; name = boop.mp3; path = tooot/boop.mp3; sourceTree = "<group>"; };
|
DF8133F098604A10B0D94952 /* boop.mp3 */ = {isa = PBXFileReference; explicitFileType = undefined; fileEncoding = 9; includeInIndex = 0; lastKnownFileType = unknown; name = boop.mp3; path = tooot/boop.mp3; sourceTree = "<group>"; };
|
||||||
E613A80A28282A01003C97D6 /* AppDelegate.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; name = AppDelegate.mm; path = tooot/AppDelegate.mm; sourceTree = "<group>"; };
|
E613A80A28282A01003C97D6 /* AppDelegate.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; name = AppDelegate.mm; path = tooot/AppDelegate.mm; sourceTree = "<group>"; };
|
||||||
E633A420281EAEAB000E540F /* ShareExtension.appex */ = {isa = PBXFileReference; explicitFileType = "wrapper.app-extension"; includeInIndex = 0; path = ShareExtension.appex; sourceTree = BUILT_PRODUCTS_DIR; };
|
E633A420281EAEAB000E540F /* ShareExtension.appex */ = {isa = PBXFileReference; explicitFileType = "wrapper.app-extension"; includeInIndex = 0; path = ShareExtension.appex; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||||
E633A427281EAEAB000E540F /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
|
E633A427281EAEAB000E540F /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
|
||||||
@ -333,13 +333,15 @@
|
|||||||
files = (
|
files = (
|
||||||
);
|
);
|
||||||
inputPaths = (
|
inputPaths = (
|
||||||
|
"$(SRCROOT)/.xcode.env.local",
|
||||||
|
"$(SRCROOT)/.xcode.env",
|
||||||
);
|
);
|
||||||
name = "Bundle React Native code and images";
|
name = "Bundle React Native code and images";
|
||||||
outputPaths = (
|
outputPaths = (
|
||||||
);
|
);
|
||||||
runOnlyForDeploymentPostprocessing = 0;
|
runOnlyForDeploymentPostprocessing = 0;
|
||||||
shellPath = /bin/sh;
|
shellPath = /bin/sh;
|
||||||
shellScript = "export NODE_BINARY=node\n../node_modules/react-native/scripts/react-native-xcode.sh\n../node_modules/expo-constants/scripts/get-app-config-ios.sh\n";
|
shellScript = "set -e\n\nWITH_ENVIRONMENT=\"../node_modules/react-native/scripts/xcode/with-environment.sh\"\nREACT_NATIVE_XCODE=\"../node_modules/react-native/scripts/react-native-xcode.sh\"\n\n/bin/sh -c \"$WITH_ENVIRONMENT $REACT_NATIVE_XCODE\"\n";
|
||||||
};
|
};
|
||||||
08A4A3CD28434E44B6B9DE2E /* [CP] Check Pods Manifest.lock */ = {
|
08A4A3CD28434E44B6B9DE2E /* [CP] Check Pods Manifest.lock */ = {
|
||||||
isa = PBXShellScriptBuildPhase;
|
isa = PBXShellScriptBuildPhase;
|
||||||
@ -370,10 +372,16 @@
|
|||||||
);
|
);
|
||||||
inputPaths = (
|
inputPaths = (
|
||||||
"${PODS_ROOT}/Target Support Files/Pods-tooot/Pods-tooot-frameworks.sh",
|
"${PODS_ROOT}/Target Support Files/Pods-tooot/Pods-tooot-frameworks.sh",
|
||||||
|
"${PODS_XCFRAMEWORKS_BUILD_DIR}/Flipper-DoubleConversion/double-conversion.framework/double-conversion",
|
||||||
|
"${PODS_XCFRAMEWORKS_BUILD_DIR}/Flipper-Glog/glog.framework/glog",
|
||||||
|
"${PODS_XCFRAMEWORKS_BUILD_DIR}/OpenSSL-Universal/OpenSSL.framework/OpenSSL",
|
||||||
"${PODS_XCFRAMEWORKS_BUILD_DIR}/hermes-engine/hermes.framework/hermes",
|
"${PODS_XCFRAMEWORKS_BUILD_DIR}/hermes-engine/hermes.framework/hermes",
|
||||||
);
|
);
|
||||||
name = "[CP] Embed Pods Frameworks";
|
name = "[CP] Embed Pods Frameworks";
|
||||||
outputPaths = (
|
outputPaths = (
|
||||||
|
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/double-conversion.framework",
|
||||||
|
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/glog.framework",
|
||||||
|
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/OpenSSL.framework",
|
||||||
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/hermes.framework",
|
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/hermes.framework",
|
||||||
);
|
);
|
||||||
runOnlyForDeploymentPostprocessing = 0;
|
runOnlyForDeploymentPostprocessing = 0;
|
||||||
@ -390,17 +398,13 @@
|
|||||||
"${PODS_ROOT}/Target Support Files/Pods-tooot/Pods-tooot-resources.sh",
|
"${PODS_ROOT}/Target Support Files/Pods-tooot/Pods-tooot-resources.sh",
|
||||||
"${PODS_CONFIGURATION_BUILD_DIR}/EXConstants/EXConstants.bundle",
|
"${PODS_CONFIGURATION_BUILD_DIR}/EXConstants/EXConstants.bundle",
|
||||||
"${PODS_CONFIGURATION_BUILD_DIR}/EXUpdates/EXUpdates.bundle",
|
"${PODS_CONFIGURATION_BUILD_DIR}/EXUpdates/EXUpdates.bundle",
|
||||||
"${PODS_CONFIGURATION_BUILD_DIR}/RNImageCropPicker/QBImagePicker.bundle",
|
|
||||||
"${PODS_CONFIGURATION_BUILD_DIR}/React-Core/AccessibilityResources.bundle",
|
"${PODS_CONFIGURATION_BUILD_DIR}/React-Core/AccessibilityResources.bundle",
|
||||||
"${PODS_CONFIGURATION_BUILD_DIR}/TOCropViewController/TOCropViewControllerBundle.bundle",
|
|
||||||
);
|
);
|
||||||
name = "[CP] Copy Pods Resources";
|
name = "[CP] Copy Pods Resources";
|
||||||
outputPaths = (
|
outputPaths = (
|
||||||
"${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/EXConstants.bundle",
|
"${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/EXConstants.bundle",
|
||||||
"${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/EXUpdates.bundle",
|
"${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/EXUpdates.bundle",
|
||||||
"${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/QBImagePicker.bundle",
|
|
||||||
"${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/AccessibilityResources.bundle",
|
"${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/AccessibilityResources.bundle",
|
||||||
"${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/TOCropViewControllerBundle.bundle",
|
|
||||||
);
|
);
|
||||||
runOnlyForDeploymentPostprocessing = 0;
|
runOnlyForDeploymentPostprocessing = 0;
|
||||||
shellPath = /bin/sh;
|
shellPath = /bin/sh;
|
||||||
@ -524,7 +528,6 @@
|
|||||||
isa = XCBuildConfiguration;
|
isa = XCBuildConfiguration;
|
||||||
baseConfigurationReference = 6C2E3173556A471DD304B334 /* Pods-tooot.debug.xcconfig */;
|
baseConfigurationReference = 6C2E3173556A471DD304B334 /* Pods-tooot.debug.xcconfig */;
|
||||||
buildSettings = {
|
buildSettings = {
|
||||||
ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES;
|
|
||||||
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
||||||
CLANG_ENABLE_MODULES = YES;
|
CLANG_ENABLE_MODULES = YES;
|
||||||
CODE_SIGN_ENTITLEMENTS = tooot/tooot.entitlements;
|
CODE_SIGN_ENTITLEMENTS = tooot/tooot.entitlements;
|
||||||
@ -533,13 +536,12 @@
|
|||||||
CURRENT_PROJECT_VERSION = 2102022230;
|
CURRENT_PROJECT_VERSION = 2102022230;
|
||||||
DEVELOPMENT_TEAM = 8EGBLQ2MA6;
|
DEVELOPMENT_TEAM = 8EGBLQ2MA6;
|
||||||
ENABLE_BITCODE = NO;
|
ENABLE_BITCODE = NO;
|
||||||
"EXCLUDED_ARCHS[sdk=iphonesimulator*]" = arm64;
|
|
||||||
GCC_PREPROCESSOR_DEFINITIONS = (
|
GCC_PREPROCESSOR_DEFINITIONS = (
|
||||||
"$(inherited)",
|
"$(inherited)",
|
||||||
"FB_SONARKIT_ENABLED=1",
|
"FB_SONARKIT_ENABLED=1",
|
||||||
);
|
);
|
||||||
INFOPLIST_FILE = tooot/Info.plist;
|
INFOPLIST_FILE = tooot/Info.plist;
|
||||||
IPHONEOS_DEPLOYMENT_TARGET = 12.0;
|
IPHONEOS_DEPLOYMENT_TARGET = 12.4;
|
||||||
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
|
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
|
||||||
OTHER_LDFLAGS = (
|
OTHER_LDFLAGS = (
|
||||||
"$(inherited)",
|
"$(inherited)",
|
||||||
@ -564,7 +566,6 @@
|
|||||||
isa = XCBuildConfiguration;
|
isa = XCBuildConfiguration;
|
||||||
baseConfigurationReference = 7A4D352CD337FB3A3BF06240 /* Pods-tooot.release.xcconfig */;
|
baseConfigurationReference = 7A4D352CD337FB3A3BF06240 /* Pods-tooot.release.xcconfig */;
|
||||||
buildSettings = {
|
buildSettings = {
|
||||||
ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES;
|
|
||||||
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
||||||
CLANG_ENABLE_MODULES = YES;
|
CLANG_ENABLE_MODULES = YES;
|
||||||
CODE_SIGN_ENTITLEMENTS = tooot/tooot.entitlements;
|
CODE_SIGN_ENTITLEMENTS = tooot/tooot.entitlements;
|
||||||
@ -572,9 +573,8 @@
|
|||||||
CODE_SIGN_STYLE = Manual;
|
CODE_SIGN_STYLE = Manual;
|
||||||
CURRENT_PROJECT_VERSION = 2102022230;
|
CURRENT_PROJECT_VERSION = 2102022230;
|
||||||
DEVELOPMENT_TEAM = 8EGBLQ2MA6;
|
DEVELOPMENT_TEAM = 8EGBLQ2MA6;
|
||||||
"EXCLUDED_ARCHS[sdk=iphonesimulator*]" = arm64;
|
|
||||||
INFOPLIST_FILE = tooot/Info.plist;
|
INFOPLIST_FILE = tooot/Info.plist;
|
||||||
IPHONEOS_DEPLOYMENT_TARGET = 12.0;
|
IPHONEOS_DEPLOYMENT_TARGET = 12.4;
|
||||||
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
|
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
|
||||||
OTHER_LDFLAGS = (
|
OTHER_LDFLAGS = (
|
||||||
"$(inherited)",
|
"$(inherited)",
|
||||||
@ -599,7 +599,7 @@
|
|||||||
buildSettings = {
|
buildSettings = {
|
||||||
ALWAYS_SEARCH_USER_PATHS = NO;
|
ALWAYS_SEARCH_USER_PATHS = NO;
|
||||||
CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES;
|
CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES;
|
||||||
CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
|
CLANG_CXX_LANGUAGE_STANDARD = "c++0x";
|
||||||
CLANG_CXX_LIBRARY = "libc++";
|
CLANG_CXX_LIBRARY = "libc++";
|
||||||
CLANG_ENABLE_MODULES = YES;
|
CLANG_ENABLE_MODULES = YES;
|
||||||
CLANG_ENABLE_OBJC_ARC = YES;
|
CLANG_ENABLE_OBJC_ARC = YES;
|
||||||
@ -644,15 +644,12 @@
|
|||||||
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
|
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
|
||||||
GCC_WARN_UNUSED_FUNCTION = YES;
|
GCC_WARN_UNUSED_FUNCTION = YES;
|
||||||
GCC_WARN_UNUSED_VARIABLE = YES;
|
GCC_WARN_UNUSED_VARIABLE = YES;
|
||||||
IPHONEOS_DEPLOYMENT_TARGET = 12.0;
|
IPHONEOS_DEPLOYMENT_TARGET = 12.4;
|
||||||
LD_RUNPATH_SEARCH_PATHS = "/usr/lib/swift $(inherited)";
|
LD_RUNPATH_SEARCH_PATHS = "/usr/lib/swift $(inherited)";
|
||||||
LIBRARY_SEARCH_PATHS = (
|
LIBRARY_SEARCH_PATHS = "\"\"";
|
||||||
"$(SDKROOT)/usr/lib/swift",
|
|
||||||
"\"$(TOOLCHAIN_DIR)/usr/lib/swift/$(PLATFORM_NAME)\"",
|
|
||||||
"\"$(inherited)\"",
|
|
||||||
);
|
|
||||||
MTL_ENABLE_DEBUG_INFO = YES;
|
MTL_ENABLE_DEBUG_INFO = YES;
|
||||||
ONLY_ACTIVE_ARCH = YES;
|
ONLY_ACTIVE_ARCH = YES;
|
||||||
|
REACT_NATIVE_PATH = "${PODS_ROOT}/../../node_modules/react-native";
|
||||||
SDKROOT = iphoneos;
|
SDKROOT = iphoneos;
|
||||||
};
|
};
|
||||||
name = Debug;
|
name = Debug;
|
||||||
@ -662,7 +659,7 @@
|
|||||||
buildSettings = {
|
buildSettings = {
|
||||||
ALWAYS_SEARCH_USER_PATHS = NO;
|
ALWAYS_SEARCH_USER_PATHS = NO;
|
||||||
CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES;
|
CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES;
|
||||||
CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
|
CLANG_CXX_LANGUAGE_STANDARD = "c++0x";
|
||||||
CLANG_CXX_LIBRARY = "libc++";
|
CLANG_CXX_LIBRARY = "libc++";
|
||||||
CLANG_ENABLE_MODULES = YES;
|
CLANG_ENABLE_MODULES = YES;
|
||||||
CLANG_ENABLE_OBJC_ARC = YES;
|
CLANG_ENABLE_OBJC_ARC = YES;
|
||||||
@ -700,14 +697,12 @@
|
|||||||
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
|
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
|
||||||
GCC_WARN_UNUSED_FUNCTION = YES;
|
GCC_WARN_UNUSED_FUNCTION = YES;
|
||||||
GCC_WARN_UNUSED_VARIABLE = YES;
|
GCC_WARN_UNUSED_VARIABLE = YES;
|
||||||
IPHONEOS_DEPLOYMENT_TARGET = 12.0;
|
IPHONEOS_DEPLOYMENT_TARGET = 12.4;
|
||||||
LD_RUNPATH_SEARCH_PATHS = "/usr/lib/swift $(inherited)";
|
LD_RUNPATH_SEARCH_PATHS = "/usr/lib/swift $(inherited)";
|
||||||
LIBRARY_SEARCH_PATHS = (
|
LIBRARY_SEARCH_PATHS = "\"\"";
|
||||||
"$(SDKROOT)/usr/lib/swift",
|
|
||||||
"\"$(TOOLCHAIN_DIR)/usr/lib/swift/$(PLATFORM_NAME)\"",
|
|
||||||
"\"$(inherited)\"",
|
|
||||||
);
|
|
||||||
MTL_ENABLE_DEBUG_INFO = NO;
|
MTL_ENABLE_DEBUG_INFO = NO;
|
||||||
|
ONLY_ACTIVE_ARCH = NO;
|
||||||
|
REACT_NATIVE_PATH = "${PODS_ROOT}/../../node_modules/react-native";
|
||||||
SDKROOT = iphoneos;
|
SDKROOT = iphoneos;
|
||||||
SWIFT_COMPILATION_MODE = wholemodule;
|
SWIFT_COMPILATION_MODE = wholemodule;
|
||||||
VALIDATE_PRODUCT = YES;
|
VALIDATE_PRODUCT = YES;
|
||||||
@ -736,7 +731,7 @@
|
|||||||
INFOPLIST_FILE = ShareExtension/Info.plist;
|
INFOPLIST_FILE = ShareExtension/Info.plist;
|
||||||
INFOPLIST_KEY_CFBundleDisplayName = ShareExtension;
|
INFOPLIST_KEY_CFBundleDisplayName = ShareExtension;
|
||||||
INFOPLIST_KEY_NSHumanReadableCopyright = "";
|
INFOPLIST_KEY_NSHumanReadableCopyright = "";
|
||||||
IPHONEOS_DEPLOYMENT_TARGET = 12.0;
|
IPHONEOS_DEPLOYMENT_TARGET = 12.4;
|
||||||
"IPHONEOS_DEPLOYMENT_TARGET[sdk=macosx*]" = 14.2;
|
"IPHONEOS_DEPLOYMENT_TARGET[sdk=macosx*]" = 14.2;
|
||||||
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @executable_path/../../Frameworks";
|
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @executable_path/../../Frameworks";
|
||||||
MARKETING_VERSION = 1.0;
|
MARKETING_VERSION = 1.0;
|
||||||
@ -781,7 +776,7 @@
|
|||||||
INFOPLIST_FILE = ShareExtension/Info.plist;
|
INFOPLIST_FILE = ShareExtension/Info.plist;
|
||||||
INFOPLIST_KEY_CFBundleDisplayName = ShareExtension;
|
INFOPLIST_KEY_CFBundleDisplayName = ShareExtension;
|
||||||
INFOPLIST_KEY_NSHumanReadableCopyright = "";
|
INFOPLIST_KEY_NSHumanReadableCopyright = "";
|
||||||
IPHONEOS_DEPLOYMENT_TARGET = 12.0;
|
IPHONEOS_DEPLOYMENT_TARGET = 12.4;
|
||||||
"IPHONEOS_DEPLOYMENT_TARGET[sdk=macosx*]" = 14.2;
|
"IPHONEOS_DEPLOYMENT_TARGET[sdk=macosx*]" = 14.2;
|
||||||
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @executable_path/../../Frameworks";
|
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @executable_path/../../Frameworks";
|
||||||
MARKETING_VERSION = 1.0;
|
MARKETING_VERSION = 1.0;
|
||||||
|
@ -19,6 +19,8 @@
|
|||||||
|
|
||||||
#import <react/config/ReactNativeConfig.h>
|
#import <react/config/ReactNativeConfig.h>
|
||||||
|
|
||||||
|
static NSString *const kRNConcurrentRoot = @"concurrentRoot";
|
||||||
|
|
||||||
@interface AppDelegate () <RCTCxxBridgeDelegate, RCTTurboModuleManagerDelegate> {
|
@interface AppDelegate () <RCTCxxBridgeDelegate, RCTTurboModuleManagerDelegate> {
|
||||||
RCTTurboModuleManager *_turboModuleManager;
|
RCTTurboModuleManager *_turboModuleManager;
|
||||||
RCTSurfacePresenterBridgeAdapter *_bridgeAdapter;
|
RCTSurfacePresenterBridgeAdapter *_bridgeAdapter;
|
||||||
@ -46,6 +48,9 @@
|
|||||||
|
|
||||||
UIView *rootView = [self.reactDelegate createRootViewWithBridge:bridge moduleName:@"main" initialProperties:nil];
|
UIView *rootView = [self.reactDelegate createRootViewWithBridge:bridge moduleName:@"main" initialProperties:nil];
|
||||||
|
|
||||||
|
// NSDictionary *initProps = [self prepareInitialProps];
|
||||||
|
// UIView *rootView = RCTAppSetupDefaultRootView(bridge, @"tooot", initProps);
|
||||||
|
|
||||||
if (@available(iOS 13.0, *)) {
|
if (@available(iOS 13.0, *)) {
|
||||||
rootView.backgroundColor = [UIColor colorNamed:@"SplashScreenBackgroundColor"];
|
rootView.backgroundColor = [UIColor colorNamed:@"SplashScreenBackgroundColor"];
|
||||||
} else {
|
} else {
|
||||||
@ -61,6 +66,25 @@
|
|||||||
return YES;
|
return YES;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// This method controls whether the `concurrentRoot`feature of React18 is turned on or off.
|
||||||
|
///
|
||||||
|
/// @see: https://reactjs.org/blog/2022/03/29/react-v18.html
|
||||||
|
/// @note: This requires to be rendering on Fabric (i.e. on the New Architecture).
|
||||||
|
/// @return: `true` if the `concurrentRoot` feture is enabled. Otherwise, it returns `false`.
|
||||||
|
- (BOOL)concurrentRootEnabled
|
||||||
|
{
|
||||||
|
// Switch this bool to turn on and off the concurrent root
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
- (NSDictionary *)prepareInitialProps
|
||||||
|
{
|
||||||
|
NSMutableDictionary *initProps = [NSMutableDictionary new];
|
||||||
|
#ifdef RCT_NEW_ARCH_ENABLED
|
||||||
|
initProps[kRNConcurrentRoot] = @([self concurrentRootEnabled]);
|
||||||
|
#endif
|
||||||
|
return initProps;
|
||||||
|
}
|
||||||
|
|
||||||
- (NSURL *)sourceURLForBridge:(RCTBridge *)bridge
|
- (NSURL *)sourceURLForBridge:(RCTBridge *)bridge
|
||||||
{
|
{
|
||||||
#if DEBUG
|
#if DEBUG
|
||||||
|
196
package.json
196
package.json
@ -1,11 +1,11 @@
|
|||||||
{
|
{
|
||||||
"name": "tooot",
|
"name": "tooot",
|
||||||
"versions": {
|
"versions": {
|
||||||
"native": "220603",
|
"native": "220806",
|
||||||
"major": 4,
|
"major": 4,
|
||||||
"minor": 1,
|
"minor": 3,
|
||||||
"patch": 0,
|
"patch": 0,
|
||||||
"expo": "45.0.0"
|
"expo": "46.0.0"
|
||||||
},
|
},
|
||||||
"description": "tooot app for Mastodon",
|
"description": "tooot app for Mastodon",
|
||||||
"author": "xmflsct <me@xmflsct.com>",
|
"author": "xmflsct <me@xmflsct.com>",
|
||||||
@ -25,112 +25,106 @@
|
|||||||
"postinstall": "patch-package"
|
"postinstall": "patch-package"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@expo/react-native-action-sheet": "3.13.0",
|
"@expo/react-native-action-sheet": "^3.13.0",
|
||||||
"@formatjs/intl-datetimeformat": "^6.0.2",
|
"@formatjs/intl-datetimeformat": "^6.0.3",
|
||||||
"@formatjs/intl-getcanonicallocales": "^2.0.2",
|
"@formatjs/intl-getcanonicallocales": "^2.0.2",
|
||||||
"@formatjs/intl-locale": "^3.0.2",
|
"@formatjs/intl-locale": "^3.0.3",
|
||||||
"@formatjs/intl-numberformat": "^8.0.2",
|
"@formatjs/intl-numberformat": "^8.0.4",
|
||||||
"@formatjs/intl-pluralrules": "^5.0.2",
|
"@formatjs/intl-pluralrules": "^5.0.3",
|
||||||
"@formatjs/intl-relativetimeformat": "^11.0.2",
|
"@formatjs/intl-relativetimeformat": "^11.0.3",
|
||||||
"@mattermost/react-native-paste-input": "^0.4.2",
|
"@mattermost/react-native-paste-input": "^0.5.0",
|
||||||
"@neverdull-agency/expo-unlimited-secure-store": "1.0.10",
|
"@neverdull-agency/expo-unlimited-secure-store": "^1.0.10",
|
||||||
"@react-native-async-storage/async-storage": "1.17.6",
|
"@react-native-async-storage/async-storage": "^1.17.7",
|
||||||
"@react-native-community/blur": "3.6.0",
|
"@react-native-community/blur": "^4.2.0",
|
||||||
"@react-native-community/cameraroll": "4.1.2",
|
"@react-native-community/cameraroll": "^4.1.2",
|
||||||
"@react-native-community/netinfo": "9.0.0",
|
"@react-native-community/netinfo": "^9.3.0",
|
||||||
"@react-native-community/segmented-control": "2.2.2",
|
"@react-native-community/segmented-control": "^2.2.2",
|
||||||
"@react-navigation/bottom-tabs": "6.3.1",
|
"@react-navigation/bottom-tabs": "^6.3.2",
|
||||||
"@react-navigation/native": "6.0.10",
|
"@react-navigation/native": "^6.0.11",
|
||||||
"@react-navigation/native-stack": "6.6.2",
|
"@react-navigation/native-stack": "^6.7.0",
|
||||||
"@react-navigation/stack": "6.2.1",
|
"@react-navigation/stack": "^6.2.2",
|
||||||
"@reduxjs/toolkit": "1.8.2",
|
"@reduxjs/toolkit": "^1.8.3",
|
||||||
"@sentry/react-native": "3.4.3",
|
"@sentry/react-native": "^4.2.2",
|
||||||
"@sharcoux/slider": "6.0.3",
|
"@sharcoux/slider": "^6.0.3",
|
||||||
"axios": "0.27.2",
|
"axios": "^0.27.2",
|
||||||
"expo": "45.0.5",
|
"expo": "^46.0.2",
|
||||||
"expo-auth-session": "3.6.1",
|
"expo-auth-session": "^3.7.1",
|
||||||
"expo-av": "11.2.3",
|
"expo-av": "^12.0.3",
|
||||||
"expo-constants": "^13.1.1",
|
"expo-constants": "^13.2.3",
|
||||||
"expo-crypto": "10.2.0",
|
"expo-crypto": "^11.0.0",
|
||||||
"expo-device": "4.2.0",
|
"expo-device": "^4.3.0",
|
||||||
"expo-file-system": "14.0.0",
|
"expo-file-system": "^14.1.0",
|
||||||
"expo-firebase-analytics": "7.0.0",
|
"expo-firebase-analytics": "^7.1.1",
|
||||||
"expo-haptics": "11.2.0",
|
"expo-haptics": "^11.3.0",
|
||||||
"expo-image-manipulator": "^10.3.1",
|
"expo-linking": "^3.2.2",
|
||||||
"expo-image-picker": "13.1.1",
|
"expo-localization": "^13.1.0",
|
||||||
"expo-linking": "3.1.0",
|
"expo-notifications": "^0.16.1",
|
||||||
"expo-localization": "13.0.0",
|
"expo-random": "^12.3.0",
|
||||||
"expo-notifications": "0.15.2",
|
"expo-screen-capture": "^4.3.0",
|
||||||
"expo-random": "12.2.0",
|
"expo-secure-store": "^11.3.0",
|
||||||
"expo-screen-capture": "4.2.0",
|
"expo-splash-screen": "^0.16.1",
|
||||||
"expo-secure-store": "11.2.0",
|
"expo-store-review": "^5.3.0",
|
||||||
"expo-splash-screen": "0.15.1",
|
"expo-updates": "^0.14.3",
|
||||||
"expo-store-review": "5.2.0",
|
"expo-video-thumbnails": "^6.4.0",
|
||||||
"expo-updates": "0.13.2",
|
"expo-web-browser": "^11.0.0",
|
||||||
"expo-video-thumbnails": "6.3.0",
|
"i18next": "^21.8.16",
|
||||||
"expo-web-browser": "10.2.1",
|
"li": "^1.3.0",
|
||||||
"i18next": "21.8.8",
|
"lodash": "^4.17.21",
|
||||||
"li": "1.3.0",
|
"react": "^18.2.0",
|
||||||
"lodash": "4.17.21",
|
"react-dom": "^18.2.0",
|
||||||
"react": "17.0.2",
|
"react-i18next": "^11.18.3",
|
||||||
"react-dom": "17.0.2",
|
"react-intl": "^6.0.5",
|
||||||
"react-i18next": "11.17.0",
|
"react-native": "^0.69.3",
|
||||||
"react-intl": "^6.0.4",
|
"react-native-animated-spinkit": "^1.5.2",
|
||||||
"react-native": "0.68.2",
|
|
||||||
"react-native-animated-spinkit": "1.5.2",
|
|
||||||
"react-native-base64": "^0.2.1",
|
"react-native-base64": "^0.2.1",
|
||||||
"react-native-blurhash": "1.1.10",
|
"react-native-blurhash": "^1.1.10",
|
||||||
"react-native-context-menu-view": "xmflsct/react-native-context-menu-view",
|
"react-native-context-menu-view": "xmflsct/react-native-context-menu-view",
|
||||||
"react-native-fast-image": "8.5.11",
|
"react-native-fast-image": "^8.5.11",
|
||||||
"react-native-feather": "1.1.2",
|
"react-native-feather": "^1.1.2",
|
||||||
"react-native-flash-message": "0.2.1",
|
"react-native-flash-message": "^0.3.1",
|
||||||
"react-native-gesture-handler": "2.4.2",
|
"react-native-gesture-handler": "^2.5.0",
|
||||||
"react-native-htmlview": "0.16.0",
|
"react-native-htmlview": "^0.16.0",
|
||||||
"react-native-image-crop-picker": "^0.37.3",
|
"react-native-image-picker": "^4.8.4",
|
||||||
"react-native-language-detection": "^0.1.0",
|
"react-native-language-detection": "^0.1.0",
|
||||||
"react-native-pager-view": "5.4.11",
|
"react-native-pager-view": "^5.4.25",
|
||||||
"react-native-reanimated": "2.8.0",
|
"react-native-reanimated": "^2.9.1",
|
||||||
"react-native-safe-area-context": "4.3.1",
|
"react-native-safe-area-context": "^4.3.1",
|
||||||
"react-native-screens": "3.13.1",
|
"react-native-screens": "^3.15.0",
|
||||||
"react-native-share-menu": "^5.0.5",
|
"react-native-share-menu": "^5.0.5",
|
||||||
"react-native-svg": "12.3.0",
|
"react-native-svg": "^12.4.3",
|
||||||
"react-native-swipe-list-view": "3.2.9",
|
"react-native-swipe-list-view": "^3.2.9",
|
||||||
"react-native-tab-view": "3.1.1",
|
"react-native-tab-view": "^3.1.1",
|
||||||
"react-query": "3.39.1",
|
"react-query": "^3.39.2",
|
||||||
"react-redux": "8.0.2",
|
"react-redux": "^8.0.2",
|
||||||
"redux-persist": "6.0.0",
|
"redux-persist": "^6.0.0",
|
||||||
"rn-placeholder": "3.0.3",
|
"rn-placeholder": "^3.0.3",
|
||||||
"sentry-expo": "4.2.0",
|
"sentry-expo": "^5.0.1",
|
||||||
"tslib": "2.4.0",
|
"tslib": "^2.4.0",
|
||||||
"valid-url": "1.0.9"
|
"valid-url": "^1.0.9"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@babel/core": "7.18.2",
|
"@babel/core": "^7.18.10",
|
||||||
"@babel/plugin-proposal-optional-chaining": "7.17.12",
|
"@babel/plugin-proposal-optional-chaining": "^7.18.9",
|
||||||
"@babel/preset-react": "^7.17.12",
|
"@babel/preset-react": "^7.18.6",
|
||||||
"@babel/preset-typescript": "7.17.12",
|
"@babel/preset-typescript": "^7.18.6",
|
||||||
"@expo/config": "6.0.24",
|
"@expo/config": "^7.0.0",
|
||||||
"@types/lodash": "4.14.182",
|
"@types/lodash": "^4.14.182",
|
||||||
"@types/react": "17.0.43",
|
"@types/react": "^18.0.15",
|
||||||
"@types/react-dom": "17.0.14",
|
"@types/react-dom": "^18.0.6",
|
||||||
"@types/react-native": "0.67.8",
|
"@types/react-native": "^0.69.5",
|
||||||
"@types/react-native-base64": "^0.2.0",
|
"@types/react-native-base64": "^0.2.0",
|
||||||
"@types/react-native-share-menu": "^5.0.2",
|
"@types/react-native-share-menu": "^5.0.2",
|
||||||
"@types/react-timeago": "4.1.3",
|
"@types/react-timeago": "^4.1.3",
|
||||||
"@types/valid-url": "1.0.3",
|
"@types/valid-url": "^1.0.3",
|
||||||
"@welldone-software/why-did-you-render": "7.0.1",
|
"@welldone-software/why-did-you-render": "^7.0.1",
|
||||||
"babel-plugin-module-resolver": "4.1.0",
|
"babel-plugin-module-resolver": "^4.1.0",
|
||||||
"babel-plugin-transform-remove-console": "6.9.4",
|
"babel-plugin-transform-remove-console": "^6.9.4",
|
||||||
"chalk": "4.1.2",
|
"chalk": "^4.1.2",
|
||||||
"dotenv": "16.0.1",
|
"dotenv": "^16.0.1",
|
||||||
"patch-package": "6.4.7",
|
"patch-package": "^6.4.7",
|
||||||
"postinstall-postinstall": "2.1.0",
|
"postinstall-postinstall": "^2.1.0",
|
||||||
"react-native-clean-project": "4.0.1",
|
"react-native-clean-project": "^4.0.1",
|
||||||
"typescript": "4.7.3"
|
"typescript": "^4.7.4"
|
||||||
},
|
|
||||||
"resolutions": {
|
|
||||||
"@types/react": "17.0.43",
|
|
||||||
"@types/react-dom": "17.0.14"
|
|
||||||
},
|
},
|
||||||
"expo": {
|
"expo": {
|
||||||
"autolinking": {
|
"autolinking": {
|
||||||
@ -154,4 +148,4 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
diff --git a/node_modules/react-native-fast-image/RNFastImage.podspec b/node_modules/react-native-fast-image/RNFastImage.podspec
|
diff --git a/node_modules/react-native-fast-image/RNFastImage.podspec b/node_modules/react-native-fast-image/RNFastImage.podspec
|
||||||
index db0fada..54d8d5b 100644
|
index db0fada..9379119 100644
|
||||||
--- a/node_modules/react-native-fast-image/RNFastImage.podspec
|
--- a/node_modules/react-native-fast-image/RNFastImage.podspec
|
||||||
+++ b/node_modules/react-native-fast-image/RNFastImage.podspec
|
+++ b/node_modules/react-native-fast-image/RNFastImage.podspec
|
||||||
@@ -16,6 +16,6 @@ Pod::Spec.new do |s|
|
@@ -16,6 +16,6 @@ Pod::Spec.new do |s|
|
||||||
@ -7,8 +7,9 @@ index db0fada..54d8d5b 100644
|
|||||||
|
|
||||||
s.dependency 'React-Core'
|
s.dependency 'React-Core'
|
||||||
- s.dependency 'SDWebImage', '~> 5.11.1'
|
- s.dependency 'SDWebImage', '~> 5.11.1'
|
||||||
+ s.dependency 'SDWebImage', '~> 5.12.5'
|
- s.dependency 'SDWebImageWebPCoder', '~> 0.8.4'
|
||||||
s.dependency 'SDWebImageWebPCoder', '~> 0.8.4'
|
+ s.dependency 'SDWebImage', '~> 5.13.2'
|
||||||
|
+ s.dependency 'SDWebImageWebPCoder', '~> 0.9.0'
|
||||||
end
|
end
|
||||||
diff --git a/node_modules/react-native-fast-image/android/build.gradle b/node_modules/react-native-fast-image/android/build.gradle
|
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
|
index 5b21cd5..19d82f8 100644
|
||||||
|
22
patches/react-native-htmlview+0.16.0.patch
Normal file
22
patches/react-native-htmlview+0.16.0.patch
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
diff --git a/node_modules/react-native-htmlview/HTMLView.js b/node_modules/react-native-htmlview/HTMLView.js
|
||||||
|
index 43f8b7e..728112b 100644
|
||||||
|
--- a/node_modules/react-native-htmlview/HTMLView.js
|
||||||
|
+++ b/node_modules/react-native-htmlview/HTMLView.js
|
||||||
|
@@ -1,7 +1,7 @@
|
||||||
|
import React, {PureComponent} from 'react';
|
||||||
|
import PropTypes from 'prop-types';
|
||||||
|
import htmlToElement from './htmlToElement';
|
||||||
|
-import {Linking, Platform, StyleSheet, View, ViewPropTypes} from 'react-native';
|
||||||
|
+import {Linking, Platform, StyleSheet, View} from 'react-native';
|
||||||
|
|
||||||
|
const boldStyle = {fontWeight: 'bold'};
|
||||||
|
const italicStyle = {fontStyle: 'italic'};
|
||||||
|
@@ -146,7 +146,7 @@ HtmlView.propTypes = {
|
||||||
|
renderNode: PropTypes.func,
|
||||||
|
RootComponent: PropTypes.func,
|
||||||
|
rootComponentProps: PropTypes.object,
|
||||||
|
- style: ViewPropTypes.style,
|
||||||
|
+ style: PropTypes.any,
|
||||||
|
stylesheet: PropTypes.object,
|
||||||
|
TextComponent: PropTypes.func,
|
||||||
|
textComponentProps: PropTypes.object,
|
59
src/App.tsx
59
src/App.tsx
@ -74,37 +74,6 @@ const App: React.FC = () => {
|
|||||||
}
|
}
|
||||||
}, [])
|
}, [])
|
||||||
|
|
||||||
const children = useCallback(
|
|
||||||
bootstrapped => {
|
|
||||||
log('log', 'App', 'bootstrapped')
|
|
||||||
if (bootstrapped) {
|
|
||||||
log('log', 'App', 'loading actual app :)')
|
|
||||||
const language = getSettingsLanguage(store.getState())
|
|
||||||
if (!language) {
|
|
||||||
store.dispatch(changeLanguage('en'))
|
|
||||||
i18n.changeLanguage('en')
|
|
||||||
} else {
|
|
||||||
i18n.changeLanguage(language)
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Sentry.Native.TouchEventBoundary>
|
|
||||||
<ActionSheetProvider>
|
|
||||||
<AccessibilityManager>
|
|
||||||
<ThemeManager>
|
|
||||||
<Screens localCorrupt={localCorrupt} />
|
|
||||||
</ThemeManager>
|
|
||||||
</AccessibilityManager>
|
|
||||||
</ActionSheetProvider>
|
|
||||||
</Sentry.Native.TouchEventBoundary>
|
|
||||||
)
|
|
||||||
} else {
|
|
||||||
return null
|
|
||||||
}
|
|
||||||
},
|
|
||||||
[localCorrupt]
|
|
||||||
)
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<GestureHandlerRootView style={{ flex: 1 }}>
|
<GestureHandlerRootView style={{ flex: 1 }}>
|
||||||
<QueryClientProvider client={queryClient}>
|
<QueryClientProvider client={queryClient}>
|
||||||
@ -112,7 +81,33 @@ const App: React.FC = () => {
|
|||||||
<PersistGate
|
<PersistGate
|
||||||
persistor={persistor}
|
persistor={persistor}
|
||||||
onBeforeLift={onBeforeLift}
|
onBeforeLift={onBeforeLift}
|
||||||
children={children}
|
children={bootstrapped => {
|
||||||
|
log('log', 'App', 'bootstrapped')
|
||||||
|
if (bootstrapped) {
|
||||||
|
log('log', 'App', 'loading actual app :)')
|
||||||
|
const language = getSettingsLanguage(store.getState())
|
||||||
|
if (!language) {
|
||||||
|
store.dispatch(changeLanguage('en'))
|
||||||
|
i18n.changeLanguage('en')
|
||||||
|
} else {
|
||||||
|
i18n.changeLanguage(language)
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Sentry.Native.TouchEventBoundary>
|
||||||
|
<ActionSheetProvider>
|
||||||
|
<AccessibilityManager>
|
||||||
|
<ThemeManager>
|
||||||
|
<Screens localCorrupt={localCorrupt} />
|
||||||
|
</ThemeManager>
|
||||||
|
</AccessibilityManager>
|
||||||
|
</ActionSheetProvider>
|
||||||
|
</Sentry.Native.TouchEventBoundary>
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
}}
|
||||||
/>
|
/>
|
||||||
</Provider>
|
</Provider>
|
||||||
</QueryClientProvider>
|
</QueryClientProvider>
|
||||||
|
@ -170,6 +170,7 @@ const Screens: React.FC<Props> = ({ localCorrupt }) => {
|
|||||||
}
|
}
|
||||||
| { data: string | string[]; mimeType: string }
|
| { data: string | string[]; mimeType: string }
|
||||||
) => {
|
) => {
|
||||||
|
console.log('item', item)
|
||||||
if (instanceActive < 0) {
|
if (instanceActive < 0) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@ -253,6 +254,7 @@ const Screens: React.FC<Props> = ({ localCorrupt }) => {
|
|||||||
if (!text && !media.length) {
|
if (!text && !media.length) {
|
||||||
return
|
return
|
||||||
} else {
|
} else {
|
||||||
|
console.log('media', media)
|
||||||
navigationRef.navigate('Screen-Compose', { type: 'share', text, media })
|
navigationRef.navigate('Screen-Compose', { type: 'share', text, media })
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@ -271,8 +273,9 @@ const Screens: React.FC<Props> = ({ localCorrupt }) => {
|
|||||||
return (
|
return (
|
||||||
<IntlProvider locale={i18n.language}>
|
<IntlProvider locale={i18n.language}>
|
||||||
<StatusBar
|
<StatusBar
|
||||||
{...(Platform.OS === 'ios' && {
|
backgroundColor={colors.backgroundDefault}
|
||||||
backgroundColor: colors.backgroundDefault
|
{...(Platform.OS === 'android' && {
|
||||||
|
barStyle: theme === 'light' ? 'dark-content' : 'light-content'
|
||||||
})}
|
})}
|
||||||
/>
|
/>
|
||||||
<NavigationContainer
|
<NavigationContainer
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
import analytics from '@components/analytics'
|
import analytics from '@components/analytics'
|
||||||
import { displayMessage } from '@components/Message'
|
import { displayMessage } from '@components/Message'
|
||||||
|
import { useRelationshipQuery } from '@utils/queryHooks/relationship'
|
||||||
import {
|
import {
|
||||||
MutationVarsTimelineUpdateAccountProperty,
|
MutationVarsTimelineUpdateAccountProperty,
|
||||||
QueryKeyTimeline,
|
QueryKeyTimeline,
|
||||||
@ -8,13 +9,13 @@ import {
|
|||||||
import { getInstanceAccount } from '@utils/slices/instancesSlice'
|
import { getInstanceAccount } from '@utils/slices/instancesSlice'
|
||||||
import { useTheme } from '@utils/styles/ThemeManager'
|
import { useTheme } from '@utils/styles/ThemeManager'
|
||||||
import { useTranslation } from 'react-i18next'
|
import { useTranslation } from 'react-i18next'
|
||||||
import { Platform } from 'react-native'
|
|
||||||
import { ContextMenuAction } from 'react-native-context-menu-view'
|
import { ContextMenuAction } from 'react-native-context-menu-view'
|
||||||
import { useQueryClient } from 'react-query'
|
import { useQueryClient } from 'react-query'
|
||||||
import { useSelector } from 'react-redux'
|
import { useSelector } from 'react-redux'
|
||||||
|
|
||||||
export interface Props {
|
export interface Props {
|
||||||
actions: ContextMenuAction[]
|
actions: ContextMenuAction[]
|
||||||
|
type: 'status' | 'account' // Do not need to fetch relationship in timeline
|
||||||
queryKey?: QueryKeyTimeline
|
queryKey?: QueryKeyTimeline
|
||||||
rootQueryKey?: QueryKeyTimeline
|
rootQueryKey?: QueryKeyTimeline
|
||||||
id: Mastodon.Account['id']
|
id: Mastodon.Account['id']
|
||||||
@ -22,6 +23,7 @@ export interface Props {
|
|||||||
|
|
||||||
const contextMenuAccount = ({
|
const contextMenuAccount = ({
|
||||||
actions,
|
actions,
|
||||||
|
type,
|
||||||
queryKey,
|
queryKey,
|
||||||
rootQueryKey,
|
rootQueryKey,
|
||||||
id: accountId
|
id: accountId
|
||||||
@ -32,12 +34,17 @@ const contextMenuAccount = ({
|
|||||||
const queryClient = useQueryClient()
|
const queryClient = useQueryClient()
|
||||||
const mutateion = useTimelineMutation({
|
const mutateion = useTimelineMutation({
|
||||||
onSuccess: (_, params) => {
|
onSuccess: (_, params) => {
|
||||||
|
queryClient.refetchQueries(['Relationship', { id: accountId }])
|
||||||
const theParams = params as MutationVarsTimelineUpdateAccountProperty
|
const theParams = params as MutationVarsTimelineUpdateAccountProperty
|
||||||
displayMessage({
|
displayMessage({
|
||||||
theme,
|
theme,
|
||||||
type: 'success',
|
type: 'success',
|
||||||
message: t('common:message.success.message', {
|
message: t('common:message.success.message', {
|
||||||
function: t(`account.${theParams.payload.property}.action`)
|
function: t(`account.${theParams.payload.property}.action`, {
|
||||||
|
...(typeof theParams.payload.currentValue === 'boolean' && {
|
||||||
|
context: theParams.payload.currentValue.toString()
|
||||||
|
})
|
||||||
|
})
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
@ -47,7 +54,11 @@ const contextMenuAccount = ({
|
|||||||
theme,
|
theme,
|
||||||
type: 'error',
|
type: 'error',
|
||||||
message: t('common:message.error.message', {
|
message: t('common:message.error.message', {
|
||||||
function: t(`account.${theParams.payload.property}.action`)
|
function: t(`account.${theParams.payload.property}.action`, {
|
||||||
|
...(typeof theParams.payload.currentValue === 'boolean' && {
|
||||||
|
context: theParams.payload.currentValue.toString()
|
||||||
|
})
|
||||||
|
})
|
||||||
}),
|
}),
|
||||||
...(err.status &&
|
...(err.status &&
|
||||||
typeof err.status === 'number' &&
|
typeof err.status === 'number' &&
|
||||||
@ -70,93 +81,70 @@ const contextMenuAccount = ({
|
|||||||
)
|
)
|
||||||
const ownAccount = instanceAccount?.id === accountId
|
const ownAccount = instanceAccount?.id === accountId
|
||||||
|
|
||||||
|
const { data: relationship } = useRelationshipQuery({
|
||||||
|
id: accountId,
|
||||||
|
options: { enabled: type === 'account' }
|
||||||
|
})
|
||||||
|
|
||||||
if (!ownAccount) {
|
if (!ownAccount) {
|
||||||
switch (Platform.OS) {
|
actions.push(
|
||||||
case 'ios':
|
{
|
||||||
actions.push({
|
id: 'account-mute',
|
||||||
id: 'account',
|
title: t('account.mute.action', {
|
||||||
title: t('account.title'),
|
context: (relationship?.muting || false).toString()
|
||||||
inlineChildren: true,
|
}),
|
||||||
actions: [
|
systemIcon: 'eye.slash'
|
||||||
{
|
},
|
||||||
id: 'account-mute',
|
{
|
||||||
title: t('account.mute.action'),
|
id: 'account-block',
|
||||||
systemIcon: 'eye.slash'
|
title: t('account.block.action', {
|
||||||
},
|
context: (relationship?.blocking || false).toString()
|
||||||
{
|
}),
|
||||||
id: 'account-block',
|
systemIcon: 'xmark.circle',
|
||||||
title: t('account.block.action'),
|
destructive: true
|
||||||
systemIcon: 'xmark.circle',
|
},
|
||||||
destructive: true
|
{
|
||||||
},
|
id: 'account-reports',
|
||||||
{
|
title: t('account.reports.action'),
|
||||||
id: 'account-reports',
|
systemIcon: 'flag',
|
||||||
title: t('account.reports.action'),
|
destructive: true
|
||||||
systemIcon: 'flag',
|
}
|
||||||
destructive: true
|
)
|
||||||
}
|
|
||||||
]
|
|
||||||
})
|
|
||||||
break
|
|
||||||
default:
|
|
||||||
actions.push(
|
|
||||||
{
|
|
||||||
id: 'account-mute',
|
|
||||||
title: t('account.mute.action'),
|
|
||||||
systemIcon: 'eye.slash'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 'account-block',
|
|
||||||
title: t('account.block.action'),
|
|
||||||
systemIcon: 'xmark.circle',
|
|
||||||
destructive: true
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 'account-reports',
|
|
||||||
title: t('account.reports.action'),
|
|
||||||
systemIcon: 'flag',
|
|
||||||
destructive: true
|
|
||||||
}
|
|
||||||
)
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return (id: string) => {
|
return (index: number) => {
|
||||||
switch (id) {
|
if (actions[index].id === 'account-mute') {
|
||||||
case 'account-mute':
|
analytics('timeline_shared_headeractions_account_mute_press', {
|
||||||
analytics('timeline_shared_headeractions_account_mute_press', {
|
page: queryKey && queryKey[1].page
|
||||||
page: queryKey && queryKey[1].page
|
})
|
||||||
})
|
mutateion.mutate({
|
||||||
mutateion.mutate({
|
type: 'updateAccountProperty',
|
||||||
type: 'updateAccountProperty',
|
queryKey,
|
||||||
queryKey,
|
id: accountId,
|
||||||
id: accountId,
|
payload: { property: 'mute', currentValue: relationship?.muting }
|
||||||
payload: { property: 'mute' }
|
})
|
||||||
})
|
}
|
||||||
break
|
if (actions[index].id === 'account-block') {
|
||||||
case 'account-block':
|
analytics('timeline_shared_headeractions_account_block_press', {
|
||||||
analytics('timeline_shared_headeractions_account_block_press', {
|
page: queryKey && queryKey[1].page
|
||||||
page: queryKey && queryKey[1].page
|
})
|
||||||
})
|
mutateion.mutate({
|
||||||
mutateion.mutate({
|
type: 'updateAccountProperty',
|
||||||
type: 'updateAccountProperty',
|
queryKey,
|
||||||
queryKey,
|
id: accountId,
|
||||||
id: accountId,
|
payload: { property: 'block', currentValue: relationship?.blocking }
|
||||||
payload: { property: 'block' }
|
})
|
||||||
})
|
}
|
||||||
break
|
if (actions[index].id === 'account-report') {
|
||||||
case 'account-report':
|
analytics('timeline_shared_headeractions_account_reports_press', {
|
||||||
analytics('timeline_shared_headeractions_account_reports_press', {
|
page: queryKey && queryKey[1].page
|
||||||
page: queryKey && queryKey[1].page
|
})
|
||||||
})
|
mutateion.mutate({
|
||||||
mutateion.mutate({
|
type: 'updateAccountProperty',
|
||||||
type: 'updateAccountProperty',
|
queryKey,
|
||||||
queryKey,
|
id: accountId,
|
||||||
id: accountId,
|
payload: { property: 'reports' }
|
||||||
payload: { property: 'reports' }
|
})
|
||||||
})
|
|
||||||
break
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -29,7 +29,7 @@ const contextMenuInstance = ({
|
|||||||
const { theme } = useTheme()
|
const { theme } = useTheme()
|
||||||
|
|
||||||
const currentInstance = useSelector(getInstanceUrl)
|
const currentInstance = useSelector(getInstanceUrl)
|
||||||
const instance = status.uri && status.uri.split(new RegExp(/\/\/(.*?)\//))[1]
|
const instance = status?.uri && status.uri.split(new RegExp(/\/\/(.*?)\//))[1]
|
||||||
|
|
||||||
const queryClient = useQueryClient()
|
const queryClient = useQueryClient()
|
||||||
const mutation = useTimelineMutation({
|
const mutation = useTimelineMutation({
|
||||||
@ -71,36 +71,38 @@ const contextMenuInstance = ({
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return (id: string) => {
|
return (index: number) => {
|
||||||
switch (id) {
|
if (
|
||||||
case 'instance-block':
|
actions[index].id === 'instance-block' ||
|
||||||
analytics('timeline_shared_headeractions_domain_block_press', {
|
(actions[index].id === 'instance' &&
|
||||||
page: queryKey[1].page
|
actions[index].actions?.[0].id === 'instance-block')
|
||||||
})
|
) {
|
||||||
Alert.alert(
|
analytics('timeline_shared_headeractions_domain_block_press', {
|
||||||
t('instance.block.alert.title', { instance }),
|
page: queryKey[1].page
|
||||||
t('instance.block.alert.message'),
|
})
|
||||||
[
|
Alert.alert(
|
||||||
{
|
t('instance.block.alert.title', { instance }),
|
||||||
text: t('instance.block.alert.buttons.confirm'),
|
t('instance.block.alert.message'),
|
||||||
style: 'destructive',
|
[
|
||||||
onPress: () => {
|
{
|
||||||
analytics(
|
text: t('instance.block.alert.buttons.confirm'),
|
||||||
'timeline_shared_headeractions_domain_block_confirm',
|
style: 'destructive',
|
||||||
{ page: queryKey && queryKey[1].page }
|
onPress: () => {
|
||||||
)
|
analytics('timeline_shared_headeractions_domain_block_confirm', {
|
||||||
mutation.mutate({
|
page: queryKey && queryKey[1].page
|
||||||
type: 'domainBlock',
|
})
|
||||||
queryKey,
|
mutation.mutate({
|
||||||
domain: instance
|
type: 'domainBlock',
|
||||||
})
|
queryKey,
|
||||||
}
|
domain: instance
|
||||||
},
|
})
|
||||||
{
|
|
||||||
text: t('common:buttons.cancel')
|
|
||||||
}
|
}
|
||||||
]
|
},
|
||||||
)
|
{
|
||||||
|
text: t('common:buttons.cancel')
|
||||||
|
}
|
||||||
|
]
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -18,19 +18,17 @@ const contextMenuShare = ({ actions, type, url }: Props) => {
|
|||||||
systemIcon: 'square.and.arrow.up'
|
systemIcon: 'square.and.arrow.up'
|
||||||
})
|
})
|
||||||
|
|
||||||
return (id: string) => {
|
return (index: number) => {
|
||||||
switch (id) {
|
if (actions[index].id === 'share') {
|
||||||
case 'share':
|
analytics('timeline_shared_headeractions_share_press')
|
||||||
analytics('timeline_shared_headeractions_share_press')
|
switch (Platform.OS) {
|
||||||
switch (Platform.OS) {
|
case 'ios':
|
||||||
case 'ios':
|
Share.share({ url })
|
||||||
Share.share({ url })
|
break
|
||||||
break
|
case 'android':
|
||||||
case 'android':
|
Share.share({ message: url })
|
||||||
Share.share({ message: url })
|
break
|
||||||
break
|
}
|
||||||
}
|
|
||||||
break
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -15,7 +15,7 @@ import {
|
|||||||
} from '@utils/slices/instancesSlice'
|
} from '@utils/slices/instancesSlice'
|
||||||
import { useTheme } from '@utils/styles/ThemeManager'
|
import { useTheme } from '@utils/styles/ThemeManager'
|
||||||
import { useTranslation } from 'react-i18next'
|
import { useTranslation } from 'react-i18next'
|
||||||
import { Alert, Platform } from 'react-native'
|
import { Alert } from 'react-native'
|
||||||
import { ContextMenuAction } from 'react-native-context-menu-view'
|
import { ContextMenuAction } from 'react-native-context-menu-view'
|
||||||
import { useQueryClient } from 'react-query'
|
import { useQueryClient } from 'react-query'
|
||||||
import { useSelector } from 'react-redux'
|
import { useSelector } from 'react-redux'
|
||||||
@ -70,7 +70,7 @@ const contextMenuStatus = ({
|
|||||||
getInstanceAccount,
|
getInstanceAccount,
|
||||||
(prev, next) => prev.id === next.id
|
(prev, next) => prev.id === next.id
|
||||||
)
|
)
|
||||||
const ownAccount = instanceAccount?.id === status.account.id
|
const ownAccount = instanceAccount?.id === status?.account.id
|
||||||
|
|
||||||
if (ownAccount) {
|
if (ownAccount) {
|
||||||
const accountMenuItems: ContextMenuAction[] = [
|
const accountMenuItems: ContextMenuAction[] = [
|
||||||
@ -88,7 +88,7 @@ const contextMenuStatus = ({
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: 'status-mute',
|
id: 'status-mute',
|
||||||
title: t('status.mute.action-muted', {
|
title: t('status.mute.action', {
|
||||||
context: status.muted.toString()
|
context: status.muted.toString()
|
||||||
}),
|
}),
|
||||||
systemIcon: status.muted ? 'speaker' : 'speaker.slash'
|
systemIcon: status.muted ? 'speaker' : 'speaker.slash'
|
||||||
@ -107,178 +107,161 @@ const contextMenuStatus = ({
|
|||||||
if (status.visibility === 'public' || status.visibility === 'unlisted') {
|
if (status.visibility === 'public' || status.visibility === 'unlisted') {
|
||||||
accountMenuItems.push({
|
accountMenuItems.push({
|
||||||
id: 'status-pin',
|
id: 'status-pin',
|
||||||
title: t('status.pin.action-pinned', {
|
title: t('status.pin.action', {
|
||||||
context: status.pinned.toString()
|
context: status.pinned.toString()
|
||||||
}),
|
}),
|
||||||
systemIcon: status.pinned ? 'pin.slash' : 'pin'
|
systemIcon: status.pinned ? 'pin.slash' : 'pin'
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
switch (Platform.OS) {
|
actions.push(...accountMenuItems)
|
||||||
case 'ios':
|
|
||||||
actions.push({
|
|
||||||
id: 'status',
|
|
||||||
title: t('status.title'),
|
|
||||||
inlineChildren: true,
|
|
||||||
actions: accountMenuItems
|
|
||||||
})
|
|
||||||
break
|
|
||||||
default:
|
|
||||||
actions.push(...accountMenuItems)
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return async (id: string) => {
|
return async (index: number) => {
|
||||||
switch (id) {
|
if (actions[index].id === 'status-delete') {
|
||||||
case 'status-delete':
|
analytics('timeline_shared_headeractions_status_delete_press', {
|
||||||
analytics('timeline_shared_headeractions_status_delete_press', {
|
page: queryKey && queryKey[1].page
|
||||||
page: queryKey && queryKey[1].page
|
})
|
||||||
})
|
Alert.alert(
|
||||||
Alert.alert(
|
t('status.delete.alert.title'),
|
||||||
t('status.delete.alert.title'),
|
t('status.delete.alert.message'),
|
||||||
t('status.delete.alert.message'),
|
[
|
||||||
[
|
{
|
||||||
{
|
text: t('status.delete.alert.buttons.confirm'),
|
||||||
text: t('status.delete.alert.buttons.confirm'),
|
style: 'destructive',
|
||||||
style: 'destructive',
|
onPress: async () => {
|
||||||
onPress: async () => {
|
analytics('timeline_shared_headeractions_status_delete_confirm', {
|
||||||
analytics(
|
page: queryKey && queryKey[1].page
|
||||||
'timeline_shared_headeractions_status_delete_confirm',
|
})
|
||||||
{
|
mutation.mutate({
|
||||||
page: queryKey && queryKey[1].page
|
type: 'deleteItem',
|
||||||
}
|
source: 'statuses',
|
||||||
)
|
queryKey,
|
||||||
mutation.mutate({
|
rootQueryKey,
|
||||||
|
id: status.id
|
||||||
|
})
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
text: t('common:buttons.cancel')
|
||||||
|
}
|
||||||
|
]
|
||||||
|
)
|
||||||
|
}
|
||||||
|
if (actions[index].id === 'status-delete-edit') {
|
||||||
|
analytics('timeline_shared_headeractions_status_deleteedit_press', {
|
||||||
|
page: queryKey && queryKey[1].page
|
||||||
|
})
|
||||||
|
Alert.alert(
|
||||||
|
t('status.deleteEdit.alert.title'),
|
||||||
|
t('status.deleteEdit.alert.message'),
|
||||||
|
[
|
||||||
|
{
|
||||||
|
text: t('status.deleteEdit.alert.buttons.confirm'),
|
||||||
|
style: 'destructive',
|
||||||
|
onPress: async () => {
|
||||||
|
analytics(
|
||||||
|
'timeline_shared_headeractions_status_deleteedit_confirm',
|
||||||
|
{
|
||||||
|
page: queryKey && queryKey[1].page
|
||||||
|
}
|
||||||
|
)
|
||||||
|
let replyToStatus: Mastodon.Status | undefined = undefined
|
||||||
|
if (status.in_reply_to_id) {
|
||||||
|
replyToStatus = await apiInstance<Mastodon.Status>({
|
||||||
|
method: 'get',
|
||||||
|
url: `statuses/${status.in_reply_to_id}`
|
||||||
|
}).then(res => res.body)
|
||||||
|
}
|
||||||
|
mutation
|
||||||
|
.mutateAsync({
|
||||||
type: 'deleteItem',
|
type: 'deleteItem',
|
||||||
source: 'statuses',
|
source: 'statuses',
|
||||||
queryKey,
|
queryKey,
|
||||||
rootQueryKey,
|
|
||||||
id: status.id
|
id: status.id
|
||||||
})
|
})
|
||||||
}
|
.then(res => {
|
||||||
},
|
navigation.navigate('Screen-Compose', {
|
||||||
{
|
type: 'deleteEdit',
|
||||||
text: t('common:buttons.cancel')
|
incomingStatus: res.body as Mastodon.Status,
|
||||||
}
|
...(replyToStatus && { replyToStatus }),
|
||||||
]
|
queryKey
|
||||||
)
|
|
||||||
break
|
|
||||||
case 'status-delete-edit':
|
|
||||||
analytics('timeline_shared_headeractions_status_deleteedit_press', {
|
|
||||||
page: queryKey && queryKey[1].page
|
|
||||||
})
|
|
||||||
Alert.alert(
|
|
||||||
t('status.deleteEdit.alert.title'),
|
|
||||||
t('status.deleteEdit.alert.message'),
|
|
||||||
[
|
|
||||||
{
|
|
||||||
text: t('status.deleteEdit.alert.buttons.confirm'),
|
|
||||||
style: 'destructive',
|
|
||||||
onPress: async () => {
|
|
||||||
analytics(
|
|
||||||
'timeline_shared_headeractions_status_deleteedit_confirm',
|
|
||||||
{
|
|
||||||
page: queryKey && queryKey[1].page
|
|
||||||
}
|
|
||||||
)
|
|
||||||
let replyToStatus: Mastodon.Status | undefined = undefined
|
|
||||||
if (status.in_reply_to_id) {
|
|
||||||
replyToStatus = await apiInstance<Mastodon.Status>({
|
|
||||||
method: 'get',
|
|
||||||
url: `statuses/${status.in_reply_to_id}`
|
|
||||||
}).then(res => res.body)
|
|
||||||
}
|
|
||||||
mutation
|
|
||||||
.mutateAsync({
|
|
||||||
type: 'deleteItem',
|
|
||||||
source: 'statuses',
|
|
||||||
queryKey,
|
|
||||||
id: status.id
|
|
||||||
})
|
})
|
||||||
.then(res => {
|
})
|
||||||
navigation.navigate('Screen-Compose', {
|
|
||||||
type: 'deleteEdit',
|
|
||||||
incomingStatus: res.body as Mastodon.Status,
|
|
||||||
...(replyToStatus && { replyToStatus }),
|
|
||||||
queryKey
|
|
||||||
})
|
|
||||||
})
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
text: t('common:buttons.cancel')
|
|
||||||
}
|
}
|
||||||
]
|
},
|
||||||
)
|
{
|
||||||
break
|
text: t('common:buttons.cancel')
|
||||||
case 'status-mute':
|
|
||||||
analytics('timeline_shared_headeractions_status_mute_press', {
|
|
||||||
page: queryKey && queryKey[1].page
|
|
||||||
})
|
|
||||||
mutation.mutate({
|
|
||||||
type: 'updateStatusProperty',
|
|
||||||
queryKey,
|
|
||||||
rootQueryKey,
|
|
||||||
id: status.id,
|
|
||||||
payload: {
|
|
||||||
property: 'muted',
|
|
||||||
currentValue: status.muted,
|
|
||||||
propertyCount: undefined,
|
|
||||||
countValue: undefined
|
|
||||||
}
|
}
|
||||||
})
|
]
|
||||||
break
|
)
|
||||||
case 'status-edit':
|
}
|
||||||
analytics('timeline_shared_headeractions_status_edit_press', {
|
if (actions[index].id === 'status-mute') {
|
||||||
page: queryKey && queryKey[1].page
|
analytics('timeline_shared_headeractions_status_mute_press', {
|
||||||
})
|
page: queryKey && queryKey[1].page
|
||||||
let replyToStatus: Mastodon.Status | undefined = undefined
|
})
|
||||||
if (status.in_reply_to_id) {
|
mutation.mutate({
|
||||||
replyToStatus = await apiInstance<Mastodon.Status>({
|
type: 'updateStatusProperty',
|
||||||
method: 'get',
|
queryKey,
|
||||||
url: `statuses/${status.in_reply_to_id}`
|
rootQueryKey,
|
||||||
}).then(res => res.body)
|
id: status.id,
|
||||||
|
payload: {
|
||||||
|
property: 'muted',
|
||||||
|
currentValue: status.muted,
|
||||||
|
propertyCount: undefined,
|
||||||
|
countValue: undefined
|
||||||
}
|
}
|
||||||
apiInstance<{
|
})
|
||||||
id: Mastodon.Status['id']
|
}
|
||||||
text: NonNullable<Mastodon.Status['text']>
|
if (actions[index].id === 'status-edit') {
|
||||||
spoiler_text: Mastodon.Status['spoiler_text']
|
analytics('timeline_shared_headeractions_status_edit_press', {
|
||||||
}>({
|
page: queryKey && queryKey[1].page
|
||||||
|
})
|
||||||
|
let replyToStatus: Mastodon.Status | undefined = undefined
|
||||||
|
if (status.in_reply_to_id) {
|
||||||
|
replyToStatus = await apiInstance<Mastodon.Status>({
|
||||||
method: 'get',
|
method: 'get',
|
||||||
url: `statuses/${status.id}/source`
|
url: `statuses/${status.in_reply_to_id}`
|
||||||
}).then(res => {
|
}).then(res => res.body)
|
||||||
navigation.navigate('Screen-Compose', {
|
}
|
||||||
type: 'edit',
|
apiInstance<{
|
||||||
incomingStatus: {
|
id: Mastodon.Status['id']
|
||||||
...status,
|
text: NonNullable<Mastodon.Status['text']>
|
||||||
text: res.body.text,
|
spoiler_text: Mastodon.Status['spoiler_text']
|
||||||
spoiler_text: res.body.spoiler_text
|
}>({
|
||||||
},
|
method: 'get',
|
||||||
...(replyToStatus && { replyToStatus }),
|
url: `statuses/${status.id}/source`
|
||||||
queryKey,
|
}).then(res => {
|
||||||
rootQueryKey
|
navigation.navigate('Screen-Compose', {
|
||||||
})
|
type: 'edit',
|
||||||
})
|
incomingStatus: {
|
||||||
break
|
...status,
|
||||||
case 'status-pin':
|
text: res.body.text,
|
||||||
// Also note that reblogs cannot be pinned.
|
spoiler_text: res.body.spoiler_text
|
||||||
analytics('timeline_shared_headeractions_status_pin_press', {
|
},
|
||||||
page: queryKey && queryKey[1].page
|
...(replyToStatus && { replyToStatus }),
|
||||||
})
|
|
||||||
mutation.mutate({
|
|
||||||
type: 'updateStatusProperty',
|
|
||||||
queryKey,
|
queryKey,
|
||||||
rootQueryKey,
|
rootQueryKey
|
||||||
id: status.id,
|
|
||||||
payload: {
|
|
||||||
property: 'pinned',
|
|
||||||
currentValue: status.pinned,
|
|
||||||
propertyCount: undefined,
|
|
||||||
countValue: undefined
|
|
||||||
}
|
|
||||||
})
|
})
|
||||||
break
|
})
|
||||||
|
}
|
||||||
|
if (actions[index].id === 'status-pin') {
|
||||||
|
// Also note that reblogs cannot be pinned.
|
||||||
|
analytics('timeline_shared_headeractions_status_pin_press', {
|
||||||
|
page: queryKey && queryKey[1].page
|
||||||
|
})
|
||||||
|
mutation.mutate({
|
||||||
|
type: 'updateStatusProperty',
|
||||||
|
queryKey,
|
||||||
|
rootQueryKey,
|
||||||
|
id: status.id,
|
||||||
|
payload: {
|
||||||
|
property: 'pinned',
|
||||||
|
currentValue: status.pinned,
|
||||||
|
propertyCount: undefined,
|
||||||
|
countValue: undefined
|
||||||
|
}
|
||||||
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -7,6 +7,7 @@ import { chunk, forEach, groupBy, sortBy } from 'lodash'
|
|||||||
import React, {
|
import React, {
|
||||||
Dispatch,
|
Dispatch,
|
||||||
MutableRefObject,
|
MutableRefObject,
|
||||||
|
PropsWithChildren,
|
||||||
SetStateAction,
|
SetStateAction,
|
||||||
useCallback,
|
useCallback,
|
||||||
useEffect,
|
useEffect,
|
||||||
@ -57,7 +58,7 @@ export interface Props {
|
|||||||
maxLength?: number
|
maxLength?: number
|
||||||
}
|
}
|
||||||
|
|
||||||
const ComponentEmojis: React.FC<Props> = ({
|
const ComponentEmojis: React.FC<Props & PropsWithChildren> = ({
|
||||||
enabled = false,
|
enabled = false,
|
||||||
value,
|
value,
|
||||||
setValue,
|
setValue,
|
||||||
|
@ -27,18 +27,6 @@ const EmojisList = React.memo(
|
|||||||
const { emojisState, emojisDispatch } = useContext(EmojisContext)
|
const { emojisState, emojisDispatch } = useContext(EmojisContext)
|
||||||
const { colors } = useTheme()
|
const { colors } = useTheme()
|
||||||
|
|
||||||
const listHeader = useCallback(
|
|
||||||
({ section: { title } }) => (
|
|
||||||
<CustomText
|
|
||||||
fontStyle='S'
|
|
||||||
style={{ position: 'absolute', color: colors.secondary }}
|
|
||||||
>
|
|
||||||
{title}
|
|
||||||
</CustomText>
|
|
||||||
),
|
|
||||||
[]
|
|
||||||
)
|
|
||||||
|
|
||||||
const listItem = useCallback(
|
const listItem = useCallback(
|
||||||
({ index, item }: { item: Mastodon.Emoji[]; index: number }) => {
|
({ index, item }: { item: Mastodon.Emoji[]; index: number }) => {
|
||||||
return (
|
return (
|
||||||
@ -112,7 +100,14 @@ const EmojisList = React.memo(
|
|||||||
keyboardShouldPersistTaps='always'
|
keyboardShouldPersistTaps='always'
|
||||||
sections={emojisState.emojis}
|
sections={emojisState.emojis}
|
||||||
keyExtractor={item => item[0].shortcode}
|
keyExtractor={item => item[0].shortcode}
|
||||||
renderSectionHeader={listHeader}
|
renderSectionHeader={({ section: { title } }) => (
|
||||||
|
<CustomText
|
||||||
|
fontStyle='S'
|
||||||
|
style={{ position: 'absolute', color: colors.secondary }}
|
||||||
|
>
|
||||||
|
{title}
|
||||||
|
</CustomText>
|
||||||
|
)}
|
||||||
renderItem={listItem}
|
renderItem={listItem}
|
||||||
windowSize={4}
|
windowSize={4}
|
||||||
/>
|
/>
|
||||||
|
@ -5,12 +5,14 @@ import {
|
|||||||
AccessibilityProps,
|
AccessibilityProps,
|
||||||
Image,
|
Image,
|
||||||
ImageStyle,
|
ImageStyle,
|
||||||
|
Platform,
|
||||||
Pressable,
|
Pressable,
|
||||||
StyleProp,
|
StyleProp,
|
||||||
StyleSheet,
|
StyleSheet,
|
||||||
View,
|
View,
|
||||||
ViewStyle
|
ViewStyle
|
||||||
} from 'react-native'
|
} from 'react-native'
|
||||||
|
import FastImage from 'react-native-fast-image'
|
||||||
import { Blurhash } from 'react-native-blurhash'
|
import { Blurhash } from 'react-native-blurhash'
|
||||||
|
|
||||||
// blurhas -> if blurhash, show before any loading succeed
|
// blurhas -> if blurhash, show before any loading succeed
|
||||||
@ -125,13 +127,24 @@ const GracefullyImage = ({
|
|||||||
]}
|
]}
|
||||||
/>
|
/>
|
||||||
) : null}
|
) : null}
|
||||||
<Image
|
{Platform.OS === 'ios' ? (
|
||||||
fadeDuration={0}
|
<Image
|
||||||
source={source}
|
fadeDuration={0}
|
||||||
style={[{ flex: 1 }, imageStyle]}
|
source={source}
|
||||||
onLoad={onLoad}
|
style={[{ flex: 1 }, imageStyle]}
|
||||||
onError={onError}
|
onLoad={onLoad}
|
||||||
/>
|
onError={onError}
|
||||||
|
/>
|
||||||
|
) : (
|
||||||
|
<FastImage
|
||||||
|
fadeDuration={0}
|
||||||
|
source={source}
|
||||||
|
// @ts-ignore
|
||||||
|
style={[{ flex: 1 }, imageStyle]}
|
||||||
|
onLoad={onLoad}
|
||||||
|
onError={onError}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
{blurhashView}
|
{blurhashView}
|
||||||
</Pressable>
|
</Pressable>
|
||||||
)
|
)
|
||||||
|
@ -4,7 +4,6 @@ import { useTheme } from '@utils/styles/ThemeManager'
|
|||||||
import React, {
|
import React, {
|
||||||
Dispatch,
|
Dispatch,
|
||||||
SetStateAction,
|
SetStateAction,
|
||||||
useCallback,
|
|
||||||
useEffect,
|
useEffect,
|
||||||
useRef,
|
useRef,
|
||||||
useState
|
useState
|
||||||
@ -81,10 +80,6 @@ const Input: React.FC<Props> = ({
|
|||||||
}
|
}
|
||||||
: { start: 0, end: 0 }
|
: { start: 0, end: 0 }
|
||||||
)
|
)
|
||||||
const onSelectionChange = useCallback(
|
|
||||||
({ nativeEvent: { selection } }) => (selectionRange.current = selection),
|
|
||||||
[]
|
|
||||||
)
|
|
||||||
|
|
||||||
const [inputFocused, setInputFocused] = useState(false)
|
const [inputFocused, setInputFocused] = useState(false)
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@ -128,7 +123,9 @@ const Input: React.FC<Props> = ({
|
|||||||
: undefined
|
: undefined
|
||||||
}}
|
}}
|
||||||
onChangeText={setValue}
|
onChangeText={setValue}
|
||||||
onSelectionChange={onSelectionChange}
|
onSelectionChange={({ nativeEvent: { selection } }) =>
|
||||||
|
(selectionRange.current = selection)
|
||||||
|
}
|
||||||
value={value}
|
value={value}
|
||||||
{...(multiline && {
|
{...(multiline && {
|
||||||
multiline,
|
multiline,
|
||||||
|
@ -88,21 +88,6 @@ const ComponentInstance: React.FC<Props> = ({
|
|||||||
}
|
}
|
||||||
}, [domain])
|
}, [domain])
|
||||||
|
|
||||||
const onSubmitEditing = useCallback(
|
|
||||||
({ nativeEvent: { text } }) => {
|
|
||||||
analytics('instance_textinput_submit', { match: text === domain })
|
|
||||||
if (
|
|
||||||
text === domain &&
|
|
||||||
instanceQuery.isSuccess &&
|
|
||||||
instanceQuery.data &&
|
|
||||||
instanceQuery.data.uri
|
|
||||||
) {
|
|
||||||
processUpdate()
|
|
||||||
}
|
|
||||||
},
|
|
||||||
[domain, instanceQuery.isSuccess, instanceQuery.data]
|
|
||||||
)
|
|
||||||
|
|
||||||
const requestAuth = useMemo(() => {
|
const requestAuth = useMemo(() => {
|
||||||
if (
|
if (
|
||||||
domain &&
|
domain &&
|
||||||
@ -180,7 +165,17 @@ const ComponentInstance: React.FC<Props> = ({
|
|||||||
clearButtonMode='never'
|
clearButtonMode='never'
|
||||||
keyboardType='url'
|
keyboardType='url'
|
||||||
textContentType='URL'
|
textContentType='URL'
|
||||||
onSubmitEditing={onSubmitEditing}
|
onSubmitEditing={({ nativeEvent: { text } }) => {
|
||||||
|
analytics('instance_textinput_submit', { match: text === domain })
|
||||||
|
if (
|
||||||
|
text === domain &&
|
||||||
|
instanceQuery.isSuccess &&
|
||||||
|
instanceQuery.data &&
|
||||||
|
instanceQuery.data.uri
|
||||||
|
) {
|
||||||
|
processUpdate()
|
||||||
|
}
|
||||||
|
}}
|
||||||
placeholder={' ' + t('server.textInput.placeholder')}
|
placeholder={' ' + t('server.textInput.placeholder')}
|
||||||
placeholderTextColor={colors.secondary}
|
placeholderTextColor={colors.secondary}
|
||||||
returnKeyType='go'
|
returnKeyType='go'
|
||||||
|
@ -5,7 +5,7 @@ import { StyleConstants } from '@utils/styles/constants'
|
|||||||
import { useTheme } from '@utils/styles/ThemeManager'
|
import { useTheme } from '@utils/styles/ThemeManager'
|
||||||
import { ColorDefinitions } from '@utils/styles/themes'
|
import { ColorDefinitions } from '@utils/styles/themes'
|
||||||
import React, { useMemo } from 'react'
|
import React, { useMemo } from 'react'
|
||||||
import { StyleSheet, Text, View } from 'react-native'
|
import { View } from 'react-native'
|
||||||
import { Flow } from 'react-native-animated-spinkit'
|
import { Flow } from 'react-native-animated-spinkit'
|
||||||
import { State, Switch, TapGestureHandler } from 'react-native-gesture-handler'
|
import { State, Switch, TapGestureHandler } from 'react-native-gesture-handler'
|
||||||
|
|
||||||
@ -61,7 +61,7 @@ const MenuRow: React.FC<Props> = ({
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<View
|
<View
|
||||||
style={styles.base}
|
style={{ minHeight: 50 }}
|
||||||
accessible
|
accessible
|
||||||
accessibilityRole={switchValue ? 'switch' : 'button'}
|
accessibilityRole={switchValue ? 'switch' : 'button'}
|
||||||
accessibilityState={switchValue ? { checked: switchValue } : undefined}
|
accessibilityState={switchValue ? { checked: switchValue } : undefined}
|
||||||
@ -78,14 +78,26 @@ const MenuRow: React.FC<Props> = ({
|
|||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<View style={{ flex: 1 }}>
|
<View style={{ flex: 1 }}>
|
||||||
<View style={styles.core}>
|
<View
|
||||||
<View style={styles.front}>
|
style={{
|
||||||
|
flex: 1,
|
||||||
|
flexDirection: 'row',
|
||||||
|
paddingTop: StyleConstants.Spacing.S
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<View
|
||||||
|
style={{
|
||||||
|
flexGrow: 3,
|
||||||
|
flexDirection: 'row',
|
||||||
|
alignItems: 'center'
|
||||||
|
}}
|
||||||
|
>
|
||||||
{iconFront && (
|
{iconFront && (
|
||||||
<Icon
|
<Icon
|
||||||
name={iconFront}
|
name={iconFront}
|
||||||
size={StyleConstants.Font.Size.L}
|
size={StyleConstants.Font.Size.L}
|
||||||
color={colors[iconFrontColor]}
|
color={colors[iconFrontColor]}
|
||||||
style={styles.iconFront}
|
style={{ marginRight: StyleConstants.Spacing.S }}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
{badge ? (
|
{badge ? (
|
||||||
@ -99,19 +111,25 @@ const MenuRow: React.FC<Props> = ({
|
|||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
) : null}
|
) : null}
|
||||||
<View style={styles.main}>
|
<CustomText
|
||||||
<CustomText
|
fontStyle='M'
|
||||||
fontStyle='M'
|
style={{ color: colors.primaryDefault }}
|
||||||
style={{ color: colors.primaryDefault }}
|
numberOfLines={1}
|
||||||
numberOfLines={1}
|
>
|
||||||
>
|
{title}
|
||||||
{title}
|
</CustomText>
|
||||||
</CustomText>
|
|
||||||
</View>
|
|
||||||
</View>
|
</View>
|
||||||
|
|
||||||
{content || switchValue !== undefined || iconBack ? (
|
{content || switchValue !== undefined || iconBack ? (
|
||||||
<View style={styles.back}>
|
<View
|
||||||
|
style={{
|
||||||
|
flexShrink: 1,
|
||||||
|
flexDirection: 'row',
|
||||||
|
justifyContent: 'flex-end',
|
||||||
|
alignItems: 'center',
|
||||||
|
marginLeft: StyleConstants.Spacing.M
|
||||||
|
}}
|
||||||
|
>
|
||||||
{content ? (
|
{content ? (
|
||||||
typeof content === 'string' ? (
|
typeof content === 'string' ? (
|
||||||
<CustomText
|
<CustomText
|
||||||
@ -141,7 +159,7 @@ const MenuRow: React.FC<Props> = ({
|
|||||||
name={iconBack}
|
name={iconBack}
|
||||||
size={StyleConstants.Font.Size.L}
|
size={StyleConstants.Font.Size.L}
|
||||||
color={colors[iconBackColor]}
|
color={colors[iconBackColor]}
|
||||||
style={[styles.iconBack, { opacity: loading ? 0 : 1 }]}
|
style={{ marginLeft: 8, opacity: loading ? 0 : 1 }}
|
||||||
/>
|
/>
|
||||||
) : null}
|
) : null}
|
||||||
{loading && loadingSpinkit}
|
{loading && loadingSpinkit}
|
||||||
@ -159,42 +177,4 @@ const MenuRow: React.FC<Props> = ({
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
const styles = StyleSheet.create({
|
|
||||||
base: {
|
|
||||||
minHeight: 50
|
|
||||||
},
|
|
||||||
core: {
|
|
||||||
flex: 1,
|
|
||||||
flexDirection: 'row',
|
|
||||||
paddingTop: StyleConstants.Spacing.S
|
|
||||||
},
|
|
||||||
front: {
|
|
||||||
flex: 2,
|
|
||||||
flexDirection: 'row',
|
|
||||||
alignItems: 'center'
|
|
||||||
},
|
|
||||||
back: {
|
|
||||||
flex: 1,
|
|
||||||
flexDirection: 'row',
|
|
||||||
justifyContent: 'flex-end',
|
|
||||||
alignItems: 'center',
|
|
||||||
marginLeft: StyleConstants.Spacing.M
|
|
||||||
},
|
|
||||||
iconFront: {
|
|
||||||
marginRight: StyleConstants.Spacing.S
|
|
||||||
},
|
|
||||||
main: {
|
|
||||||
flex: 1
|
|
||||||
},
|
|
||||||
description: {
|
|
||||||
...StyleConstants.FontStyle.S
|
|
||||||
},
|
|
||||||
content: {
|
|
||||||
...StyleConstants.FontStyle.M
|
|
||||||
},
|
|
||||||
iconBack: {
|
|
||||||
marginLeft: 8
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
export default MenuRow
|
export default MenuRow
|
||||||
|
@ -80,7 +80,7 @@ const displayMessage = ({
|
|||||||
})
|
})
|
||||||
} else {
|
} else {
|
||||||
showMessage({
|
showMessage({
|
||||||
duration: type === 'error' ? 3500 : duration === 'short' ? 1500 : 2500,
|
duration: type === 'error' ? 8000 : duration === 'short' ? 3000 : 5000,
|
||||||
autoHide,
|
autoHide,
|
||||||
message,
|
message,
|
||||||
description,
|
description,
|
||||||
@ -124,7 +124,8 @@ const Message = React.forwardRef<FlashMessage>((_, ref) => {
|
|||||||
shadowColor: colors.primaryDefault,
|
shadowColor: colors.primaryDefault,
|
||||||
shadowOffset: { width: 0, height: 0 },
|
shadowOffset: { width: 0, height: 0 },
|
||||||
shadowOpacity: theme === 'light' ? 0.16 : 0.24,
|
shadowOpacity: theme === 'light' ? 0.16 : 0.24,
|
||||||
shadowRadius: 4
|
shadowRadius: 4,
|
||||||
|
paddingRight: StyleConstants.Spacing.M * 2
|
||||||
}}
|
}}
|
||||||
titleStyle={{
|
titleStyle={{
|
||||||
color: colors.primaryDefault,
|
color: colors.primaryDefault,
|
||||||
|
@ -215,7 +215,7 @@ const ParseHTML = React.memo(
|
|||||||
}
|
}
|
||||||
|
|
||||||
const renderNodeCallback = useCallback(
|
const renderNodeCallback = useCallback(
|
||||||
(node, index) =>
|
(node: any, index: any) =>
|
||||||
renderNode({
|
renderNode({
|
||||||
routeParams: route.params,
|
routeParams: route.params,
|
||||||
colors,
|
colors,
|
||||||
@ -231,7 +231,7 @@ const ParseHTML = React.memo(
|
|||||||
}),
|
}),
|
||||||
[]
|
[]
|
||||||
)
|
)
|
||||||
const textComponent = useCallback(({ children }) => {
|
const textComponent = useCallback(({ children }: any) => {
|
||||||
if (children) {
|
if (children) {
|
||||||
return (
|
return (
|
||||||
<ParseEmojis
|
<ParseEmojis
|
||||||
@ -246,26 +246,24 @@ const ParseHTML = React.memo(
|
|||||||
}
|
}
|
||||||
}, [])
|
}, [])
|
||||||
const rootComponent = useCallback(
|
const rootComponent = useCallback(
|
||||||
({ children }) => {
|
({ children }: any) => {
|
||||||
const { t } = useTranslation('componentParse')
|
const { t } = useTranslation('componentParse')
|
||||||
|
|
||||||
const [expandAllow, setExpandAllow] = useState(false)
|
const [expandAllow, setExpandAllow] = useState(false)
|
||||||
const [expanded, setExpanded] = useState(highlighted)
|
const [expanded, setExpanded] = useState(highlighted)
|
||||||
|
|
||||||
const onTextLayout = useCallback(({ nativeEvent }) => {
|
|
||||||
if (
|
|
||||||
numberOfLines === 1 ||
|
|
||||||
nativeEvent.lines.length >= numberOfLines + 5
|
|
||||||
) {
|
|
||||||
setExpandAllow(true)
|
|
||||||
}
|
|
||||||
}, [])
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<View style={{ overflow: 'hidden' }}>
|
<View style={{ overflow: 'hidden' }}>
|
||||||
<CustomText
|
<CustomText
|
||||||
children={children}
|
children={children}
|
||||||
onTextLayout={onTextLayout}
|
onTextLayout={({ nativeEvent }) => {
|
||||||
|
if (
|
||||||
|
numberOfLines === 1 ||
|
||||||
|
nativeEvent.lines.length >= numberOfLines + 5
|
||||||
|
) {
|
||||||
|
setExpandAllow(true)
|
||||||
|
}
|
||||||
|
}}
|
||||||
numberOfLines={
|
numberOfLines={
|
||||||
expandAllow ? (expanded ? 999 : numberOfLines) : undefined
|
expandAllow ? (expanded ? 999 : numberOfLines) : undefined
|
||||||
}
|
}
|
||||||
|
@ -3,11 +3,10 @@ import { FormattedRelativeTime } from 'react-intl'
|
|||||||
import { AppState } from 'react-native'
|
import { AppState } from 'react-native'
|
||||||
|
|
||||||
export interface Props {
|
export interface Props {
|
||||||
type: 'past' | 'future'
|
|
||||||
time: string | number
|
time: string | number
|
||||||
}
|
}
|
||||||
|
|
||||||
const RelativeTime: React.FC<Props> = ({ type, time }) => {
|
const RelativeTime: React.FC<Props> = ({ time }) => {
|
||||||
const [now, setNow] = useState(new Date().getTime())
|
const [now, setNow] = useState(new Date().getTime())
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const appStateListener = AppState.addEventListener('change', state => {
|
const appStateListener = AppState.addEventListener('change', state => {
|
||||||
@ -21,9 +20,7 @@ const RelativeTime: React.FC<Props> = ({ type, time }) => {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<FormattedRelativeTime
|
<FormattedRelativeTime
|
||||||
value={
|
value={(new Date(time).getTime() - now) / 1000}
|
||||||
((type === 'past' ? -1 : 1) * (now - new Date(time).getTime())) / 1000
|
|
||||||
}
|
|
||||||
updateIntervalInSeconds={1}
|
updateIntervalInSeconds={1}
|
||||||
/>
|
/>
|
||||||
)
|
)
|
||||||
|
@ -64,17 +64,6 @@ const Timeline: React.FC<Props> = ({
|
|||||||
? data.pages?.flatMap(page => [...page.body])
|
? data.pages?.flatMap(page => [...page.body])
|
||||||
: []
|
: []
|
||||||
|
|
||||||
const ItemSeparatorComponent = useCallback(
|
|
||||||
({ leadingItem }) =>
|
|
||||||
queryKey[1].page === 'Toot' && queryKey[1].toot === leadingItem.id ? (
|
|
||||||
<ComponentSeparator extraMarginLeft={0} />
|
|
||||||
) : (
|
|
||||||
<ComponentSeparator
|
|
||||||
extraMarginLeft={StyleConstants.Avatar.M + StyleConstants.Spacing.S}
|
|
||||||
/>
|
|
||||||
),
|
|
||||||
[]
|
|
||||||
)
|
|
||||||
const onEndReached = useCallback(
|
const onEndReached = useCallback(
|
||||||
() => !disableInfinity && !isFetchingNextPage && fetchNextPage(),
|
() => !disableInfinity && !isFetchingNextPage && fetchNextPage(),
|
||||||
[isFetchingNextPage]
|
[isFetchingNextPage]
|
||||||
@ -151,7 +140,17 @@ const Timeline: React.FC<Props> = ({
|
|||||||
/>
|
/>
|
||||||
}
|
}
|
||||||
ListEmptyComponent={<TimelineEmpty queryKey={queryKey} />}
|
ListEmptyComponent={<TimelineEmpty queryKey={queryKey} />}
|
||||||
ItemSeparatorComponent={ItemSeparatorComponent}
|
ItemSeparatorComponent={({ leadingItem }) =>
|
||||||
|
queryKey[1].page === 'Toot' && queryKey[1].toot === leadingItem.id ? (
|
||||||
|
<ComponentSeparator extraMarginLeft={0} />
|
||||||
|
) : (
|
||||||
|
<ComponentSeparator
|
||||||
|
extraMarginLeft={
|
||||||
|
StyleConstants.Avatar.M + StyleConstants.Spacing.S
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
}
|
||||||
maintainVisibleContentPosition={
|
maintainVisibleContentPosition={
|
||||||
isFetching
|
isFetching
|
||||||
? {
|
? {
|
||||||
|
@ -78,6 +78,7 @@ const TimelineDefault: React.FC<Props> = ({
|
|||||||
status={actualStatus}
|
status={actualStatus}
|
||||||
queryKey={queryKey}
|
queryKey={queryKey}
|
||||||
rootQueryKey={rootQueryKey}
|
rootQueryKey={rootQueryKey}
|
||||||
|
disabled={highlighted}
|
||||||
>
|
>
|
||||||
<Pressable
|
<Pressable
|
||||||
accessible={highlighted ? false : true}
|
accessible={highlighted ? false : true}
|
||||||
|
@ -59,7 +59,11 @@ const TimelineNotifications = React.memo(
|
|||||||
}, [])
|
}, [])
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<TimelineContextMenu status={notification.status} queryKey={queryKey}>
|
<TimelineContextMenu
|
||||||
|
status={notification.status}
|
||||||
|
queryKey={queryKey}
|
||||||
|
disabled={highlighted}
|
||||||
|
>
|
||||||
<Pressable
|
<Pressable
|
||||||
style={{
|
style={{
|
||||||
padding: StyleConstants.Spacing.Global.PagePadding,
|
padding: StyleConstants.Spacing.Global.PagePadding,
|
||||||
@ -96,7 +100,10 @@ const TimelineNotifications = React.memo(
|
|||||||
account={actualAccount}
|
account={actualAccount}
|
||||||
highlighted={highlighted}
|
highlighted={highlighted}
|
||||||
/>
|
/>
|
||||||
<TimelineHeaderNotification notification={notification} />
|
<TimelineHeaderNotification
|
||||||
|
queryKey={queryKey}
|
||||||
|
notification={notification}
|
||||||
|
/>
|
||||||
</View>
|
</View>
|
||||||
|
|
||||||
{notification.status ? (
|
{notification.status ? (
|
||||||
|
@ -9,7 +9,7 @@ import { StyleConstants } from '@utils/styles/constants'
|
|||||||
import { useTheme } from '@utils/styles/ThemeManager'
|
import { useTheme } from '@utils/styles/ThemeManager'
|
||||||
import React, { RefObject, useCallback, useRef, useState } from 'react'
|
import React, { RefObject, useCallback, useRef, useState } from 'react'
|
||||||
import { useTranslation } from 'react-i18next'
|
import { useTranslation } from 'react-i18next'
|
||||||
import { FlatList, Platform, StyleSheet, Text, View } from 'react-native'
|
import { FlatList, LayoutChangeEvent, Platform, StyleSheet, Text, View } from 'react-native'
|
||||||
import { Circle } from 'react-native-animated-spinkit'
|
import { Circle } from 'react-native-animated-spinkit'
|
||||||
import Animated, {
|
import Animated, {
|
||||||
Extrapolate,
|
Extrapolate,
|
||||||
@ -169,7 +169,7 @@ const TimelineRefresh: React.FC<Props> = ({
|
|||||||
|
|
||||||
const arrowStage = useSharedValue(0)
|
const arrowStage = useSharedValue(0)
|
||||||
const onLayout = useCallback(
|
const onLayout = useCallback(
|
||||||
({ nativeEvent }) => {
|
({ nativeEvent }: LayoutChangeEvent) => {
|
||||||
if (nativeEvent.layout.x + nativeEvent.layout.width > textRight) {
|
if (nativeEvent.layout.x + nativeEvent.layout.width > textRight) {
|
||||||
setTextRight(nativeEvent.layout.x + nativeEvent.layout.width)
|
setTextRight(nativeEvent.layout.x + nativeEvent.layout.width)
|
||||||
}
|
}
|
||||||
|
@ -5,6 +5,7 @@ import contextMenuStatus from '@components/ContextMenu/status'
|
|||||||
import { QueryKeyTimeline } from '@utils/queryHooks/timeline'
|
import { QueryKeyTimeline } from '@utils/queryHooks/timeline'
|
||||||
import React from 'react'
|
import React from 'react'
|
||||||
import { createContext } from 'react'
|
import { createContext } from 'react'
|
||||||
|
import { Platform } from 'react-native'
|
||||||
import ContextMenu, {
|
import ContextMenu, {
|
||||||
ContextMenuAction,
|
ContextMenuAction,
|
||||||
ContextMenuProps
|
ContextMenuProps
|
||||||
@ -14,6 +15,7 @@ export interface Props {
|
|||||||
status?: Mastodon.Status
|
status?: Mastodon.Status
|
||||||
queryKey?: QueryKeyTimeline
|
queryKey?: QueryKeyTimeline
|
||||||
rootQueryKey?: QueryKeyTimeline
|
rootQueryKey?: QueryKeyTimeline
|
||||||
|
disabled?: boolean // Allowing toot to be copied when highlighted
|
||||||
}
|
}
|
||||||
|
|
||||||
export const ContextMenuContext = createContext<ContextMenuAction[]>([])
|
export const ContextMenuContext = createContext<ContextMenuAction[]>([])
|
||||||
@ -23,9 +25,10 @@ const TimelineContextMenu: React.FC<Props & ContextMenuProps> = ({
|
|||||||
status,
|
status,
|
||||||
queryKey,
|
queryKey,
|
||||||
rootQueryKey,
|
rootQueryKey,
|
||||||
|
disabled,
|
||||||
...props
|
...props
|
||||||
}) => {
|
}) => {
|
||||||
if (!status || !queryKey) {
|
if (!status || !queryKey || disabled || Platform.OS === 'android') {
|
||||||
return <>{children}</>
|
return <>{children}</>
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -47,6 +50,7 @@ const TimelineContextMenu: React.FC<Props & ContextMenuProps> = ({
|
|||||||
})
|
})
|
||||||
const accountOnPress = contextMenuAccount({
|
const accountOnPress = contextMenuAccount({
|
||||||
actions,
|
actions,
|
||||||
|
type: 'status',
|
||||||
queryKey,
|
queryKey,
|
||||||
rootQueryKey,
|
rootQueryKey,
|
||||||
id: status.account.id
|
id: status.account.id
|
||||||
@ -62,14 +66,14 @@ const TimelineContextMenu: React.FC<Props & ContextMenuProps> = ({
|
|||||||
<ContextMenuContext.Provider value={actions}>
|
<ContextMenuContext.Provider value={actions}>
|
||||||
<ContextMenu
|
<ContextMenu
|
||||||
actions={actions}
|
actions={actions}
|
||||||
onPress={({ nativeEvent: { id } }) => {
|
onPress={({ nativeEvent: { index } }) => {
|
||||||
for (const on of [
|
for (const on of [
|
||||||
shareOnPress,
|
shareOnPress,
|
||||||
statusOnPress,
|
statusOnPress,
|
||||||
accountOnPress,
|
accountOnPress,
|
||||||
instanceOnPress
|
instanceOnPress
|
||||||
]) {
|
]) {
|
||||||
on && on(id)
|
on && on(index)
|
||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
children={children}
|
children={children}
|
||||||
|
120
src/components/Timeline/Shared/HeaderDefault.android.tsx
Normal file
120
src/components/Timeline/Shared/HeaderDefault.android.tsx
Normal file
@ -0,0 +1,120 @@
|
|||||||
|
import contextMenuAccount from '@components/ContextMenu/account'
|
||||||
|
import contextMenuInstance from '@components/ContextMenu/instance'
|
||||||
|
import contextMenuShare from '@components/ContextMenu/share'
|
||||||
|
import contextMenuStatus from '@components/ContextMenu/status'
|
||||||
|
import Icon from '@components/Icon'
|
||||||
|
import { QueryKeyTimeline } from '@utils/queryHooks/timeline'
|
||||||
|
import { StyleConstants } from '@utils/styles/constants'
|
||||||
|
import { useTheme } from '@utils/styles/ThemeManager'
|
||||||
|
import React, { useContext } from 'react'
|
||||||
|
import { useTranslation } from 'react-i18next'
|
||||||
|
import { Platform, Pressable, View } from 'react-native'
|
||||||
|
import ContextMenu, { ContextMenuAction } from 'react-native-context-menu-view'
|
||||||
|
import { ContextMenuContext } from './ContextMenu'
|
||||||
|
import HeaderSharedAccount from './HeaderShared/Account'
|
||||||
|
import HeaderSharedApplication from './HeaderShared/Application'
|
||||||
|
import HeaderSharedCreated from './HeaderShared/Created'
|
||||||
|
import HeaderSharedMuted from './HeaderShared/Muted'
|
||||||
|
import HeaderSharedVisibility from './HeaderShared/Visibility'
|
||||||
|
|
||||||
|
export interface Props {
|
||||||
|
queryKey?: QueryKeyTimeline
|
||||||
|
status: Mastodon.Status
|
||||||
|
highlighted: boolean
|
||||||
|
}
|
||||||
|
|
||||||
|
const TimelineHeaderDefault = ({ queryKey, status, highlighted }: Props) => {
|
||||||
|
if (!queryKey) return null
|
||||||
|
|
||||||
|
const { t } = useTranslation('componentContextMenu')
|
||||||
|
const { colors } = useTheme()
|
||||||
|
|
||||||
|
const actions: ContextMenuAction[] = []
|
||||||
|
|
||||||
|
const shareOnPress =
|
||||||
|
status.visibility !== 'direct'
|
||||||
|
? contextMenuShare({
|
||||||
|
actions,
|
||||||
|
type: 'status',
|
||||||
|
url: status.url || status.uri
|
||||||
|
})
|
||||||
|
: null
|
||||||
|
const statusOnPress = contextMenuStatus({
|
||||||
|
actions,
|
||||||
|
status,
|
||||||
|
queryKey
|
||||||
|
})
|
||||||
|
const accountOnPress = contextMenuAccount({
|
||||||
|
actions,
|
||||||
|
type: 'status',
|
||||||
|
queryKey,
|
||||||
|
id: status.account.id
|
||||||
|
})
|
||||||
|
const instanceOnPress = contextMenuInstance({
|
||||||
|
actions,
|
||||||
|
status,
|
||||||
|
queryKey
|
||||||
|
})
|
||||||
|
|
||||||
|
return (
|
||||||
|
<View style={{ flex: 1, flexDirection: 'row' }}>
|
||||||
|
<View style={{ flex: 7 }}>
|
||||||
|
<HeaderSharedAccount account={status.account} />
|
||||||
|
<View
|
||||||
|
style={{
|
||||||
|
flexDirection: 'row',
|
||||||
|
alignItems: 'center',
|
||||||
|
marginTop: StyleConstants.Spacing.XS,
|
||||||
|
marginBottom: StyleConstants.Spacing.S
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<HeaderSharedCreated
|
||||||
|
created_at={status.created_at}
|
||||||
|
edited_at={status.edited_at}
|
||||||
|
highlighted={highlighted}
|
||||||
|
/>
|
||||||
|
<HeaderSharedVisibility visibility={status.visibility} />
|
||||||
|
<HeaderSharedMuted muted={status.muted} />
|
||||||
|
<HeaderSharedApplication application={status.application} />
|
||||||
|
</View>
|
||||||
|
</View>
|
||||||
|
|
||||||
|
{queryKey ? (
|
||||||
|
<Pressable
|
||||||
|
accessibilityHint={t('accessibilityHint')}
|
||||||
|
style={{
|
||||||
|
flex: 1,
|
||||||
|
flexDirection: 'row',
|
||||||
|
justifyContent: 'center',
|
||||||
|
marginBottom: StyleConstants.Spacing.L
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<ContextMenu
|
||||||
|
dropdownMenuMode
|
||||||
|
actions={actions}
|
||||||
|
onPress={({ nativeEvent: { index } }) => {
|
||||||
|
console.log('index', index)
|
||||||
|
for (const on of [
|
||||||
|
shareOnPress,
|
||||||
|
statusOnPress,
|
||||||
|
accountOnPress,
|
||||||
|
instanceOnPress
|
||||||
|
]) {
|
||||||
|
on && on(index)
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
children={
|
||||||
|
<Icon
|
||||||
|
name='MoreHorizontal'
|
||||||
|
color={colors.secondary}
|
||||||
|
size={StyleConstants.Font.Size.L}
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
</Pressable>
|
||||||
|
) : null}
|
||||||
|
</View>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default TimelineHeaderDefault
|
@ -4,7 +4,7 @@ import { StyleConstants } from '@utils/styles/constants'
|
|||||||
import { useTheme } from '@utils/styles/ThemeManager'
|
import { useTheme } from '@utils/styles/ThemeManager'
|
||||||
import React, { useContext } from 'react'
|
import React, { useContext } from 'react'
|
||||||
import { useTranslation } from 'react-i18next'
|
import { useTranslation } from 'react-i18next'
|
||||||
import { Pressable, View } from 'react-native'
|
import { Platform, Pressable, View } from 'react-native'
|
||||||
import ContextMenu from 'react-native-context-menu-view'
|
import ContextMenu from 'react-native-context-menu-view'
|
||||||
import { ContextMenuContext } from './ContextMenu'
|
import { ContextMenuContext } from './ContextMenu'
|
||||||
import HeaderSharedAccount from './HeaderShared/Account'
|
import HeaderSharedAccount from './HeaderShared/Account'
|
||||||
@ -48,7 +48,7 @@ const TimelineHeaderDefault = ({ queryKey, status, highlighted }: Props) => {
|
|||||||
</View>
|
</View>
|
||||||
</View>
|
</View>
|
||||||
|
|
||||||
{queryKey ? (
|
{queryKey && !highlighted ? (
|
||||||
<Pressable
|
<Pressable
|
||||||
accessibilityHint={t('accessibilityHint')}
|
accessibilityHint={t('accessibilityHint')}
|
||||||
style={{
|
style={{
|
163
src/components/Timeline/Shared/HeaderNotification.android.tsx
Normal file
163
src/components/Timeline/Shared/HeaderNotification.android.tsx
Normal file
@ -0,0 +1,163 @@
|
|||||||
|
import contextMenuAccount from '@components/ContextMenu/account'
|
||||||
|
import contextMenuInstance from '@components/ContextMenu/instance'
|
||||||
|
import contextMenuShare from '@components/ContextMenu/share'
|
||||||
|
import contextMenuStatus from '@components/ContextMenu/status'
|
||||||
|
import Icon from '@components/Icon'
|
||||||
|
import {
|
||||||
|
RelationshipIncoming,
|
||||||
|
RelationshipOutgoing
|
||||||
|
} from '@components/Relationship'
|
||||||
|
import { QueryKeyTimeline } from '@utils/queryHooks/timeline'
|
||||||
|
import { StyleConstants } from '@utils/styles/constants'
|
||||||
|
import { useTheme } from '@utils/styles/ThemeManager'
|
||||||
|
import React, { useMemo } from 'react'
|
||||||
|
import { Pressable, View } from 'react-native'
|
||||||
|
import ContextMenu, { ContextMenuAction } from 'react-native-context-menu-view'
|
||||||
|
import HeaderSharedAccount from './HeaderShared/Account'
|
||||||
|
import HeaderSharedApplication from './HeaderShared/Application'
|
||||||
|
import HeaderSharedCreated from './HeaderShared/Created'
|
||||||
|
import HeaderSharedMuted from './HeaderShared/Muted'
|
||||||
|
import HeaderSharedVisibility from './HeaderShared/Visibility'
|
||||||
|
|
||||||
|
export interface Props {
|
||||||
|
queryKey?: QueryKeyTimeline
|
||||||
|
notification: Mastodon.Notification
|
||||||
|
}
|
||||||
|
|
||||||
|
const TimelineHeaderNotification = ({ queryKey, notification }: Props) => {
|
||||||
|
const { colors } = useTheme()
|
||||||
|
|
||||||
|
const contextMenuActions: ContextMenuAction[] = []
|
||||||
|
const status = notification.status
|
||||||
|
const shareOnPress =
|
||||||
|
status && status?.visibility !== 'direct'
|
||||||
|
? contextMenuShare({
|
||||||
|
actions: contextMenuActions,
|
||||||
|
type: 'status',
|
||||||
|
url: status.url || status.uri
|
||||||
|
})
|
||||||
|
: null
|
||||||
|
const statusOnPress = contextMenuStatus({
|
||||||
|
actions: contextMenuActions,
|
||||||
|
status,
|
||||||
|
queryKey
|
||||||
|
})
|
||||||
|
const accountOnPress = contextMenuAccount({
|
||||||
|
actions: contextMenuActions,
|
||||||
|
type: 'status',
|
||||||
|
queryKey,
|
||||||
|
id: status?.account.id
|
||||||
|
})
|
||||||
|
const instanceOnPress = contextMenuInstance({
|
||||||
|
actions: contextMenuActions,
|
||||||
|
status,
|
||||||
|
queryKey
|
||||||
|
})
|
||||||
|
|
||||||
|
const actions = useMemo(() => {
|
||||||
|
switch (notification.type) {
|
||||||
|
case 'follow':
|
||||||
|
return <RelationshipOutgoing id={notification.account.id} />
|
||||||
|
case 'follow_request':
|
||||||
|
return <RelationshipIncoming id={notification.account.id} />
|
||||||
|
default:
|
||||||
|
if (notification.status) {
|
||||||
|
return (
|
||||||
|
<Pressable
|
||||||
|
style={{
|
||||||
|
flex: 1,
|
||||||
|
flexDirection: 'row',
|
||||||
|
justifyContent: 'center',
|
||||||
|
paddingBottom: StyleConstants.Spacing.S
|
||||||
|
}}
|
||||||
|
children={
|
||||||
|
<ContextMenu
|
||||||
|
dropdownMenuMode
|
||||||
|
actions={contextMenuActions}
|
||||||
|
onPress={({ nativeEvent: { index } }) => {
|
||||||
|
for (const on of [
|
||||||
|
shareOnPress,
|
||||||
|
statusOnPress,
|
||||||
|
accountOnPress,
|
||||||
|
instanceOnPress
|
||||||
|
]) {
|
||||||
|
on && on(index)
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
children={
|
||||||
|
<Icon
|
||||||
|
name='MoreHorizontal'
|
||||||
|
color={colors.secondary}
|
||||||
|
size={StyleConstants.Font.Size.L}
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, [notification.type])
|
||||||
|
|
||||||
|
return (
|
||||||
|
<View style={{ flex: 1, flexDirection: 'row' }}>
|
||||||
|
<View
|
||||||
|
style={{
|
||||||
|
flex:
|
||||||
|
notification.type === 'follow' ||
|
||||||
|
notification.type === 'follow_request'
|
||||||
|
? 1
|
||||||
|
: 4
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<HeaderSharedAccount
|
||||||
|
account={
|
||||||
|
notification.status
|
||||||
|
? notification.status.account
|
||||||
|
: notification.account
|
||||||
|
}
|
||||||
|
{...((notification.type === 'follow' ||
|
||||||
|
notification.type === 'follow_request') && { withoutName: true })}
|
||||||
|
/>
|
||||||
|
<View
|
||||||
|
style={{
|
||||||
|
flexDirection: 'row',
|
||||||
|
alignItems: 'center',
|
||||||
|
marginTop: StyleConstants.Spacing.XS,
|
||||||
|
marginBottom: StyleConstants.Spacing.S
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<HeaderSharedCreated
|
||||||
|
created_at={
|
||||||
|
notification.status?.created_at || notification.created_at
|
||||||
|
}
|
||||||
|
edited_at={notification.status?.edited_at}
|
||||||
|
/>
|
||||||
|
{notification.status?.visibility ? (
|
||||||
|
<HeaderSharedVisibility
|
||||||
|
visibility={notification.status.visibility}
|
||||||
|
/>
|
||||||
|
) : null}
|
||||||
|
<HeaderSharedMuted muted={notification.status?.muted} />
|
||||||
|
<HeaderSharedApplication
|
||||||
|
application={notification.status?.application}
|
||||||
|
/>
|
||||||
|
</View>
|
||||||
|
</View>
|
||||||
|
|
||||||
|
<View
|
||||||
|
style={[
|
||||||
|
{ marginLeft: StyleConstants.Spacing.M },
|
||||||
|
notification.type === 'follow' ||
|
||||||
|
notification.type === 'follow_request'
|
||||||
|
? { flexShrink: 1 }
|
||||||
|
: { flex: 1 }
|
||||||
|
]}
|
||||||
|
>
|
||||||
|
{actions}
|
||||||
|
</View>
|
||||||
|
</View>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default TimelineHeaderNotification
|
@ -3,6 +3,7 @@ import {
|
|||||||
RelationshipIncoming,
|
RelationshipIncoming,
|
||||||
RelationshipOutgoing
|
RelationshipOutgoing
|
||||||
} from '@components/Relationship'
|
} from '@components/Relationship'
|
||||||
|
import { QueryKeyTimeline } from '@utils/queryHooks/timeline'
|
||||||
import { StyleConstants } from '@utils/styles/constants'
|
import { StyleConstants } from '@utils/styles/constants'
|
||||||
import { useTheme } from '@utils/styles/ThemeManager'
|
import { useTheme } from '@utils/styles/ThemeManager'
|
||||||
import React, { useContext, useMemo } from 'react'
|
import React, { useContext, useMemo } from 'react'
|
||||||
@ -16,6 +17,7 @@ import HeaderSharedMuted from './HeaderShared/Muted'
|
|||||||
import HeaderSharedVisibility from './HeaderShared/Visibility'
|
import HeaderSharedVisibility from './HeaderShared/Visibility'
|
||||||
|
|
||||||
export interface Props {
|
export interface Props {
|
||||||
|
queryKey: QueryKeyTimeline
|
||||||
notification: Mastodon.Notification
|
notification: Mastodon.Notification
|
||||||
}
|
}
|
||||||
|
|
@ -32,7 +32,7 @@ const HeaderSharedCreated = React.memo(
|
|||||||
/>
|
/>
|
||||||
</>
|
</>
|
||||||
) : (
|
) : (
|
||||||
<RelativeTime type='past' time={actualTime} />
|
<RelativeTime time={actualTime} />
|
||||||
)}
|
)}
|
||||||
</CustomText>
|
</CustomText>
|
||||||
{edited_at ? (
|
{edited_at ? (
|
||||||
|
@ -269,7 +269,7 @@ const TimelinePoll: React.FC<Props> = ({
|
|||||||
))
|
))
|
||||||
}, [theme, allOptions])
|
}, [theme, allOptions])
|
||||||
|
|
||||||
const pollVoteCounts = useMemo(() => {
|
const pollVoteCounts = () => {
|
||||||
if (poll.voters_count !== null) {
|
if (poll.voters_count !== null) {
|
||||||
return (
|
return (
|
||||||
t('shared.poll.meta.count.voters', { count: poll.voters_count }) + ' • '
|
t('shared.poll.meta.count.voters', { count: poll.voters_count }) + ' • '
|
||||||
@ -279,9 +279,9 @@ const TimelinePoll: React.FC<Props> = ({
|
|||||||
t('shared.poll.meta.count.votes', { count: poll.votes_count }) + ' • '
|
t('shared.poll.meta.count.votes', { count: poll.votes_count }) + ' • '
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}, [poll.voters_count, poll.votes_count])
|
}
|
||||||
|
|
||||||
const pollExpiration = useMemo(() => {
|
const pollExpiration = () => {
|
||||||
if (poll.expired) {
|
if (poll.expired) {
|
||||||
return t('shared.poll.meta.expiration.expired')
|
return t('shared.poll.meta.expiration.expired')
|
||||||
} else {
|
} else {
|
||||||
@ -289,12 +289,12 @@ const TimelinePoll: React.FC<Props> = ({
|
|||||||
return (
|
return (
|
||||||
<Trans
|
<Trans
|
||||||
i18nKey='componentTimeline:shared.poll.meta.expiration.until'
|
i18nKey='componentTimeline:shared.poll.meta.expiration.until'
|
||||||
components={[<RelativeTime type='future' time={poll.expires_at} />]}
|
components={[<RelativeTime time={poll.expires_at} />]}
|
||||||
/>
|
/>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}, [theme, i18n.language, poll.expired, poll.expires_at])
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<View style={{ marginTop: StyleConstants.Spacing.M }}>
|
<View style={{ marginTop: StyleConstants.Spacing.M }}>
|
||||||
@ -312,8 +312,8 @@ const TimelinePoll: React.FC<Props> = ({
|
|||||||
fontStyle='S'
|
fontStyle='S'
|
||||||
style={{ flexShrink: 1, color: colors.secondary }}
|
style={{ flexShrink: 1, color: colors.secondary }}
|
||||||
>
|
>
|
||||||
{pollVoteCounts}
|
{pollVoteCounts()}
|
||||||
{pollExpiration}
|
{pollExpiration()}
|
||||||
</CustomText>
|
</CustomText>
|
||||||
</View>
|
</View>
|
||||||
</View>
|
</View>
|
||||||
|
@ -48,14 +48,18 @@ const TimelineTranslate = React.memo(
|
|||||||
const [detectedLanguage, setDetectedLanguage] = useState<string>('')
|
const [detectedLanguage, setDetectedLanguage] = useState<string>('')
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const detect = async () => {
|
const detect = async () => {
|
||||||
const result = await detectLanguage(text.join(`\n\n`))
|
const result = await detectLanguage(text.join(`\n\n`)).catch(() => {
|
||||||
setDetectedLanguage(result.detected.slice(0, 2))
|
// No need to log language detection failure
|
||||||
|
})
|
||||||
|
result?.detected && setDetectedLanguage(result.detected.slice(0, 2))
|
||||||
}
|
}
|
||||||
detect()
|
detect()
|
||||||
}, [])
|
}, [])
|
||||||
|
|
||||||
const settingsLanguage = useSelector(getSettingsLanguage)
|
const settingsLanguage = useSelector(getSettingsLanguage)
|
||||||
const targetLanguage = settingsLanguage || Localization.locale || 'en'
|
const targetLanguage = settingsLanguage?.startsWith('en')
|
||||||
|
? Localization.locale || settingsLanguage || 'en'
|
||||||
|
: settingsLanguage || Localization.locale || 'en'
|
||||||
|
|
||||||
const [enabled, setEnabled] = useState(false)
|
const [enabled, setEnabled] = useState(false)
|
||||||
const { refetch, data, isLoading, isSuccess, isError } = useTranslateQuery({
|
const { refetch, data, isLoading, isSuccess, isError } = useTranslateQuery({
|
||||||
|
@ -1,15 +1,8 @@
|
|||||||
import analytics from '@components/analytics'
|
|
||||||
import { ActionSheetOptions } from '@expo/react-native-action-sheet'
|
import { ActionSheetOptions } from '@expo/react-native-action-sheet'
|
||||||
import { store } from '@root/store'
|
import { store } from '@root/store'
|
||||||
import { getInstanceConfigurationStatusMaxAttachments } from '@utils/slices/instancesSlice'
|
import { getInstanceConfigurationStatusMaxAttachments } from '@utils/slices/instancesSlice'
|
||||||
import { manipulateAsync, SaveFormat } from 'expo-image-manipulator'
|
|
||||||
import * as ExpoImagePicker from 'expo-image-picker'
|
|
||||||
import i18next from 'i18next'
|
import i18next from 'i18next'
|
||||||
import { Alert, Linking, Platform } from 'react-native'
|
import { Asset, launchImageLibrary } from 'react-native-image-picker'
|
||||||
import ImagePicker, {
|
|
||||||
Image,
|
|
||||||
ImageOrVideo
|
|
||||||
} from 'react-native-image-crop-picker'
|
|
||||||
|
|
||||||
export interface Props {
|
export interface Props {
|
||||||
mediaType?: 'photo' | 'video'
|
mediaType?: 'photo' | 'video'
|
||||||
@ -28,43 +21,7 @@ const mediaSelector = async ({
|
|||||||
maximum,
|
maximum,
|
||||||
indicateMaximum = false,
|
indicateMaximum = false,
|
||||||
showActionSheetWithOptions
|
showActionSheetWithOptions
|
||||||
}: Props): Promise<({ uri: string } & Omit<ImageOrVideo, 'path'>)[]> => {
|
}: Props): Promise<Asset[]> => {
|
||||||
const checkLibraryPermission = async (): Promise<boolean> => {
|
|
||||||
const { status } =
|
|
||||||
await ExpoImagePicker.requestMediaLibraryPermissionsAsync()
|
|
||||||
if (status !== 'granted') {
|
|
||||||
Alert.alert(
|
|
||||||
i18next.t('componentMediaSelector:library.alert.title'),
|
|
||||||
i18next.t('componentMediaSelector:library.alert.message'),
|
|
||||||
[
|
|
||||||
{
|
|
||||||
text: i18next.t('common:buttons.cancel'),
|
|
||||||
style: 'cancel',
|
|
||||||
onPress: () =>
|
|
||||||
analytics('mediaSelector_nopermission', {
|
|
||||||
action: 'cancel'
|
|
||||||
})
|
|
||||||
},
|
|
||||||
{
|
|
||||||
text: i18next.t(
|
|
||||||
'componentMediaSelector:library.alert.buttons.settings'
|
|
||||||
),
|
|
||||||
style: 'default',
|
|
||||||
onPress: () => {
|
|
||||||
analytics('mediaSelector_nopermission', {
|
|
||||||
action: 'settings'
|
|
||||||
})
|
|
||||||
Linking.openURL('app-settings:')
|
|
||||||
}
|
|
||||||
}
|
|
||||||
]
|
|
||||||
)
|
|
||||||
return false
|
|
||||||
} else {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const _maximum =
|
const _maximum =
|
||||||
maximum ||
|
maximum ||
|
||||||
getInstanceConfigurationStatusMaxAttachments(store.getState()) ||
|
getInstanceConfigurationStatusMaxAttachments(store.getState()) ||
|
||||||
@ -105,79 +62,30 @@ const mediaSelector = async ({
|
|||||||
|
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
const selectImage = async () => {
|
const selectImage = async () => {
|
||||||
const images = await ImagePicker.openPicker({
|
const images = await launchImageLibrary({
|
||||||
mediaType: 'photo',
|
mediaType: 'photo',
|
||||||
includeExif: false,
|
...(resize && { maxWidth: resize.width, maxHeight: resize.height }),
|
||||||
multiple: true,
|
includeBase64: false,
|
||||||
minFiles: 1,
|
includeExtra: false,
|
||||||
maxFiles: _maximum,
|
selectionLimit: _maximum
|
||||||
smartAlbums: ['UserLibrary'],
|
})
|
||||||
writeTempFile: false,
|
|
||||||
loadingLabelText: ''
|
|
||||||
}).catch(() => {})
|
|
||||||
|
|
||||||
if (!images) {
|
if (!images.assets) {
|
||||||
return reject()
|
return reject()
|
||||||
}
|
}
|
||||||
|
|
||||||
// react-native-image-crop-picker may return HEIC as JPG that causes upload failure
|
return resolve(images.assets)
|
||||||
if (Platform.OS === 'ios') {
|
|
||||||
for (const [index, image] of images.entries()) {
|
|
||||||
if (image.mime === 'image/heic') {
|
|
||||||
const converted = await manipulateAsync(image.sourceURL!, [], {
|
|
||||||
base64: false,
|
|
||||||
compress: 0.8,
|
|
||||||
format: SaveFormat.JPEG
|
|
||||||
})
|
|
||||||
images[index] = {
|
|
||||||
...images[index],
|
|
||||||
sourceURL: converted.uri,
|
|
||||||
mime: 'image/jpeg'
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!resize) {
|
|
||||||
return resolve(
|
|
||||||
images.map(image => ({
|
|
||||||
...image,
|
|
||||||
uri: image.sourceURL || `file://${image.path}`
|
|
||||||
}))
|
|
||||||
)
|
|
||||||
} else {
|
|
||||||
const croppedImages: Image[] = []
|
|
||||||
for (const image of images) {
|
|
||||||
const croppedImage = await ImagePicker.openCropper({
|
|
||||||
mediaType: 'photo',
|
|
||||||
path: image.path,
|
|
||||||
width: resize.width,
|
|
||||||
height: resize.height,
|
|
||||||
cropperChooseText: i18next.t('common:buttons.apply'),
|
|
||||||
cropperCancelText: i18next.t('common:buttons.cancel'),
|
|
||||||
hideBottomControls: true
|
|
||||||
}).catch(() => {})
|
|
||||||
croppedImage && croppedImages.push(croppedImage)
|
|
||||||
}
|
|
||||||
return resolve(
|
|
||||||
croppedImages.map(image => ({
|
|
||||||
...image,
|
|
||||||
uri: `file://${image.path}`
|
|
||||||
}))
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
const selectVideo = async () => {
|
const selectVideo = async () => {
|
||||||
const video = await ImagePicker.openPicker({
|
const video = await launchImageLibrary({
|
||||||
mediaType: 'video',
|
mediaType: 'video',
|
||||||
includeExif: false,
|
includeBase64: false,
|
||||||
loadingLabelText: ''
|
includeExtra: false,
|
||||||
}).catch(() => {})
|
selectionLimit: 1
|
||||||
|
})
|
||||||
|
|
||||||
if (video) {
|
if (video.assets?.[0]) {
|
||||||
return resolve([
|
return resolve(video.assets)
|
||||||
{ ...video, uri: video.sourceURL || `file://${video.path}` }
|
|
||||||
])
|
|
||||||
} else {
|
} else {
|
||||||
return reject()
|
return reject()
|
||||||
}
|
}
|
||||||
@ -189,10 +97,6 @@ const mediaSelector = async ({
|
|||||||
cancelButtonIndex: mediaType ? 1 : 2
|
cancelButtonIndex: mediaType ? 1 : 2
|
||||||
},
|
},
|
||||||
async buttonIndex => {
|
async buttonIndex => {
|
||||||
if (!(await checkLibraryPermission())) {
|
|
||||||
return reject()
|
|
||||||
}
|
|
||||||
|
|
||||||
switch (mediaType) {
|
switch (mediaType) {
|
||||||
case 'photo':
|
case 'photo':
|
||||||
if (buttonIndex === 0) {
|
if (buttonIndex === 0) {
|
||||||
|
@ -1,149 +0,0 @@
|
|||||||
import { store } from '@root/store'
|
|
||||||
import { getInstanceConfigurationMediaAttachments } from '@utils/slices/instancesSlice'
|
|
||||||
import { Action, manipulateAsync, SaveFormat } from 'expo-image-manipulator'
|
|
||||||
import i18next from 'i18next'
|
|
||||||
import { Platform } from 'react-native'
|
|
||||||
import ImagePicker from 'react-native-image-crop-picker'
|
|
||||||
|
|
||||||
export interface Props {
|
|
||||||
type: 'image' | 'video'
|
|
||||||
uri: string // This can be pure path or uri starting with file://
|
|
||||||
mime?: string
|
|
||||||
transform: {
|
|
||||||
imageFormat?: SaveFormat.JPEG | SaveFormat.PNG
|
|
||||||
resize?: boolean
|
|
||||||
width?: number
|
|
||||||
height?: number
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const getFileExtension = (uri: string) => {
|
|
||||||
const extension = uri.split('.').pop()
|
|
||||||
// Using mime type standard of jpeg
|
|
||||||
return extension === 'jpg' ? 'jpeg' : extension
|
|
||||||
}
|
|
||||||
|
|
||||||
const mediaTransformation = async ({
|
|
||||||
type,
|
|
||||||
uri,
|
|
||||||
mime,
|
|
||||||
transform
|
|
||||||
}: Props): Promise<{
|
|
||||||
uri: string
|
|
||||||
mime: string
|
|
||||||
width: number
|
|
||||||
height: number
|
|
||||||
}> => {
|
|
||||||
const configurationMediaAttachments =
|
|
||||||
getInstanceConfigurationMediaAttachments(store.getState())
|
|
||||||
|
|
||||||
const fileExtension = getFileExtension(uri)
|
|
||||||
|
|
||||||
switch (type) {
|
|
||||||
case 'image':
|
|
||||||
if (mime === 'image/gif' || fileExtension === 'gif') {
|
|
||||||
return Promise.reject('GIFs should not be transformed')
|
|
||||||
}
|
|
||||||
let targetFormat: SaveFormat.JPEG | SaveFormat.PNG = SaveFormat.JPEG
|
|
||||||
|
|
||||||
const supportedImageTypes =
|
|
||||||
configurationMediaAttachments.supported_mime_types.filter(mime =>
|
|
||||||
mime.startsWith('image/')
|
|
||||||
)
|
|
||||||
|
|
||||||
// @ts-ignore
|
|
||||||
const transformations: Action[] = [
|
|
||||||
!transform.resize && (transform.width || transform.height)
|
|
||||||
? {
|
|
||||||
resize: { width: transform.width, height: transform.height }
|
|
||||||
}
|
|
||||||
: null
|
|
||||||
].filter(t => !!t)
|
|
||||||
|
|
||||||
if (mime) {
|
|
||||||
if (
|
|
||||||
mime !== `image/${fileExtension}` ||
|
|
||||||
!supportedImageTypes.includes(mime)
|
|
||||||
) {
|
|
||||||
targetFormat = transform.imageFormat || SaveFormat.JPEG
|
|
||||||
} else {
|
|
||||||
targetFormat = mime.split('/').pop() as any
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
if (!fileExtension) {
|
|
||||||
return Promise.reject('Unable to get file extension')
|
|
||||||
}
|
|
||||||
if (!supportedImageTypes.includes(`image/${fileExtension}`)) {
|
|
||||||
targetFormat = transform.imageFormat || SaveFormat.JPEG
|
|
||||||
} else {
|
|
||||||
targetFormat = fileExtension as any
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const converted = await manipulateAsync(uri, transformations, {
|
|
||||||
base64: false,
|
|
||||||
compress: Platform.OS === 'ios' ? 0.8 : 1,
|
|
||||||
format: targetFormat
|
|
||||||
})
|
|
||||||
|
|
||||||
if (transform.resize) {
|
|
||||||
const resized = await ImagePicker.openCropper({
|
|
||||||
mediaType: 'photo',
|
|
||||||
path: converted.uri,
|
|
||||||
width: transform.width,
|
|
||||||
height: transform.height,
|
|
||||||
cropperChooseText: i18next.t('common:buttons.apply'),
|
|
||||||
cropperCancelText: i18next.t('common:buttons.cancel'),
|
|
||||||
hideBottomControls: true
|
|
||||||
})
|
|
||||||
if (!resized) {
|
|
||||||
return Promise.reject('Resize failed')
|
|
||||||
} else {
|
|
||||||
return {
|
|
||||||
uri: resized.path,
|
|
||||||
mime: resized.mime,
|
|
||||||
width: resized.width,
|
|
||||||
height: resized.height
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
return {
|
|
||||||
uri: converted.uri,
|
|
||||||
mime: transform.imageFormat || SaveFormat.JPEG,
|
|
||||||
width: converted.width,
|
|
||||||
height: converted.height
|
|
||||||
}
|
|
||||||
}
|
|
||||||
case 'video':
|
|
||||||
const supportedVideoTypes =
|
|
||||||
configurationMediaAttachments.supported_mime_types.filter(mime =>
|
|
||||||
mime.startsWith('video/')
|
|
||||||
)
|
|
||||||
|
|
||||||
if (mime) {
|
|
||||||
if (mime !== `video/${fileExtension}`) {
|
|
||||||
console.warn('Video mime type and file extension does not match')
|
|
||||||
}
|
|
||||||
if (!supportedVideoTypes.includes(mime)) {
|
|
||||||
return Promise.reject('Video file type is not supported')
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
if (!fileExtension) {
|
|
||||||
return Promise.reject('Unable to get file extension')
|
|
||||||
}
|
|
||||||
if (!supportedVideoTypes.includes(`video/${fileExtension}`)) {
|
|
||||||
return Promise.reject('Video file type is not supported')
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
uri: uri,
|
|
||||||
mime: mime || `video/${fileExtension}`,
|
|
||||||
width: 0,
|
|
||||||
height: 0
|
|
||||||
}
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export default mediaTransformation
|
|
@ -8,6 +8,7 @@ export default {
|
|||||||
screenImageViewer: require('./screens/imageViewer'),
|
screenImageViewer: require('./screens/imageViewer'),
|
||||||
screenTabs: require('./screens/tabs'),
|
screenTabs: require('./screens/tabs'),
|
||||||
|
|
||||||
|
componentContextMenu: require('./components/contextMenu'),
|
||||||
componentEmojis: require('./components/emojis'),
|
componentEmojis: require('./components/emojis'),
|
||||||
componentInstance: require('./components/instance'),
|
componentInstance: require('./components/instance'),
|
||||||
componentMediaSelector: require('./components/mediaSelector'),
|
componentMediaSelector: require('./components/mediaSelector'),
|
||||||
|
@ -1,12 +1,14 @@
|
|||||||
{
|
{
|
||||||
"accessibilityHint": "Funktionen für diesen Tröt - wie z. B. Autor und Originaltröt",
|
"accessibilityHint": "Funktionen für diesen Tröt - wie z. B. Autor und Originaltröt",
|
||||||
"account": {
|
"account": {
|
||||||
"title": "",
|
"title": "Benutzeraktionen",
|
||||||
"mute": {
|
"mute": {
|
||||||
"action": "Profil stummschalten"
|
"action_false": "Profil stummschalten",
|
||||||
|
"action_true": ""
|
||||||
},
|
},
|
||||||
"block": {
|
"block": {
|
||||||
"action": "Nutzer blockieren"
|
"action_false": "Nutzer blockieren",
|
||||||
|
"action_true": ""
|
||||||
},
|
},
|
||||||
"reports": {
|
"reports": {
|
||||||
"action": "User melden"
|
"action": "User melden"
|
||||||
@ -59,12 +61,12 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"mute": {
|
"mute": {
|
||||||
"action-muted_false": "",
|
"action_false": "",
|
||||||
"action-muted_true": ""
|
"action_true": ""
|
||||||
},
|
},
|
||||||
"pin": {
|
"pin": {
|
||||||
"action-pinned_false": "",
|
"action_false": "",
|
||||||
"action-pinned_true": ""
|
"action_true": ""
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -139,7 +139,7 @@
|
|||||||
},
|
},
|
||||||
"expiration": {
|
"expiration": {
|
||||||
"expired": "Abstimmung abgelaufen",
|
"expired": "Abstimmung abgelaufen",
|
||||||
"until": "Läuft in <0 /> ab"
|
"until": "Läuft <0 /> ab"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -78,9 +78,7 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"fontSize": {
|
"fontSize": {
|
||||||
"showcase": "Beispieltröt",
|
|
||||||
"demo": "<p>Dies ist ein Beispieltröt😊. Du kannst aus mehreren der unteren Möglichkeiten auswählen.<br /><br />Diese Einstellung betrifft ausschließlich die Haupteinstellungen, nicht die Schriftgröße in anderen Bereichen der App.</p>",
|
"demo": "<p>Dies ist ein Beispieltröt😊. Du kannst aus mehreren der unteren Möglichkeiten auswählen.<br /><br />Diese Einstellung betrifft ausschließlich die Haupteinstellungen, nicht die Schriftgröße in anderen Bereichen der App.</p>",
|
||||||
"availableSizes": "Verfügbare Größen",
|
|
||||||
"sizes": {
|
"sizes": {
|
||||||
"S": "S",
|
"S": "S",
|
||||||
"M": "M – Standard",
|
"M": "M – Standard",
|
||||||
|
@ -3,10 +3,12 @@
|
|||||||
"account": {
|
"account": {
|
||||||
"title": "User actions",
|
"title": "User actions",
|
||||||
"mute": {
|
"mute": {
|
||||||
"action": "Mute user"
|
"action_false": "Mute user",
|
||||||
|
"action_true": "Unmute user"
|
||||||
},
|
},
|
||||||
"block": {
|
"block": {
|
||||||
"action": "Block user"
|
"action_false": "Block user",
|
||||||
|
"action_true": "Unblock user"
|
||||||
},
|
},
|
||||||
"reports": {
|
"reports": {
|
||||||
"action": "Report user"
|
"action": "Report user"
|
||||||
@ -59,12 +61,12 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"mute": {
|
"mute": {
|
||||||
"action-muted_false": "Mute toot and replies",
|
"action_false": "Mute toot and replies",
|
||||||
"action-muted_true": "Unmute toot and replies"
|
"action_true": "Unmute toot and replies"
|
||||||
},
|
},
|
||||||
"pin": {
|
"pin": {
|
||||||
"action-pinned_false": "Pin toot",
|
"action_false": "Pin toot",
|
||||||
"action-pinned_true": "Unpin toot"
|
"action_true": "Unpin toot"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -139,7 +139,7 @@
|
|||||||
},
|
},
|
||||||
"expiration": {
|
"expiration": {
|
||||||
"expired": "Vote expired",
|
"expired": "Vote expired",
|
||||||
"until": "Expires in <0 />"
|
"until": "Expires <0 />"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -78,9 +78,7 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"fontSize": {
|
"fontSize": {
|
||||||
"showcase": "Example toot",
|
|
||||||
"demo": "<p>This is a demo toot😊. You can choose from several options from below.<br /><br />This setting only affects the main content of toots, but not other font sizes.</p>",
|
"demo": "<p>This is a demo toot😊. You can choose from several options from below.<br /><br />This setting only affects the main content of toots, but not other font sizes.</p>",
|
||||||
"availableSizes": "Available sizes",
|
|
||||||
"sizes": {
|
"sizes": {
|
||||||
"S": "S",
|
"S": "S",
|
||||||
"M": "M - Default",
|
"M": "M - Default",
|
||||||
@ -147,7 +145,8 @@
|
|||||||
"group": "Group {{index}}",
|
"group": "Group {{index}}",
|
||||||
"label": "Label",
|
"label": "Label",
|
||||||
"content": "Content"
|
"content": "Content"
|
||||||
}
|
},
|
||||||
|
"mediaSelectionFailed": "Image processing failed. Please try again."
|
||||||
},
|
},
|
||||||
"push": {
|
"push": {
|
||||||
"notAvailable": "Your phone does not support tooot's push notification",
|
"notAvailable": "Your phone does not support tooot's push notification",
|
||||||
|
@ -8,6 +8,7 @@ export default {
|
|||||||
screenImageViewer: require('./screens/imageViewer'),
|
screenImageViewer: require('./screens/imageViewer'),
|
||||||
screenTabs: require('./screens/tabs'),
|
screenTabs: require('./screens/tabs'),
|
||||||
|
|
||||||
|
componentContextMenu: require('./components/contextMenu'),
|
||||||
componentEmojis: require('./components/emojis'),
|
componentEmojis: require('./components/emojis'),
|
||||||
componentInstance: require('./components/instance'),
|
componentInstance: require('./components/instance'),
|
||||||
componentMediaSelector: require('./components/mediaSelector'),
|
componentMediaSelector: require('./components/mediaSelector'),
|
||||||
|
@ -1,23 +1,25 @@
|
|||||||
{
|
{
|
||||||
"accessibilityHint": "Azioni per questo toot, per l'utente che l'ha pubblicato o per il toot stesso",
|
"accessibilityHint": "Azioni per questo toot, per l'utente che l'ha pubblicato o per il toot stesso",
|
||||||
"account": {
|
"account": {
|
||||||
"title": "",
|
"title": "Azioni utente",
|
||||||
"mute": {
|
"mute": {
|
||||||
"action": "Muta utente"
|
"action_false": "Muta utente",
|
||||||
|
"action_true": "Riattiva utente"
|
||||||
},
|
},
|
||||||
"block": {
|
"block": {
|
||||||
"action": "Blocca utente"
|
"action_false": "Blocca utente",
|
||||||
|
"action_true": "Sblocca utente"
|
||||||
},
|
},
|
||||||
"reports": {
|
"reports": {
|
||||||
"action": "Segnala utente"
|
"action": "Segnala utente"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"instance": {
|
"instance": {
|
||||||
"title": "",
|
"title": "Azione sull'istanza",
|
||||||
"block": {
|
"block": {
|
||||||
"action": "Blocca istanza {{instance}}",
|
"action": "Blocca istanza {{instance}}",
|
||||||
"alert": {
|
"alert": {
|
||||||
"title": "",
|
"title": "Confermi di voler bloccare l'istanza {{instance}}?",
|
||||||
"message": "Sarebbe meglio mutare o bloccare singoli utenti.\n\nSe blocchi un'istanza, tutti i suoi contenuti a te relativi, inclusi tutti i tuoi seguaci da questa, saranno rimossi.",
|
"message": "Sarebbe meglio mutare o bloccare singoli utenti.\n\nSe blocchi un'istanza, tutti i suoi contenuti a te relativi, inclusi tutti i tuoi seguaci da questa, saranno rimossi.",
|
||||||
"buttons": {
|
"buttons": {
|
||||||
"confirm": "Ho capito"
|
"confirm": "Ho capito"
|
||||||
@ -34,7 +36,7 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"status": {
|
"status": {
|
||||||
"title": "",
|
"title": "Azioni sul toot",
|
||||||
"edit": {
|
"edit": {
|
||||||
"action": "Modifica toot"
|
"action": "Modifica toot"
|
||||||
},
|
},
|
||||||
@ -42,29 +44,29 @@
|
|||||||
"action": "Cancella toot",
|
"action": "Cancella toot",
|
||||||
"alert": {
|
"alert": {
|
||||||
"title": "Conferma?",
|
"title": "Conferma?",
|
||||||
"message": "",
|
"message": "Tutti i retoot, gli apprezzamenti, e le risposte, saranno cancellati.",
|
||||||
"buttons": {
|
"buttons": {
|
||||||
"confirm": "Ho capito"
|
"confirm": "Ho capito"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"deleteEdit": {
|
"deleteEdit": {
|
||||||
"action": "",
|
"action": "Cancella e ripubblica toot",
|
||||||
"alert": {
|
"alert": {
|
||||||
"title": "",
|
"title": "Confermi cancellazione e ripubblicazione?",
|
||||||
"message": "",
|
"message": "Tutti i retoot, gli apprezzamenti, e le risposte, saranno cancellati.",
|
||||||
"buttons": {
|
"buttons": {
|
||||||
"confirm": "Ho capito"
|
"confirm": "Ho capito"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"mute": {
|
"mute": {
|
||||||
"action-muted_false": "",
|
"action_false": "Muta toot e le sue risposte",
|
||||||
"action-muted_true": ""
|
"action_true": "Riattiva toot e le sue risposte"
|
||||||
},
|
},
|
||||||
"pin": {
|
"pin": {
|
||||||
"action-pinned_false": "",
|
"action_false": "Fissa toot",
|
||||||
"action-pinned_true": ""
|
"action_true": "Togli toot all'alto"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -14,7 +14,7 @@
|
|||||||
"base": "Per accedere, verrà aperta una pagina del browser di sistema. I dati di accesso del tuo account sono protetti."
|
"base": "Per accedere, verrà aperta una pagina del browser di sistema. I dati di accesso del tuo account sono protetti."
|
||||||
},
|
},
|
||||||
"terms": {
|
"terms": {
|
||||||
"base": ""
|
"base": "Accedendo, accetti la <0>politica sulla privacy</0> e i <1>termini di servizio</1>."
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"update": {
|
"update": {
|
||||||
|
@ -1,10 +1,10 @@
|
|||||||
{
|
{
|
||||||
"title": "Seleziona origine media",
|
"title": "Seleziona origine media",
|
||||||
"options": {
|
"options": {
|
||||||
"image": "",
|
"image": "Carica foto",
|
||||||
"image_max": "",
|
"image_max": "Carica foto (massimo {{max}})",
|
||||||
"video": "",
|
"video": "Carica video",
|
||||||
"video_max": ""
|
"video_max": "Carica video (max {{max}})"
|
||||||
},
|
},
|
||||||
"library": {
|
"library": {
|
||||||
"alert": {
|
"alert": {
|
||||||
|
@ -30,7 +30,7 @@
|
|||||||
"default": "{{name}} ha ricondiviso",
|
"default": "{{name}} ha ricondiviso",
|
||||||
"notification": "{{name}} ha ricondiviso il tuo toot"
|
"notification": "{{name}} ha ricondiviso il tuo toot"
|
||||||
},
|
},
|
||||||
"update": ""
|
"update": "Retoot ha subito una modifica"
|
||||||
},
|
},
|
||||||
"actions": {
|
"actions": {
|
||||||
"reply": {
|
"reply": {
|
||||||
@ -139,7 +139,7 @@
|
|||||||
},
|
},
|
||||||
"expiration": {
|
"expiration": {
|
||||||
"expired": "Voto scaduto",
|
"expired": "Voto scaduto",
|
||||||
"until": "Scade in <0 />"
|
"until": "Scade <0 />"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"content": {
|
"content": {
|
||||||
"altText": {
|
"altText": {
|
||||||
"heading": ""
|
"heading": "Testo descrittivo"
|
||||||
},
|
},
|
||||||
"notificationsFilter": {
|
"notificationsFilter": {
|
||||||
"heading": "Filtra notifiche per tipo",
|
"heading": "Filtra notifiche per tipo",
|
||||||
@ -12,8 +12,8 @@
|
|||||||
"reblog": "$t(screenTabs:me.push.reblog.heading)",
|
"reblog": "$t(screenTabs:me.push.reblog.heading)",
|
||||||
"mention": "$t(screenTabs:me.push.mention.heading)",
|
"mention": "$t(screenTabs:me.push.mention.heading)",
|
||||||
"poll": "$t(screenTabs:me.push.poll.heading)",
|
"poll": "$t(screenTabs:me.push.poll.heading)",
|
||||||
"status": "",
|
"status": "Toot da utenti seguiti",
|
||||||
"update": ""
|
"update": "Retoot ha subito una modifica"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -168,7 +168,7 @@
|
|||||||
"header": {
|
"header": {
|
||||||
"title": "Bozza"
|
"title": "Bozza"
|
||||||
},
|
},
|
||||||
"warning": "",
|
"warning": "Le bozze sono salvate solo in locale, e potrebbero essere perse in seguito a problemi. Evita di usarle per archiviazione a lungo termine.",
|
||||||
"content": {
|
"content": {
|
||||||
"accessibilityHint": "Bozza salvata, premi per modificarla",
|
"accessibilityHint": "Bozza salvata, premi per modificarla",
|
||||||
"textEmpty": "Testo vuoto"
|
"textEmpty": "Testo vuoto"
|
||||||
|
@ -78,9 +78,7 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"fontSize": {
|
"fontSize": {
|
||||||
"showcase": "Toot di esempio",
|
|
||||||
"demo": "<p>Questo toot è un esempio 😺️. Puoi scegliere diverse opzioni di grandezza del testo qui sotto.<br /><br />Questa impostazione si applica solo al testo dei toot, non anche agli altri testi della app.</p>",
|
"demo": "<p>Questo toot è un esempio 😺️. Puoi scegliere diverse opzioni di grandezza del testo qui sotto.<br /><br />Questa impostazione si applica solo al testo dei toot, non anche agli altri testi della app.</p>",
|
||||||
"availableSizes": "Dimensioni testo",
|
|
||||||
"sizes": {
|
"sizes": {
|
||||||
"S": "S",
|
"S": "S",
|
||||||
"M": "M - Predefinito",
|
"M": "M - Predefinito",
|
||||||
@ -170,7 +168,7 @@
|
|||||||
"heading": "Nuovi seguaci"
|
"heading": "Nuovi seguaci"
|
||||||
},
|
},
|
||||||
"follow_request": {
|
"follow_request": {
|
||||||
"heading": ""
|
"heading": "Richiesta di seguirti"
|
||||||
},
|
},
|
||||||
"favourite": {
|
"favourite": {
|
||||||
"heading": "Apprezzamenti"
|
"heading": "Apprezzamenti"
|
||||||
@ -185,7 +183,7 @@
|
|||||||
"heading": "Novità sui sondaggi"
|
"heading": "Novità sui sondaggi"
|
||||||
},
|
},
|
||||||
"status": {
|
"status": {
|
||||||
"heading": ""
|
"heading": "Toot da utenti seguiti"
|
||||||
},
|
},
|
||||||
"howitworks": "Scopri come funziona il traversamento dei messaggi"
|
"howitworks": "Scopri come funziona il traversamento dei messaggi"
|
||||||
},
|
},
|
||||||
|
22
src/i18n/ja/common.json
Normal file
22
src/i18n/ja/common.json
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
{
|
||||||
|
"buttons": {
|
||||||
|
"OK": "",
|
||||||
|
"apply": "",
|
||||||
|
"cancel": ""
|
||||||
|
},
|
||||||
|
"customEmoji": {
|
||||||
|
"accessibilityLabel": ""
|
||||||
|
},
|
||||||
|
"message": {
|
||||||
|
"success": {
|
||||||
|
"message": ""
|
||||||
|
},
|
||||||
|
"warning": {
|
||||||
|
"message": ""
|
||||||
|
},
|
||||||
|
"error": {
|
||||||
|
"message": ""
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"separator": ""
|
||||||
|
}
|
72
src/i18n/ja/components/contextMenu.json
Normal file
72
src/i18n/ja/components/contextMenu.json
Normal file
@ -0,0 +1,72 @@
|
|||||||
|
{
|
||||||
|
"accessibilityHint": "",
|
||||||
|
"account": {
|
||||||
|
"title": "",
|
||||||
|
"mute": {
|
||||||
|
"action_false": "",
|
||||||
|
"action_true": ""
|
||||||
|
},
|
||||||
|
"block": {
|
||||||
|
"action_false": "",
|
||||||
|
"action_true": ""
|
||||||
|
},
|
||||||
|
"reports": {
|
||||||
|
"action": ""
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"instance": {
|
||||||
|
"title": "",
|
||||||
|
"block": {
|
||||||
|
"action": "",
|
||||||
|
"alert": {
|
||||||
|
"title": "",
|
||||||
|
"message": "",
|
||||||
|
"buttons": {
|
||||||
|
"confirm": ""
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"share": {
|
||||||
|
"status": {
|
||||||
|
"action": ""
|
||||||
|
},
|
||||||
|
"account": {
|
||||||
|
"action": ""
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"status": {
|
||||||
|
"title": "",
|
||||||
|
"edit": {
|
||||||
|
"action": ""
|
||||||
|
},
|
||||||
|
"delete": {
|
||||||
|
"action": "",
|
||||||
|
"alert": {
|
||||||
|
"title": "",
|
||||||
|
"message": "",
|
||||||
|
"buttons": {
|
||||||
|
"confirm": ""
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"deleteEdit": {
|
||||||
|
"action": "",
|
||||||
|
"alert": {
|
||||||
|
"title": "",
|
||||||
|
"message": "",
|
||||||
|
"buttons": {
|
||||||
|
"confirm": ""
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"mute": {
|
||||||
|
"action_false": "",
|
||||||
|
"action_true": ""
|
||||||
|
},
|
||||||
|
"pin": {
|
||||||
|
"action_false": "",
|
||||||
|
"action_true": ""
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
1
src/i18n/ja/components/emojis.json
Normal file
1
src/i18n/ja/components/emojis.json
Normal file
@ -0,0 +1 @@
|
|||||||
|
{}
|
30
src/i18n/ja/components/instance.json
Normal file
30
src/i18n/ja/components/instance.json
Normal file
@ -0,0 +1,30 @@
|
|||||||
|
{
|
||||||
|
"server": {
|
||||||
|
"textInput": {
|
||||||
|
"placeholder": ""
|
||||||
|
},
|
||||||
|
"button": "",
|
||||||
|
"information": {
|
||||||
|
"name": "",
|
||||||
|
"accounts": "",
|
||||||
|
"statuses": "",
|
||||||
|
"domains": ""
|
||||||
|
},
|
||||||
|
"disclaimer": {
|
||||||
|
"base": ""
|
||||||
|
},
|
||||||
|
"terms": {
|
||||||
|
"base": ""
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"update": {
|
||||||
|
"alert": {
|
||||||
|
"title": "",
|
||||||
|
"message": "",
|
||||||
|
"buttons": {
|
||||||
|
"cancel": "",
|
||||||
|
"continue": ""
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
18
src/i18n/ja/components/mediaSelector.json
Normal file
18
src/i18n/ja/components/mediaSelector.json
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
{
|
||||||
|
"title": "",
|
||||||
|
"options": {
|
||||||
|
"image": "",
|
||||||
|
"image_max": "",
|
||||||
|
"video": "",
|
||||||
|
"video_max": ""
|
||||||
|
},
|
||||||
|
"library": {
|
||||||
|
"alert": {
|
||||||
|
"title": "",
|
||||||
|
"message": "",
|
||||||
|
"buttons": {
|
||||||
|
"settings": ""
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
9
src/i18n/ja/components/parse.json
Normal file
9
src/i18n/ja/components/parse.json
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
{
|
||||||
|
"HTML": {
|
||||||
|
"expanded": {
|
||||||
|
"true": "",
|
||||||
|
"false": ""
|
||||||
|
},
|
||||||
|
"defaultHint": ""
|
||||||
|
}
|
||||||
|
}
|
16
src/i18n/ja/components/relationship.json
Normal file
16
src/i18n/ja/components/relationship.json
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
{
|
||||||
|
"follow": {
|
||||||
|
"function": ""
|
||||||
|
},
|
||||||
|
"block": {
|
||||||
|
"function": ""
|
||||||
|
},
|
||||||
|
"button": {
|
||||||
|
"error": "",
|
||||||
|
"blocked_by": "",
|
||||||
|
"blocking": "",
|
||||||
|
"following": "",
|
||||||
|
"requested": "",
|
||||||
|
"default": ""
|
||||||
|
}
|
||||||
|
}
|
147
src/i18n/ja/components/timeline.json
Normal file
147
src/i18n/ja/components/timeline.json
Normal file
@ -0,0 +1,147 @@
|
|||||||
|
{
|
||||||
|
"empty": {
|
||||||
|
"error": {
|
||||||
|
"message": "",
|
||||||
|
"button": ""
|
||||||
|
},
|
||||||
|
"success": {
|
||||||
|
"message": ""
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"end": {
|
||||||
|
"message": ""
|
||||||
|
},
|
||||||
|
"lookback": {
|
||||||
|
"message": ""
|
||||||
|
},
|
||||||
|
"refresh": {
|
||||||
|
"fetchPreviousPage": "",
|
||||||
|
"refetch": ""
|
||||||
|
},
|
||||||
|
"shared": {
|
||||||
|
"actioned": {
|
||||||
|
"pinned": "",
|
||||||
|
"favourite": "",
|
||||||
|
"status": "",
|
||||||
|
"follow": "",
|
||||||
|
"follow_request": "",
|
||||||
|
"poll": "",
|
||||||
|
"reblog": {
|
||||||
|
"default": "",
|
||||||
|
"notification": ""
|
||||||
|
},
|
||||||
|
"update": ""
|
||||||
|
},
|
||||||
|
"actions": {
|
||||||
|
"reply": {
|
||||||
|
"accessibilityLabel": ""
|
||||||
|
},
|
||||||
|
"reblogged": {
|
||||||
|
"accessibilityLabel": "",
|
||||||
|
"function": ""
|
||||||
|
},
|
||||||
|
"favourited": {
|
||||||
|
"accessibilityLabel": "",
|
||||||
|
"function": ""
|
||||||
|
},
|
||||||
|
"bookmarked": {
|
||||||
|
"accessibilityLabel": "",
|
||||||
|
"function": ""
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"actionsUsers": {
|
||||||
|
"reblogged_by": {
|
||||||
|
"accessibilityLabel": "",
|
||||||
|
"accessibilityHint": "",
|
||||||
|
"text": ""
|
||||||
|
},
|
||||||
|
"favourited_by": {
|
||||||
|
"accessibilityLabel": "",
|
||||||
|
"accessibilityHint": "",
|
||||||
|
"text": ""
|
||||||
|
},
|
||||||
|
"history": {
|
||||||
|
"accessibilityLabel": "",
|
||||||
|
"accessibilityHint": "",
|
||||||
|
"text_one": "",
|
||||||
|
"text_other": ""
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"attachment": {
|
||||||
|
"sensitive": {
|
||||||
|
"button": ""
|
||||||
|
},
|
||||||
|
"unsupported": {
|
||||||
|
"text": "",
|
||||||
|
"button": ""
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"avatar": {
|
||||||
|
"accessibilityLabel": "",
|
||||||
|
"accessibilityHint": ""
|
||||||
|
},
|
||||||
|
"content": {
|
||||||
|
"expandHint": ""
|
||||||
|
},
|
||||||
|
"filtered": "",
|
||||||
|
"fullConversation": "",
|
||||||
|
"translate": {
|
||||||
|
"default": "",
|
||||||
|
"succeed": "",
|
||||||
|
"failed": "",
|
||||||
|
"source_not_supported": "",
|
||||||
|
"target_not_supported": ""
|
||||||
|
},
|
||||||
|
"header": {
|
||||||
|
"shared": {
|
||||||
|
"account": {
|
||||||
|
"name": {
|
||||||
|
"accessibilityHint": ""
|
||||||
|
},
|
||||||
|
"account": {
|
||||||
|
"accessibilityHint": ""
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"application": "",
|
||||||
|
"edited": {
|
||||||
|
"accessibilityLabel": ""
|
||||||
|
},
|
||||||
|
"muted": {
|
||||||
|
"accessibilityLabel": ""
|
||||||
|
},
|
||||||
|
"visibility": {
|
||||||
|
"direct": {
|
||||||
|
"accessibilityLabel": ""
|
||||||
|
},
|
||||||
|
"private": {
|
||||||
|
"accessibilityLabel": ""
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"conversation": {
|
||||||
|
"withAccounts": "",
|
||||||
|
"delete": {
|
||||||
|
"function": ""
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"poll": {
|
||||||
|
"meta": {
|
||||||
|
"button": {
|
||||||
|
"vote": "",
|
||||||
|
"refresh": ""
|
||||||
|
},
|
||||||
|
"count": {
|
||||||
|
"voters_one": "",
|
||||||
|
"voters_other": "",
|
||||||
|
"votes_one": "",
|
||||||
|
"votes_other": ""
|
||||||
|
},
|
||||||
|
"expiration": {
|
||||||
|
"expired": "",
|
||||||
|
"until": ""
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
18
src/i18n/ja/screens.json
Normal file
18
src/i18n/ja/screens.json
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
{
|
||||||
|
"screenshot": {
|
||||||
|
"title": "",
|
||||||
|
"message": "",
|
||||||
|
"button": ""
|
||||||
|
},
|
||||||
|
"localCorrupt": {
|
||||||
|
"message": ""
|
||||||
|
},
|
||||||
|
"pushError": {
|
||||||
|
"message": "",
|
||||||
|
"description": ""
|
||||||
|
},
|
||||||
|
"shareError": {
|
||||||
|
"imageNotSupported": "",
|
||||||
|
"videoNotSupported": ""
|
||||||
|
}
|
||||||
|
}
|
20
src/i18n/ja/screens/actions.json
Normal file
20
src/i18n/ja/screens/actions.json
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
{
|
||||||
|
"content": {
|
||||||
|
"altText": {
|
||||||
|
"heading": ""
|
||||||
|
},
|
||||||
|
"notificationsFilter": {
|
||||||
|
"heading": "",
|
||||||
|
"content": {
|
||||||
|
"follow": "",
|
||||||
|
"follow_request": "",
|
||||||
|
"favourite": "",
|
||||||
|
"reblog": "",
|
||||||
|
"mention": "",
|
||||||
|
"poll": "",
|
||||||
|
"status": "",
|
||||||
|
"update": ""
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
10
src/i18n/ja/screens/announcements.json
Normal file
10
src/i18n/ja/screens/announcements.json
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
{
|
||||||
|
"heading": "",
|
||||||
|
"content": {
|
||||||
|
"published": "",
|
||||||
|
"button": {
|
||||||
|
"read": "",
|
||||||
|
"unread": ""
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
179
src/i18n/ja/screens/compose.json
Normal file
179
src/i18n/ja/screens/compose.json
Normal file
@ -0,0 +1,179 @@
|
|||||||
|
{
|
||||||
|
"heading": {
|
||||||
|
"left": {
|
||||||
|
"button": "",
|
||||||
|
"alert": {
|
||||||
|
"title": "",
|
||||||
|
"buttons": {
|
||||||
|
"save": "",
|
||||||
|
"delete": "",
|
||||||
|
"cancel": ""
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"right": {
|
||||||
|
"button": {
|
||||||
|
"default": "",
|
||||||
|
"conversation": "",
|
||||||
|
"reply": "",
|
||||||
|
"deleteEdit": "",
|
||||||
|
"edit": "",
|
||||||
|
"share": ""
|
||||||
|
},
|
||||||
|
"alert": {
|
||||||
|
"default": {
|
||||||
|
"title": "",
|
||||||
|
"button": ""
|
||||||
|
},
|
||||||
|
"removeReply": {
|
||||||
|
"title": "",
|
||||||
|
"description": "",
|
||||||
|
"cancel": "",
|
||||||
|
"confirm": ""
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"content": {
|
||||||
|
"root": {
|
||||||
|
"header": {
|
||||||
|
"postingAs": "",
|
||||||
|
"spoilerInput": {
|
||||||
|
"placeholder": ""
|
||||||
|
},
|
||||||
|
"textInput": {
|
||||||
|
"placeholder": "",
|
||||||
|
"keyboardImage": {
|
||||||
|
"exceedMaximum": {
|
||||||
|
"title": "",
|
||||||
|
"OK": ""
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"footer": {
|
||||||
|
"attachments": {
|
||||||
|
"sensitive": "",
|
||||||
|
"remove": {
|
||||||
|
"accessibilityLabel": ""
|
||||||
|
},
|
||||||
|
"edit": {
|
||||||
|
"accessibilityLabel": ""
|
||||||
|
},
|
||||||
|
"upload": {
|
||||||
|
"accessibilityLabel": ""
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"emojis": {
|
||||||
|
"accessibilityHint": ""
|
||||||
|
},
|
||||||
|
"poll": {
|
||||||
|
"option": {
|
||||||
|
"placeholder": {
|
||||||
|
"accessibilityLabel": "",
|
||||||
|
"single": "",
|
||||||
|
"multiple": ""
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"quantity": {
|
||||||
|
"reduce": {
|
||||||
|
"accessibilityLabel": "",
|
||||||
|
"accessibilityHint": ""
|
||||||
|
},
|
||||||
|
"increase": {
|
||||||
|
"accessibilityLabel": "",
|
||||||
|
"accessibilityHint": ""
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"multiple": {
|
||||||
|
"heading": "",
|
||||||
|
"options": {
|
||||||
|
"single": "",
|
||||||
|
"multiple": "",
|
||||||
|
"cancel": ""
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"expiration": {
|
||||||
|
"heading": "",
|
||||||
|
"options": {
|
||||||
|
"300": "",
|
||||||
|
"1800": "",
|
||||||
|
"3600": "",
|
||||||
|
"21600": "",
|
||||||
|
"86400": "",
|
||||||
|
"259200": "",
|
||||||
|
"604800": "",
|
||||||
|
"cancel": ""
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"actions": {
|
||||||
|
"attachment": {
|
||||||
|
"accessibilityLabel": "",
|
||||||
|
"accessibilityHint": "",
|
||||||
|
"failed": {
|
||||||
|
"alert": {
|
||||||
|
"title": "",
|
||||||
|
"button": ""
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"poll": {
|
||||||
|
"accessibilityLabel": "",
|
||||||
|
"accessibilityHint": ""
|
||||||
|
},
|
||||||
|
"visibility": {
|
||||||
|
"accessibilityLabel": "",
|
||||||
|
"title": "",
|
||||||
|
"options": {
|
||||||
|
"public": "",
|
||||||
|
"unlisted": "",
|
||||||
|
"private": "",
|
||||||
|
"direct": "",
|
||||||
|
"cancel": ""
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"spoiler": {
|
||||||
|
"accessibilityLabel": ""
|
||||||
|
},
|
||||||
|
"emoji": {
|
||||||
|
"accessibilityLabel": "",
|
||||||
|
"accessibilityHint": ""
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"drafts_one": "",
|
||||||
|
"drafts_other": ""
|
||||||
|
},
|
||||||
|
"editAttachment": {
|
||||||
|
"header": {
|
||||||
|
"title": "",
|
||||||
|
"right": {
|
||||||
|
"accessibilityLabel": "",
|
||||||
|
"failed": {
|
||||||
|
"title": "",
|
||||||
|
"button": ""
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"content": {
|
||||||
|
"altText": {
|
||||||
|
"heading": "",
|
||||||
|
"placeholder": ""
|
||||||
|
},
|
||||||
|
"imageFocus": ""
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"draftsList": {
|
||||||
|
"header": {
|
||||||
|
"title": ""
|
||||||
|
},
|
||||||
|
"warning": "",
|
||||||
|
"content": {
|
||||||
|
"accessibilityHint": "",
|
||||||
|
"textEmpty": ""
|
||||||
|
},
|
||||||
|
"checkAttachment": ""
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
17
src/i18n/ja/screens/imageViewer.json
Normal file
17
src/i18n/ja/screens/imageViewer.json
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
{
|
||||||
|
"content": {
|
||||||
|
"actions": {
|
||||||
|
"accessibilityLabel": "",
|
||||||
|
"accessibilityHint": ""
|
||||||
|
},
|
||||||
|
"options": {
|
||||||
|
"save": "",
|
||||||
|
"share": "",
|
||||||
|
"cancel": ""
|
||||||
|
},
|
||||||
|
"save": {
|
||||||
|
"succeed": "",
|
||||||
|
"failed": ""
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
354
src/i18n/ja/screens/tabs.json
Normal file
354
src/i18n/ja/screens/tabs.json
Normal file
@ -0,0 +1,354 @@
|
|||||||
|
{
|
||||||
|
"tabs": {
|
||||||
|
"local": {
|
||||||
|
"name": ""
|
||||||
|
},
|
||||||
|
"public": {
|
||||||
|
"name": "",
|
||||||
|
"segments": {
|
||||||
|
"left": "",
|
||||||
|
"right": ""
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"notifications": {
|
||||||
|
"name": ""
|
||||||
|
},
|
||||||
|
"me": {
|
||||||
|
"name": ""
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"common": {
|
||||||
|
"search": {
|
||||||
|
"accessibilityLabel": "",
|
||||||
|
"accessibilityHint": ""
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"notifications": {
|
||||||
|
"filter": {
|
||||||
|
"accessibilityLabel": "",
|
||||||
|
"accessibilityHint": ""
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"me": {
|
||||||
|
"stacks": {
|
||||||
|
"bookmarks": {
|
||||||
|
"name": ""
|
||||||
|
},
|
||||||
|
"conversations": {
|
||||||
|
"name": ""
|
||||||
|
},
|
||||||
|
"favourites": {
|
||||||
|
"name": ""
|
||||||
|
},
|
||||||
|
"fontSize": {
|
||||||
|
"name": ""
|
||||||
|
},
|
||||||
|
"language": {
|
||||||
|
"name": ""
|
||||||
|
},
|
||||||
|
"lists": {
|
||||||
|
"name": ""
|
||||||
|
},
|
||||||
|
"list": {
|
||||||
|
"name": ""
|
||||||
|
},
|
||||||
|
"push": {
|
||||||
|
"name": ""
|
||||||
|
},
|
||||||
|
"profile": {
|
||||||
|
"name": ""
|
||||||
|
},
|
||||||
|
"profileName": {
|
||||||
|
"name": ""
|
||||||
|
},
|
||||||
|
"profileNote": {
|
||||||
|
"name": ""
|
||||||
|
},
|
||||||
|
"profileFields": {
|
||||||
|
"name": ""
|
||||||
|
},
|
||||||
|
"settings": {
|
||||||
|
"name": ""
|
||||||
|
},
|
||||||
|
"webSettings": {
|
||||||
|
"name": ""
|
||||||
|
},
|
||||||
|
"switch": {
|
||||||
|
"name": ""
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"fontSize": {
|
||||||
|
"showcase": "",
|
||||||
|
"demo": "",
|
||||||
|
"availableSizes": "",
|
||||||
|
"sizes": {
|
||||||
|
"S": "",
|
||||||
|
"M": "",
|
||||||
|
"L": "",
|
||||||
|
"XL": "",
|
||||||
|
"XXL": ""
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"profile": {
|
||||||
|
"cancellation": {
|
||||||
|
"title": "",
|
||||||
|
"message": "",
|
||||||
|
"buttons": {
|
||||||
|
"cancel": "",
|
||||||
|
"discard": ""
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"feedback": {
|
||||||
|
"succeed": "",
|
||||||
|
"failed": ""
|
||||||
|
},
|
||||||
|
"root": {
|
||||||
|
"name": {
|
||||||
|
"title": ""
|
||||||
|
},
|
||||||
|
"avatar": {
|
||||||
|
"title": "",
|
||||||
|
"description": ""
|
||||||
|
},
|
||||||
|
"header": {
|
||||||
|
"title": "",
|
||||||
|
"description": ""
|
||||||
|
},
|
||||||
|
"note": {
|
||||||
|
"title": ""
|
||||||
|
},
|
||||||
|
"fields": {
|
||||||
|
"title": "",
|
||||||
|
"total_one": "",
|
||||||
|
"total_other": ""
|
||||||
|
},
|
||||||
|
"visibility": {
|
||||||
|
"title": "",
|
||||||
|
"options": {
|
||||||
|
"public": "",
|
||||||
|
"unlisted": "",
|
||||||
|
"private": "",
|
||||||
|
"cancel": ""
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"sensitive": {
|
||||||
|
"title": ""
|
||||||
|
},
|
||||||
|
"lock": {
|
||||||
|
"title": "",
|
||||||
|
"description": ""
|
||||||
|
},
|
||||||
|
"bot": {
|
||||||
|
"title": "",
|
||||||
|
"description": ""
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"fields": {
|
||||||
|
"group": "",
|
||||||
|
"label": "",
|
||||||
|
"content": ""
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"push": {
|
||||||
|
"notAvailable": "",
|
||||||
|
"enable": {
|
||||||
|
"direct": "",
|
||||||
|
"settings": ""
|
||||||
|
},
|
||||||
|
"global": {
|
||||||
|
"heading": "",
|
||||||
|
"description": ""
|
||||||
|
},
|
||||||
|
"decode": {
|
||||||
|
"heading": "",
|
||||||
|
"description": ""
|
||||||
|
},
|
||||||
|
"default": {
|
||||||
|
"heading": ""
|
||||||
|
},
|
||||||
|
"follow": {
|
||||||
|
"heading": ""
|
||||||
|
},
|
||||||
|
"follow_request": {
|
||||||
|
"heading": ""
|
||||||
|
},
|
||||||
|
"favourite": {
|
||||||
|
"heading": ""
|
||||||
|
},
|
||||||
|
"reblog": {
|
||||||
|
"heading": ""
|
||||||
|
},
|
||||||
|
"mention": {
|
||||||
|
"heading": ""
|
||||||
|
},
|
||||||
|
"poll": {
|
||||||
|
"heading": ""
|
||||||
|
},
|
||||||
|
"status": {
|
||||||
|
"heading": ""
|
||||||
|
},
|
||||||
|
"howitworks": ""
|
||||||
|
},
|
||||||
|
"root": {
|
||||||
|
"announcements": {
|
||||||
|
"content": {
|
||||||
|
"unread": "",
|
||||||
|
"read": "",
|
||||||
|
"empty": ""
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"push": {
|
||||||
|
"content": {
|
||||||
|
"enabled": "",
|
||||||
|
"disabled": ""
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"update": {
|
||||||
|
"title": ""
|
||||||
|
},
|
||||||
|
"logout": {
|
||||||
|
"button": "",
|
||||||
|
"alert": {
|
||||||
|
"title": "",
|
||||||
|
"message": "",
|
||||||
|
"buttons": {
|
||||||
|
"logout": "",
|
||||||
|
"cancel": ""
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"settings": {
|
||||||
|
"fontsize": {
|
||||||
|
"heading": "",
|
||||||
|
"content": {
|
||||||
|
"S": "",
|
||||||
|
"M": "",
|
||||||
|
"L": "",
|
||||||
|
"XL": "",
|
||||||
|
"XXL": ""
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"language": {
|
||||||
|
"heading": "",
|
||||||
|
"options": {
|
||||||
|
"cancel": ""
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"theme": {
|
||||||
|
"heading": "",
|
||||||
|
"options": {
|
||||||
|
"auto": "",
|
||||||
|
"light": "",
|
||||||
|
"dark": "",
|
||||||
|
"cancel": ""
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"darkTheme": {
|
||||||
|
"heading": "",
|
||||||
|
"options": {
|
||||||
|
"lighter": "",
|
||||||
|
"darker": "",
|
||||||
|
"cancel": ""
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"browser": {
|
||||||
|
"heading": "",
|
||||||
|
"options": {
|
||||||
|
"internal": "",
|
||||||
|
"external": "",
|
||||||
|
"cancel": ""
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"staticEmoji": {
|
||||||
|
"heading": "",
|
||||||
|
"description": ""
|
||||||
|
},
|
||||||
|
"feedback": {
|
||||||
|
"heading": ""
|
||||||
|
},
|
||||||
|
"support": {
|
||||||
|
"heading": ""
|
||||||
|
},
|
||||||
|
"review": {
|
||||||
|
"heading": ""
|
||||||
|
},
|
||||||
|
"contact": {
|
||||||
|
"heading": ""
|
||||||
|
},
|
||||||
|
"analytics": {
|
||||||
|
"heading": "",
|
||||||
|
"description": ""
|
||||||
|
},
|
||||||
|
"version": "",
|
||||||
|
"instanceVersion": ""
|
||||||
|
},
|
||||||
|
"switch": {
|
||||||
|
"existing": "",
|
||||||
|
"new": ""
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"shared": {
|
||||||
|
"account": {
|
||||||
|
"actions": {
|
||||||
|
"accessibilityLabel": "",
|
||||||
|
"accessibilityHint": ""
|
||||||
|
},
|
||||||
|
"followed_by": "",
|
||||||
|
"moved": "",
|
||||||
|
"created_at": "",
|
||||||
|
"summary": {
|
||||||
|
"statuses_count": "",
|
||||||
|
"following_count": "",
|
||||||
|
"followers_count": ""
|
||||||
|
},
|
||||||
|
"toots": {
|
||||||
|
"default": "",
|
||||||
|
"all": ""
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"attachments": {
|
||||||
|
"name": ""
|
||||||
|
},
|
||||||
|
"search": {
|
||||||
|
"header": {
|
||||||
|
"prefix": "",
|
||||||
|
"placeholder": ""
|
||||||
|
},
|
||||||
|
"empty": {
|
||||||
|
"general": "",
|
||||||
|
"advanced": {
|
||||||
|
"header": "",
|
||||||
|
"example": {
|
||||||
|
"account": "",
|
||||||
|
"hashtag": "",
|
||||||
|
"statusLink": "",
|
||||||
|
"accountLink": ""
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"sections": {
|
||||||
|
"accounts": "",
|
||||||
|
"hashtags": "",
|
||||||
|
"statuses": ""
|
||||||
|
},
|
||||||
|
"notFound": ""
|
||||||
|
},
|
||||||
|
"toot": {
|
||||||
|
"name": ""
|
||||||
|
},
|
||||||
|
"users": {
|
||||||
|
"accounts": {
|
||||||
|
"following": "",
|
||||||
|
"followers": ""
|
||||||
|
},
|
||||||
|
"statuses": {
|
||||||
|
"reblogged_by": "",
|
||||||
|
"favourited_by": ""
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"history": {
|
||||||
|
"name": ""
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -8,6 +8,7 @@ export default {
|
|||||||
screenImageViewer: require('./screens/imageViewer'),
|
screenImageViewer: require('./screens/imageViewer'),
|
||||||
screenTabs: require('./screens/tabs'),
|
screenTabs: require('./screens/tabs'),
|
||||||
|
|
||||||
|
componentContextMenu: require('./components/contextMenu'),
|
||||||
componentEmojis: require('./components/emojis'),
|
componentEmojis: require('./components/emojis'),
|
||||||
componentInstance: require('./components/instance'),
|
componentInstance: require('./components/instance'),
|
||||||
componentMediaSelector: require('./components/mediaSelector'),
|
componentMediaSelector: require('./components/mediaSelector'),
|
||||||
|
@ -3,10 +3,12 @@
|
|||||||
"account": {
|
"account": {
|
||||||
"title": "",
|
"title": "",
|
||||||
"mute": {
|
"mute": {
|
||||||
"action": "사용자 음소거"
|
"action_false": "사용자 음소거",
|
||||||
|
"action_true": ""
|
||||||
},
|
},
|
||||||
"block": {
|
"block": {
|
||||||
"action": "사용자 차단"
|
"action_false": "사용자 차단",
|
||||||
|
"action_true": ""
|
||||||
},
|
},
|
||||||
"reports": {
|
"reports": {
|
||||||
"action": "사용자 신고"
|
"action": "사용자 신고"
|
||||||
@ -59,12 +61,12 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"mute": {
|
"mute": {
|
||||||
"action-muted_false": "",
|
"action_false": "",
|
||||||
"action-muted_true": ""
|
"action_true": ""
|
||||||
},
|
},
|
||||||
"pin": {
|
"pin": {
|
||||||
"action-pinned_false": "",
|
"action_false": "",
|
||||||
"action-pinned_true": ""
|
"action_true": ""
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -78,9 +78,7 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"fontSize": {
|
"fontSize": {
|
||||||
"showcase": "예시 툿",
|
|
||||||
"demo": "<p>데모 툿이에요😊. 아래의 여러 옵션 중에서 선택할 수 있어요.<br /><br />이 설정은 툿의 메인 내용에만 적용되고, 다른 폰트 크기에 영향을 미치지 않아요.</p>",
|
"demo": "<p>데모 툿이에요😊. 아래의 여러 옵션 중에서 선택할 수 있어요.<br /><br />이 설정은 툿의 메인 내용에만 적용되고, 다른 폰트 크기에 영향을 미치지 않아요.</p>",
|
||||||
"availableSizes": "사용할 수 있는 크기",
|
|
||||||
"sizes": {
|
"sizes": {
|
||||||
"S": "작게",
|
"S": "작게",
|
||||||
"M": "중간 - 기본값",
|
"M": "중간 - 기본값",
|
||||||
|
@ -8,6 +8,7 @@ export default {
|
|||||||
screenImageViewer: require('./screens/imageViewer'),
|
screenImageViewer: require('./screens/imageViewer'),
|
||||||
screenTabs: require('./screens/tabs'),
|
screenTabs: require('./screens/tabs'),
|
||||||
|
|
||||||
|
componentContextMenu: require('./components/contextMenu'),
|
||||||
componentEmojis: require('./components/emojis'),
|
componentEmojis: require('./components/emojis'),
|
||||||
componentInstance: require('./components/instance'),
|
componentInstance: require('./components/instance'),
|
||||||
componentMediaSelector: require('./components/mediaSelector'),
|
componentMediaSelector: require('./components/mediaSelector'),
|
||||||
|
@ -3,10 +3,12 @@
|
|||||||
"account": {
|
"account": {
|
||||||
"title": "Ações do Usuário",
|
"title": "Ações do Usuário",
|
||||||
"mute": {
|
"mute": {
|
||||||
"action": "Silenciar usuário"
|
"action_false": "Silenciar usuário",
|
||||||
|
"action_true": "Desativar o silêncio do usuário"
|
||||||
},
|
},
|
||||||
"block": {
|
"block": {
|
||||||
"action": "Bloquear usuário"
|
"action_false": "Bloquear usuário",
|
||||||
|
"action_true": "Desbloquear usuário"
|
||||||
},
|
},
|
||||||
"reports": {
|
"reports": {
|
||||||
"action": "Denunciar usuário"
|
"action": "Denunciar usuário"
|
||||||
@ -59,12 +61,12 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"mute": {
|
"mute": {
|
||||||
"action-muted_false": "Silenciar este toot e respostas",
|
"action_false": "Silenciar este toot e respostas",
|
||||||
"action-muted_true": "Desbloquear este toot e respostas"
|
"action_true": "Desbloquear este toot e respostas"
|
||||||
},
|
},
|
||||||
"pin": {
|
"pin": {
|
||||||
"action-pinned_false": "Toot fixado",
|
"action_false": "Toot fixado",
|
||||||
"action-pinned_true": "Desafixar toot"
|
"action_true": "Desafixar toot"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -139,7 +139,7 @@
|
|||||||
},
|
},
|
||||||
"expiration": {
|
"expiration": {
|
||||||
"expired": "Voto expirado",
|
"expired": "Voto expirado",
|
||||||
"until": "Expira em <0 />"
|
"until": "Expira <0 />"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -78,9 +78,7 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"fontSize": {
|
"fontSize": {
|
||||||
"showcase": "Exemplo de toot",
|
|
||||||
"demo": "<p>Esta é uma demonstração também😊. Você pode escolher entre várias opções abaixo.<br /><br />Esta configuração afeta apenas o conteúdo principal dos toots, mas não os tamanhos de outra fonte.</p>",
|
"demo": "<p>Esta é uma demonstração também😊. Você pode escolher entre várias opções abaixo.<br /><br />Esta configuração afeta apenas o conteúdo principal dos toots, mas não os tamanhos de outra fonte.</p>",
|
||||||
"availableSizes": "Tamanhos disponíveis",
|
|
||||||
"sizes": {
|
"sizes": {
|
||||||
"S": "P",
|
"S": "P",
|
||||||
"M": "M - Padrão",
|
"M": "M - Padrão",
|
||||||
|
@ -8,6 +8,7 @@ export default {
|
|||||||
screenImageViewer: require('./screens/imageViewer'),
|
screenImageViewer: require('./screens/imageViewer'),
|
||||||
screenTabs: require('./screens/tabs'),
|
screenTabs: require('./screens/tabs'),
|
||||||
|
|
||||||
|
componentContextMenu: require('./components/contextMenu'),
|
||||||
componentEmojis: require('./components/emojis'),
|
componentEmojis: require('./components/emojis'),
|
||||||
componentInstance: require('./components/instance'),
|
componentInstance: require('./components/instance'),
|
||||||
componentMediaSelector: require('./components/mediaSelector'),
|
componentMediaSelector: require('./components/mediaSelector'),
|
||||||
|
@ -3,10 +3,12 @@
|
|||||||
"account": {
|
"account": {
|
||||||
"title": "Hành động người dùng",
|
"title": "Hành động người dùng",
|
||||||
"mute": {
|
"mute": {
|
||||||
"action": "Ẩn người này"
|
"action_false": "Ẩn người này",
|
||||||
|
"action_true": "Bỏ ẩn người dùng"
|
||||||
},
|
},
|
||||||
"block": {
|
"block": {
|
||||||
"action": "Chặn người này"
|
"action_false": "Chặn người này",
|
||||||
|
"action_true": "Bỏ chặn người dùng"
|
||||||
},
|
},
|
||||||
"reports": {
|
"reports": {
|
||||||
"action": "Báo cáo"
|
"action": "Báo cáo"
|
||||||
@ -59,12 +61,12 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"mute": {
|
"mute": {
|
||||||
"action-muted_false": "Ẩn tút này",
|
"action_false": "Ẩn tút này",
|
||||||
"action-muted_true": "Bỏ ẩn tút này"
|
"action_true": "Bỏ ẩn tút này"
|
||||||
},
|
},
|
||||||
"pin": {
|
"pin": {
|
||||||
"action-pinned_false": "Tút ghim",
|
"action_false": "Tút ghim",
|
||||||
"action-pinned_true": "Bỏ ghim tút"
|
"action_true": "Bỏ ghim tút"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -139,7 +139,7 @@
|
|||||||
},
|
},
|
||||||
"expiration": {
|
"expiration": {
|
||||||
"expired": "Đã kết thúc",
|
"expired": "Đã kết thúc",
|
||||||
"until": "Kết thúc sau <0 />"
|
"until": "Kết thúc <0 />"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -78,9 +78,7 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"fontSize": {
|
"fontSize": {
|
||||||
"showcase": "Xem trước",
|
|
||||||
"demo": "<p>Đây là một tút mẫu 😊 Bạn có thể chọn một trong nhiều lựa chọn bên dưới.<br /><br />Tùy chọn này chỉ áp dụng cho nội dung tút chứ không ảnh hưởng những phần tử khác của app.</p>",
|
"demo": "<p>Đây là một tút mẫu 😊 Bạn có thể chọn một trong nhiều lựa chọn bên dưới.<br /><br />Tùy chọn này chỉ áp dụng cho nội dung tút chứ không ảnh hưởng những phần tử khác của app.</p>",
|
||||||
"availableSizes": "Kích cỡ",
|
|
||||||
"sizes": {
|
"sizes": {
|
||||||
"S": "S",
|
"S": "S",
|
||||||
"M": "M - Mặc định",
|
"M": "M - Mặc định",
|
||||||
|
@ -8,6 +8,7 @@ export default {
|
|||||||
screenImageViewer: require('./screens/imageViewer'),
|
screenImageViewer: require('./screens/imageViewer'),
|
||||||
screenTabs: require('./screens/tabs'),
|
screenTabs: require('./screens/tabs'),
|
||||||
|
|
||||||
|
componentContextMenu: require('./components/contextMenu'),
|
||||||
componentEmojis: require('./components/emojis'),
|
componentEmojis: require('./components/emojis'),
|
||||||
componentInstance: require('./components/instance'),
|
componentInstance: require('./components/instance'),
|
||||||
componentMediaSelector: require('./components/mediaSelector'),
|
componentMediaSelector: require('./components/mediaSelector'),
|
||||||
|
@ -3,10 +3,12 @@
|
|||||||
"account": {
|
"account": {
|
||||||
"title": "用户操作",
|
"title": "用户操作",
|
||||||
"mute": {
|
"mute": {
|
||||||
"action": "静音用户"
|
"action_false": "静音用户",
|
||||||
|
"action_true": "取消静音用户"
|
||||||
},
|
},
|
||||||
"block": {
|
"block": {
|
||||||
"action": "屏蔽用户"
|
"action_false": "屏蔽用户",
|
||||||
|
"action_true": "取消屏蔽用户"
|
||||||
},
|
},
|
||||||
"reports": {
|
"reports": {
|
||||||
"action": "举报用户"
|
"action": "举报用户"
|
||||||
@ -59,12 +61,12 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"mute": {
|
"mute": {
|
||||||
"action-muted_false": "静音嘟文及回复",
|
"action_false": "静音嘟文及回复",
|
||||||
"action-muted_true": "取消静音嘟文及回复"
|
"action_true": "取消静音嘟文及回复"
|
||||||
},
|
},
|
||||||
"pin": {
|
"pin": {
|
||||||
"action-pinned_false": "置顶嘟文",
|
"action_false": "置顶嘟文",
|
||||||
"action-pinned_true": "取消置顶嘟文"
|
"action_true": "取消置顶嘟文"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -78,9 +78,7 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"fontSize": {
|
"fontSize": {
|
||||||
"showcase": "嘟文示例",
|
|
||||||
"demo": "<p>这是一条测试用的嘟文😊。以下是可供选择的字号,从小号至超大号。<br /><br />这个设置仅会调整嘟文的正文字号,不影响其它字号。</p>",
|
"demo": "<p>这是一条测试用的嘟文😊。以下是可供选择的字号,从小号至超大号。<br /><br />这个设置仅会调整嘟文的正文字号,不影响其它字号。</p>",
|
||||||
"availableSizes": "可选字号",
|
|
||||||
"sizes": {
|
"sizes": {
|
||||||
"S": "小号",
|
"S": "小号",
|
||||||
"M": "默认",
|
"M": "默认",
|
||||||
|
@ -3,10 +3,12 @@
|
|||||||
"account": {
|
"account": {
|
||||||
"title": "",
|
"title": "",
|
||||||
"mute": {
|
"mute": {
|
||||||
"action": ""
|
"action_false": "",
|
||||||
|
"action_true": ""
|
||||||
},
|
},
|
||||||
"block": {
|
"block": {
|
||||||
"action": ""
|
"action_false": "",
|
||||||
|
"action_true": ""
|
||||||
},
|
},
|
||||||
"reports": {
|
"reports": {
|
||||||
"action": ""
|
"action": ""
|
||||||
@ -59,12 +61,12 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"mute": {
|
"mute": {
|
||||||
"action-muted_false": "",
|
"action_false": "",
|
||||||
"action-muted_true": ""
|
"action_true": ""
|
||||||
},
|
},
|
||||||
"pin": {
|
"pin": {
|
||||||
"action-pinned_false": "",
|
"action_false": "",
|
||||||
"action-pinned_true": ""
|
"action_true": ""
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -78,9 +78,7 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"fontSize": {
|
"fontSize": {
|
||||||
"showcase": "嘟文範例",
|
|
||||||
"demo": "",
|
"demo": "",
|
||||||
"availableSizes": "",
|
|
||||||
"sizes": {
|
"sizes": {
|
||||||
"S": "",
|
"S": "",
|
||||||
"M": "",
|
"M": "",
|
||||||
|
@ -15,8 +15,15 @@ import { StyleConstants } from '@utils/styles/constants'
|
|||||||
import { useTheme } from '@utils/styles/ThemeManager'
|
import { useTheme } from '@utils/styles/ThemeManager'
|
||||||
import React, { useCallback, useEffect, useState } from 'react'
|
import React, { useCallback, useEffect, useState } from 'react'
|
||||||
import { Trans, useTranslation } from 'react-i18next'
|
import { Trans, useTranslation } from 'react-i18next'
|
||||||
import { FormattedRelativeTime } from 'react-intl'
|
import {
|
||||||
import { Dimensions, Platform, Pressable, StyleSheet, View } from 'react-native'
|
Dimensions,
|
||||||
|
NativeScrollEvent,
|
||||||
|
NativeSyntheticEvent,
|
||||||
|
Platform,
|
||||||
|
Pressable,
|
||||||
|
StyleSheet,
|
||||||
|
View
|
||||||
|
} from 'react-native'
|
||||||
import { Circle } from 'react-native-animated-spinkit'
|
import { Circle } from 'react-native-animated-spinkit'
|
||||||
import FastImage from 'react-native-fast-image'
|
import FastImage from 'react-native-fast-image'
|
||||||
import { FlatList, ScrollView } from 'react-native-gesture-handler'
|
import { FlatList, ScrollView } from 'react-native-gesture-handler'
|
||||||
@ -92,9 +99,7 @@ const ScreenAnnouncements: React.FC<
|
|||||||
>
|
>
|
||||||
<Trans
|
<Trans
|
||||||
i18nKey='screenAnnouncements:content.published'
|
i18nKey='screenAnnouncements:content.published'
|
||||||
components={[
|
components={[<RelativeTime time={item.published_at} />]}
|
||||||
<RelativeTime type='past' time={item.published_at} />
|
|
||||||
]}
|
|
||||||
/>
|
/>
|
||||||
</CustomText>
|
</CustomText>
|
||||||
<ScrollView
|
<ScrollView
|
||||||
@ -218,7 +223,7 @@ const ScreenAnnouncements: React.FC<
|
|||||||
contentOffset: { x },
|
contentOffset: { x },
|
||||||
layoutMeasurement: { width }
|
layoutMeasurement: { width }
|
||||||
}
|
}
|
||||||
}) => {
|
}: NativeSyntheticEvent<NativeScrollEvent>) => {
|
||||||
setIndex(Math.floor(x / width))
|
setIndex(Math.floor(x / width))
|
||||||
},
|
},
|
||||||
[]
|
[]
|
||||||
|
@ -150,7 +150,7 @@ const ScreenCompose: React.FC<RootStackScreenProps<'Screen-Compose'>> = ({
|
|||||||
for (const m of params.media) {
|
for (const m of params.media) {
|
||||||
uploadAttachment({
|
uploadAttachment({
|
||||||
composeDispatch,
|
composeDispatch,
|
||||||
media: { ...m, width: 100, height: 100 }
|
media: { uri: m.uri, fileName: 'temp.jpg', type: m.mime }
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -47,10 +47,6 @@ const ComposeDraftsListRoot: React.FC<Props> = ({ timestamp }) => {
|
|||||||
|
|
||||||
const [checkingAttachments, setCheckingAttachments] = useState(false)
|
const [checkingAttachments, setCheckingAttachments] = useState(false)
|
||||||
|
|
||||||
const removeDraft = useCallback(ts => {
|
|
||||||
dispatch(removeInstanceDraft(ts))
|
|
||||||
}, [])
|
|
||||||
|
|
||||||
const renderItem = useCallback(
|
const renderItem = useCallback(
|
||||||
({ item }: { item: ComposeStateDraft }) => {
|
({ item }: { item: ComposeStateDraft }) => {
|
||||||
return (
|
return (
|
||||||
@ -144,7 +140,7 @@ const ComposeDraftsListRoot: React.FC<Props> = ({ timestamp }) => {
|
|||||||
}}
|
}}
|
||||||
source={{
|
source={{
|
||||||
uri:
|
uri:
|
||||||
attachment.local?.local_thumbnail ||
|
attachment.local?.thumbnail ||
|
||||||
attachment.remote?.preview_url
|
attachment.remote?.preview_url
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
@ -157,38 +153,6 @@ const ComposeDraftsListRoot: React.FC<Props> = ({ timestamp }) => {
|
|||||||
},
|
},
|
||||||
[theme]
|
[theme]
|
||||||
)
|
)
|
||||||
const renderHiddenItem = useCallback(
|
|
||||||
({ item }) => (
|
|
||||||
<View
|
|
||||||
style={{
|
|
||||||
flex: 1,
|
|
||||||
flexDirection: 'row',
|
|
||||||
justifyContent: 'flex-end',
|
|
||||||
backgroundColor: colors.red
|
|
||||||
}}
|
|
||||||
children={
|
|
||||||
<Pressable
|
|
||||||
style={{
|
|
||||||
flexBasis:
|
|
||||||
StyleConstants.Font.Size.L +
|
|
||||||
StyleConstants.Spacing.Global.PagePadding * 4,
|
|
||||||
justifyContent: 'center',
|
|
||||||
alignItems: 'center'
|
|
||||||
}}
|
|
||||||
onPress={() => removeDraft(item.timestamp)}
|
|
||||||
children={
|
|
||||||
<Icon
|
|
||||||
name='Trash'
|
|
||||||
size={StyleConstants.Font.Size.L}
|
|
||||||
color={colors.primaryOverlay}
|
|
||||||
/>
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
),
|
|
||||||
[theme]
|
|
||||||
)
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
@ -220,7 +184,35 @@ const ComposeDraftsListRoot: React.FC<Props> = ({ timestamp }) => {
|
|||||||
<SwipeListView
|
<SwipeListView
|
||||||
data={instanceDrafts}
|
data={instanceDrafts}
|
||||||
renderItem={renderItem}
|
renderItem={renderItem}
|
||||||
renderHiddenItem={renderHiddenItem}
|
renderHiddenItem={({ item }) => (
|
||||||
|
<View
|
||||||
|
style={{
|
||||||
|
flex: 1,
|
||||||
|
flexDirection: 'row',
|
||||||
|
justifyContent: 'flex-end',
|
||||||
|
backgroundColor: colors.red
|
||||||
|
}}
|
||||||
|
children={
|
||||||
|
<Pressable
|
||||||
|
style={{
|
||||||
|
flexBasis:
|
||||||
|
StyleConstants.Font.Size.L +
|
||||||
|
StyleConstants.Spacing.Global.PagePadding * 4,
|
||||||
|
justifyContent: 'center',
|
||||||
|
alignItems: 'center'
|
||||||
|
}}
|
||||||
|
onPress={() => dispatch(removeInstanceDraft(item.timestamp))}
|
||||||
|
children={
|
||||||
|
<Icon
|
||||||
|
name='Trash'
|
||||||
|
size={StyleConstants.Font.Size.L}
|
||||||
|
color={colors.primaryOverlay}
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
disableRightSwipe={true}
|
disableRightSwipe={true}
|
||||||
rightOpenValue={-actionWidth}
|
rightOpenValue={-actionWidth}
|
||||||
// previewRowKey={
|
// previewRowKey={
|
||||||
|
@ -35,7 +35,7 @@ const ComposeEditAttachmentRoot: React.FC<Props> = ({ index }) => {
|
|||||||
video.local
|
video.local
|
||||||
? ({
|
? ({
|
||||||
url: video.local.uri,
|
url: video.local.uri,
|
||||||
preview_url: video.local.local_thumbnail,
|
preview_url: video.local.thumbnail,
|
||||||
blurhash: video.remote?.blurhash
|
blurhash: video.remote?.blurhash
|
||||||
} as Mastodon.AttachmentVideo)
|
} as Mastodon.AttachmentVideo)
|
||||||
: (video.remote as Mastodon.AttachmentVideo)
|
: (video.remote as Mastodon.AttachmentVideo)
|
||||||
|
@ -4,13 +4,7 @@ import { useSearchQuery } from '@utils/queryHooks/search'
|
|||||||
import { StyleConstants } from '@utils/styles/constants'
|
import { StyleConstants } from '@utils/styles/constants'
|
||||||
import { useTheme } from '@utils/styles/ThemeManager'
|
import { useTheme } from '@utils/styles/ThemeManager'
|
||||||
import { chunk, forEach, groupBy, sortBy } from 'lodash'
|
import { chunk, forEach, groupBy, sortBy } from 'lodash'
|
||||||
import React, {
|
import React, { useContext, useEffect, useMemo, useRef } from 'react'
|
||||||
useCallback,
|
|
||||||
useContext,
|
|
||||||
useEffect,
|
|
||||||
useMemo,
|
|
||||||
useRef
|
|
||||||
} from 'react'
|
|
||||||
import {
|
import {
|
||||||
AccessibilityInfo,
|
AccessibilityInfo,
|
||||||
findNodeHandle,
|
findNodeHandle,
|
||||||
@ -147,35 +141,25 @@ const ComposeRoot = React.memo(
|
|||||||
}
|
}
|
||||||
}, [isFetching])
|
}, [isFetching])
|
||||||
|
|
||||||
const listItem = useCallback(
|
|
||||||
({ item }) => (
|
|
||||||
<ComposeRootSuggestion
|
|
||||||
item={item}
|
|
||||||
composeState={composeState}
|
|
||||||
composeDispatch={composeDispatch}
|
|
||||||
/>
|
|
||||||
),
|
|
||||||
[composeState]
|
|
||||||
)
|
|
||||||
|
|
||||||
const ListFooter = useCallback(
|
|
||||||
() => (
|
|
||||||
<ComposeRootFooter
|
|
||||||
accessibleRefAttachments={accessibleRefAttachments}
|
|
||||||
accessibleRefEmojis={accessibleRefEmojis}
|
|
||||||
/>
|
|
||||||
),
|
|
||||||
[]
|
|
||||||
)
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<View style={styles.base}>
|
<View style={styles.base}>
|
||||||
<FlatList
|
<FlatList
|
||||||
renderItem={listItem}
|
renderItem={({ item }) => (
|
||||||
|
<ComposeRootSuggestion
|
||||||
|
item={item}
|
||||||
|
composeState={composeState}
|
||||||
|
composeDispatch={composeDispatch}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
ListEmptyComponent={listEmpty}
|
ListEmptyComponent={listEmpty}
|
||||||
keyboardShouldPersistTaps='always'
|
keyboardShouldPersistTaps='always'
|
||||||
ListHeaderComponent={ComposeRootHeader}
|
ListHeaderComponent={ComposeRootHeader}
|
||||||
ListFooterComponent={ListFooter}
|
ListFooterComponent={() => (
|
||||||
|
<ComposeRootFooter
|
||||||
|
accessibleRefAttachments={accessibleRefAttachments}
|
||||||
|
accessibleRefEmojis={accessibleRefEmojis}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
ItemSeparatorComponent={ComponentSeparator}
|
ItemSeparatorComponent={ComponentSeparator}
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
data={data ? data[composeState.tag?.type] : undefined}
|
data={data ? data[composeState.tag?.type] : undefined}
|
||||||
|
@ -56,9 +56,12 @@ const ComposeAttachments: React.FC<Props> = ({ accessibleRefAttachments }) => {
|
|||||||
})
|
})
|
||||||
}, [composeState.attachments.sensitive])
|
}, [composeState.attachments.sensitive])
|
||||||
|
|
||||||
const calculateWidth = useCallback(item => {
|
const calculateWidth = useCallback((item: ExtendedAttachment) => {
|
||||||
if (item.local) {
|
if (item.local) {
|
||||||
return (item.local.width / item.local.height) * DEFAULT_HEIGHT
|
return (
|
||||||
|
((item.local.width || 100) / (item.local.height || 100)) *
|
||||||
|
DEFAULT_HEIGHT
|
||||||
|
)
|
||||||
} else {
|
} else {
|
||||||
if (item.remote) {
|
if (item.remote) {
|
||||||
if (item.remote.meta.original.aspect) {
|
if (item.remote.meta.original.aspect) {
|
||||||
@ -135,7 +138,7 @@ const ComposeAttachments: React.FC<Props> = ({ accessibleRefAttachments }) => {
|
|||||||
<FastImage
|
<FastImage
|
||||||
style={{ width: '100%', height: '100%' }}
|
style={{ width: '100%', height: '100%' }}
|
||||||
source={{
|
source={{
|
||||||
uri: item.local?.local_thumbnail || item.remote?.preview_url
|
uri: item.local?.thumbnail || item.remote?.preview_url
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
{item.remote?.meta?.original?.duration ? (
|
{item.remote?.meta?.original?.duration ? (
|
||||||
|
@ -42,22 +42,6 @@ const ComposeEmojis: React.FC<Props> = ({ accessibleRefEmojis }) => {
|
|||||||
}
|
}
|
||||||
}, [composeState.emoji.active])
|
}, [composeState.emoji.active])
|
||||||
|
|
||||||
const listHeader = useCallback(
|
|
||||||
({ section: { title } }) => (
|
|
||||||
<CustomText
|
|
||||||
fontStyle='S'
|
|
||||||
style={{
|
|
||||||
position: 'absolute',
|
|
||||||
left: StyleConstants.Spacing.L,
|
|
||||||
color: colors.secondary
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{title}
|
|
||||||
</CustomText>
|
|
||||||
),
|
|
||||||
[]
|
|
||||||
)
|
|
||||||
|
|
||||||
const listItem = useCallback(
|
const listItem = useCallback(
|
||||||
({ index, item }: { item: Mastodon.Emoji[]; index: number }) => {
|
({ index, item }: { item: Mastodon.Emoji[]; index: number }) => {
|
||||||
return (
|
return (
|
||||||
@ -155,7 +139,18 @@ const ComposeEmojis: React.FC<Props> = ({ accessibleRefEmojis }) => {
|
|||||||
keyboardShouldPersistTaps='always'
|
keyboardShouldPersistTaps='always'
|
||||||
sections={composeState.emoji.emojis || []}
|
sections={composeState.emoji.emojis || []}
|
||||||
keyExtractor={item => item[0].shortcode}
|
keyExtractor={item => item[0].shortcode}
|
||||||
renderSectionHeader={listHeader}
|
renderSectionHeader={({ section: { title } }) => (
|
||||||
|
<CustomText
|
||||||
|
fontStyle='S'
|
||||||
|
style={{
|
||||||
|
position: 'absolute',
|
||||||
|
left: StyleConstants.Spacing.L,
|
||||||
|
color: colors.secondary
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{title}
|
||||||
|
</CustomText>
|
||||||
|
)}
|
||||||
renderItem={listItem}
|
renderItem={listItem}
|
||||||
windowSize={2}
|
windowSize={2}
|
||||||
/>
|
/>
|
||||||
|
@ -7,7 +7,7 @@ import { ActionSheetOptions } from '@expo/react-native-action-sheet'
|
|||||||
import i18next from 'i18next'
|
import i18next from 'i18next'
|
||||||
import apiInstance from '@api/instance'
|
import apiInstance from '@api/instance'
|
||||||
import mediaSelector from '@components/mediaSelector'
|
import mediaSelector from '@components/mediaSelector'
|
||||||
import { ImageOrVideo } from 'react-native-image-crop-picker'
|
import { Asset } from 'react-native-image-picker'
|
||||||
|
|
||||||
export interface Props {
|
export interface Props {
|
||||||
composeDispatch: Dispatch<ComposeAction>
|
composeDispatch: Dispatch<ComposeAction>
|
||||||
@ -22,46 +22,40 @@ export const uploadAttachment = async ({
|
|||||||
media
|
media
|
||||||
}: {
|
}: {
|
||||||
composeDispatch: Dispatch<ComposeAction>
|
composeDispatch: Dispatch<ComposeAction>
|
||||||
media: { uri: string } & Pick<ImageOrVideo, 'mime' | 'width' | 'height'>
|
media: Required<Pick<Asset, 'uri' | 'type' | 'fileName'>>
|
||||||
}) => {
|
}) => {
|
||||||
const hash = await Crypto.digestStringAsync(
|
const hash = await Crypto.digestStringAsync(
|
||||||
Crypto.CryptoDigestAlgorithm.SHA256,
|
Crypto.CryptoDigestAlgorithm.SHA256,
|
||||||
media.uri + Math.random()
|
media.uri + Math.random()
|
||||||
)
|
)
|
||||||
|
|
||||||
switch (media.mime.split('/')[0]) {
|
switch (media.type.split('/')[0]) {
|
||||||
case 'image':
|
case 'image':
|
||||||
composeDispatch({
|
composeDispatch({
|
||||||
type: 'attachment/upload/start',
|
type: 'attachment/upload/start',
|
||||||
payload: {
|
payload: {
|
||||||
local: { ...media, type: 'image', local_thumbnail: media.uri, hash },
|
local: { ...media, thumbnail: media.uri, hash },
|
||||||
uploading: true
|
uploading: true
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
break
|
break
|
||||||
case 'video':
|
case 'video':
|
||||||
VideoThumbnails.getThumbnailAsync(media.uri)
|
VideoThumbnails.getThumbnailAsync(media.uri)
|
||||||
.then(({ uri, width, height }) =>
|
.then(({ uri, width, height }) => {
|
||||||
|
console.log('new', uri, width, height)
|
||||||
composeDispatch({
|
composeDispatch({
|
||||||
type: 'attachment/upload/start',
|
type: 'attachment/upload/start',
|
||||||
payload: {
|
payload: {
|
||||||
local: {
|
local: { ...media, thumbnail: uri, hash, width, height },
|
||||||
...media,
|
|
||||||
type: 'video',
|
|
||||||
local_thumbnail: uri,
|
|
||||||
hash,
|
|
||||||
width,
|
|
||||||
height
|
|
||||||
},
|
|
||||||
uploading: true
|
uploading: true
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
)
|
})
|
||||||
.catch(() =>
|
.catch(() =>
|
||||||
composeDispatch({
|
composeDispatch({
|
||||||
type: 'attachment/upload/start',
|
type: 'attachment/upload/start',
|
||||||
payload: {
|
payload: {
|
||||||
local: { ...media, type: 'video', hash },
|
local: { ...media, hash },
|
||||||
uploading: true
|
uploading: true
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
@ -71,7 +65,7 @@ export const uploadAttachment = async ({
|
|||||||
composeDispatch({
|
composeDispatch({
|
||||||
type: 'attachment/upload/start',
|
type: 'attachment/upload/start',
|
||||||
payload: {
|
payload: {
|
||||||
local: { ...media, type: 'unknown', hash },
|
local: { ...media, hash },
|
||||||
uploading: true
|
uploading: true
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
@ -102,8 +96,8 @@ export const uploadAttachment = async ({
|
|||||||
const formData = new FormData()
|
const formData = new FormData()
|
||||||
formData.append('file', {
|
formData.append('file', {
|
||||||
uri: media.uri,
|
uri: media.uri,
|
||||||
name: media.uri.match(new RegExp(/.*\/(.*)/))?.[1] || 'file.jpg',
|
name: media.fileName,
|
||||||
type: media.mime
|
type: media.type
|
||||||
} as any)
|
} as any)
|
||||||
|
|
||||||
return apiInstance<Mastodon.Attachment>({
|
return apiInstance<Mastodon.Attachment>({
|
||||||
@ -140,7 +134,8 @@ const chooseAndUploadAttachment = async ({
|
|||||||
showActionSheetWithOptions
|
showActionSheetWithOptions
|
||||||
})
|
})
|
||||||
for (const media of result) {
|
for (const media of result) {
|
||||||
uploadAttachment({ composeDispatch, media })
|
const requiredMedia = media as Required<Asset>
|
||||||
|
uploadAttachment({ composeDispatch, media: requiredMedia })
|
||||||
await new Promise(res => setTimeout(res, 500))
|
await new Promise(res => setTimeout(res, 500))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,9 +1,12 @@
|
|||||||
import CustomText from '@components/Text'
|
import CustomText from '@components/Text'
|
||||||
|
import { getSettingsFontsize } from '@utils/slices/settingsSlice'
|
||||||
import { StyleConstants } from '@utils/styles/constants'
|
import { StyleConstants } from '@utils/styles/constants'
|
||||||
|
import { adaptiveScale } from '@utils/styles/scaling'
|
||||||
import { useTheme } from '@utils/styles/ThemeManager'
|
import { useTheme } from '@utils/styles/ThemeManager'
|
||||||
import React, { useContext } from 'react'
|
import React, { useContext } from 'react'
|
||||||
import { useTranslation } from 'react-i18next'
|
import { useTranslation } from 'react-i18next'
|
||||||
import { TextInput } from 'react-native'
|
import { TextInput } from 'react-native'
|
||||||
|
import { useSelector } from 'react-redux'
|
||||||
import formatText from '../../formatText'
|
import formatText from '../../formatText'
|
||||||
import ComposeContext from '../../utils/createContext'
|
import ComposeContext from '../../utils/createContext'
|
||||||
|
|
||||||
@ -12,6 +15,16 @@ const ComposeSpoilerInput: React.FC = () => {
|
|||||||
const { t } = useTranslation('screenCompose')
|
const { t } = useTranslation('screenCompose')
|
||||||
const { colors, mode } = useTheme()
|
const { colors, mode } = useTheme()
|
||||||
|
|
||||||
|
const adaptiveFontsize = useSelector(getSettingsFontsize)
|
||||||
|
const adaptedFontsize = adaptiveScale(
|
||||||
|
StyleConstants.Font.Size.M,
|
||||||
|
adaptiveFontsize
|
||||||
|
)
|
||||||
|
const adaptedLineheight = adaptiveScale(
|
||||||
|
StyleConstants.Font.LineHeight.M,
|
||||||
|
adaptiveFontsize
|
||||||
|
)
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<TextInput
|
<TextInput
|
||||||
keyboardAppearance={mode}
|
keyboardAppearance={mode}
|
||||||
@ -23,7 +36,9 @@ const ComposeSpoilerInput: React.FC = () => {
|
|||||||
marginRight: StyleConstants.Spacing.Global.PagePadding,
|
marginRight: StyleConstants.Spacing.Global.PagePadding,
|
||||||
borderBottomWidth: 0.5,
|
borderBottomWidth: 0.5,
|
||||||
color: colors.primaryDefault,
|
color: colors.primaryDefault,
|
||||||
borderBottomColor: colors.border
|
borderBottomColor: colors.border,
|
||||||
|
fontSize: adaptedFontsize,
|
||||||
|
lineHeight: adaptedLineheight
|
||||||
}}
|
}}
|
||||||
autoCapitalize='none'
|
autoCapitalize='none'
|
||||||
autoCorrect={false}
|
autoCorrect={false}
|
||||||
|
@ -1,7 +1,9 @@
|
|||||||
import CustomText from '@components/Text'
|
import CustomText from '@components/Text'
|
||||||
import PasteInput, { PastedFile } from '@mattermost/react-native-paste-input'
|
import PasteInput, { PastedFile } from '@mattermost/react-native-paste-input'
|
||||||
import { getInstanceConfigurationStatusMaxAttachments } from '@utils/slices/instancesSlice'
|
import { getInstanceConfigurationStatusMaxAttachments } from '@utils/slices/instancesSlice'
|
||||||
|
import { getSettingsFontsize } from '@utils/slices/settingsSlice'
|
||||||
import { StyleConstants } from '@utils/styles/constants'
|
import { StyleConstants } from '@utils/styles/constants'
|
||||||
|
import { adaptiveScale } from '@utils/styles/scaling'
|
||||||
import { useTheme } from '@utils/styles/ThemeManager'
|
import { useTheme } from '@utils/styles/ThemeManager'
|
||||||
import React, { useContext } from 'react'
|
import React, { useContext } from 'react'
|
||||||
import { useTranslation } from 'react-i18next'
|
import { useTranslation } from 'react-i18next'
|
||||||
@ -21,6 +23,16 @@ const ComposeTextInput: React.FC = () => {
|
|||||||
() => true
|
() => true
|
||||||
)
|
)
|
||||||
|
|
||||||
|
const adaptiveFontsize = useSelector(getSettingsFontsize)
|
||||||
|
const adaptedFontsize = adaptiveScale(
|
||||||
|
StyleConstants.Font.Size.M,
|
||||||
|
adaptiveFontsize
|
||||||
|
)
|
||||||
|
const adaptedLineheight = adaptiveScale(
|
||||||
|
StyleConstants.Font.LineHeight.M,
|
||||||
|
adaptiveFontsize
|
||||||
|
)
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<PasteInput
|
<PasteInput
|
||||||
keyboardAppearance={mode}
|
keyboardAppearance={mode}
|
||||||
@ -31,7 +43,9 @@ const ComposeTextInput: React.FC = () => {
|
|||||||
marginLeft: StyleConstants.Spacing.Global.PagePadding,
|
marginLeft: StyleConstants.Spacing.Global.PagePadding,
|
||||||
marginRight: StyleConstants.Spacing.Global.PagePadding,
|
marginRight: StyleConstants.Spacing.Global.PagePadding,
|
||||||
color: colors.primaryDefault,
|
color: colors.primaryDefault,
|
||||||
borderBottomColor: colors.border
|
borderBottomColor: colors.border,
|
||||||
|
fontSize: adaptedFontsize,
|
||||||
|
lineHeight: adaptedLineheight
|
||||||
}}
|
}}
|
||||||
autoFocus
|
autoFocus
|
||||||
enablesReturnKeyAutomatically
|
enablesReturnKeyAutomatically
|
||||||
@ -87,15 +101,7 @@ const ComposeTextInput: React.FC = () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
for (const file of files) {
|
for (const file of files) {
|
||||||
uploadAttachment({
|
uploadAttachment({ composeDispatch, media: file })
|
||||||
composeDispatch,
|
|
||||||
media: {
|
|
||||||
uri: file.uri,
|
|
||||||
mime: file.type,
|
|
||||||
width: 100,
|
|
||||||
height: 100
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
|
13
src/screens/Compose/utils/types.d.ts
vendored
13
src/screens/Compose/utils/types.d.ts
vendored
@ -1,12 +1,8 @@
|
|||||||
import { ImageOrVideo } from 'react-native-image-crop-picker'
|
import { Asset } from 'react-native-image-picker'
|
||||||
|
|
||||||
export type ExtendedAttachment = {
|
export type ExtendedAttachment = {
|
||||||
remote?: Mastodon.Attachment
|
remote?: Mastodon.Attachment
|
||||||
local?: { uri: string } & Pick<ImageOrVideo, 'width' | 'height' | 'mime'> & {
|
local?: Asset & { thumbnail?: string; hash: string }
|
||||||
type: 'image' | 'video' | 'unknown'
|
|
||||||
local_thumbnail?: string
|
|
||||||
hash?: string
|
|
||||||
}
|
|
||||||
uploading?: boolean
|
uploading?: boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -121,10 +117,7 @@ export type ComposeAction =
|
|||||||
}
|
}
|
||||||
| {
|
| {
|
||||||
type: 'attachment/upload/end'
|
type: 'attachment/upload/end'
|
||||||
payload: {
|
payload: { remote: Mastodon.Attachment; local: Asset }
|
||||||
remote: Mastodon.Attachment
|
|
||||||
local: { uri: string } & Pick<ImageOrVideo, 'width' | 'height' | 'mime'>
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
| {
|
| {
|
||||||
type: 'attachment/upload/fail'
|
type: 'attachment/upload/fail'
|
||||||
|
@ -50,11 +50,9 @@ const usePanResponder = ({
|
|||||||
onLongPress,
|
onLongPress,
|
||||||
delayLongPress,
|
delayLongPress,
|
||||||
onRequestClose
|
onRequestClose
|
||||||
}: Props): Readonly<[
|
}: Props): Readonly<
|
||||||
GestureResponderHandlers,
|
[GestureResponderHandlers, Animated.Value, Animated.ValueXY]
|
||||||
Animated.Value,
|
> => {
|
||||||
Animated.ValueXY
|
|
||||||
]> => {
|
|
||||||
let numberInitialTouches = 1
|
let numberInitialTouches = 1
|
||||||
let initialTouches: NativeTouchEvent[] = []
|
let initialTouches: NativeTouchEvent[] = []
|
||||||
let currentScale = initialScale
|
let currentScale = initialScale
|
||||||
@ -137,6 +135,7 @@ const usePanResponder = ({
|
|||||||
|
|
||||||
if (gestureState.numberActiveTouches > 1) return
|
if (gestureState.numberActiveTouches > 1) return
|
||||||
|
|
||||||
|
// @ts-ignore
|
||||||
longPressHandlerRef = setTimeout(onLongPress, delayLongPress)
|
longPressHandlerRef = setTimeout(onLongPress, delayLongPress)
|
||||||
},
|
},
|
||||||
onStart: (
|
onStart: (
|
||||||
@ -150,6 +149,7 @@ const usePanResponder = ({
|
|||||||
|
|
||||||
const tapTS = Date.now()
|
const tapTS = Date.now()
|
||||||
!timer &&
|
!timer &&
|
||||||
|
// @ts-ignore
|
||||||
(timer = setTimeout(() => onRequestClose(), DOUBLE_TAP_DELAY + 50))
|
(timer = setTimeout(() => onRequestClose(), DOUBLE_TAP_DELAY + 50))
|
||||||
// Handle double tap event by calculating diff between first and second taps timestamps
|
// Handle double tap event by calculating diff between first and second taps timestamps
|
||||||
|
|
||||||
@ -158,6 +158,7 @@ const usePanResponder = ({
|
|||||||
)
|
)
|
||||||
|
|
||||||
if (doubleTapToZoomEnabled && isDoubleTapPerformed) {
|
if (doubleTapToZoomEnabled && isDoubleTapPerformed) {
|
||||||
|
// @ts-ignore
|
||||||
clearTimeout(timer)
|
clearTimeout(timer)
|
||||||
const isScaled = currentTranslate.x !== initialTranslate.x // currentScale !== initialScale;
|
const isScaled = currentTranslate.x !== initialTranslate.x // currentScale !== initialScale;
|
||||||
const { pageX: touchX, pageY: touchY } = event.nativeEvent.touches[0]
|
const { pageX: touchX, pageY: touchY } = event.nativeEvent.touches[0]
|
||||||
@ -291,9 +292,8 @@ const usePanResponder = ({
|
|||||||
if (isTapGesture && currentScale > initialScale) {
|
if (isTapGesture && currentScale > initialScale) {
|
||||||
const { x, y } = currentTranslate
|
const { x, y } = currentTranslate
|
||||||
const { dx, dy } = gestureState
|
const { dx, dy } = gestureState
|
||||||
const [topBound, leftBound, bottomBound, rightBound] = getBounds(
|
const [topBound, leftBound, bottomBound, rightBound] =
|
||||||
currentScale
|
getBounds(currentScale)
|
||||||
)
|
|
||||||
|
|
||||||
let nextTranslateX = x + dx
|
let nextTranslateX = x + dx
|
||||||
let nextTranslateY = y + dy
|
let nextTranslateY = y + dy
|
||||||
@ -357,9 +357,8 @@ const usePanResponder = ({
|
|||||||
|
|
||||||
if (tmpTranslate) {
|
if (tmpTranslate) {
|
||||||
const { x, y } = tmpTranslate
|
const { x, y } = tmpTranslate
|
||||||
const [topBound, leftBound, bottomBound, rightBound] = getBounds(
|
const [topBound, leftBound, bottomBound, rightBound] =
|
||||||
currentScale
|
getBounds(currentScale)
|
||||||
)
|
|
||||||
|
|
||||||
let nextTranslateX = x
|
let nextTranslateX = x
|
||||||
let nextTranslateY = y
|
let nextTranslateY = y
|
||||||
|
@ -44,6 +44,7 @@ export const getImageStyles = (
|
|||||||
const transform = translate.getTranslateTransform()
|
const transform = translate.getTranslateTransform()
|
||||||
|
|
||||||
if (scale) {
|
if (scale) {
|
||||||
|
// @ts-ignore
|
||||||
transform.push({ scale }, { perspective: new Animated.Value(1000) })
|
transform.push({ scale }, { perspective: new Animated.Value(1000) })
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,10 +1,7 @@
|
|||||||
import GracefullyImage from '@components/GracefullyImage'
|
import GracefullyImage from '@components/GracefullyImage'
|
||||||
import haptics from '@components/haptics'
|
import haptics from '@components/haptics'
|
||||||
import Icon from '@components/Icon'
|
import Icon from '@components/Icon'
|
||||||
import {
|
import { createBottomTabNavigator } from '@react-navigation/bottom-tabs'
|
||||||
BottomTabNavigationOptions,
|
|
||||||
createBottomTabNavigator
|
|
||||||
} from '@react-navigation/bottom-tabs'
|
|
||||||
import { useAppDispatch } from '@root/store'
|
import { useAppDispatch } from '@root/store'
|
||||||
import {
|
import {
|
||||||
RootStackScreenProps,
|
RootStackScreenProps,
|
||||||
@ -15,10 +12,7 @@ import {
|
|||||||
getInstanceAccount,
|
getInstanceAccount,
|
||||||
getInstanceActive
|
getInstanceActive
|
||||||
} from '@utils/slices/instancesSlice'
|
} from '@utils/slices/instancesSlice'
|
||||||
import {
|
import { getVersionUpdate, retriveVersionLatest } from '@utils/slices/appSlice'
|
||||||
getVersionUpdate,
|
|
||||||
retriveVersionLatest
|
|
||||||
} from '@utils/slices/appSlice'
|
|
||||||
import { useTheme } from '@utils/styles/ThemeManager'
|
import { useTheme } from '@utils/styles/ThemeManager'
|
||||||
import React, { useCallback, useEffect, useMemo } from 'react'
|
import React, { useCallback, useEffect, useMemo } from 'react'
|
||||||
import { Platform } from 'react-native'
|
import { Platform } from 'react-native'
|
||||||
@ -32,7 +26,7 @@ const Tab = createBottomTabNavigator<ScreenTabsStackParamList>()
|
|||||||
|
|
||||||
const ScreenTabs = React.memo(
|
const ScreenTabs = React.memo(
|
||||||
({ navigation }: RootStackScreenProps<'Screen-Tabs'>) => {
|
({ navigation }: RootStackScreenProps<'Screen-Tabs'>) => {
|
||||||
const { colors, theme } = useTheme()
|
const { colors } = useTheme()
|
||||||
|
|
||||||
const instanceActive = useSelector(getInstanceActive)
|
const instanceActive = useSelector(getInstanceActive)
|
||||||
const instanceAccount = useSelector(
|
const instanceAccount = useSelector(
|
||||||
@ -40,57 +34,6 @@ const ScreenTabs = React.memo(
|
|||||||
(prev, next) => prev?.avatarStatic === next?.avatarStatic
|
(prev, next) => prev?.avatarStatic === next?.avatarStatic
|
||||||
)
|
)
|
||||||
|
|
||||||
const screenOptions = useCallback(
|
|
||||||
({ route }): BottomTabNavigationOptions => ({
|
|
||||||
headerShown: false,
|
|
||||||
tabBarActiveTintColor: colors.primaryDefault,
|
|
||||||
tabBarInactiveTintColor: colors.secondary,
|
|
||||||
tabBarShowLabel: false,
|
|
||||||
...(Platform.OS === 'android' && { tabBarHideOnKeyboard: true }),
|
|
||||||
tabBarStyle: { display: instanceActive !== -1 ? 'flex' : 'none' },
|
|
||||||
tabBarIcon: ({
|
|
||||||
focused,
|
|
||||||
color,
|
|
||||||
size
|
|
||||||
}: {
|
|
||||||
focused: boolean
|
|
||||||
color: string
|
|
||||||
size: number
|
|
||||||
}) => {
|
|
||||||
switch (route.name) {
|
|
||||||
case 'Tab-Local':
|
|
||||||
return <Icon name='Home' size={size} color={color} />
|
|
||||||
case 'Tab-Public':
|
|
||||||
return <Icon name='Globe' size={size} color={color} />
|
|
||||||
case 'Tab-Compose':
|
|
||||||
return <Icon name='Plus' size={size} color={color} />
|
|
||||||
case 'Tab-Notifications':
|
|
||||||
return <Icon name='Bell' size={size} color={color} />
|
|
||||||
case 'Tab-Me':
|
|
||||||
return (
|
|
||||||
<GracefullyImage
|
|
||||||
key={instanceAccount?.avatarStatic}
|
|
||||||
uri={{ original: instanceAccount?.avatarStatic }}
|
|
||||||
dimension={{
|
|
||||||
width: size,
|
|
||||||
height: size
|
|
||||||
}}
|
|
||||||
style={{
|
|
||||||
borderRadius: size,
|
|
||||||
overflow: 'hidden',
|
|
||||||
borderWidth: focused ? 2 : 0,
|
|
||||||
borderColor: focused ? colors.secondary : color
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
)
|
|
||||||
default:
|
|
||||||
return <Icon name='AlertOctagon' size={size} color={color} />
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}),
|
|
||||||
[instanceAccount?.avatarStatic, instanceActive, theme]
|
|
||||||
)
|
|
||||||
|
|
||||||
const composeListeners = useMemo(
|
const composeListeners = useMemo(
|
||||||
() => ({
|
() => ({
|
||||||
tabPress: (e: any) => {
|
tabPress: (e: any) => {
|
||||||
@ -132,7 +75,53 @@ const ScreenTabs = React.memo(
|
|||||||
return (
|
return (
|
||||||
<Tab.Navigator
|
<Tab.Navigator
|
||||||
initialRouteName={instanceActive !== -1 ? previousTab : 'Tab-Me'}
|
initialRouteName={instanceActive !== -1 ? previousTab : 'Tab-Me'}
|
||||||
screenOptions={screenOptions}
|
screenOptions={({ route }) => ({
|
||||||
|
headerShown: false,
|
||||||
|
tabBarActiveTintColor: colors.primaryDefault,
|
||||||
|
tabBarInactiveTintColor: colors.secondary,
|
||||||
|
tabBarShowLabel: false,
|
||||||
|
...(Platform.OS === 'android' && { tabBarHideOnKeyboard: true }),
|
||||||
|
tabBarStyle: { display: instanceActive !== -1 ? 'flex' : 'none' },
|
||||||
|
tabBarIcon: ({
|
||||||
|
focused,
|
||||||
|
color,
|
||||||
|
size
|
||||||
|
}: {
|
||||||
|
focused: boolean
|
||||||
|
color: string
|
||||||
|
size: number
|
||||||
|
}) => {
|
||||||
|
switch (route.name) {
|
||||||
|
case 'Tab-Local':
|
||||||
|
return <Icon name='Home' size={size} color={color} />
|
||||||
|
case 'Tab-Public':
|
||||||
|
return <Icon name='Globe' size={size} color={color} />
|
||||||
|
case 'Tab-Compose':
|
||||||
|
return <Icon name='Plus' size={size} color={color} />
|
||||||
|
case 'Tab-Notifications':
|
||||||
|
return <Icon name='Bell' size={size} color={color} />
|
||||||
|
case 'Tab-Me':
|
||||||
|
return (
|
||||||
|
<GracefullyImage
|
||||||
|
key={instanceAccount?.avatarStatic}
|
||||||
|
uri={{ original: instanceAccount?.avatarStatic }}
|
||||||
|
dimension={{
|
||||||
|
width: size,
|
||||||
|
height: size
|
||||||
|
}}
|
||||||
|
style={{
|
||||||
|
borderRadius: size,
|
||||||
|
overflow: 'hidden',
|
||||||
|
borderWidth: focused ? 2 : 0,
|
||||||
|
borderColor: focused ? colors.secondary : color
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
default:
|
||||||
|
return <Icon name='AlertOctagon' size={size} color={color} />
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})}
|
||||||
>
|
>
|
||||||
<Tab.Screen name='Tab-Local' component={TabLocal} />
|
<Tab.Screen name='Tab-Local' component={TabLocal} />
|
||||||
<Tab.Screen name='Tab-Public' component={TabPublic} />
|
<Tab.Screen name='Tab-Public' component={TabPublic} />
|
||||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user