mirror of https://github.com/tooot-app/app
Compare commits
17 Commits
6ad59c9c3a
...
24d48bbffa
Author | SHA1 | Date |
---|---|---|
xmflsct | 24d48bbffa | |
xmflsct | 415fe9eaa3 | |
xmflsct | c8a5fff26e | |
xmflsct | 551b67cd90 | |
xmflsct | 0ef9ff44e6 | |
xmflsct | 5c7e09c609 | |
xmflsct | a8f8445a9c | |
xmflsct | a5c023bd11 | |
xmflsct | 8041b2309d | |
xmflsct | e2ba4660df | |
xmflsct | 99b38f421c | |
xmflsct | 28b5a2e17e | |
xmflsct | 33d8a97efb | |
xmflsct | b9cf0ceb6b | |
xmflsct | 51a2ff012a | |
xmflsct | 621ed3561d | |
xmflsct | 87ff05d132 |
|
@ -31,6 +31,7 @@ DerivedData
|
|||
*.ipa
|
||||
*.xcuserstate
|
||||
project.xcworkspace
|
||||
ios/.xcode.env.local
|
||||
|
||||
# Android/IntelliJ
|
||||
#
|
||||
|
|
|
@ -0,0 +1,3 @@
|
|||
{
|
||||
"javascript.inlayHints.functionLikeReturnTypes.enabled": false
|
||||
}
|
|
@ -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-community/cli-platform-ios/package.json')"`), "native_modules")
|
||||
|
||||
platform :ios, '12.0'
|
||||
platform :ios, '12.4'
|
||||
install! 'cocoapods', :deterministic_uuids => false
|
||||
|
||||
production = ENV["PRODUCTION"] == "1"
|
||||
|
||||
require 'json'
|
||||
podfile_properties = JSON.parse(File.read('./Podfile.properties.json')) rescue {}
|
||||
|
||||
target 'tooot' do
|
||||
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!
|
||||
|
||||
# Flags change depending on the env values.
|
||||
|
@ -25,27 +19,33 @@ target 'tooot' do
|
|||
|
||||
use_react_native!(
|
||||
:path => config[:reactNativePath],
|
||||
:production => production,
|
||||
:hermes_enabled => podfile_properties['expo.jsEngine'] == 'hermes',
|
||||
:fabric_enabled => flags[:fabric_enabled],
|
||||
:flipper_configuration => FlipperConfiguration.enabled,
|
||||
# An absolute path to your application 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|
|
||||
react_native_post_install(installer)
|
||||
__apply_Xcode_12_5_M1_post_install_workaround(installer)
|
||||
|
||||
# For share extension
|
||||
installer.pods_project.targets.each do |target|
|
||||
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
|
||||
|
||||
post_integrate do |installer|
|
||||
begin
|
||||
expo_patch_react_imports!(installer)
|
||||
rescue => e
|
||||
Pod::UI.warn e
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
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>"; };
|
||||
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>"; };
|
||||
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>"; };
|
||||
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>"; };
|
||||
|
@ -333,13 +333,15 @@
|
|||
files = (
|
||||
);
|
||||
inputPaths = (
|
||||
"$(SRCROOT)/.xcode.env.local",
|
||||
"$(SRCROOT)/.xcode.env",
|
||||
);
|
||||
name = "Bundle React Native code and images";
|
||||
outputPaths = (
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
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 */ = {
|
||||
isa = PBXShellScriptBuildPhase;
|
||||
|
@ -370,10 +372,16 @@
|
|||
);
|
||||
inputPaths = (
|
||||
"${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",
|
||||
);
|
||||
name = "[CP] Embed Pods Frameworks";
|
||||
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",
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
|
@ -390,17 +398,13 @@
|
|||
"${PODS_ROOT}/Target Support Files/Pods-tooot/Pods-tooot-resources.sh",
|
||||
"${PODS_CONFIGURATION_BUILD_DIR}/EXConstants/EXConstants.bundle",
|
||||
"${PODS_CONFIGURATION_BUILD_DIR}/EXUpdates/EXUpdates.bundle",
|
||||
"${PODS_CONFIGURATION_BUILD_DIR}/RNImageCropPicker/QBImagePicker.bundle",
|
||||
"${PODS_CONFIGURATION_BUILD_DIR}/React-Core/AccessibilityResources.bundle",
|
||||
"${PODS_CONFIGURATION_BUILD_DIR}/TOCropViewController/TOCropViewControllerBundle.bundle",
|
||||
);
|
||||
name = "[CP] Copy Pods Resources";
|
||||
outputPaths = (
|
||||
"${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/EXConstants.bundle",
|
||||
"${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/EXUpdates.bundle",
|
||||
"${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/QBImagePicker.bundle",
|
||||
"${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/AccessibilityResources.bundle",
|
||||
"${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/TOCropViewControllerBundle.bundle",
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
shellPath = /bin/sh;
|
||||
|
@ -524,7 +528,6 @@
|
|||
isa = XCBuildConfiguration;
|
||||
baseConfigurationReference = 6C2E3173556A471DD304B334 /* Pods-tooot.debug.xcconfig */;
|
||||
buildSettings = {
|
||||
ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES;
|
||||
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
||||
CLANG_ENABLE_MODULES = YES;
|
||||
CODE_SIGN_ENTITLEMENTS = tooot/tooot.entitlements;
|
||||
|
@ -533,13 +536,12 @@
|
|||
CURRENT_PROJECT_VERSION = 2102022230;
|
||||
DEVELOPMENT_TEAM = 8EGBLQ2MA6;
|
||||
ENABLE_BITCODE = NO;
|
||||
"EXCLUDED_ARCHS[sdk=iphonesimulator*]" = arm64;
|
||||
GCC_PREPROCESSOR_DEFINITIONS = (
|
||||
"$(inherited)",
|
||||
"FB_SONARKIT_ENABLED=1",
|
||||
);
|
||||
INFOPLIST_FILE = tooot/Info.plist;
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 12.0;
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 12.4;
|
||||
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
|
||||
OTHER_LDFLAGS = (
|
||||
"$(inherited)",
|
||||
|
@ -564,7 +566,6 @@
|
|||
isa = XCBuildConfiguration;
|
||||
baseConfigurationReference = 7A4D352CD337FB3A3BF06240 /* Pods-tooot.release.xcconfig */;
|
||||
buildSettings = {
|
||||
ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES;
|
||||
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
||||
CLANG_ENABLE_MODULES = YES;
|
||||
CODE_SIGN_ENTITLEMENTS = tooot/tooot.entitlements;
|
||||
|
@ -572,9 +573,8 @@
|
|||
CODE_SIGN_STYLE = Manual;
|
||||
CURRENT_PROJECT_VERSION = 2102022230;
|
||||
DEVELOPMENT_TEAM = 8EGBLQ2MA6;
|
||||
"EXCLUDED_ARCHS[sdk=iphonesimulator*]" = arm64;
|
||||
INFOPLIST_FILE = tooot/Info.plist;
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 12.0;
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 12.4;
|
||||
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
|
||||
OTHER_LDFLAGS = (
|
||||
"$(inherited)",
|
||||
|
@ -599,7 +599,7 @@
|
|||
buildSettings = {
|
||||
ALWAYS_SEARCH_USER_PATHS = NO;
|
||||
CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES;
|
||||
CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
|
||||
CLANG_CXX_LANGUAGE_STANDARD = "c++0x";
|
||||
CLANG_CXX_LIBRARY = "libc++";
|
||||
CLANG_ENABLE_MODULES = YES;
|
||||
CLANG_ENABLE_OBJC_ARC = YES;
|
||||
|
@ -644,15 +644,12 @@
|
|||
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
|
||||
GCC_WARN_UNUSED_FUNCTION = YES;
|
||||
GCC_WARN_UNUSED_VARIABLE = YES;
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 12.0;
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 12.4;
|
||||
LD_RUNPATH_SEARCH_PATHS = "/usr/lib/swift $(inherited)";
|
||||
LIBRARY_SEARCH_PATHS = (
|
||||
"$(SDKROOT)/usr/lib/swift",
|
||||
"\"$(TOOLCHAIN_DIR)/usr/lib/swift/$(PLATFORM_NAME)\"",
|
||||
"\"$(inherited)\"",
|
||||
);
|
||||
LIBRARY_SEARCH_PATHS = "\"\"";
|
||||
MTL_ENABLE_DEBUG_INFO = YES;
|
||||
ONLY_ACTIVE_ARCH = YES;
|
||||
REACT_NATIVE_PATH = "${PODS_ROOT}/../../node_modules/react-native";
|
||||
SDKROOT = iphoneos;
|
||||
};
|
||||
name = Debug;
|
||||
|
@ -662,7 +659,7 @@
|
|||
buildSettings = {
|
||||
ALWAYS_SEARCH_USER_PATHS = NO;
|
||||
CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES;
|
||||
CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
|
||||
CLANG_CXX_LANGUAGE_STANDARD = "c++0x";
|
||||
CLANG_CXX_LIBRARY = "libc++";
|
||||
CLANG_ENABLE_MODULES = YES;
|
||||
CLANG_ENABLE_OBJC_ARC = YES;
|
||||
|
@ -700,14 +697,12 @@
|
|||
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
|
||||
GCC_WARN_UNUSED_FUNCTION = YES;
|
||||
GCC_WARN_UNUSED_VARIABLE = YES;
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 12.0;
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 12.4;
|
||||
LD_RUNPATH_SEARCH_PATHS = "/usr/lib/swift $(inherited)";
|
||||
LIBRARY_SEARCH_PATHS = (
|
||||
"$(SDKROOT)/usr/lib/swift",
|
||||
"\"$(TOOLCHAIN_DIR)/usr/lib/swift/$(PLATFORM_NAME)\"",
|
||||
"\"$(inherited)\"",
|
||||
);
|
||||
LIBRARY_SEARCH_PATHS = "\"\"";
|
||||
MTL_ENABLE_DEBUG_INFO = NO;
|
||||
ONLY_ACTIVE_ARCH = NO;
|
||||
REACT_NATIVE_PATH = "${PODS_ROOT}/../../node_modules/react-native";
|
||||
SDKROOT = iphoneos;
|
||||
SWIFT_COMPILATION_MODE = wholemodule;
|
||||
VALIDATE_PRODUCT = YES;
|
||||
|
@ -736,7 +731,7 @@
|
|||
INFOPLIST_FILE = ShareExtension/Info.plist;
|
||||
INFOPLIST_KEY_CFBundleDisplayName = ShareExtension;
|
||||
INFOPLIST_KEY_NSHumanReadableCopyright = "";
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 12.0;
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 12.4;
|
||||
"IPHONEOS_DEPLOYMENT_TARGET[sdk=macosx*]" = 14.2;
|
||||
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @executable_path/../../Frameworks";
|
||||
MARKETING_VERSION = 1.0;
|
||||
|
@ -781,7 +776,7 @@
|
|||
INFOPLIST_FILE = ShareExtension/Info.plist;
|
||||
INFOPLIST_KEY_CFBundleDisplayName = ShareExtension;
|
||||
INFOPLIST_KEY_NSHumanReadableCopyright = "";
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 12.0;
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 12.4;
|
||||
"IPHONEOS_DEPLOYMENT_TARGET[sdk=macosx*]" = 14.2;
|
||||
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @executable_path/../../Frameworks";
|
||||
MARKETING_VERSION = 1.0;
|
||||
|
|
|
@ -19,6 +19,8 @@
|
|||
|
||||
#import <react/config/ReactNativeConfig.h>
|
||||
|
||||
static NSString *const kRNConcurrentRoot = @"concurrentRoot";
|
||||
|
||||
@interface AppDelegate () <RCTCxxBridgeDelegate, RCTTurboModuleManagerDelegate> {
|
||||
RCTTurboModuleManager *_turboModuleManager;
|
||||
RCTSurfacePresenterBridgeAdapter *_bridgeAdapter;
|
||||
|
@ -46,6 +48,9 @@
|
|||
|
||||
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, *)) {
|
||||
rootView.backgroundColor = [UIColor colorNamed:@"SplashScreenBackgroundColor"];
|
||||
} else {
|
||||
|
@ -61,6 +66,25 @@
|
|||
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
|
||||
{
|
||||
#if DEBUG
|
||||
|
|
196
package.json
196
package.json
|
@ -1,11 +1,11 @@
|
|||
{
|
||||
"name": "tooot",
|
||||
"versions": {
|
||||
"native": "220603",
|
||||
"native": "220806",
|
||||
"major": 4,
|
||||
"minor": 1,
|
||||
"patch": 6,
|
||||
"expo": "45.0.0"
|
||||
"minor": 3,
|
||||
"patch": 0,
|
||||
"expo": "46.0.0"
|
||||
},
|
||||
"description": "tooot app for Mastodon",
|
||||
"author": "xmflsct <me@xmflsct.com>",
|
||||
|
@ -25,112 +25,106 @@
|
|||
"postinstall": "patch-package"
|
||||
},
|
||||
"dependencies": {
|
||||
"@expo/react-native-action-sheet": "3.13.0",
|
||||
"@formatjs/intl-datetimeformat": "^6.0.2",
|
||||
"@expo/react-native-action-sheet": "^3.13.0",
|
||||
"@formatjs/intl-datetimeformat": "^6.0.3",
|
||||
"@formatjs/intl-getcanonicallocales": "^2.0.2",
|
||||
"@formatjs/intl-locale": "^3.0.2",
|
||||
"@formatjs/intl-numberformat": "^8.0.2",
|
||||
"@formatjs/intl-pluralrules": "^5.0.2",
|
||||
"@formatjs/intl-relativetimeformat": "^11.0.2",
|
||||
"@mattermost/react-native-paste-input": "^0.4.2",
|
||||
"@neverdull-agency/expo-unlimited-secure-store": "1.0.10",
|
||||
"@react-native-async-storage/async-storage": "1.17.6",
|
||||
"@react-native-community/blur": "3.6.0",
|
||||
"@react-native-community/cameraroll": "4.1.2",
|
||||
"@react-native-community/netinfo": "9.0.0",
|
||||
"@react-native-community/segmented-control": "2.2.2",
|
||||
"@react-navigation/bottom-tabs": "6.3.1",
|
||||
"@react-navigation/native": "6.0.10",
|
||||
"@react-navigation/native-stack": "6.6.2",
|
||||
"@react-navigation/stack": "6.2.1",
|
||||
"@reduxjs/toolkit": "1.8.2",
|
||||
"@sentry/react-native": "3.4.3",
|
||||
"@sharcoux/slider": "6.0.3",
|
||||
"axios": "0.27.2",
|
||||
"expo": "45.0.5",
|
||||
"expo-auth-session": "3.6.1",
|
||||
"expo-av": "11.2.3",
|
||||
"expo-constants": "^13.1.1",
|
||||
"expo-crypto": "10.2.0",
|
||||
"expo-device": "4.2.0",
|
||||
"expo-file-system": "14.0.0",
|
||||
"expo-firebase-analytics": "7.0.0",
|
||||
"expo-haptics": "11.2.0",
|
||||
"expo-image-manipulator": "^10.3.1",
|
||||
"expo-image-picker": "13.1.1",
|
||||
"expo-linking": "3.1.0",
|
||||
"expo-localization": "13.0.0",
|
||||
"expo-notifications": "0.15.2",
|
||||
"expo-random": "12.2.0",
|
||||
"expo-screen-capture": "4.2.0",
|
||||
"expo-secure-store": "11.2.0",
|
||||
"expo-splash-screen": "0.15.1",
|
||||
"expo-store-review": "5.2.0",
|
||||
"expo-updates": "0.13.2",
|
||||
"expo-video-thumbnails": "6.3.0",
|
||||
"expo-web-browser": "10.2.1",
|
||||
"i18next": "21.8.8",
|
||||
"li": "1.3.0",
|
||||
"lodash": "4.17.21",
|
||||
"react": "17.0.2",
|
||||
"react-dom": "17.0.2",
|
||||
"react-i18next": "11.17.0",
|
||||
"react-intl": "^6.0.4",
|
||||
"react-native": "0.68.2",
|
||||
"react-native-animated-spinkit": "1.5.2",
|
||||
"@formatjs/intl-locale": "^3.0.3",
|
||||
"@formatjs/intl-numberformat": "^8.0.4",
|
||||
"@formatjs/intl-pluralrules": "^5.0.3",
|
||||
"@formatjs/intl-relativetimeformat": "^11.0.3",
|
||||
"@mattermost/react-native-paste-input": "^0.5.0",
|
||||
"@neverdull-agency/expo-unlimited-secure-store": "^1.0.10",
|
||||
"@react-native-async-storage/async-storage": "^1.17.7",
|
||||
"@react-native-community/blur": "^4.2.0",
|
||||
"@react-native-community/cameraroll": "^4.1.2",
|
||||
"@react-native-community/netinfo": "^9.3.0",
|
||||
"@react-native-community/segmented-control": "^2.2.2",
|
||||
"@react-navigation/bottom-tabs": "^6.3.2",
|
||||
"@react-navigation/native": "^6.0.11",
|
||||
"@react-navigation/native-stack": "^6.7.0",
|
||||
"@react-navigation/stack": "^6.2.2",
|
||||
"@reduxjs/toolkit": "^1.8.3",
|
||||
"@sentry/react-native": "^4.2.2",
|
||||
"@sharcoux/slider": "^6.0.3",
|
||||
"axios": "^0.27.2",
|
||||
"expo": "^46.0.2",
|
||||
"expo-auth-session": "^3.7.1",
|
||||
"expo-av": "^12.0.3",
|
||||
"expo-constants": "^13.2.3",
|
||||
"expo-crypto": "^11.0.0",
|
||||
"expo-device": "^4.3.0",
|
||||
"expo-file-system": "^14.1.0",
|
||||
"expo-firebase-analytics": "^7.1.1",
|
||||
"expo-haptics": "^11.3.0",
|
||||
"expo-linking": "^3.2.2",
|
||||
"expo-localization": "^13.1.0",
|
||||
"expo-notifications": "^0.16.1",
|
||||
"expo-random": "^12.3.0",
|
||||
"expo-screen-capture": "^4.3.0",
|
||||
"expo-secure-store": "^11.3.0",
|
||||
"expo-splash-screen": "^0.16.1",
|
||||
"expo-store-review": "^5.3.0",
|
||||
"expo-updates": "^0.14.3",
|
||||
"expo-video-thumbnails": "^6.4.0",
|
||||
"expo-web-browser": "^11.0.0",
|
||||
"i18next": "^21.8.16",
|
||||
"li": "^1.3.0",
|
||||
"lodash": "^4.17.21",
|
||||
"react": "^18.2.0",
|
||||
"react-dom": "^18.2.0",
|
||||
"react-i18next": "^11.18.3",
|
||||
"react-intl": "^6.0.5",
|
||||
"react-native": "^0.69.3",
|
||||
"react-native-animated-spinkit": "^1.5.2",
|
||||
"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-fast-image": "8.5.11",
|
||||
"react-native-feather": "1.1.2",
|
||||
"react-native-flash-message": "0.2.1",
|
||||
"react-native-gesture-handler": "2.4.2",
|
||||
"react-native-htmlview": "0.16.0",
|
||||
"react-native-image-crop-picker": "^0.37.3",
|
||||
"react-native-fast-image": "^8.5.11",
|
||||
"react-native-feather": "^1.1.2",
|
||||
"react-native-flash-message": "^0.3.1",
|
||||
"react-native-gesture-handler": "^2.5.0",
|
||||
"react-native-htmlview": "^0.16.0",
|
||||
"react-native-image-picker": "^4.8.4",
|
||||
"react-native-language-detection": "^0.1.0",
|
||||
"react-native-pager-view": "5.4.11",
|
||||
"react-native-reanimated": "2.8.0",
|
||||
"react-native-safe-area-context": "4.3.1",
|
||||
"react-native-screens": "3.13.1",
|
||||
"react-native-pager-view": "^5.4.25",
|
||||
"react-native-reanimated": "^2.9.1",
|
||||
"react-native-safe-area-context": "^4.3.1",
|
||||
"react-native-screens": "^3.15.0",
|
||||
"react-native-share-menu": "^5.0.5",
|
||||
"react-native-svg": "12.3.0",
|
||||
"react-native-swipe-list-view": "3.2.9",
|
||||
"react-native-tab-view": "3.1.1",
|
||||
"react-query": "3.39.1",
|
||||
"react-redux": "8.0.2",
|
||||
"redux-persist": "6.0.0",
|
||||
"rn-placeholder": "3.0.3",
|
||||
"sentry-expo": "4.2.0",
|
||||
"tslib": "2.4.0",
|
||||
"valid-url": "1.0.9"
|
||||
"react-native-svg": "^12.4.3",
|
||||
"react-native-swipe-list-view": "^3.2.9",
|
||||
"react-native-tab-view": "^3.1.1",
|
||||
"react-query": "^3.39.2",
|
||||
"react-redux": "^8.0.2",
|
||||
"redux-persist": "^6.0.0",
|
||||
"rn-placeholder": "^3.0.3",
|
||||
"sentry-expo": "^5.0.1",
|
||||
"tslib": "^2.4.0",
|
||||
"valid-url": "^1.0.9"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@babel/core": "7.18.2",
|
||||
"@babel/plugin-proposal-optional-chaining": "7.17.12",
|
||||
"@babel/preset-react": "^7.17.12",
|
||||
"@babel/preset-typescript": "7.17.12",
|
||||
"@expo/config": "6.0.24",
|
||||
"@types/lodash": "4.14.182",
|
||||
"@types/react": "17.0.43",
|
||||
"@types/react-dom": "17.0.14",
|
||||
"@types/react-native": "0.67.8",
|
||||
"@babel/core": "^7.18.10",
|
||||
"@babel/plugin-proposal-optional-chaining": "^7.18.9",
|
||||
"@babel/preset-react": "^7.18.6",
|
||||
"@babel/preset-typescript": "^7.18.6",
|
||||
"@expo/config": "^7.0.0",
|
||||
"@types/lodash": "^4.14.182",
|
||||
"@types/react": "^18.0.15",
|
||||
"@types/react-dom": "^18.0.6",
|
||||
"@types/react-native": "^0.69.5",
|
||||
"@types/react-native-base64": "^0.2.0",
|
||||
"@types/react-native-share-menu": "^5.0.2",
|
||||
"@types/react-timeago": "4.1.3",
|
||||
"@types/valid-url": "1.0.3",
|
||||
"@welldone-software/why-did-you-render": "7.0.1",
|
||||
"babel-plugin-module-resolver": "4.1.0",
|
||||
"babel-plugin-transform-remove-console": "6.9.4",
|
||||
"chalk": "4.1.2",
|
||||
"dotenv": "16.0.1",
|
||||
"patch-package": "6.4.7",
|
||||
"postinstall-postinstall": "2.1.0",
|
||||
"react-native-clean-project": "4.0.1",
|
||||
"typescript": "4.7.3"
|
||||
},
|
||||
"resolutions": {
|
||||
"@types/react": "17.0.43",
|
||||
"@types/react-dom": "17.0.14"
|
||||
"@types/react-timeago": "^4.1.3",
|
||||
"@types/valid-url": "^1.0.3",
|
||||
"@welldone-software/why-did-you-render": "^7.0.1",
|
||||
"babel-plugin-module-resolver": "^4.1.0",
|
||||
"babel-plugin-transform-remove-console": "^6.9.4",
|
||||
"chalk": "^4.1.2",
|
||||
"dotenv": "^16.0.1",
|
||||
"patch-package": "^6.4.7",
|
||||
"postinstall-postinstall": "^2.1.0",
|
||||
"react-native-clean-project": "^4.0.1",
|
||||
"typescript": "^4.7.4"
|
||||
},
|
||||
"expo": {
|
||||
"autolinking": {
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
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
|
||||
+++ b/node_modules/react-native-fast-image/RNFastImage.podspec
|
||||
@@ -16,6 +16,6 @@ Pod::Spec.new do |s|
|
||||
|
@ -7,8 +7,9 @@ index db0fada..54d8d5b 100644
|
|||
|
||||
s.dependency 'React-Core'
|
||||
- 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
|
||||
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
|
||||
|
|
|
@ -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,
|
23
src/App.tsx
23
src/App.tsx
|
@ -74,8 +74,14 @@ const App: React.FC = () => {
|
|||
}
|
||||
}, [])
|
||||
|
||||
const children = useCallback(
|
||||
bootstrapped => {
|
||||
return (
|
||||
<GestureHandlerRootView style={{ flex: 1 }}>
|
||||
<QueryClientProvider client={queryClient}>
|
||||
<Provider store={store}>
|
||||
<PersistGate
|
||||
persistor={persistor}
|
||||
onBeforeLift={onBeforeLift}
|
||||
children={bootstrapped => {
|
||||
log('log', 'App', 'bootstrapped')
|
||||
if (bootstrapped) {
|
||||
log('log', 'App', 'loading actual app :)')
|
||||
|
@ -101,18 +107,7 @@ const App: React.FC = () => {
|
|||
} else {
|
||||
return null
|
||||
}
|
||||
},
|
||||
[localCorrupt]
|
||||
)
|
||||
|
||||
return (
|
||||
<GestureHandlerRootView style={{ flex: 1 }}>
|
||||
<QueryClientProvider client={queryClient}>
|
||||
<Provider store={store}>
|
||||
<PersistGate
|
||||
persistor={persistor}
|
||||
onBeforeLift={onBeforeLift}
|
||||
children={children}
|
||||
}}
|
||||
/>
|
||||
</Provider>
|
||||
</QueryClientProvider>
|
||||
|
|
|
@ -170,6 +170,7 @@ const Screens: React.FC<Props> = ({ localCorrupt }) => {
|
|||
}
|
||||
| { data: string | string[]; mimeType: string }
|
||||
) => {
|
||||
console.log('item', item)
|
||||
if (instanceActive < 0) {
|
||||
return
|
||||
}
|
||||
|
@ -253,6 +254,7 @@ const Screens: React.FC<Props> = ({ localCorrupt }) => {
|
|||
if (!text && !media.length) {
|
||||
return
|
||||
} else {
|
||||
console.log('media', media)
|
||||
navigationRef.navigate('Screen-Compose', { type: 'share', text, media })
|
||||
}
|
||||
},
|
||||
|
|
|
@ -7,6 +7,7 @@ import { chunk, forEach, groupBy, sortBy } from 'lodash'
|
|||
import React, {
|
||||
Dispatch,
|
||||
MutableRefObject,
|
||||
PropsWithChildren,
|
||||
SetStateAction,
|
||||
useCallback,
|
||||
useEffect,
|
||||
|
@ -57,7 +58,7 @@ export interface Props {
|
|||
maxLength?: number
|
||||
}
|
||||
|
||||
const ComponentEmojis: React.FC<Props> = ({
|
||||
const ComponentEmojis: React.FC<Props & PropsWithChildren> = ({
|
||||
enabled = false,
|
||||
value,
|
||||
setValue,
|
||||
|
|
|
@ -27,18 +27,6 @@ const EmojisList = React.memo(
|
|||
const { emojisState, emojisDispatch } = useContext(EmojisContext)
|
||||
const { colors } = useTheme()
|
||||
|
||||
const listHeader = useCallback(
|
||||
({ section: { title } }) => (
|
||||
<CustomText
|
||||
fontStyle='S'
|
||||
style={{ position: 'absolute', color: colors.secondary }}
|
||||
>
|
||||
{title}
|
||||
</CustomText>
|
||||
),
|
||||
[]
|
||||
)
|
||||
|
||||
const listItem = useCallback(
|
||||
({ index, item }: { item: Mastodon.Emoji[]; index: number }) => {
|
||||
return (
|
||||
|
@ -112,7 +100,14 @@ const EmojisList = React.memo(
|
|||
keyboardShouldPersistTaps='always'
|
||||
sections={emojisState.emojis}
|
||||
keyExtractor={item => item[0].shortcode}
|
||||
renderSectionHeader={listHeader}
|
||||
renderSectionHeader={({ section: { title } }) => (
|
||||
<CustomText
|
||||
fontStyle='S'
|
||||
style={{ position: 'absolute', color: colors.secondary }}
|
||||
>
|
||||
{title}
|
||||
</CustomText>
|
||||
)}
|
||||
renderItem={listItem}
|
||||
windowSize={4}
|
||||
/>
|
||||
|
|
|
@ -4,7 +4,6 @@ import { useTheme } from '@utils/styles/ThemeManager'
|
|||
import React, {
|
||||
Dispatch,
|
||||
SetStateAction,
|
||||
useCallback,
|
||||
useEffect,
|
||||
useRef,
|
||||
useState
|
||||
|
@ -81,10 +80,6 @@ const Input: React.FC<Props> = ({
|
|||
}
|
||||
: { start: 0, end: 0 }
|
||||
)
|
||||
const onSelectionChange = useCallback(
|
||||
({ nativeEvent: { selection } }) => (selectionRange.current = selection),
|
||||
[]
|
||||
)
|
||||
|
||||
const [inputFocused, setInputFocused] = useState(false)
|
||||
useEffect(() => {
|
||||
|
@ -128,7 +123,9 @@ const Input: React.FC<Props> = ({
|
|||
: undefined
|
||||
}}
|
||||
onChangeText={setValue}
|
||||
onSelectionChange={onSelectionChange}
|
||||
onSelectionChange={({ nativeEvent: { selection } }) =>
|
||||
(selectionRange.current = selection)
|
||||
}
|
||||
value={value}
|
||||
{...(multiline && {
|
||||
multiline,
|
||||
|
|
|
@ -88,21 +88,6 @@ const ComponentInstance: React.FC<Props> = ({
|
|||
}
|
||||
}, [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(() => {
|
||||
if (
|
||||
domain &&
|
||||
|
@ -180,7 +165,17 @@ const ComponentInstance: React.FC<Props> = ({
|
|||
clearButtonMode='never'
|
||||
keyboardType='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')}
|
||||
placeholderTextColor={colors.secondary}
|
||||
returnKeyType='go'
|
||||
|
|
|
@ -80,7 +80,7 @@ const displayMessage = ({
|
|||
})
|
||||
} else {
|
||||
showMessage({
|
||||
duration: type === 'error' ? 3500 : duration === 'short' ? 1500 : 2500,
|
||||
duration: type === 'error' ? 8000 : duration === 'short' ? 3000 : 5000,
|
||||
autoHide,
|
||||
message,
|
||||
description,
|
||||
|
@ -124,7 +124,8 @@ const Message = React.forwardRef<FlashMessage>((_, ref) => {
|
|||
shadowColor: colors.primaryDefault,
|
||||
shadowOffset: { width: 0, height: 0 },
|
||||
shadowOpacity: theme === 'light' ? 0.16 : 0.24,
|
||||
shadowRadius: 4
|
||||
shadowRadius: 4,
|
||||
paddingRight: StyleConstants.Spacing.M * 2
|
||||
}}
|
||||
titleStyle={{
|
||||
color: colors.primaryDefault,
|
||||
|
|
|
@ -215,7 +215,7 @@ const ParseHTML = React.memo(
|
|||
}
|
||||
|
||||
const renderNodeCallback = useCallback(
|
||||
(node, index) =>
|
||||
(node: any, index: any) =>
|
||||
renderNode({
|
||||
routeParams: route.params,
|
||||
colors,
|
||||
|
@ -231,7 +231,7 @@ const ParseHTML = React.memo(
|
|||
}),
|
||||
[]
|
||||
)
|
||||
const textComponent = useCallback(({ children }) => {
|
||||
const textComponent = useCallback(({ children }: any) => {
|
||||
if (children) {
|
||||
return (
|
||||
<ParseEmojis
|
||||
|
@ -246,26 +246,24 @@ const ParseHTML = React.memo(
|
|||
}
|
||||
}, [])
|
||||
const rootComponent = useCallback(
|
||||
({ children }) => {
|
||||
({ children }: any) => {
|
||||
const { t } = useTranslation('componentParse')
|
||||
|
||||
const [expandAllow, setExpandAllow] = useState(false)
|
||||
const [expanded, setExpanded] = useState(highlighted)
|
||||
|
||||
const onTextLayout = useCallback(({ nativeEvent }) => {
|
||||
return (
|
||||
<View style={{ overflow: 'hidden' }}>
|
||||
<CustomText
|
||||
children={children}
|
||||
onTextLayout={({ nativeEvent }) => {
|
||||
if (
|
||||
numberOfLines === 1 ||
|
||||
nativeEvent.lines.length >= numberOfLines + 5
|
||||
) {
|
||||
setExpandAllow(true)
|
||||
}
|
||||
}, [])
|
||||
|
||||
return (
|
||||
<View style={{ overflow: 'hidden' }}>
|
||||
<CustomText
|
||||
children={children}
|
||||
onTextLayout={onTextLayout}
|
||||
}}
|
||||
numberOfLines={
|
||||
expandAllow ? (expanded ? 999 : numberOfLines) : undefined
|
||||
}
|
||||
|
|
|
@ -64,17 +64,6 @@ const Timeline: React.FC<Props> = ({
|
|||
? 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(
|
||||
() => !disableInfinity && !isFetchingNextPage && fetchNextPage(),
|
||||
[isFetchingNextPage]
|
||||
|
@ -151,7 +140,17 @@ const Timeline: React.FC<Props> = ({
|
|||
/>
|
||||
}
|
||||
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={
|
||||
isFetching
|
||||
? {
|
||||
|
|
|
@ -9,7 +9,7 @@ import { StyleConstants } from '@utils/styles/constants'
|
|||
import { useTheme } from '@utils/styles/ThemeManager'
|
||||
import React, { RefObject, useCallback, useRef, useState } from 'react'
|
||||
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 Animated, {
|
||||
Extrapolate,
|
||||
|
@ -169,7 +169,7 @@ const TimelineRefresh: React.FC<Props> = ({
|
|||
|
||||
const arrowStage = useSharedValue(0)
|
||||
const onLayout = useCallback(
|
||||
({ nativeEvent }) => {
|
||||
({ nativeEvent }: LayoutChangeEvent) => {
|
||||
if (nativeEvent.layout.x + nativeEvent.layout.width > textRight) {
|
||||
setTextRight(nativeEvent.layout.x + nativeEvent.layout.width)
|
||||
}
|
||||
|
|
|
@ -1,15 +1,8 @@
|
|||
import analytics from '@components/analytics'
|
||||
import { ActionSheetOptions } from '@expo/react-native-action-sheet'
|
||||
import { store } from '@root/store'
|
||||
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 { Alert, Linking, Platform } from 'react-native'
|
||||
import ImagePicker, {
|
||||
Image,
|
||||
ImageOrVideo
|
||||
} from 'react-native-image-crop-picker'
|
||||
import { Asset, launchImageLibrary } from 'react-native-image-picker'
|
||||
|
||||
export interface Props {
|
||||
mediaType?: 'photo' | 'video'
|
||||
|
@ -28,43 +21,7 @@ const mediaSelector = async ({
|
|||
maximum,
|
||||
indicateMaximum = false,
|
||||
showActionSheetWithOptions
|
||||
}: Props): Promise<({ uri: string } & Omit<ImageOrVideo, 'path'>)[]> => {
|
||||
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
|
||||
}
|
||||
}
|
||||
|
||||
}: Props): Promise<Asset[]> => {
|
||||
const _maximum =
|
||||
maximum ||
|
||||
getInstanceConfigurationStatusMaxAttachments(store.getState()) ||
|
||||
|
@ -105,79 +62,30 @@ const mediaSelector = async ({
|
|||
|
||||
return new Promise((resolve, reject) => {
|
||||
const selectImage = async () => {
|
||||
const images = await ImagePicker.openPicker({
|
||||
const images = await launchImageLibrary({
|
||||
mediaType: 'photo',
|
||||
includeExif: false,
|
||||
multiple: true,
|
||||
minFiles: 1,
|
||||
maxFiles: _maximum,
|
||||
smartAlbums: ['UserLibrary'],
|
||||
writeTempFile: false,
|
||||
loadingLabelText: ''
|
||||
}).catch(() => {})
|
||||
...(resize && { maxWidth: resize.width, maxHeight: resize.height }),
|
||||
includeBase64: false,
|
||||
includeExtra: false,
|
||||
selectionLimit: _maximum
|
||||
})
|
||||
|
||||
if (!images) {
|
||||
if (!images.assets) {
|
||||
return reject()
|
||||
}
|
||||
|
||||
// react-native-image-crop-picker may return HEIC as JPG that causes upload failure
|
||||
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.sourceURL || 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}`
|
||||
}))
|
||||
)
|
||||
}
|
||||
return resolve(images.assets)
|
||||
}
|
||||
const selectVideo = async () => {
|
||||
const video = await ImagePicker.openPicker({
|
||||
const video = await launchImageLibrary({
|
||||
mediaType: 'video',
|
||||
includeExif: false,
|
||||
loadingLabelText: ''
|
||||
}).catch(() => {})
|
||||
includeBase64: false,
|
||||
includeExtra: false,
|
||||
selectionLimit: 1
|
||||
})
|
||||
|
||||
if (video) {
|
||||
return resolve([
|
||||
{ ...video, uri: video.sourceURL || `file://${video.path}` }
|
||||
])
|
||||
if (video.assets?.[0]) {
|
||||
return resolve(video.assets)
|
||||
} else {
|
||||
return reject()
|
||||
}
|
||||
|
@ -189,10 +97,6 @@ const mediaSelector = async ({
|
|||
cancelButtonIndex: mediaType ? 1 : 2
|
||||
},
|
||||
async buttonIndex => {
|
||||
if (!(await checkLibraryPermission())) {
|
||||
return reject()
|
||||
}
|
||||
|
||||
switch (mediaType) {
|
||||
case 'photo':
|
||||
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
|
|
@ -78,9 +78,7 @@
|
|||
}
|
||||
},
|
||||
"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>",
|
||||
"availableSizes": "Verfügbare Größen",
|
||||
"sizes": {
|
||||
"S": "S",
|
||||
"M": "M – Standard",
|
||||
|
@ -147,7 +145,8 @@
|
|||
"group": "Gruppe {{index}}",
|
||||
"label": "Kennzeichnung",
|
||||
"content": "Inhalt"
|
||||
}
|
||||
},
|
||||
"mediaSelectionFailed": ""
|
||||
},
|
||||
"push": {
|
||||
"notAvailable": "Dein Gerät unterstützt keine Push-Benachrichtigung",
|
||||
|
|
|
@ -78,9 +78,7 @@
|
|||
}
|
||||
},
|
||||
"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>",
|
||||
"availableSizes": "Available sizes",
|
||||
"sizes": {
|
||||
"S": "S",
|
||||
"M": "M - Default",
|
||||
|
@ -147,7 +145,8 @@
|
|||
"group": "Group {{index}}",
|
||||
"label": "Label",
|
||||
"content": "Content"
|
||||
}
|
||||
},
|
||||
"mediaSelectionFailed": "Image processing failed. Please try again."
|
||||
},
|
||||
"push": {
|
||||
"notAvailable": "Your phone does not support tooot's push notification",
|
||||
|
|
|
@ -78,9 +78,7 @@
|
|||
}
|
||||
},
|
||||
"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>",
|
||||
"availableSizes": "Dimensioni testo",
|
||||
"sizes": {
|
||||
"S": "S",
|
||||
"M": "M - Predefinito",
|
||||
|
@ -147,7 +145,8 @@
|
|||
"group": "Gruppo {{index}}",
|
||||
"label": "Etichetta",
|
||||
"content": "Contenuto"
|
||||
}
|
||||
},
|
||||
"mediaSelectionFailed": ""
|
||||
},
|
||||
"push": {
|
||||
"notAvailable": "Il tuo dispositivo non supporta le notifiche push per tooot",
|
||||
|
|
|
@ -78,9 +78,7 @@
|
|||
}
|
||||
},
|
||||
"fontSize": {
|
||||
"showcase": "",
|
||||
"demo": "",
|
||||
"availableSizes": "",
|
||||
"sizes": {
|
||||
"S": "",
|
||||
"M": "",
|
||||
|
@ -147,7 +145,8 @@
|
|||
"group": "",
|
||||
"label": "",
|
||||
"content": ""
|
||||
}
|
||||
},
|
||||
"mediaSelectionFailed": ""
|
||||
},
|
||||
"push": {
|
||||
"notAvailable": "",
|
||||
|
|
|
@ -78,9 +78,7 @@
|
|||
}
|
||||
},
|
||||
"fontSize": {
|
||||
"showcase": "예시 툿",
|
||||
"demo": "<p>데모 툿이에요😊. 아래의 여러 옵션 중에서 선택할 수 있어요.<br /><br />이 설정은 툿의 메인 내용에만 적용되고, 다른 폰트 크기에 영향을 미치지 않아요.</p>",
|
||||
"availableSizes": "사용할 수 있는 크기",
|
||||
"sizes": {
|
||||
"S": "작게",
|
||||
"M": "중간 - 기본값",
|
||||
|
@ -147,7 +145,8 @@
|
|||
"group": "그룹 {{index}}",
|
||||
"label": "라벨",
|
||||
"content": "내용"
|
||||
}
|
||||
},
|
||||
"mediaSelectionFailed": ""
|
||||
},
|
||||
"push": {
|
||||
"notAvailable": "이 기기는 tooot의 푸시 알림을 지원하지 않아요",
|
||||
|
|
|
@ -78,9 +78,7 @@
|
|||
}
|
||||
},
|
||||
"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>",
|
||||
"availableSizes": "Tamanhos disponíveis",
|
||||
"sizes": {
|
||||
"S": "P",
|
||||
"M": "M - Padrão",
|
||||
|
@ -147,7 +145,8 @@
|
|||
"group": "Grupo {{index}}",
|
||||
"label": "Rótulo",
|
||||
"content": "Conteúdo"
|
||||
}
|
||||
},
|
||||
"mediaSelectionFailed": ""
|
||||
},
|
||||
"push": {
|
||||
"notAvailable": "Seu telefone não suporta notificação de envio de tooot",
|
||||
|
|
|
@ -78,9 +78,7 @@
|
|||
}
|
||||
},
|
||||
"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>",
|
||||
"availableSizes": "Kích cỡ",
|
||||
"sizes": {
|
||||
"S": "S",
|
||||
"M": "M - Mặc định",
|
||||
|
@ -147,7 +145,8 @@
|
|||
"group": "Mục {{index}}",
|
||||
"label": "Nhãn",
|
||||
"content": "Nội dung"
|
||||
}
|
||||
},
|
||||
"mediaSelectionFailed": ""
|
||||
},
|
||||
"push": {
|
||||
"notAvailable": "Điện thoại của bạn chưa bật thông báo đẩy",
|
||||
|
|
|
@ -78,9 +78,7 @@
|
|||
}
|
||||
},
|
||||
"fontSize": {
|
||||
"showcase": "嘟文示例",
|
||||
"demo": "<p>这是一条测试用的嘟文😊。以下是可供选择的字号,从小号至超大号。<br /><br />这个设置仅会调整嘟文的正文字号,不影响其它字号。</p>",
|
||||
"availableSizes": "可选字号",
|
||||
"sizes": {
|
||||
"S": "小号",
|
||||
"M": "默认",
|
||||
|
@ -147,7 +145,8 @@
|
|||
"group": "第 {{index}} 组",
|
||||
"label": "标签",
|
||||
"content": "内容"
|
||||
}
|
||||
},
|
||||
"mediaSelectionFailed": ""
|
||||
},
|
||||
"push": {
|
||||
"notAvailable": "您的手机不支持tooot推送通知",
|
||||
|
|
|
@ -78,9 +78,7 @@
|
|||
}
|
||||
},
|
||||
"fontSize": {
|
||||
"showcase": "嘟文範例",
|
||||
"demo": "",
|
||||
"availableSizes": "",
|
||||
"sizes": {
|
||||
"S": "",
|
||||
"M": "",
|
||||
|
@ -147,7 +145,8 @@
|
|||
"group": "",
|
||||
"label": "",
|
||||
"content": ""
|
||||
}
|
||||
},
|
||||
"mediaSelectionFailed": ""
|
||||
},
|
||||
"push": {
|
||||
"notAvailable": "",
|
||||
|
|
|
@ -15,8 +15,15 @@ import { StyleConstants } from '@utils/styles/constants'
|
|||
import { useTheme } from '@utils/styles/ThemeManager'
|
||||
import React, { useCallback, useEffect, useState } from 'react'
|
||||
import { Trans, useTranslation } from 'react-i18next'
|
||||
import { FormattedRelativeTime } from 'react-intl'
|
||||
import { Dimensions, Platform, Pressable, StyleSheet, View } from 'react-native'
|
||||
import {
|
||||
Dimensions,
|
||||
NativeScrollEvent,
|
||||
NativeSyntheticEvent,
|
||||
Platform,
|
||||
Pressable,
|
||||
StyleSheet,
|
||||
View
|
||||
} from 'react-native'
|
||||
import { Circle } from 'react-native-animated-spinkit'
|
||||
import FastImage from 'react-native-fast-image'
|
||||
import { FlatList, ScrollView } from 'react-native-gesture-handler'
|
||||
|
@ -92,9 +99,7 @@ const ScreenAnnouncements: React.FC<
|
|||
>
|
||||
<Trans
|
||||
i18nKey='screenAnnouncements:content.published'
|
||||
components={[
|
||||
<RelativeTime time={item.published_at} />
|
||||
]}
|
||||
components={[<RelativeTime time={item.published_at} />]}
|
||||
/>
|
||||
</CustomText>
|
||||
<ScrollView
|
||||
|
@ -218,7 +223,7 @@ const ScreenAnnouncements: React.FC<
|
|||
contentOffset: { x },
|
||||
layoutMeasurement: { width }
|
||||
}
|
||||
}) => {
|
||||
}: NativeSyntheticEvent<NativeScrollEvent>) => {
|
||||
setIndex(Math.floor(x / width))
|
||||
},
|
||||
[]
|
||||
|
|
|
@ -150,7 +150,7 @@ const ScreenCompose: React.FC<RootStackScreenProps<'Screen-Compose'>> = ({
|
|||
for (const m of params.media) {
|
||||
uploadAttachment({
|
||||
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 removeDraft = useCallback(ts => {
|
||||
dispatch(removeInstanceDraft(ts))
|
||||
}, [])
|
||||
|
||||
const renderItem = useCallback(
|
||||
({ item }: { item: ComposeStateDraft }) => {
|
||||
return (
|
||||
|
@ -144,7 +140,7 @@ const ComposeDraftsListRoot: React.FC<Props> = ({ timestamp }) => {
|
|||
}}
|
||||
source={{
|
||||
uri:
|
||||
attachment.local?.local_thumbnail ||
|
||||
attachment.local?.thumbnail ||
|
||||
attachment.remote?.preview_url
|
||||
}}
|
||||
/>
|
||||
|
@ -157,38 +153,6 @@ const ComposeDraftsListRoot: React.FC<Props> = ({ timestamp }) => {
|
|||
},
|
||||
[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 (
|
||||
<>
|
||||
|
@ -220,7 +184,35 @@ const ComposeDraftsListRoot: React.FC<Props> = ({ timestamp }) => {
|
|||
<SwipeListView
|
||||
data={instanceDrafts}
|
||||
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}
|
||||
rightOpenValue={-actionWidth}
|
||||
// previewRowKey={
|
||||
|
|
|
@ -35,7 +35,7 @@ const ComposeEditAttachmentRoot: React.FC<Props> = ({ index }) => {
|
|||
video.local
|
||||
? ({
|
||||
url: video.local.uri,
|
||||
preview_url: video.local.local_thumbnail,
|
||||
preview_url: video.local.thumbnail,
|
||||
blurhash: video.remote?.blurhash
|
||||
} 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 { useTheme } from '@utils/styles/ThemeManager'
|
||||
import { chunk, forEach, groupBy, sortBy } from 'lodash'
|
||||
import React, {
|
||||
useCallback,
|
||||
useContext,
|
||||
useEffect,
|
||||
useMemo,
|
||||
useRef
|
||||
} from 'react'
|
||||
import React, { useContext, useEffect, useMemo, useRef } from 'react'
|
||||
import {
|
||||
AccessibilityInfo,
|
||||
findNodeHandle,
|
||||
|
@ -147,35 +141,25 @@ const ComposeRoot = React.memo(
|
|||
}
|
||||
}, [isFetching])
|
||||
|
||||
const listItem = useCallback(
|
||||
({ item }) => (
|
||||
return (
|
||||
<View style={styles.base}>
|
||||
<FlatList
|
||||
renderItem={({ item }) => (
|
||||
<ComposeRootSuggestion
|
||||
item={item}
|
||||
composeState={composeState}
|
||||
composeDispatch={composeDispatch}
|
||||
/>
|
||||
),
|
||||
[composeState]
|
||||
)
|
||||
|
||||
const ListFooter = useCallback(
|
||||
() => (
|
||||
)}
|
||||
ListEmptyComponent={listEmpty}
|
||||
keyboardShouldPersistTaps='always'
|
||||
ListHeaderComponent={ComposeRootHeader}
|
||||
ListFooterComponent={() => (
|
||||
<ComposeRootFooter
|
||||
accessibleRefAttachments={accessibleRefAttachments}
|
||||
accessibleRefEmojis={accessibleRefEmojis}
|
||||
/>
|
||||
),
|
||||
[]
|
||||
)
|
||||
|
||||
return (
|
||||
<View style={styles.base}>
|
||||
<FlatList
|
||||
renderItem={listItem}
|
||||
ListEmptyComponent={listEmpty}
|
||||
keyboardShouldPersistTaps='always'
|
||||
ListHeaderComponent={ComposeRootHeader}
|
||||
ListFooterComponent={ListFooter}
|
||||
)}
|
||||
ItemSeparatorComponent={ComponentSeparator}
|
||||
// @ts-ignore
|
||||
data={data ? data[composeState.tag?.type] : undefined}
|
||||
|
|
|
@ -56,9 +56,12 @@ const ComposeAttachments: React.FC<Props> = ({ accessibleRefAttachments }) => {
|
|||
})
|
||||
}, [composeState.attachments.sensitive])
|
||||
|
||||
const calculateWidth = useCallback(item => {
|
||||
const calculateWidth = useCallback((item: ExtendedAttachment) => {
|
||||
if (item.local) {
|
||||
return (item.local.width / item.local.height) * DEFAULT_HEIGHT
|
||||
return (
|
||||
((item.local.width || 100) / (item.local.height || 100)) *
|
||||
DEFAULT_HEIGHT
|
||||
)
|
||||
} else {
|
||||
if (item.remote) {
|
||||
if (item.remote.meta.original.aspect) {
|
||||
|
@ -135,7 +138,7 @@ const ComposeAttachments: React.FC<Props> = ({ accessibleRefAttachments }) => {
|
|||
<FastImage
|
||||
style={{ width: '100%', height: '100%' }}
|
||||
source={{
|
||||
uri: item.local?.local_thumbnail || item.remote?.preview_url
|
||||
uri: item.local?.thumbnail || item.remote?.preview_url
|
||||
}}
|
||||
/>
|
||||
{item.remote?.meta?.original?.duration ? (
|
||||
|
|
|
@ -42,22 +42,6 @@ const ComposeEmojis: React.FC<Props> = ({ accessibleRefEmojis }) => {
|
|||
}
|
||||
}, [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(
|
||||
({ index, item }: { item: Mastodon.Emoji[]; index: number }) => {
|
||||
return (
|
||||
|
@ -155,7 +139,18 @@ const ComposeEmojis: React.FC<Props> = ({ accessibleRefEmojis }) => {
|
|||
keyboardShouldPersistTaps='always'
|
||||
sections={composeState.emoji.emojis || []}
|
||||
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}
|
||||
windowSize={2}
|
||||
/>
|
||||
|
|
|
@ -7,7 +7,7 @@ import { ActionSheetOptions } from '@expo/react-native-action-sheet'
|
|||
import i18next from 'i18next'
|
||||
import apiInstance from '@api/instance'
|
||||
import mediaSelector from '@components/mediaSelector'
|
||||
import { ImageOrVideo } from 'react-native-image-crop-picker'
|
||||
import { Asset } from 'react-native-image-picker'
|
||||
|
||||
export interface Props {
|
||||
composeDispatch: Dispatch<ComposeAction>
|
||||
|
@ -22,46 +22,40 @@ export const uploadAttachment = async ({
|
|||
media
|
||||
}: {
|
||||
composeDispatch: Dispatch<ComposeAction>
|
||||
media: { uri: string } & Pick<ImageOrVideo, 'mime' | 'width' | 'height'>
|
||||
media: Required<Pick<Asset, 'uri' | 'type' | 'fileName'>>
|
||||
}) => {
|
||||
const hash = await Crypto.digestStringAsync(
|
||||
Crypto.CryptoDigestAlgorithm.SHA256,
|
||||
media.uri + Math.random()
|
||||
)
|
||||
|
||||
switch (media.mime.split('/')[0]) {
|
||||
switch (media.type.split('/')[0]) {
|
||||
case 'image':
|
||||
composeDispatch({
|
||||
type: 'attachment/upload/start',
|
||||
payload: {
|
||||
local: { ...media, type: 'image', local_thumbnail: media.uri, hash },
|
||||
local: { ...media, thumbnail: media.uri, hash },
|
||||
uploading: true
|
||||
}
|
||||
})
|
||||
break
|
||||
case 'video':
|
||||
VideoThumbnails.getThumbnailAsync(media.uri)
|
||||
.then(({ uri, width, height }) =>
|
||||
.then(({ uri, width, height }) => {
|
||||
console.log('new', uri, width, height)
|
||||
composeDispatch({
|
||||
type: 'attachment/upload/start',
|
||||
payload: {
|
||||
local: {
|
||||
...media,
|
||||
type: 'video',
|
||||
local_thumbnail: uri,
|
||||
hash,
|
||||
width,
|
||||
height
|
||||
},
|
||||
local: { ...media, thumbnail: uri, hash, width, height },
|
||||
uploading: true
|
||||
}
|
||||
})
|
||||
)
|
||||
})
|
||||
.catch(() =>
|
||||
composeDispatch({
|
||||
type: 'attachment/upload/start',
|
||||
payload: {
|
||||
local: { ...media, type: 'video', hash },
|
||||
local: { ...media, hash },
|
||||
uploading: true
|
||||
}
|
||||
})
|
||||
|
@ -71,7 +65,7 @@ export const uploadAttachment = async ({
|
|||
composeDispatch({
|
||||
type: 'attachment/upload/start',
|
||||
payload: {
|
||||
local: { ...media, type: 'unknown', hash },
|
||||
local: { ...media, hash },
|
||||
uploading: true
|
||||
}
|
||||
})
|
||||
|
@ -102,8 +96,8 @@ export const uploadAttachment = async ({
|
|||
const formData = new FormData()
|
||||
formData.append('file', {
|
||||
uri: media.uri,
|
||||
name: media.uri.match(new RegExp(/.*\/(.*)/))?.[1] || 'file.jpg',
|
||||
type: media.mime
|
||||
name: media.fileName,
|
||||
type: media.type
|
||||
} as any)
|
||||
|
||||
return apiInstance<Mastodon.Attachment>({
|
||||
|
@ -140,7 +134,8 @@ const chooseAndUploadAttachment = async ({
|
|||
showActionSheetWithOptions
|
||||
})
|
||||
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))
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,9 +1,12 @@
|
|||
import CustomText from '@components/Text'
|
||||
import { getSettingsFontsize } from '@utils/slices/settingsSlice'
|
||||
import { StyleConstants } from '@utils/styles/constants'
|
||||
import { adaptiveScale } from '@utils/styles/scaling'
|
||||
import { useTheme } from '@utils/styles/ThemeManager'
|
||||
import React, { useContext } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { TextInput } from 'react-native'
|
||||
import { useSelector } from 'react-redux'
|
||||
import formatText from '../../formatText'
|
||||
import ComposeContext from '../../utils/createContext'
|
||||
|
||||
|
@ -12,6 +15,16 @@ const ComposeSpoilerInput: React.FC = () => {
|
|||
const { t } = useTranslation('screenCompose')
|
||||
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 (
|
||||
<TextInput
|
||||
keyboardAppearance={mode}
|
||||
|
@ -23,7 +36,9 @@ const ComposeSpoilerInput: React.FC = () => {
|
|||
marginRight: StyleConstants.Spacing.Global.PagePadding,
|
||||
borderBottomWidth: 0.5,
|
||||
color: colors.primaryDefault,
|
||||
borderBottomColor: colors.border
|
||||
borderBottomColor: colors.border,
|
||||
fontSize: adaptedFontsize,
|
||||
lineHeight: adaptedLineheight
|
||||
}}
|
||||
autoCapitalize='none'
|
||||
autoCorrect={false}
|
||||
|
|
|
@ -1,7 +1,9 @@
|
|||
import CustomText from '@components/Text'
|
||||
import PasteInput, { PastedFile } from '@mattermost/react-native-paste-input'
|
||||
import { getInstanceConfigurationStatusMaxAttachments } from '@utils/slices/instancesSlice'
|
||||
import { getSettingsFontsize } from '@utils/slices/settingsSlice'
|
||||
import { StyleConstants } from '@utils/styles/constants'
|
||||
import { adaptiveScale } from '@utils/styles/scaling'
|
||||
import { useTheme } from '@utils/styles/ThemeManager'
|
||||
import React, { useContext } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
|
@ -21,6 +23,16 @@ const ComposeTextInput: React.FC = () => {
|
|||
() => true
|
||||
)
|
||||
|
||||
const adaptiveFontsize = useSelector(getSettingsFontsize)
|
||||
const adaptedFontsize = adaptiveScale(
|
||||
StyleConstants.Font.Size.M,
|
||||
adaptiveFontsize
|
||||
)
|
||||
const adaptedLineheight = adaptiveScale(
|
||||
StyleConstants.Font.LineHeight.M,
|
||||
adaptiveFontsize
|
||||
)
|
||||
|
||||
return (
|
||||
<PasteInput
|
||||
keyboardAppearance={mode}
|
||||
|
@ -31,7 +43,9 @@ const ComposeTextInput: React.FC = () => {
|
|||
marginLeft: StyleConstants.Spacing.Global.PagePadding,
|
||||
marginRight: StyleConstants.Spacing.Global.PagePadding,
|
||||
color: colors.primaryDefault,
|
||||
borderBottomColor: colors.border
|
||||
borderBottomColor: colors.border,
|
||||
fontSize: adaptedFontsize,
|
||||
lineHeight: adaptedLineheight
|
||||
}}
|
||||
autoFocus
|
||||
enablesReturnKeyAutomatically
|
||||
|
@ -87,15 +101,7 @@ const ComposeTextInput: React.FC = () => {
|
|||
}
|
||||
|
||||
for (const file of files) {
|
||||
uploadAttachment({
|
||||
composeDispatch,
|
||||
media: {
|
||||
uri: file.uri,
|
||||
mime: file.type,
|
||||
width: 100,
|
||||
height: 100
|
||||
}
|
||||
})
|
||||
uploadAttachment({ composeDispatch, media: file })
|
||||
}
|
||||
}}
|
||||
>
|
||||
|
|
|
@ -1,12 +1,8 @@
|
|||
import { ImageOrVideo } from 'react-native-image-crop-picker'
|
||||
import { Asset } from 'react-native-image-picker'
|
||||
|
||||
export type ExtendedAttachment = {
|
||||
remote?: Mastodon.Attachment
|
||||
local?: { uri: string } & Pick<ImageOrVideo, 'width' | 'height' | 'mime'> & {
|
||||
type: 'image' | 'video' | 'unknown'
|
||||
local_thumbnail?: string
|
||||
hash?: string
|
||||
}
|
||||
local?: Asset & { thumbnail?: string; hash: string }
|
||||
uploading?: boolean
|
||||
}
|
||||
|
||||
|
@ -121,10 +117,7 @@ export type ComposeAction =
|
|||
}
|
||||
| {
|
||||
type: 'attachment/upload/end'
|
||||
payload: {
|
||||
remote: Mastodon.Attachment
|
||||
local: { uri: string } & Pick<ImageOrVideo, 'width' | 'height' | 'mime'>
|
||||
}
|
||||
payload: { remote: Mastodon.Attachment; local: Asset }
|
||||
}
|
||||
| {
|
||||
type: 'attachment/upload/fail'
|
||||
|
|
|
@ -50,11 +50,9 @@ const usePanResponder = ({
|
|||
onLongPress,
|
||||
delayLongPress,
|
||||
onRequestClose
|
||||
}: Props): Readonly<[
|
||||
GestureResponderHandlers,
|
||||
Animated.Value,
|
||||
Animated.ValueXY
|
||||
]> => {
|
||||
}: Props): Readonly<
|
||||
[GestureResponderHandlers, Animated.Value, Animated.ValueXY]
|
||||
> => {
|
||||
let numberInitialTouches = 1
|
||||
let initialTouches: NativeTouchEvent[] = []
|
||||
let currentScale = initialScale
|
||||
|
@ -137,6 +135,7 @@ const usePanResponder = ({
|
|||
|
||||
if (gestureState.numberActiveTouches > 1) return
|
||||
|
||||
// @ts-ignore
|
||||
longPressHandlerRef = setTimeout(onLongPress, delayLongPress)
|
||||
},
|
||||
onStart: (
|
||||
|
@ -150,6 +149,7 @@ const usePanResponder = ({
|
|||
|
||||
const tapTS = Date.now()
|
||||
!timer &&
|
||||
// @ts-ignore
|
||||
(timer = setTimeout(() => onRequestClose(), DOUBLE_TAP_DELAY + 50))
|
||||
// Handle double tap event by calculating diff between first and second taps timestamps
|
||||
|
||||
|
@ -158,6 +158,7 @@ const usePanResponder = ({
|
|||
)
|
||||
|
||||
if (doubleTapToZoomEnabled && isDoubleTapPerformed) {
|
||||
// @ts-ignore
|
||||
clearTimeout(timer)
|
||||
const isScaled = currentTranslate.x !== initialTranslate.x // currentScale !== initialScale;
|
||||
const { pageX: touchX, pageY: touchY } = event.nativeEvent.touches[0]
|
||||
|
@ -291,9 +292,8 @@ const usePanResponder = ({
|
|||
if (isTapGesture && currentScale > initialScale) {
|
||||
const { x, y } = currentTranslate
|
||||
const { dx, dy } = gestureState
|
||||
const [topBound, leftBound, bottomBound, rightBound] = getBounds(
|
||||
currentScale
|
||||
)
|
||||
const [topBound, leftBound, bottomBound, rightBound] =
|
||||
getBounds(currentScale)
|
||||
|
||||
let nextTranslateX = x + dx
|
||||
let nextTranslateY = y + dy
|
||||
|
@ -357,9 +357,8 @@ const usePanResponder = ({
|
|||
|
||||
if (tmpTranslate) {
|
||||
const { x, y } = tmpTranslate
|
||||
const [topBound, leftBound, bottomBound, rightBound] = getBounds(
|
||||
currentScale
|
||||
)
|
||||
const [topBound, leftBound, bottomBound, rightBound] =
|
||||
getBounds(currentScale)
|
||||
|
||||
let nextTranslateX = x
|
||||
let nextTranslateY = y
|
||||
|
|
|
@ -44,6 +44,7 @@ export const getImageStyles = (
|
|||
const transform = translate.getTranslateTransform()
|
||||
|
||||
if (scale) {
|
||||
// @ts-ignore
|
||||
transform.push({ scale }, { perspective: new Animated.Value(1000) })
|
||||
}
|
||||
|
||||
|
|
|
@ -1,10 +1,7 @@
|
|||
import GracefullyImage from '@components/GracefullyImage'
|
||||
import haptics from '@components/haptics'
|
||||
import Icon from '@components/Icon'
|
||||
import {
|
||||
BottomTabNavigationOptions,
|
||||
createBottomTabNavigator
|
||||
} from '@react-navigation/bottom-tabs'
|
||||
import { createBottomTabNavigator } from '@react-navigation/bottom-tabs'
|
||||
import { useAppDispatch } from '@root/store'
|
||||
import {
|
||||
RootStackScreenProps,
|
||||
|
@ -15,10 +12,7 @@ import {
|
|||
getInstanceAccount,
|
||||
getInstanceActive
|
||||
} from '@utils/slices/instancesSlice'
|
||||
import {
|
||||
getVersionUpdate,
|
||||
retriveVersionLatest
|
||||
} from '@utils/slices/appSlice'
|
||||
import { getVersionUpdate, retriveVersionLatest } from '@utils/slices/appSlice'
|
||||
import { useTheme } from '@utils/styles/ThemeManager'
|
||||
import React, { useCallback, useEffect, useMemo } from 'react'
|
||||
import { Platform } from 'react-native'
|
||||
|
@ -32,7 +26,7 @@ const Tab = createBottomTabNavigator<ScreenTabsStackParamList>()
|
|||
|
||||
const ScreenTabs = React.memo(
|
||||
({ navigation }: RootStackScreenProps<'Screen-Tabs'>) => {
|
||||
const { colors, theme } = useTheme()
|
||||
const { colors } = useTheme()
|
||||
|
||||
const instanceActive = useSelector(getInstanceActive)
|
||||
const instanceAccount = useSelector(
|
||||
|
@ -40,8 +34,48 @@ const ScreenTabs = React.memo(
|
|||
(prev, next) => prev?.avatarStatic === next?.avatarStatic
|
||||
)
|
||||
|
||||
const screenOptions = useCallback(
|
||||
({ route }): BottomTabNavigationOptions => ({
|
||||
const composeListeners = useMemo(
|
||||
() => ({
|
||||
tabPress: (e: any) => {
|
||||
e.preventDefault()
|
||||
haptics('Light')
|
||||
navigation.navigate('Screen-Compose')
|
||||
}
|
||||
}),
|
||||
[]
|
||||
)
|
||||
const composeComponent = useCallback(() => null, [])
|
||||
|
||||
const meListeners = useMemo(
|
||||
() => ({
|
||||
tabLongPress: () => {
|
||||
haptics('Light')
|
||||
//@ts-ignore
|
||||
navigation.navigate('Tab-Me', { screen: 'Tab-Me-Root' })
|
||||
//@ts-ignore
|
||||
navigation.navigate('Tab-Me', { screen: 'Tab-Me-Switch' })
|
||||
}
|
||||
}),
|
||||
[]
|
||||
)
|
||||
|
||||
const previousTab = useSelector(getPreviousTab, () => true)
|
||||
|
||||
const versionUpdate = useSelector(getVersionUpdate)
|
||||
const dispatch = useAppDispatch()
|
||||
useEffect(() => {
|
||||
dispatch(retriveVersionLatest())
|
||||
}, [])
|
||||
const tabMeOptions = useMemo(() => {
|
||||
if (versionUpdate) {
|
||||
return { tabBarBadge: 1 }
|
||||
}
|
||||
}, [versionUpdate])
|
||||
|
||||
return (
|
||||
<Tab.Navigator
|
||||
initialRouteName={instanceActive !== -1 ? previousTab : 'Tab-Me'}
|
||||
screenOptions={({ route }) => ({
|
||||
headerShown: false,
|
||||
tabBarActiveTintColor: colors.primaryDefault,
|
||||
tabBarInactiveTintColor: colors.secondary,
|
||||
|
@ -87,52 +121,7 @@ const ScreenTabs = React.memo(
|
|||
return <Icon name='AlertOctagon' size={size} color={color} />
|
||||
}
|
||||
}
|
||||
}),
|
||||
[instanceAccount?.avatarStatic, instanceActive, theme]
|
||||
)
|
||||
|
||||
const composeListeners = useMemo(
|
||||
() => ({
|
||||
tabPress: (e: any) => {
|
||||
e.preventDefault()
|
||||
haptics('Light')
|
||||
navigation.navigate('Screen-Compose')
|
||||
}
|
||||
}),
|
||||
[]
|
||||
)
|
||||
const composeComponent = useCallback(() => null, [])
|
||||
|
||||
const meListeners = useMemo(
|
||||
() => ({
|
||||
tabLongPress: () => {
|
||||
haptics('Light')
|
||||
//@ts-ignore
|
||||
navigation.navigate('Tab-Me', { screen: 'Tab-Me-Root' })
|
||||
//@ts-ignore
|
||||
navigation.navigate('Tab-Me', { screen: 'Tab-Me-Switch' })
|
||||
}
|
||||
}),
|
||||
[]
|
||||
)
|
||||
|
||||
const previousTab = useSelector(getPreviousTab, () => true)
|
||||
|
||||
const versionUpdate = useSelector(getVersionUpdate)
|
||||
const dispatch = useAppDispatch()
|
||||
useEffect(() => {
|
||||
dispatch(retriveVersionLatest())
|
||||
}, [])
|
||||
const tabMeOptions = useMemo(() => {
|
||||
if (versionUpdate) {
|
||||
return { tabBarBadge: 1 }
|
||||
}
|
||||
}, [versionUpdate])
|
||||
|
||||
return (
|
||||
<Tab.Navigator
|
||||
initialRouteName={instanceActive !== -1 ? previousTab : 'Tab-Me'}
|
||||
screenOptions={screenOptions}
|
||||
})}
|
||||
>
|
||||
<Tab.Screen name='Tab-Local' component={TabLocal} />
|
||||
<Tab.Screen name='Tab-Public' component={TabPublic} />
|
||||
|
|
|
@ -44,16 +44,16 @@ const TabLocal = React.memo(
|
|||
)
|
||||
|
||||
const queryKey: QueryKeyTimeline = ['Timeline', { page: 'Following' }]
|
||||
const renderItem = useCallback(
|
||||
({ item }) => <TimelineDefault item={item} queryKey={queryKey} />,
|
||||
[]
|
||||
)
|
||||
const children = useCallback(
|
||||
() => (
|
||||
<Timeline
|
||||
queryKey={queryKey}
|
||||
lookback='Following'
|
||||
customProps={{ renderItem }}
|
||||
customProps={{
|
||||
renderItem: ({ item }) => (
|
||||
<TimelineDefault item={item} queryKey={queryKey} />
|
||||
)
|
||||
}}
|
||||
/>
|
||||
),
|
||||
[]
|
||||
|
|
|
@ -1,16 +1,22 @@
|
|||
import Timeline from '@components/Timeline'
|
||||
import TimelineDefault from '@components/Timeline/Default'
|
||||
import { QueryKeyTimeline } from '@utils/queryHooks/timeline'
|
||||
import React, { useCallback } from 'react'
|
||||
import React from 'react'
|
||||
|
||||
const TabMeBookmarks = React.memo(
|
||||
() => {
|
||||
const queryKey: QueryKeyTimeline = ['Timeline', { page: 'Bookmarks' }]
|
||||
const renderItem = useCallback(
|
||||
({ item }) => <TimelineDefault item={item} queryKey={queryKey} />,
|
||||
[]
|
||||
|
||||
return (
|
||||
<Timeline
|
||||
queryKey={queryKey}
|
||||
customProps={{
|
||||
renderItem: ({ item }) => (
|
||||
<TimelineDefault item={item} queryKey={queryKey} />
|
||||
)
|
||||
}}
|
||||
/>
|
||||
)
|
||||
return <Timeline queryKey={queryKey} customProps={{ renderItem }} />
|
||||
},
|
||||
() => true
|
||||
)
|
||||
|
|
|
@ -1,19 +1,22 @@
|
|||
import Timeline from '@components/Timeline'
|
||||
import TimelineConversation from '@components/Timeline/Conversation'
|
||||
import { QueryKeyTimeline } from '@utils/queryHooks/timeline'
|
||||
import React, { useCallback } from 'react'
|
||||
import React from 'react'
|
||||
|
||||
const TabMeConversations = React.memo(
|
||||
() => {
|
||||
const queryKey: QueryKeyTimeline = ['Timeline', { page: 'Conversations' }]
|
||||
const renderItem = useCallback(
|
||||
({ item }) => (
|
||||
<TimelineConversation conversation={item} queryKey={queryKey} />
|
||||
),
|
||||
[]
|
||||
)
|
||||
|
||||
return <Timeline queryKey={queryKey} customProps={{ renderItem }} />
|
||||
return (
|
||||
<Timeline
|
||||
queryKey={queryKey}
|
||||
customProps={{
|
||||
renderItem: ({ item }) => (
|
||||
<TimelineConversation conversation={item} queryKey={queryKey} />
|
||||
)
|
||||
}}
|
||||
/>
|
||||
)
|
||||
},
|
||||
() => true
|
||||
)
|
||||
|
|
|
@ -1,17 +1,22 @@
|
|||
import Timeline from '@components/Timeline'
|
||||
import TimelineDefault from '@components/Timeline/Default'
|
||||
import { QueryKeyTimeline } from '@utils/queryHooks/timeline'
|
||||
import React, { useCallback } from 'react'
|
||||
import React from 'react'
|
||||
|
||||
const TabMeFavourites = React.memo(
|
||||
() => {
|
||||
const queryKey: QueryKeyTimeline = ['Timeline', { page: 'Favourites' }]
|
||||
const renderItem = useCallback(
|
||||
({ item }) => <TimelineDefault item={item} queryKey={queryKey} />,
|
||||
[]
|
||||
)
|
||||
|
||||
return <Timeline queryKey={queryKey} customProps={{ renderItem }} />
|
||||
return (
|
||||
<Timeline
|
||||
queryKey={queryKey}
|
||||
customProps={{
|
||||
renderItem: ({ item }) => (
|
||||
<TimelineDefault item={item} queryKey={queryKey} />
|
||||
)
|
||||
}}
|
||||
/>
|
||||
)
|
||||
},
|
||||
() => true
|
||||
)
|
||||
|
|
|
@ -2,7 +2,7 @@ import Timeline from '@components/Timeline'
|
|||
import TimelineDefault from '@components/Timeline/Default'
|
||||
import { TabMeStackScreenProps } from '@utils/navigation/navigators'
|
||||
import { QueryKeyTimeline } from '@utils/queryHooks/timeline'
|
||||
import React, { useCallback } from 'react'
|
||||
import React from 'react'
|
||||
|
||||
const TabMeListsList: React.FC<TabMeStackScreenProps<'Tab-Me-Lists-List'>> = ({
|
||||
route: {
|
||||
|
@ -10,12 +10,17 @@ const TabMeListsList: React.FC<TabMeStackScreenProps<'Tab-Me-Lists-List'>> = ({
|
|||
}
|
||||
}) => {
|
||||
const queryKey: QueryKeyTimeline = ['Timeline', { page: 'List', list }]
|
||||
const renderItem = useCallback(
|
||||
({ item }) => <TimelineDefault item={item} queryKey={queryKey} />,
|
||||
[]
|
||||
)
|
||||
|
||||
return <Timeline queryKey={queryKey} customProps={{ renderItem }} />
|
||||
return (
|
||||
<Timeline
|
||||
queryKey={queryKey}
|
||||
customProps={{
|
||||
renderItem: ({ item }) => (
|
||||
<TimelineDefault item={item} queryKey={queryKey} />
|
||||
)
|
||||
}}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
export default TabMeListsList
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
import mediaSelector from '@components/mediaSelector'
|
||||
import { MenuRow } from '@components/Menu'
|
||||
import { displayMessage } from '@components/Message'
|
||||
import { useActionSheet } from '@expo/react-native-action-sheet'
|
||||
import { useProfileMutation, useProfileQuery } from '@utils/queryHooks/profile'
|
||||
import { useTheme } from '@utils/styles/ThemeManager'
|
||||
|
@ -37,6 +38,15 @@ const ProfileAvatarHeader: React.FC<Props> = ({ type, messageRef }) => {
|
|||
? { width: 400, height: 400 }
|
||||
: { width: 1500, height: 500 }
|
||||
})
|
||||
if (!image[0].uri) {
|
||||
displayMessage({
|
||||
ref: messageRef,
|
||||
message: t('screenTabs:me.profile.mediaSelectionFailed'),
|
||||
theme: theme,
|
||||
type: 'error'
|
||||
})
|
||||
return
|
||||
}
|
||||
mutation.mutate({
|
||||
theme,
|
||||
messageRef,
|
||||
|
|
|
@ -102,49 +102,12 @@ const TabMeSettingsFontsize: React.FC<
|
|||
|
||||
return (
|
||||
<ScrollView>
|
||||
<CustomText
|
||||
fontStyle='M'
|
||||
style={{
|
||||
textAlign: 'center',
|
||||
marginTop: StyleConstants.Spacing.M,
|
||||
marginBottom: StyleConstants.Spacing.M,
|
||||
color: colors.primaryDefault
|
||||
}}
|
||||
>
|
||||
{t('me.fontSize.showcase')}
|
||||
</CustomText>
|
||||
<View>
|
||||
<ComponentSeparator
|
||||
extraMarginLeft={-StyleConstants.Spacing.Global.PagePadding}
|
||||
extraMarginRight={-StyleConstants.Spacing.Global.PagePadding}
|
||||
/>
|
||||
<TimelineDefault
|
||||
// @ts-ignore
|
||||
item={item}
|
||||
disableDetails
|
||||
disableOnPress
|
||||
/>
|
||||
<ComponentSeparator
|
||||
extraMarginLeft={-StyleConstants.Spacing.Global.PagePadding}
|
||||
extraMarginRight={-StyleConstants.Spacing.Global.PagePadding}
|
||||
/>
|
||||
</View>
|
||||
<CustomText
|
||||
fontStyle='M'
|
||||
style={{
|
||||
textAlign: 'center',
|
||||
marginTop: StyleConstants.Spacing.M,
|
||||
marginBottom: StyleConstants.Spacing.M,
|
||||
color: colors.primaryDefault
|
||||
}}
|
||||
>
|
||||
{t('me.fontSize.availableSizes')}
|
||||
</CustomText>
|
||||
<View
|
||||
style={{
|
||||
flexDirection: 'row',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center'
|
||||
justifyContent: 'center',
|
||||
marginTop: StyleConstants.Spacing.M
|
||||
}}
|
||||
>
|
||||
{sizesDemo}
|
||||
|
@ -185,6 +148,26 @@ const TabMeSettingsFontsize: React.FC<
|
|||
style={{ marginHorizontal: StyleConstants.Spacing.S }}
|
||||
/>
|
||||
</View>
|
||||
<View
|
||||
style={{
|
||||
marginVertical: StyleConstants.Spacing.L
|
||||
}}
|
||||
>
|
||||
<ComponentSeparator
|
||||
extraMarginLeft={-StyleConstants.Spacing.Global.PagePadding}
|
||||
extraMarginRight={-StyleConstants.Spacing.Global.PagePadding}
|
||||
/>
|
||||
<TimelineDefault
|
||||
// @ts-ignore
|
||||
item={item}
|
||||
disableDetails
|
||||
disableOnPress
|
||||
/>
|
||||
<ComponentSeparator
|
||||
extraMarginLeft={-StyleConstants.Spacing.Global.PagePadding}
|
||||
extraMarginRight={-StyleConstants.Spacing.Global.PagePadding}
|
||||
/>
|
||||
</View>
|
||||
</ScrollView>
|
||||
)
|
||||
}
|
||||
|
|
|
@ -43,14 +43,17 @@ const TabNotifications = React.memo(
|
|||
)
|
||||
|
||||
const queryKey: QueryKeyTimeline = ['Timeline', { page: 'Notifications' }]
|
||||
const renderItem = useCallback(
|
||||
({ item }) => (
|
||||
<TimelineNotifications notification={item} queryKey={queryKey} />
|
||||
),
|
||||
[]
|
||||
)
|
||||
const children = useCallback(
|
||||
() => <Timeline queryKey={queryKey} customProps={{ renderItem }} />,
|
||||
() => (
|
||||
<Timeline
|
||||
queryKey={queryKey}
|
||||
customProps={{
|
||||
renderItem: ({ item }) => (
|
||||
<TimelineNotifications notification={item} queryKey={queryKey} />
|
||||
)
|
||||
}}
|
||||
/>
|
||||
),
|
||||
[]
|
||||
)
|
||||
|
||||
|
|
|
@ -30,18 +30,10 @@ const TabSharedAccount: React.FC<
|
|||
|
||||
const scrollY = useSharedValue(0)
|
||||
|
||||
const onScroll = useCallback(({ nativeEvent }) => {
|
||||
scrollY.value = nativeEvent.contentOffset.y
|
||||
}, [])
|
||||
|
||||
const [queryKey, setQueryKey] = useState<QueryKeyTimeline>([
|
||||
'Timeline',
|
||||
{ page: 'Account_Default', account: account.id }
|
||||
])
|
||||
const renderItem = useCallback(
|
||||
({ item }) => <TimelineDefault item={item} queryKey={queryKey} />,
|
||||
[]
|
||||
)
|
||||
const isFetchingTimeline = useIsFetching(queryKey)
|
||||
const fetchedTimeline = useRef(false)
|
||||
useEffect(() => {
|
||||
|
@ -97,8 +89,11 @@ const TabSharedAccount: React.FC<
|
|||
queryKey={queryKey}
|
||||
disableRefresh
|
||||
customProps={{
|
||||
renderItem,
|
||||
onScroll,
|
||||
renderItem: ({ item }) => (
|
||||
<TimelineDefault item={item} queryKey={queryKey} />
|
||||
),
|
||||
onScroll: ({ nativeEvent }) =>
|
||||
(scrollY.value = nativeEvent.contentOffset.y),
|
||||
ListHeaderComponent,
|
||||
maintainVisibleContentPosition: undefined
|
||||
}}
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
import { useRoute } from '@react-navigation/native'
|
||||
import { StyleConstants } from '@utils/styles/constants'
|
||||
import { useTheme } from '@utils/styles/ThemeManager'
|
||||
import React, { useCallback } from 'react'
|
||||
import React from 'react'
|
||||
import { StyleSheet, View } from 'react-native'
|
||||
import { Placeholder, Fade } from 'rn-placeholder'
|
||||
import AccountInformationAccount from './Information/Account'
|
||||
|
@ -19,21 +19,21 @@ export interface Props {
|
|||
|
||||
const AccountInformation = React.memo(
|
||||
({ account }: Props) => {
|
||||
const { colors, theme } = useTheme()
|
||||
const { colors } = useTheme()
|
||||
|
||||
const { name } = useRoute()
|
||||
const myInfo = name !== 'Tab-Shared-Account'
|
||||
|
||||
const animation = useCallback(
|
||||
props => (
|
||||
<Fade {...props} style={{ backgroundColor: colors.shimmerHighlight }} />
|
||||
),
|
||||
[theme]
|
||||
)
|
||||
|
||||
return (
|
||||
<View style={styles.base}>
|
||||
<Placeholder Animation={animation}>
|
||||
<Placeholder
|
||||
Animation={props => (
|
||||
<Fade
|
||||
{...props}
|
||||
style={{ backgroundColor: colors.shimmerHighlight }}
|
||||
/>
|
||||
)}
|
||||
>
|
||||
<View style={styles.avatarAndActions}>
|
||||
<AccountInformationAvatar account={account} myInfo={myInfo} />
|
||||
<AccountInformationActions account={account} myInfo={myInfo} />
|
||||
|
|
|
@ -2,11 +2,11 @@ import Timeline from '@components/Timeline'
|
|||
import TimelineDefault from '@components/Timeline/Default'
|
||||
import { TabSharedStackScreenProps } from '@utils/navigation/navigators'
|
||||
import { QueryKeyTimeline } from '@utils/queryHooks/timeline'
|
||||
import React, { useCallback } from 'react'
|
||||
import React from 'react'
|
||||
|
||||
const TabSharedAttachments: React.FC<TabSharedStackScreenProps<
|
||||
'Tab-Shared-Attachments'
|
||||
>> = ({
|
||||
const TabSharedAttachments: React.FC<
|
||||
TabSharedStackScreenProps<'Tab-Shared-Attachments'>
|
||||
> = ({
|
||||
route: {
|
||||
params: { account }
|
||||
}
|
||||
|
@ -15,11 +15,16 @@ const TabSharedAttachments: React.FC<TabSharedStackScreenProps<
|
|||
'Timeline',
|
||||
{ page: 'Account_Attachments', account: account.id }
|
||||
]
|
||||
const renderItem = useCallback(
|
||||
({ item }) => <TimelineDefault item={item} queryKey={queryKey} />,
|
||||
[]
|
||||
return (
|
||||
<Timeline
|
||||
queryKey={queryKey}
|
||||
customProps={{
|
||||
renderItem: ({ item }) => (
|
||||
<TimelineDefault item={item} queryKey={queryKey} />
|
||||
)
|
||||
}}
|
||||
/>
|
||||
)
|
||||
return <Timeline queryKey={queryKey} customProps={{ renderItem }} />
|
||||
}
|
||||
|
||||
export default TabSharedAttachments
|
||||
|
|
|
@ -2,21 +2,26 @@ import Timeline from '@components/Timeline'
|
|||
import TimelineDefault from '@components/Timeline/Default'
|
||||
import { TabSharedStackScreenProps } from '@utils/navigation/navigators'
|
||||
import { QueryKeyTimeline } from '@utils/queryHooks/timeline'
|
||||
import React, { useCallback } from 'react'
|
||||
import React from 'react'
|
||||
|
||||
const TabSharedHashtag: React.FC<TabSharedStackScreenProps<
|
||||
'Tab-Shared-Hashtag'
|
||||
>> = ({
|
||||
const TabSharedHashtag: React.FC<
|
||||
TabSharedStackScreenProps<'Tab-Shared-Hashtag'>
|
||||
> = ({
|
||||
route: {
|
||||
params: { hashtag }
|
||||
}
|
||||
}) => {
|
||||
const queryKey: QueryKeyTimeline = ['Timeline', { page: 'Hashtag', hashtag }]
|
||||
const renderItem = useCallback(
|
||||
({ item }) => <TimelineDefault item={item} queryKey={queryKey} />,
|
||||
[]
|
||||
return (
|
||||
<Timeline
|
||||
queryKey={queryKey}
|
||||
customProps={{
|
||||
renderItem: ({ item }) => (
|
||||
<TimelineDefault item={item} queryKey={queryKey} />
|
||||
)
|
||||
}}
|
||||
/>
|
||||
)
|
||||
return <Timeline queryKey={queryKey} customProps={{ renderItem }} />
|
||||
}
|
||||
|
||||
export default TabSharedHashtag
|
||||
|
|
|
@ -139,8 +139,36 @@ const TabSharedSearch: React.FC<
|
|||
</View>
|
||||
)
|
||||
}, [status])
|
||||
const sectionHeader = useCallback(
|
||||
({ section: { translation } }) => (
|
||||
|
||||
const listItem = useCallback(
|
||||
({ item, section }: { item: any; section: any }) => {
|
||||
switch (section.title) {
|
||||
case 'accounts':
|
||||
return <ComponentAccount account={item} origin='search' />
|
||||
case 'hashtags':
|
||||
return <ComponentHashtag hashtag={item} origin='search' />
|
||||
case 'statuses':
|
||||
return <TimelineDefault item={item} disableDetails origin='search' />
|
||||
default:
|
||||
return null
|
||||
}
|
||||
},
|
||||
[]
|
||||
)
|
||||
|
||||
return (
|
||||
<KeyboardAvoidingView
|
||||
behavior={Platform.OS === 'ios' ? 'padding' : 'height'}
|
||||
style={{ flex: 1 }}
|
||||
>
|
||||
<SectionList
|
||||
style={{ minHeight: '100%' }}
|
||||
renderItem={listItem}
|
||||
stickySectionHeadersEnabled
|
||||
sections={data || []}
|
||||
ListEmptyComponent={listEmpty}
|
||||
keyboardShouldPersistTaps='always'
|
||||
renderSectionHeader={({ section: { translation } }) => (
|
||||
<View
|
||||
style={{
|
||||
padding: StyleConstants.Spacing.M,
|
||||
|
@ -158,11 +186,8 @@ const TabSharedSearch: React.FC<
|
|||
{translation}
|
||||
</CustomText>
|
||||
</View>
|
||||
),
|
||||
[]
|
||||
)
|
||||
const sectionFooter = useCallback(
|
||||
({ section: { data, translation } }) =>
|
||||
)}
|
||||
renderSectionFooter={({ section: { data, translation } }) =>
|
||||
!data.length ? (
|
||||
<View
|
||||
style={{
|
||||
|
@ -183,36 +208,8 @@ const TabSharedSearch: React.FC<
|
|||
/>
|
||||
</CustomText>
|
||||
</View>
|
||||
) : null,
|
||||
[text]
|
||||
)
|
||||
const listItem = useCallback(({ item, section }) => {
|
||||
switch (section.title) {
|
||||
case 'accounts':
|
||||
return <ComponentAccount account={item} origin='search' />
|
||||
case 'hashtags':
|
||||
return <ComponentHashtag hashtag={item} origin='search' />
|
||||
case 'statuses':
|
||||
return <TimelineDefault item={item} disableDetails origin='search' />
|
||||
default:
|
||||
return null
|
||||
) : null
|
||||
}
|
||||
}, [])
|
||||
|
||||
return (
|
||||
<KeyboardAvoidingView
|
||||
behavior={Platform.OS === 'ios' ? 'padding' : 'height'}
|
||||
style={{ flex: 1 }}
|
||||
>
|
||||
<SectionList
|
||||
style={{ minHeight: '100%' }}
|
||||
renderItem={listItem}
|
||||
stickySectionHeadersEnabled
|
||||
sections={data || []}
|
||||
ListEmptyComponent={listEmpty}
|
||||
keyboardShouldPersistTaps='always'
|
||||
renderSectionHeader={sectionHeader}
|
||||
renderSectionFooter={sectionFooter}
|
||||
keyExtractor={(item, index) => item + index}
|
||||
SectionSeparatorComponent={ComponentSeparator}
|
||||
ItemSeparatorComponent={ComponentSeparator}
|
||||
|
|
|
@ -3,7 +3,7 @@ import TimelineDefault from '@components/Timeline/Default'
|
|||
import { useNavigation } from '@react-navigation/native'
|
||||
import { TabSharedStackScreenProps } from '@utils/navigation/navigators'
|
||||
import { QueryKeyTimeline } from '@utils/queryHooks/timeline'
|
||||
import React, { useCallback, useEffect, useRef, useState } from 'react'
|
||||
import React, { useEffect, useRef, useState } from 'react'
|
||||
import { FlatList } from 'react-native'
|
||||
import { InfiniteQueryObserver, useQueryClient } from 'react-query'
|
||||
|
||||
|
@ -59,9 +59,20 @@ const TabSharedToot: React.FC<TabSharedStackScreenProps<'Tab-Shared-Toot'>> = ({
|
|||
})
|
||||
}, [scrolled.current])
|
||||
|
||||
// Toot page auto scroll to selected toot
|
||||
const onScrollToIndexFailed = useCallback(
|
||||
error => {
|
||||
return (
|
||||
<Timeline
|
||||
flRef={flRef}
|
||||
queryKey={queryKey}
|
||||
customProps={{
|
||||
renderItem: ({ item }) => (
|
||||
<TimelineDefault
|
||||
item={item}
|
||||
queryKey={queryKey}
|
||||
rootQueryKey={rootQueryKey}
|
||||
highlighted={toot.id === item.id}
|
||||
/>
|
||||
),
|
||||
onScrollToIndexFailed: error => {
|
||||
const offset = error.averageItemLength * error.index
|
||||
flRef.current?.scrollToOffset({ offset })
|
||||
try {
|
||||
|
@ -75,27 +86,8 @@ const TabSharedToot: React.FC<TabSharedStackScreenProps<'Tab-Shared-Toot'>> = ({
|
|||
500
|
||||
)
|
||||
} catch {}
|
||||
},
|
||||
[itemsLength]
|
||||
)
|
||||
|
||||
const renderItem = useCallback(
|
||||
({ item }) => (
|
||||
<TimelineDefault
|
||||
item={item}
|
||||
queryKey={queryKey}
|
||||
rootQueryKey={rootQueryKey}
|
||||
highlighted={toot.id === item.id}
|
||||
/>
|
||||
),
|
||||
[]
|
||||
)
|
||||
|
||||
return (
|
||||
<Timeline
|
||||
flRef={flRef}
|
||||
queryKey={queryKey}
|
||||
customProps={{ renderItem, onScrollToIndexFailed }}
|
||||
}
|
||||
}}
|
||||
disableRefresh
|
||||
disableInfinity
|
||||
/>
|
||||
|
|
|
@ -9,12 +9,8 @@ import { FlatList } from 'react-native-gesture-handler'
|
|||
const TabSharedUsers = React.memo(
|
||||
({ route: { params } }: TabSharedStackScreenProps<'Tab-Shared-Users'>) => {
|
||||
const queryKey: QueryKeyUsers = ['Users', params]
|
||||
const {
|
||||
data,
|
||||
hasNextPage,
|
||||
fetchNextPage,
|
||||
isFetchingNextPage
|
||||
} = useUsersQuery({
|
||||
const { data, hasNextPage, fetchNextPage, isFetchingNextPage } =
|
||||
useUsersQuery({
|
||||
...queryKey[1],
|
||||
options: {
|
||||
getPreviousPageParam: firstPage =>
|
||||
|
@ -27,10 +23,6 @@ const TabSharedUsers = React.memo(
|
|||
? data.pages.flatMap(page => [...page.body])
|
||||
: []
|
||||
|
||||
const renderItem = useCallback(
|
||||
({ item }) => <ComponentAccount account={item} origin='relationship' />,
|
||||
[]
|
||||
)
|
||||
const onEndReached = useCallback(
|
||||
() => hasNextPage && !isFetchingNextPage && fetchNextPage(),
|
||||
[hasNextPage, isFetchingNextPage]
|
||||
|
@ -41,7 +33,9 @@ const TabSharedUsers = React.memo(
|
|||
windowSize={7}
|
||||
data={flattenData}
|
||||
style={styles.flatList}
|
||||
renderItem={renderItem}
|
||||
renderItem={({ item }) => (
|
||||
<ComponentAccount account={item} origin='relationship' />
|
||||
)}
|
||||
onEndReached={onEndReached}
|
||||
onEndReachedThreshold={0.75}
|
||||
ItemSeparatorComponent={ComponentSeparator}
|
||||
|
|
|
@ -1,4 +1,10 @@
|
|||
import React, { createContext, useContext, useEffect, useState } from 'react'
|
||||
import React, {
|
||||
createContext,
|
||||
PropsWithChildren,
|
||||
useContext,
|
||||
useEffect,
|
||||
useState
|
||||
} from 'react'
|
||||
import { AccessibilityInfo } from 'react-native'
|
||||
|
||||
type ContextType = {
|
||||
|
@ -15,7 +21,7 @@ const AccessibilityContext = createContext<ContextType>({
|
|||
|
||||
export const useAccessibility = () => useContext(AccessibilityContext)
|
||||
|
||||
const AccessibilityManager: React.FC = ({ children }) => {
|
||||
const AccessibilityManager: React.FC<PropsWithChildren> = ({ children }) => {
|
||||
const [reduceMotionEnabled, setReduceMotionEnabled] = useState(false)
|
||||
const [screenReaderEnabled, setScreenReaderEnabled] = useState(false)
|
||||
const [boldTextEnabled, setBoldTextEnabled] = useState(false)
|
||||
|
|
|
@ -1,4 +1,10 @@
|
|||
import React, { createContext, useContext, useEffect, useState } from 'react'
|
||||
import React, {
|
||||
createContext,
|
||||
PropsWithChildren,
|
||||
useContext,
|
||||
useEffect,
|
||||
useState
|
||||
} from 'react'
|
||||
import { Appearance } from 'react-native'
|
||||
import { useSelector } from 'react-redux'
|
||||
import { ColorDefinitions, getColors, Theme } from '@utils/styles/themes'
|
||||
|
@ -74,7 +80,7 @@ const determineTheme = (
|
|||
}
|
||||
}
|
||||
|
||||
const ThemeManager: React.FC = ({ children }) => {
|
||||
const ThemeManager: React.FC<PropsWithChildren> = ({ children }) => {
|
||||
const osTheme = useColorSchemeDelay()
|
||||
const userTheme = useSelector(getSettingsTheme)
|
||||
const darkTheme = useSelector(getSettingsDarkTheme)
|
||||
|
|
Loading…
Reference in New Issue