iOS almost working

This commit is contained in:
Zhiyuan Zheng 2021-01-30 01:29:15 +01:00
parent 9c36052c2a
commit 247f7a7982
No known key found for this signature in database
GPG Key ID: 078A93AB607D85E0
126 changed files with 1829 additions and 1341 deletions

3
.gitignore vendored
View File

@ -13,9 +13,10 @@ web-build/
# macOS # macOS
.DS_Store .DS_Store
.env .envrc
coverage/ coverage/
builds/ builds/
# @generated expo-cli sync-28e2ab0e9ece60556eaf932abe52d017ec33db50 # @generated expo-cli sync-28e2ab0e9ece60556eaf932abe52d017ec33db50
# The following patterns were generated by expo-cli # The following patterns were generated by expo-cli

View File

@ -78,7 +78,7 @@ import com.android.build.OutputFile
*/ */
project.ext.react = [ project.ext.react = [
enableHermes: false enableHermes: true
] ]
apply from: '../../node_modules/react-native-unimodules/gradle.groovy' apply from: '../../node_modules/react-native-unimodules/gradle.groovy'

View File

@ -28,6 +28,9 @@ import java.util.Arrays;
import java.util.List; import java.util.List;
import javax.annotation.Nullable; import javax.annotation.Nullable;
import com.facebook.react.bridge.JSIModulePackage; // <- react-native-reanimated-v2
import com.swmansion.reanimated.ReanimatedJSIModulePackage; // <- react-native-reanimated-v2
public class MainApplication extends Application implements ReactApplication { public class MainApplication extends Application implements ReactApplication {
private final ReactModuleRegistryProvider mModuleRegistryProvider = new ReactModuleRegistryProvider( private final ReactModuleRegistryProvider mModuleRegistryProvider = new ReactModuleRegistryProvider(
new BasePackageList().getPackageList() new BasePackageList().getPackageList()
@ -51,6 +54,11 @@ public class MainApplication extends Application implements ReactApplication {
return "index"; return "index";
} }
@Override // <- react-native-reanimated-v2
protected JSIModulePackage getJSIModulePackage() {
return new ReanimatedJSIModulePackage();
}
@Override @Override
protected @Nullable String getJSBundleFile() { protected @Nullable String getJSBundleFile() {
if (BuildConfig.DEBUG) { if (BuildConfig.DEBUG) {

View File

@ -3,7 +3,7 @@ import 'dotenv/config'
export default (): ExpoConfig => ({ export default (): ExpoConfig => ({
name: 'tooot', name: 'tooot',
description: 'tooot for mastodon', description: 'tooot for Mastodon',
slug: 'tooot', slug: 'tooot',
privacy: 'hidden', privacy: 'hidden',
sdkVersion: '40.0.0', sdkVersion: '40.0.0',

View File

@ -1,18 +0,0 @@
#!/bin/bash
if [ $# -ne 1 ]; then
echo "Arguments incorrect. Use 'xxx'"
exit 1
fi
JAVA_HOME=`/usr/libexec/java_home -v 1.8.0` \
EXPO_USERNAME='xmflsct' \
EXPO_PASSWORD=',8d_AJ1HmYJo8lbve&QoB40t3ImGdF)Dd' \
EXPO_ANDROID_KEYSTORE_PASSWORD="9c54265087704801ba5d3d88809110a1" \
EXPO_ANDROID_KEY_PASSWORD="748bb2e11529497dad7831c409175b94" \
turtle build:android \
--release-channel $1 \
--type app-bundle \
--keystore-path ./tooot.jks \
--keystore-alias "QHhtZmxzY3QvdG9vb3Q=" \
--build-dir ./builds

View File

@ -1,6 +1,6 @@
import { registerRootComponent } from 'expo'; import { registerRootComponent } from 'expo';
import App from './App'; import App from '@root/App';
// registerRootComponent calls AppRegistry.registerComponent('main', () => App); // registerRootComponent calls AppRegistry.registerComponent('main', () => App);
// It also ensures that whether you load the app in the Expo client or in a native build, // It also ensures that whether you load the app in the Expo client or in a native build,

View File

@ -7,14 +7,12 @@ PODS:
- UMCore - UMCore
- UMFileSystemInterface - UMFileSystemInterface
- UMPermissionsInterface - UMPermissionsInterface
- EXBlur (8.2.2):
- UMCore
- EXConstants (10.0.1): - EXConstants (10.0.1):
- UMConstantsInterface - UMConstantsInterface
- UMCore - UMCore
- EXCrypto (8.4.0): - EXCrypto (8.4.0):
- UMCore - UMCore
- EXDevice (2.4.0): - EXDevice (3.1.1):
- UMCore - UMCore
- EXErrorRecovery (1.4.0): - EXErrorRecovery (1.4.0):
- UMCore - UMCore
@ -25,7 +23,7 @@ PODS:
- EXFirebaseCore - EXFirebaseCore
- Firebase/Core (= 6.14.0) - Firebase/Core (= 6.14.0)
- UMCore - UMCore
- EXFirebaseCore (1.3.0): - EXFirebaseCore (1.2.0):
- Firebase/Core (= 6.14.0) - Firebase/Core (= 6.14.0)
- UMCore - UMCore
- EXFont (8.4.0): - EXFont (8.4.0):
@ -66,8 +64,8 @@ PODS:
- UMFileSystemInterface - UMFileSystemInterface
- EXStoreReview (2.3.0): - EXStoreReview (2.3.0):
- UMCore - UMCore
- EXUpdates (0.4.1): - EXUpdates (0.3.5):
- React-Core - React
- UMCore - UMCore
- EXVideoThumbnails (4.4.0): - EXVideoThumbnails (4.4.0):
- UMCore - UMCore
@ -332,15 +330,17 @@ PODS:
- React-cxxreact (= 0.63.4) - React-cxxreact (= 0.63.4)
- React-jsi (= 0.63.4) - React-jsi (= 0.63.4)
- React-jsinspector (0.63.4) - React-jsinspector (0.63.4)
- react-native-blur (0.8.0):
- React
- react-native-blurhash (1.0.29): - react-native-blurhash (1.0.29):
- React - React
- react-native-netinfo (5.9.10): - react-native-netinfo (5.9.10):
- React-Core - React-Core
- react-native-safe-area-context (3.1.9): - react-native-safe-area-context (3.1.9):
- React-Core - React-Core
- react-native-segmented-control (2.2.1): - react-native-segmented-control (2.2.2):
- React-Core - React-Core
- react-native-viewpager (4.2.0): - react-native-viewpager (4.2.2):
- React-Core - React-Core
- React-RCTActionSheet (0.63.4): - React-RCTActionSheet (0.63.4):
- React-Core/RCTActionSheetHeaders (= 0.63.4) - React-Core/RCTActionSheetHeaders (= 0.63.4)
@ -410,9 +410,9 @@ PODS:
- React-Core - React-Core
- SDWebImage (~> 5.8) - SDWebImage (~> 5.8)
- SDWebImageWebPCoder (~> 0.6.1) - SDWebImageWebPCoder (~> 0.6.1)
- RNGestureHandler (1.8.0): - RNGestureHandler (1.9.0):
- React - React-Core
- RNReanimated (2.0.0-rc.0): - RNReanimated (2.0.0-rc.2):
- DoubleConversion - DoubleConversion
- FBLazyVector - FBLazyVector
- FBReactNativeSpec - FBReactNativeSpec
@ -441,11 +441,13 @@ PODS:
- React-RCTVibration - React-RCTVibration
- ReactCommon/turbomodule/core - ReactCommon/turbomodule/core
- Yoga - Yoga
- RNScreens (2.15.2): - RNScreens (2.17.1):
- React-Core - React-Core
- RNSentry (2.1.1): - RNSentry (2.1.1):
- React-Core - React-Core
- Sentry (= 6.0.9) - Sentry (= 6.0.9)
- RNSharedElement (0.7.0):
- React
- RNSVG (12.1.0): - RNSVG (12.1.0):
- React - React
- SDWebImage (5.10.3): - SDWebImage (5.10.3):
@ -480,8 +482,7 @@ DEPENDENCIES:
- DoubleConversion (from `../node_modules/react-native/third-party-podspecs/DoubleConversion.podspec`) - DoubleConversion (from `../node_modules/react-native/third-party-podspecs/DoubleConversion.podspec`)
- EXApplication (from `../node_modules/expo-application/ios`) - EXApplication (from `../node_modules/expo-application/ios`)
- EXAV (from `../node_modules/expo-av/ios`) - EXAV (from `../node_modules/expo-av/ios`)
- EXBlur (from `../node_modules/expo-blur/ios`) - EXConstants (from `../node_modules/sentry-expo/node_modules/expo-constants/ios`)
- EXConstants (from `../node_modules/expo-constants/ios`)
- EXCrypto (from `../node_modules/expo-crypto/ios`) - EXCrypto (from `../node_modules/expo-crypto/ios`)
- EXDevice (from `../node_modules/expo-device/ios`) - EXDevice (from `../node_modules/expo-device/ios`)
- EXErrorRecovery (from `../node_modules/expo-error-recovery/ios`) - EXErrorRecovery (from `../node_modules/expo-error-recovery/ios`)
@ -521,6 +522,7 @@ DEPENDENCIES:
- React-jsi (from `../node_modules/react-native/ReactCommon/jsi`) - React-jsi (from `../node_modules/react-native/ReactCommon/jsi`)
- React-jsiexecutor (from `../node_modules/react-native/ReactCommon/jsiexecutor`) - React-jsiexecutor (from `../node_modules/react-native/ReactCommon/jsiexecutor`)
- React-jsinspector (from `../node_modules/react-native/ReactCommon/jsinspector`) - React-jsinspector (from `../node_modules/react-native/ReactCommon/jsinspector`)
- "react-native-blur (from `../node_modules/@react-native-community/blur`)"
- react-native-blurhash (from `../node_modules/react-native-blurhash`) - react-native-blurhash (from `../node_modules/react-native-blurhash`)
- "react-native-netinfo (from `../node_modules/@react-native-community/netinfo`)" - "react-native-netinfo (from `../node_modules/@react-native-community/netinfo`)"
- react-native-safe-area-context (from `../node_modules/react-native-safe-area-context`) - react-native-safe-area-context (from `../node_modules/react-native-safe-area-context`)
@ -543,6 +545,7 @@ DEPENDENCIES:
- RNReanimated (from `../node_modules/react-native-reanimated`) - RNReanimated (from `../node_modules/react-native-reanimated`)
- RNScreens (from `../node_modules/react-native-screens`) - RNScreens (from `../node_modules/react-native-screens`)
- "RNSentry (from `../node_modules/@sentry/react-native`)" - "RNSentry (from `../node_modules/@sentry/react-native`)"
- RNSharedElement (from `../node_modules/react-native-shared-element`)
- RNSVG (from `../node_modules/react-native-svg`) - RNSVG (from `../node_modules/react-native-svg`)
- UMAppLoader (from `../node_modules/unimodules-app-loader/ios`) - UMAppLoader (from `../node_modules/unimodules-app-loader/ios`)
- UMBarCodeScannerInterface (from `../node_modules/unimodules-barcode-scanner-interface/ios`) - UMBarCodeScannerInterface (from `../node_modules/unimodules-barcode-scanner-interface/ios`)
@ -586,10 +589,8 @@ EXTERNAL SOURCES:
:path: "../node_modules/expo-application/ios" :path: "../node_modules/expo-application/ios"
EXAV: EXAV:
:path: "../node_modules/expo-av/ios" :path: "../node_modules/expo-av/ios"
EXBlur:
:path: "../node_modules/expo-blur/ios"
EXConstants: EXConstants:
:path: "../node_modules/expo-constants/ios" :path: "../node_modules/sentry-expo/node_modules/expo-constants/ios"
EXCrypto: EXCrypto:
:path: "../node_modules/expo-crypto/ios" :path: "../node_modules/expo-crypto/ios"
EXDevice: EXDevice:
@ -664,6 +665,8 @@ EXTERNAL SOURCES:
:path: "../node_modules/react-native/ReactCommon/jsiexecutor" :path: "../node_modules/react-native/ReactCommon/jsiexecutor"
React-jsinspector: React-jsinspector:
:path: "../node_modules/react-native/ReactCommon/jsinspector" :path: "../node_modules/react-native/ReactCommon/jsinspector"
react-native-blur:
:path: "../node_modules/@react-native-community/blur"
react-native-blurhash: react-native-blurhash:
:path: "../node_modules/react-native-blurhash" :path: "../node_modules/react-native-blurhash"
react-native-netinfo: react-native-netinfo:
@ -708,6 +711,8 @@ EXTERNAL SOURCES:
:path: "../node_modules/react-native-screens" :path: "../node_modules/react-native-screens"
RNSentry: RNSentry:
:path: "../node_modules/@sentry/react-native" :path: "../node_modules/@sentry/react-native"
RNSharedElement:
:path: "../node_modules/react-native-shared-element"
RNSVG: RNSVG:
:path: "../node_modules/react-native-svg" :path: "../node_modules/react-native-svg"
UMAppLoader: UMAppLoader:
@ -744,14 +749,13 @@ SPEC CHECKSUMS:
DoubleConversion: cde416483dac037923206447da6e1454df403714 DoubleConversion: cde416483dac037923206447da6e1454df403714
EXApplication: e3c201e7b913d081bbd37bd3c5d0e2cdc21733b4 EXApplication: e3c201e7b913d081bbd37bd3c5d0e2cdc21733b4
EXAV: e32adf44c10a82f723172d5b01d5693890888dd6 EXAV: e32adf44c10a82f723172d5b01d5693890888dd6
EXBlur: 569d38c2b0b7b000866d928bc36acadfdc02ea8e
EXConstants: d9483a8fe0a963dd7ee3389f371303773e6ecd37 EXConstants: d9483a8fe0a963dd7ee3389f371303773e6ecd37
EXCrypto: 81948191c3d2caf18fb32f18ee88d87d42532d62 EXCrypto: 81948191c3d2caf18fb32f18ee88d87d42532d62
EXDevice: 01f54314f618aa4098893f66cd8f2a8a411f33ee EXDevice: 101eddd9bc28faba8246ba0c499a65a4821a6b5e
EXErrorRecovery: b46af4b91e2b4ec598ab1fa51d2cf088aaf5511f EXErrorRecovery: b46af4b91e2b4ec598ab1fa51d2cf088aaf5511f
EXFileSystem: 0e4974ab77bff04cda68d2886d070cbe64b93a6b EXFileSystem: 0e4974ab77bff04cda68d2886d070cbe64b93a6b
EXFirebaseAnalytics: d72f553dc0c0a8ee451714499fac21883c3f7369 EXFirebaseAnalytics: d72f553dc0c0a8ee451714499fac21883c3f7369
EXFirebaseCore: f986c71bdd1cb5941c9cfa686b2395fe588a53d0 EXFirebaseCore: d5befdb22015f9b71168ed89dda0b50dbb07fed1
EXFont: 30c64ed8735a180e3f20046e4fdac4ea074d71d3 EXFont: 30c64ed8735a180e3f20046e4fdac4ea074d71d3
EXHaptics: 6d2e09387f0eb4d3c8dc97ae905cbea8afe33651 EXHaptics: 6d2e09387f0eb4d3c8dc97ae905cbea8afe33651
EXImageLoader: f96ec9992733a4224418bbd9382e5485c8948944 EXImageLoader: f96ec9992733a4224418bbd9382e5485c8948944
@ -766,7 +770,7 @@ SPEC CHECKSUMS:
EXSplashScreen: 8c7c1112ce7611a853486af4737fe2298eda7657 EXSplashScreen: 8c7c1112ce7611a853486af4737fe2298eda7657
EXSQLite: bda6a286dded0637bb312ee781239dcca163ff4b EXSQLite: bda6a286dded0637bb312ee781239dcca163ff4b
EXStoreReview: 9b161bd86c172a9755c787982bfc32c7193cf803 EXStoreReview: 9b161bd86c172a9755c787982bfc32c7193cf803
EXUpdates: 28368049118dbe4ceaf4422ec72e0ad5f770df1f EXUpdates: 74b39409f68eca207075d87b0077bdf37865a8bf
EXVideoThumbnails: 8ff241784f1d89fdd451bcdb7f733d06361d44a8 EXVideoThumbnails: 8ff241784f1d89fdd451bcdb7f733d06361d44a8
EXWebBrowser: cb4811e6c883876d2e2ba1c10527de96963d410a EXWebBrowser: cb4811e6c883876d2e2ba1c10527de96963d410a
FBLazyVector: 3bb422f41b18121b71783a905c10e58606f7dc3e FBLazyVector: 3bb422f41b18121b71783a905c10e58606f7dc3e
@ -796,11 +800,12 @@ SPEC CHECKSUMS:
React-jsi: a0418934cf48f25b485631deb27c64dc40fb4c31 React-jsi: a0418934cf48f25b485631deb27c64dc40fb4c31
React-jsiexecutor: 93bd528844ad21dc07aab1c67cb10abae6df6949 React-jsiexecutor: 93bd528844ad21dc07aab1c67cb10abae6df6949
React-jsinspector: 58aef7155bc9a9683f5b60b35eccea8722a4f53a React-jsinspector: 58aef7155bc9a9683f5b60b35eccea8722a4f53a
react-native-blur: cad4d93b364f91e7b7931b3fa935455487e5c33c
react-native-blurhash: 90886ae897cafbbdf2773cb3654656bcb34e8f43 react-native-blurhash: 90886ae897cafbbdf2773cb3654656bcb34e8f43
react-native-netinfo: 30fb89fa913c342be82a887b56e96be6d71201dd react-native-netinfo: 30fb89fa913c342be82a887b56e96be6d71201dd
react-native-safe-area-context: b6e0e284002381d2ff29fa4fff42b4d8282e3c94 react-native-safe-area-context: b6e0e284002381d2ff29fa4fff42b4d8282e3c94
react-native-segmented-control: eb9e25fbfbce226ecf66d643428fbe97601e691a react-native-segmented-control: 65df6cd0619b780b3843d574a72d4c7cec396097
react-native-viewpager: c1a686e7b5e164a52387f1522358623c4f52070f react-native-viewpager: ea945e2881ce9a4a8bcdc84de4ec65ff23c90f6e
React-RCTActionSheet: 89a0ca9f4a06c1f93c26067af074ccdce0f40336 React-RCTActionSheet: 89a0ca9f4a06c1f93c26067af074ccdce0f40336
React-RCTAnimation: 1bde3ecc0c104c55df246eda516e0deb03c4e49b React-RCTAnimation: 1bde3ecc0c104c55df246eda516e0deb03c4e49b
React-RCTBlob: a97d378b527740cc667e03ebfa183a75231ab0f0 React-RCTBlob: a97d378b527740cc667e03ebfa183a75231ab0f0
@ -814,10 +819,11 @@ SPEC CHECKSUMS:
RNCAsyncStorage: da95b83e241a7f5efe3da1a949b3ec3175380be0 RNCAsyncStorage: da95b83e241a7f5efe3da1a949b3ec3175380be0
RNCMaskedView: 5a8ec07677aa885546a0d98da336457e2bea557f RNCMaskedView: 5a8ec07677aa885546a0d98da336457e2bea557f
RNFastImage: d4870d58f5936111c56218dbd7fcfc18e65b58ff RNFastImage: d4870d58f5936111c56218dbd7fcfc18e65b58ff
RNGestureHandler: 7a5833d0f788dbd107fbb913e09aa0c1ff333c39 RNGestureHandler: 9b7e605a741412e20e13c512738a31bd1611759b
RNReanimated: b9c929bfff7dedc9c89ab1875f1c6151023358d9 RNReanimated: e8a1520b15df106c96214161078c69e4a23b8b29
RNScreens: 3d682bcaba69a4f8e55543d90818704f34338db1 RNScreens: b6c9607e6fe47c1b6e2f1910d2acd46dd7ecea3a
RNSentry: 6b46b6fc1d715a378fbaa5d7d43bc9ce99b500e5 RNSentry: 6b46b6fc1d715a378fbaa5d7d43bc9ce99b500e5
RNSharedElement: 00b1a1420d213a34459bb9a5aacabb38107d7948
RNSVG: ce9d996113475209013317e48b05c21ee988d42e RNSVG: ce9d996113475209013317e48b05c21ee988d42e
SDWebImage: e378178472b735e84b007bfb55514c97948a0598 SDWebImage: e378178472b735e84b007bfb55514c97948a0598
SDWebImageWebPCoder: d0dac55073088d24b2ac1b191a71a8f8d0adac21 SDWebImageWebPCoder: d0dac55073088d24b2ac1b191a71a8f8d0adac21

View File

@ -177,6 +177,7 @@
LastUpgradeCheck = 1130; LastUpgradeCheck = 1130;
TargetAttributes = { TargetAttributes = {
13B07F861A680F5B00A75B9A = { 13B07F861A680F5B00A75B9A = {
DevelopmentTeam = 8EGBLQ2MA6;
LastSwiftMigration = 1120; LastSwiftMigration = 1120;
}; };
}; };
@ -229,7 +230,7 @@
); );
runOnlyForDeploymentPostprocessing = 0; runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh; shellPath = /bin/sh;
shellScript = "export NODE_BINARY=node\n../node_modules/react-native/scripts/react-native-xcode.sh\n../node_modules/expo-constants/scripts/get-app-config-ios.sh\n../node_modules/expo-updates/scripts/create-manifest-ios.sh\n../node_modules/expo-constants/scripts/get-app-config-ios.sh\n"; 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../node_modules/expo-updates/scripts/create-manifest-ios.sh\n";
}; };
08A4A3CD28434E44B6B9DE2E /* [CP] Check Pods Manifest.lock */ = { 08A4A3CD28434E44B6B9DE2E /* [CP] Check Pods Manifest.lock */ = {
isa = PBXShellScriptBuildPhase; isa = PBXShellScriptBuildPhase;
@ -325,6 +326,7 @@
CLANG_ENABLE_MODULES = YES; CLANG_ENABLE_MODULES = YES;
CODE_SIGN_ENTITLEMENTS = tooot/tooot.entitlements; CODE_SIGN_ENTITLEMENTS = tooot/tooot.entitlements;
CURRENT_PROJECT_VERSION = 1; CURRENT_PROJECT_VERSION = 1;
DEVELOPMENT_TEAM = 8EGBLQ2MA6;
ENABLE_BITCODE = NO; ENABLE_BITCODE = NO;
"EXCLUDED_ARCHS[sdk=iphonesimulator*]" = arm64; "EXCLUDED_ARCHS[sdk=iphonesimulator*]" = arm64;
GCC_PREPROCESSOR_DEFINITIONS = ( GCC_PREPROCESSOR_DEFINITIONS = (
@ -332,7 +334,7 @@
"FB_SONARKIT_ENABLED=1", "FB_SONARKIT_ENABLED=1",
); );
INFOPLIST_FILE = tooot/Info.plist; INFOPLIST_FILE = tooot/Info.plist;
IPHONEOS_DEPLOYMENT_TARGET = 10.0; IPHONEOS_DEPLOYMENT_TARGET = 11.0;
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
OTHER_LDFLAGS = ( OTHER_LDFLAGS = (
"$(inherited)", "$(inherited)",
@ -356,9 +358,10 @@
CLANG_ENABLE_MODULES = YES; CLANG_ENABLE_MODULES = YES;
CODE_SIGN_ENTITLEMENTS = tooot/tooot.entitlements; CODE_SIGN_ENTITLEMENTS = tooot/tooot.entitlements;
CURRENT_PROJECT_VERSION = 1; CURRENT_PROJECT_VERSION = 1;
DEVELOPMENT_TEAM = 8EGBLQ2MA6;
"EXCLUDED_ARCHS[sdk=iphonesimulator*]" = arm64; "EXCLUDED_ARCHS[sdk=iphonesimulator*]" = arm64;
INFOPLIST_FILE = tooot/Info.plist; INFOPLIST_FILE = tooot/Info.plist;
IPHONEOS_DEPLOYMENT_TARGET = 10.0; IPHONEOS_DEPLOYMENT_TARGET = 11.0;
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
OTHER_LDFLAGS = ( OTHER_LDFLAGS = (
"$(inherited)", "$(inherited)",
@ -421,7 +424,7 @@
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES; GCC_WARN_UNUSED_VARIABLE = YES;
IPHONEOS_DEPLOYMENT_TARGET = 10.0; IPHONEOS_DEPLOYMENT_TARGET = 11.0;
LD_RUNPATH_SEARCH_PATHS = "/usr/lib/swift $(inherited)"; LD_RUNPATH_SEARCH_PATHS = "/usr/lib/swift $(inherited)";
LIBRARY_SEARCH_PATHS = ( LIBRARY_SEARCH_PATHS = (
"\"$(TOOLCHAIN_DIR)/usr/lib/swift/$(PLATFORM_NAME)\"", "\"$(TOOLCHAIN_DIR)/usr/lib/swift/$(PLATFORM_NAME)\"",
@ -475,7 +478,7 @@
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES; GCC_WARN_UNUSED_VARIABLE = YES;
IPHONEOS_DEPLOYMENT_TARGET = 10.0; IPHONEOS_DEPLOYMENT_TARGET = 11.0;
LD_RUNPATH_SEARCH_PATHS = "/usr/lib/swift $(inherited)"; LD_RUNPATH_SEARCH_PATHS = "/usr/lib/swift $(inherited)";
LIBRARY_SEARCH_PATHS = ( LIBRARY_SEARCH_PATHS = (
"\"$(TOOLCHAIN_DIR)/usr/lib/swift/$(PLATFORM_NAME)\"", "\"$(TOOLCHAIN_DIR)/usr/lib/swift/$(PLATFORM_NAME)\"",

View File

@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>IDEDidComputeMac32BitWarning</key>
<true/>
</dict>
</plist>

View File

@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>PreviewsEnabled</key>
<false/>
</dict>
</plist>

View File

@ -6,95 +6,91 @@
"web": "expo start --web", "web": "expo start --web",
"eject": "expo eject", "eject": "expo eject",
"test": "jest --watchAll", "test": "jest --watchAll",
"release": "./publish/publish.sh" "release": "scripts/release.sh"
}, },
"dependencies": { "dependencies": {
"@expo/react-native-action-sheet": "^3.8.0", "@expo/react-native-action-sheet": "^3.8.0",
"@neverdull-agency/expo-unlimited-secure-store": "^1.0.10", "@neverdull-agency/expo-unlimited-secure-store": "^1.0.10",
"@react-native-async-storage/async-storage": "^1.13.2", "@react-native-async-storage/async-storage": "^1.13.3",
"@react-native-community/blur": "^3.6.0",
"@react-native-community/masked-view": "0.1.10", "@react-native-community/masked-view": "0.1.10",
"@react-native-community/netinfo": "^5.9.7", "@react-native-community/netinfo": "^5.9.10",
"@react-native-community/segmented-control": "2.2.1", "@react-native-community/segmented-control": "2.2.2",
"@react-native-community/viewpager": "4.2.0", "@react-native-community/viewpager": "4.2.2",
"@react-navigation/bottom-tabs": "^5.11.2", "@react-navigation/bottom-tabs": "^5.11.7",
"@react-navigation/native": "^5.8.10", "@react-navigation/native": "^5.9.2",
"@react-navigation/stack": "^5.14.2",
"@reduxjs/toolkit": "^1.5.0", "@reduxjs/toolkit": "^1.5.0",
"@sentry/react-native": "^2.1.1", "@sentry/react-native": "^2.1.1",
"@sharcoux/slider": "^5.0.1", "@sharcoux/slider": "^5.0.4",
"axios": "^0.21.1", "axios": "^0.21.1",
"expo": "^40.0.0", "expo": "^40.0.1",
"expo-auth-session": "~3.0.0", "expo-auth-session": "~3.0.0",
"expo-av": "~8.7.0", "expo-av": "~8.7.0",
"expo-blur": "~8.2.2",
"expo-constants": "~10.0.1",
"expo-crypto": "~8.4.0", "expo-crypto": "~8.4.0",
"expo-firebase-analytics": "~2.6.0", "expo-firebase-analytics": "~2.6.0",
"expo-firebase-core": "~1.3.0",
"expo-haptics": "~8.4.0", "expo-haptics": "~8.4.0",
"expo-image-picker": "~9.2.0", "expo-image-picker": "~9.2.0",
"expo-linear-gradient": "~8.4.0", "expo-linear-gradient": "~8.4.0",
"expo-linking": "~2.0.0", "expo-linking": "~2.0.1",
"expo-localization": "~9.1.0", "expo-localization": "~9.1.0",
"expo-random": "~10.0.0", "expo-random": "~10.0.0",
"expo-secure-store": "~9.3.0", "expo-secure-store": "~9.3.0",
"expo-splash-screen": "~0.8.1", "expo-splash-screen": "~0.8.1",
"expo-status-bar": "~1.0.3", "expo-status-bar": "~1.0.3",
"expo-store-review": "~2.3.0", "expo-store-review": "~2.3.0",
"expo-updates": "~0.4.0",
"expo-video-thumbnails": "~4.4.0", "expo-video-thumbnails": "~4.4.0",
"expo-web-browser": "~8.6.0", "expo-web-browser": "~8.6.0",
"i18next": "^19.8.4", "i18next": "^19.8.5",
"lodash": "^4.17.20", "lodash": "^4.17.20",
"react": "16.13.1", "react": "16.13.1",
"react-dom": "16.13.1", "react-dom": "16.13.1",
"react-i18next": "^11.8.5", "react-i18next": "^11.8.5",
"react-native": "~0.63.4", "react-native": "~0.63.4",
"react-native-animated-spinkit": "^1.4.2", "react-native-animated-spinkit": "^1.5.1",
"react-native-blurhash": "^1.0.29", "react-native-blurhash": "^1.0.29",
"react-native-fast-image": "^8.3.4", "react-native-fast-image": "^8.3.4",
"react-native-feather": "^1.0.2", "react-native-feather": "^1.0.2",
"react-native-gesture-handler": "~1.8.0", "react-native-gesture-handler": "~1.9.0",
"react-native-htmlview": "^0.16.0", "react-native-htmlview": "^0.16.0",
"react-native-image-zoom-viewer": "^3.0.1", "react-native-image-zoom-viewer": "^3.0.1",
"react-native-reanimated": "2.0.0-rc.0", "react-native-reanimated": "^2.0.0-rc.2",
"react-native-safe-area-context": "3.1.9", "react-native-safe-area-context": "3.1.9",
"react-native-screens": "~2.15.0", "react-native-screens": "~2.17.1",
"react-native-shared-element": "^0.7.0",
"react-native-svg": "12.1.0", "react-native-svg": "12.1.0",
"react-native-tab-view": "^2.15.2", "react-native-tab-view": "^2.15.2",
"react-native-tab-view-viewpager-adapter": "^1.1.0", "react-native-tab-view-viewpager-adapter": "^1.1.0",
"react-native-toast-message": "^1.4.2", "react-native-toast-message": "^1.4.3",
"react-native-unimodules": "~0.12.0", "react-native-unimodules": "~0.12.0",
"react-native-web": "~0.13.12", "react-navigation-shared-element": "^3.0.0",
"react-navigation": "^4.4.3", "react-query": "^3.6.0",
"react-query": "^3.5.6",
"react-redux": "^7.2.2", "react-redux": "^7.2.2",
"react-timeago": "^5.2.0", "react-timeago": "^5.2.0",
"redux-persist": "^6.0.0", "redux-persist": "^6.0.0",
"rn-placeholder": "^3.0.3", "rn-placeholder": "^3.0.3",
"sentry-expo": "^3.0.4", "sentry-expo": "^3.0.4",
"tslib": "^2.0.3" "tslib": "^2.1.0"
}, },
"devDependencies": { "devDependencies": {
"@babel/core": "~7.9.0", "@babel/core": "~7.12.10",
"@babel/plugin-proposal-optional-chaining": "^7.12.1", "@babel/plugin-proposal-optional-chaining": "^7.12.7",
"@babel/preset-typescript": "^7.12.7", "@babel/preset-typescript": "^7.12.7",
"@expo/config": "^3.3.15", "@expo/config": "^3.3.26",
"@jest/types": "^26.6.2", "@jest/types": "^26.6.2",
"@react-navigation/stack": "^5.12.8",
"@testing-library/jest-native": "^3.4.3", "@testing-library/jest-native": "^3.4.3",
"@testing-library/react-hooks": "^3.7.0", "@testing-library/react-hooks": "^3.7.0",
"@testing-library/react-native": "^7.1.0", "@testing-library/react-native": "^7.1.0",
"@types/crypto-js": "^4.0.1",
"@types/jest": "^26.0.19", "@types/jest": "^26.0.19",
"@types/lodash": "^4.14.164", "@types/lodash": "^4.14.164",
"@types/react": "~16.9.35", "@types/react": "~16.9.35",
"@types/react-dom": "~16.9.8", "@types/react-dom": "~16.9.8",
"@types/react-native": "~0.63.2", "@types/react-native": "~0.63.46",
"@types/react-navigation": "^3.4.0", "@types/react-navigation": "^3.4.0",
"@types/react-redux": "^7.1.12", "@types/react-redux": "^7.1.16",
"@types/react-test-renderer": "^17.0.0", "@types/react-test-renderer": "^17.0.0",
"@types/react-timeago": "^4.1.2", "@types/react-timeago": "^4.1.2",
"@welldone-software/why-did-you-render": "^6.0.4", "@welldone-software/why-did-you-render": "^6.0.5",
"babel-jest": "~25.2.6", "babel-jest": "~25.2.6",
"babel-plugin-module-resolver": "^4.1.0", "babel-plugin-module-resolver": "^4.1.0",
"chalk": "^4.1.0", "chalk": "^4.1.0",
@ -102,6 +98,8 @@
"jest": "^26.6.3", "jest": "^26.6.3",
"jest-expo": "^40.0.1", "jest-expo": "^40.0.1",
"nock": "^13.0.5", "nock": "^13.0.5",
"react-navigation": "^4.4.3",
"react-navigation-stack": "^2.10.2",
"react-test-renderer": "^16.13.1", "react-test-renderer": "^16.13.1",
"typescript": "~4.1.3" "typescript": "~4.1.3"
}, },

View File

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

View File

@ -1,19 +1,22 @@
interface IImageInfo {
url: string
width?: number
height?: number
originUrl?: string
props?: any
}
declare namespace Nav { declare namespace Nav {
type RootStackParamList = { type RootStackParamList = {
'Screen-Local': undefined 'Screen-Tabs': undefined
'Screen-Public': undefined 'Screen-Actions': {
'Screen-Post': undefined queryKey: QueryKeyTimeline
'Screen-Notifications': undefined status: Mastodon.Status
'Screen-Me': undefined url?: string
} type?: 'status' | 'account'
type SharedStackParamList = {
'Screen-Shared-Account': {
account: Mastodon.Account | Mastodon.Mention
} }
'Screen-Shared-Announcements': { showAll?: boolean } 'Screen-Announcements': { showAll: boolean }
'Screen-Shared-Attachments': { account: Mastodon.Account } 'Screen-Compose':
'Screen-Shared-Compose':
| { | {
type: 'edit' type: 'edit'
incomingStatus: Mastodon.Status incomingStatus: Mastodon.Status
@ -48,54 +51,71 @@ declare namespace Nav {
accts: Mastodon.Account['acct'][] accts: Mastodon.Account['acct'][]
} }
| undefined | undefined
'Screen-Shared-Hashtag': { 'Screen-ImagesViewer': {
hashtag: Mastodon.Tag['name'] imageUrls: (IImageInfo & {
}
'Screen-Shared-ImagesViewer': {
imageUrls: ({
url: string
width?: number
height?: number
originUrl?: string
props?: any
} & {
preview_url: Mastodon.AttachmentImage['preview_url'] preview_url: Mastodon.AttachmentImage['preview_url']
remote_url: Mastodon.AttachmentImage['remote_url'] remote_url?: Mastodon.AttachmentImage['remote_url']
imageIndex: number imageIndex: number
})[] })[]
imageIndex: number imageIndex: number
} }
'Screen-Shared-Relationships': { }
type ScreenTabsStackParamList = {
'Tab-Local': undefined
'Tab-Public': undefined
'Tab-Compose': undefined
'Tab-Notifications': undefined
'Tab-Me': undefined
}
type TabSharedStackParamList = {
'Tab-Shared-Account': {
account: Mastodon.Account | Mastodon.Mention
}
'Tab-Shared-Attachments': { account: Mastodon.Account }
'Tab-Shared-Hashtag': {
hashtag: Mastodon.Tag['name']
}
'Tab-Shared-Relationships': {
account: Mastodon.Account account: Mastodon.Account
initialType: 'following' | 'followers' initialType: 'following' | 'followers'
} }
'Screen-Shared-Search': undefined 'Tab-Shared-Search': undefined
'Screen-Shared-Toot': { 'Tab-Shared-Toot': {
toot: Mastodon.Status toot: Mastodon.Status
} }
} }
type LocalStackParamList = { type TabLocalStackParamList = {
'Screen-Local-Root': undefined 'Tab-Local-Root': undefined
} & SharedStackParamList } & TabSharedStackParamList
type RemoteStackParamList = { type TabPublicStackParamList = {
'Screen-Remote-Root': undefined 'Tab-Public-Root': undefined
} & SharedStackParamList } & TabSharedStackParamList
type NotificationsStackParamList = { type TabComposeStackParamList = {
'Screen-Notifications-Root': undefined 'Tab-Compose-Root': undefined
} & SharedStackParamList 'Tab-Compose-EditAttachment': unknown
}
type MeStackParamList = { type TabNotificationsStackParamList = {
'Screen-Me-Root': { navigateAway?: 'Screen-Me-Settings-UpdateRemote' } 'Tab-Notifications-Root': undefined
'Screen-Me-Bookmarks': undefined } & TabSharedStackParamList
'Screen-Me-Conversations': undefined
'Screen-Me-Favourites': undefined type TabMeStackParamList = {
'Screen-Me-Lists': undefined 'Tab-Me-Root': undefined
'Screen-Me-Lists-List': undefined 'Tab-Me-Bookmarks': undefined
'Screen-Me-Settings': undefined 'Tab-Me-Conversations': undefined
'Screen-Me-Settings-UpdateRemote': undefined 'Tab-Me-Favourites': undefined
'Screen-Me-Switch': undefined 'Tab-Me-Lists': undefined
} & SharedStackParamList 'Tab-Me-Lists-List': {
list: Mastodon.List['id']
title: Mastodon.List['title']
}
'Tab-Me-Settings': undefined
'Tab-Me-Settings-UpdateRemote': undefined
'Tab-Me-Switch': undefined
} & TabSharedStackParamList
} }

View File

@ -1,6 +1,6 @@
import { ActionSheetProvider } from '@expo/react-native-action-sheet' import { ActionSheetProvider } from '@expo/react-native-action-sheet'
import i18n from '@root/i18n/i18n' import i18n from '@root/i18n/i18n'
import Index from '@root/Index' import Index from '@root/Screens'
import audio from '@root/startup/audio' import audio from '@root/startup/audio'
import dev from '@root/startup/dev' import dev from '@root/startup/dev'
import log from '@root/startup/log' import log from '@root/startup/log'

View File

@ -1,324 +0,0 @@
import client from '@api/client'
import haptics from '@components/haptics'
import Icon from '@components/Icon'
import { toast, toastConfig } from '@components/toast'
import {
BottomTabNavigationOptions,
createBottomTabNavigator
} from '@react-navigation/bottom-tabs'
import {
getFocusedRouteNameFromRoute,
NavigationContainer,
NavigationContainerRef
} from '@react-navigation/native'
import ScreenLocal from '@screens/Local'
import ScreenMe from '@screens/Me'
import ScreenNotifications from '@screens/Notifications'
import ScreenPublic from '@screens/Public'
import { useTimelineQuery } from '@utils/queryHooks/timeline'
import {
getLocalAccount,
getLocalActiveIndex,
getLocalNotification,
localUpdateAccountPreferences,
localUpdateNotification
} from '@utils/slices/instancesSlice'
import { useTheme } from '@utils/styles/ThemeManager'
import { themes } from '@utils/styles/themes'
import * as Analytics from 'expo-firebase-analytics'
import React, {
createRef,
useCallback,
useEffect,
useMemo,
useRef
} from 'react'
import { useTranslation } from 'react-i18next'
import { Image, Platform, StatusBar } from 'react-native'
import Toast from 'react-native-toast-message'
import { useDispatch, useSelector } from 'react-redux'
const Tab = createBottomTabNavigator<Nav.RootStackParamList>()
export interface Props {
localCorrupt?: string
}
export const navigationRef = createRef<NavigationContainerRef>()
const Index: React.FC<Props> = ({ localCorrupt }) => {
const dispatch = useDispatch()
const localActiveIndex = useSelector(getLocalActiveIndex)
const { mode, theme } = useTheme()
enum barStyle {
light = 'dark-content',
dark = 'light-content'
}
const routeNameRef = useRef<string | undefined>()
// const isConnected = useNetInfo().isConnected
// const [firstRender, setFirstRender] = useState(false)
// useEffect(() => {
// if (firstRender) {
// // bug in netInfo on first render as false
// if (isConnected !== false) {
// toast({ type: 'error', content: '手机🈚️网络', autoHide: false })
// }
// } else {
// setFirstRender(true)
// }
// }, [isConnected, firstRender])
// On launch display login credentials corrupt information
const { t } = useTranslation('common')
useEffect(() => {
const showLocalCorrect = localCorrupt
? toast({
type: 'error',
message: t('index.localCorrupt'),
description: localCorrupt.length ? localCorrupt : undefined,
autoHide: false
})
: undefined
return showLocalCorrect
}, [localCorrupt])
// On launch check if there is any unread announcements
useEffect(() => {
console.log('Checking announcements')
localActiveIndex !== null &&
client<Mastodon.Announcement[]>({
method: 'get',
instance: 'local',
url: `announcements`
})
.then(res => {
if (res?.filter(announcement => !announcement.read).length) {
navigationRef.current?.navigate('Screen-Shared-Announcements', {
showAll: false
})
}
})
.catch(() => {})
}, [])
// On launch check if there is any unread noficiations
const queryNotification = useTimelineQuery({
page: 'Notifications',
options: {
enabled: localActiveIndex !== null ? true : false,
refetchInterval: 1000 * 60,
refetchIntervalInBackground: true
}
})
const prevNotification = useSelector(getLocalNotification)
useEffect(() => {
if (queryNotification.data?.pages) {
const flattenData = queryNotification.data.pages.flatMap(d => [...d])
const latestNotificationTime = flattenData.length
? (flattenData[0] as Mastodon.Notification).created_at
: undefined
if (!prevNotification || !prevNotification.latestTime) {
dispatch(
localUpdateNotification({
unread: false
})
)
} else if (
latestNotificationTime &&
new Date(prevNotification.latestTime) < new Date(latestNotificationTime)
) {
dispatch(
localUpdateNotification({
unread: true,
latestTime: latestNotificationTime
})
)
}
}
}, [queryNotification.data?.pages])
// Lazily update users's preferences, for e.g. composing default visibility
useEffect(() => {
if (localActiveIndex !== null) {
dispatch(localUpdateAccountPreferences())
}
}, [])
// Callbacks
const navigationContainerOnReady = useCallback(
() =>
(routeNameRef.current = navigationRef.current?.getCurrentRoute()?.name),
[]
)
const navigationContainerOnStateChange = useCallback(() => {
const previousRouteName = routeNameRef.current
const currentRouteName = navigationRef.current?.getCurrentRoute()?.name
if (previousRouteName !== currentRouteName) {
Analytics.setCurrentScreen(currentRouteName)
}
routeNameRef.current = currentRouteName
}, [])
const localAccount = useSelector(getLocalAccount)
const tabNavigatorScreenOptions = useCallback(
({ route }): BottomTabNavigationOptions => ({
tabBarIcon: ({
focused,
color,
size
}: {
focused: boolean
color: string
size: number
}) => {
switch (route.name) {
case 'Screen-Local':
return <Icon name='Home' size={size} color={color} />
case 'Screen-Public':
return <Icon name='Globe' size={size} color={color} />
case 'Screen-Post':
return <Icon name='Plus' size={size} color={color} />
case 'Screen-Notifications':
return <Icon name='Bell' size={size} color={color} />
case 'Screen-Me':
return localActiveIndex !== null ? (
<Image
source={{ uri: localAccount?.avatarStatic }}
style={{
width: size,
height: size,
borderRadius: size,
borderWidth: focused ? 2 : 0,
borderColor: focused ? theme.secondary : color
}}
/>
) : (
<Icon
name={focused ? 'Meh' : 'Smile'}
size={size}
color={!focused ? theme.secondary : color}
/>
)
default:
return <Icon name='AlertOctagon' size={size} color={color} />
}
},
...(Platform.OS === 'android' && {
tabBarVisible:
getFocusedRouteNameFromRoute(route) !== 'Screen-Shared-Compose' &&
getFocusedRouteNameFromRoute(route) !==
'Screen-Shared-Announcements' &&
getFocusedRouteNameFromRoute(route) !==
'Screen-Shared-ImagesViewer' &&
getFocusedRouteNameFromRoute(route) !== 'Screen-Me-Switch'
})
}),
[localActiveIndex, localAccount]
)
const tabNavigatorTabBarOptions = useMemo(
() => ({
activeTintColor: theme.primary,
inactiveTintColor:
localActiveIndex !== null ? theme.secondary : theme.disabled,
showLabel: false,
...(Platform.OS === 'android' && { keyboardHidesTabBar: true })
}),
[theme, localActiveIndex]
)
const tabScreenLocalListeners = useCallback(
() => ({
tabPress: (e: any) => {
if (!(localActiveIndex !== null)) {
e.preventDefault()
}
}
}),
[localActiveIndex]
)
const tabScreenComposeListeners = useMemo(
() => ({
tabPress: (e: any) => {
e.preventDefault()
if (localActiveIndex !== null) {
haptics('Medium')
navigationRef.current?.navigate('Screen-Shared-Compose')
}
}
}),
[localActiveIndex]
)
const tabScreenComposeComponent = useCallback(() => null, [])
const tabScreenNotificationsListeners = useCallback(
() => ({
tabPress: (e: any) => {
if (!(localActiveIndex !== null)) {
e.preventDefault()
}
}
}),
[localActiveIndex]
)
return (
<>
<StatusBar barStyle={barStyle[mode]} backgroundColor={theme.background} />
<NavigationContainer
ref={navigationRef}
theme={themes[mode]}
onReady={navigationContainerOnReady}
onStateChange={navigationContainerOnStateChange}
>
<Tab.Navigator
initialRouteName={
localActiveIndex !== null ? 'Screen-Local' : 'Screen-Me'
}
screenOptions={tabNavigatorScreenOptions}
tabBarOptions={tabNavigatorTabBarOptions}
>
<Tab.Screen
name='Screen-Local'
component={ScreenLocal}
listeners={tabScreenLocalListeners}
/>
<Tab.Screen name='Screen-Public' component={ScreenPublic} />
<Tab.Screen
name='Screen-Post'
component={tabScreenComposeComponent}
listeners={tabScreenComposeListeners}
/>
<Tab.Screen
name='Screen-Notifications'
component={ScreenNotifications}
listeners={tabScreenNotificationsListeners}
options={
prevNotification && prevNotification.unread
? {
tabBarBadge: '',
tabBarBadgeStyle: {
transform: [{ scale: 0.5 }],
backgroundColor: theme.red
}
}
: {
tabBarBadgeStyle: {
transform: [{ scale: 0.5 }],
backgroundColor: theme.red
}
}
}
/>
<Tab.Screen name='Screen-Me' component={ScreenMe} />
</Tab.Navigator>
{Platform.OS === 'ios' ? (
<Toast ref={Toast.setRef} config={toastConfig} />
) : null}
</NavigationContainer>
</>
)
}
export default React.memo(Index, () => true)

216
src/Screens.tsx Normal file
View File

@ -0,0 +1,216 @@
import client from '@api/client'
import { HeaderLeft } from '@components/Header'
import { toast, toastConfig } from '@components/toast'
import {
NavigationContainer,
NavigationContainerRef
} from '@react-navigation/native'
import ScreenActions from '@screens/Actions'
import ScreenAnnouncements from '@screens/Announcements'
import ScreenCompose from '@screens/Compose'
import ScreenImagesViewer from '@screens/ImagesViewer'
import ScreenTabs from '@screens/Tabs'
import {
getLocalActiveIndex,
localUpdateAccountPreferences
} from '@utils/slices/instancesSlice'
import { useTheme } from '@utils/styles/ThemeManager'
import { themes } from '@utils/styles/themes'
import * as Analytics from 'expo-firebase-analytics'
import React, { createRef, useCallback, useEffect, useRef } from 'react'
import { useTranslation } from 'react-i18next'
import { Platform, StatusBar } from 'react-native'
import Toast from 'react-native-toast-message'
import { createSharedElementStackNavigator } from 'react-navigation-shared-element'
import { useDispatch, useSelector } from 'react-redux'
const Stack = createSharedElementStackNavigator<Nav.RootStackParamList>()
export interface Props {
localCorrupt?: string
}
export const navigationRef = createRef<NavigationContainerRef>()
const Index: React.FC<Props> = ({ localCorrupt }) => {
const dispatch = useDispatch()
const localActiveIndex = useSelector(getLocalActiveIndex)
const { mode, theme } = useTheme()
enum barStyle {
light = 'dark-content',
dark = 'light-content'
}
const routeNameRef = useRef<string | undefined>()
// const isConnected = useNetInfo().isConnected
// const [firstRender, setFirstRender] = useState(false)
// useEffect(() => {
// if (firstRender) {
// // bug in netInfo on first render as false
// if (isConnected !== false) {
// toast({ type: 'error', content: '手机🈚️网络', autoHide: false })
// }
// } else {
// setFirstRender(true)
// }
// }, [isConnected, firstRender])
// On launch display login credentials corrupt information
const { t } = useTranslation('common')
useEffect(() => {
const showLocalCorrect = localCorrupt
? toast({
type: 'error',
message: t('index.localCorrupt'),
description: localCorrupt.length ? localCorrupt : undefined,
autoHide: false
})
: undefined
return showLocalCorrect
}, [localCorrupt])
// On launch check if there is any unread announcements
useEffect(() => {
localActiveIndex !== null &&
client<Mastodon.Announcement[]>({
method: 'get',
instance: 'local',
url: `announcements`
})
.then(res => {
if (res?.filter(announcement => !announcement.read).length) {
navigationRef.current?.navigate('Screen-Announcements', {
showAll: false
})
}
})
.catch(() => {})
}, [])
// Lazily update users's preferences, for e.g. composing default visibility
useEffect(() => {
if (localActiveIndex !== null) {
dispatch(localUpdateAccountPreferences())
}
}, [])
// Callbacks
const navigationContainerOnReady = useCallback(
() =>
(routeNameRef.current = navigationRef.current?.getCurrentRoute()?.name),
[]
)
const navigationContainerOnStateChange = useCallback(() => {
const previousRouteName = routeNameRef.current
const currentRouteName = navigationRef.current?.getCurrentRoute()?.name
if (previousRouteName !== currentRouteName) {
Analytics.setCurrentScreen(currentRouteName)
}
routeNameRef.current = currentRouteName
}, [])
return (
<>
<StatusBar barStyle={barStyle[mode]} backgroundColor={theme.background} />
<NavigationContainer
ref={navigationRef}
theme={themes[mode]}
onReady={navigationContainerOnReady}
onStateChange={navigationContainerOnStateChange}
>
<Stack.Navigator mode='modal' initialRouteName='Screen-Tabs'>
<Stack.Screen
name='Screen-Tabs'
component={ScreenTabs}
options={{ headerShown: false }}
/>
<Stack.Screen
name='Screen-Actions'
component={ScreenActions}
options={{
headerShown: false,
cardStyle: { backgroundColor: 'transparent' },
cardStyleInterpolator: ({ current: { progress } }) => ({
cardStyle: {
opacity: progress.interpolate({
inputRange: [0, 1],
outputRange: [0, 1]
})
}
})
}}
/>
<Stack.Screen
name='Screen-Announcements'
component={ScreenAnnouncements}
options={{
gestureEnabled: false,
title: t('sharedAnnouncements:heading'),
headerTransparent: true,
headerLeft: () => (
<HeaderLeft
content='X'
native={false}
onPress={() => navigationRef.current?.goBack()}
/>
),
animationTypeForReplace: 'pop',
cardStyle: { backgroundColor: 'transparent' },
cardStyleInterpolator: ({ current: { progress } }) => ({
cardStyle: {
opacity: progress.interpolate({
inputRange: [0, 1],
outputRange: [0, 1]
})
}
})
}}
/>
<Stack.Screen
name='Screen-Compose'
component={ScreenCompose}
options={{ gestureEnabled: false, headerShown: false }}
/>
<Stack.Screen
name='Screen-ImagesViewer'
component={ScreenImagesViewer}
options={{
gestureEnabled: false,
headerTransparent: true,
headerLeft: () => (
<HeaderLeft
content='X'
native={false}
onPress={() => navigationRef.current?.goBack()}
/>
),
cardStyle: { backgroundColor: 'transparent' },
cardStyleInterpolator: ({ current: { progress } }) => ({
cardStyle: {
opacity: progress.interpolate({
inputRange: [0, 1],
outputRange: [0, 1]
})
}
})
}}
sharedElements={route => {
const { imageIndex, imageUrls } = route.params
return [{ id: `image.${imageUrls[imageIndex].url}`, debug: true }]
}}
/>
</Stack.Navigator>
{Platform.OS === 'ios' ? (
<Toast ref={Toast.setRef} config={toastConfig} />
) : null}
</NavigationContainer>
</>
)
}
export default React.memo(Index, () => true)

View File

@ -21,12 +21,12 @@ const ComponentAccount: React.FC<Props> = ({
}) => { }) => {
const { theme } = useTheme() const { theme } = useTheme()
const navigation = useNavigation< const navigation = useNavigation<
StackNavigationProp<Nav.LocalStackParamList> StackNavigationProp<Nav.TabLocalStackParamList>
>() >()
const onPress = useCallback(() => { const onPress = useCallback(() => {
analytics('search_account_press', { page: origin }) analytics('search_account_press', { page: origin })
navigation.push('Screen-Shared-Account', { account }) navigation.push('Tab-Shared-Account', { account })
}, []) }, [])
return ( return (

View File

@ -1,129 +0,0 @@
import React from 'react'
import { Dimensions, Modal, StyleSheet, View } from 'react-native'
import { useSafeAreaInsets } from 'react-native-safe-area-context'
import { useTheme } from '@utils/styles/ThemeManager'
import { StyleConstants } from '@utils/styles/constants'
import Button from '@components/Button'
import {
PanGestureHandler,
State,
TapGestureHandler
} from 'react-native-gesture-handler'
import Animated, {
Extrapolate,
interpolate,
runOnJS,
useAnimatedGestureHandler,
useAnimatedStyle,
useSharedValue,
withTiming
} from 'react-native-reanimated'
import analytics from './analytics'
export interface Props {
children: React.ReactNode
visible: boolean
handleDismiss: () => void
}
const BottomSheet: React.FC<Props> = ({ children, visible, handleDismiss }) => {
const { theme } = useTheme()
const insets = useSafeAreaInsets()
const screenHeight = Dimensions.get('screen').height
const panY = useSharedValue(0)
const styleTop = useAnimatedStyle(() => {
return {
top: interpolate(
panY.value,
[0, screenHeight],
[0, screenHeight],
Extrapolate.CLAMP
)
}
})
const callDismiss = () => {
analytics('bottomsheet_swipe_close')
handleDismiss()
}
const onGestureEvent = useAnimatedGestureHandler({
onActive: ({ translationY }) => {
panY.value = translationY
},
onEnd: ({ velocityY }) => {
if (velocityY > 500) {
runOnJS(callDismiss)()
} else {
panY.value = withTiming(0)
}
}
})
return (
<Modal animated animationType='fade' visible={visible} transparent>
<TapGestureHandler
onHandlerStateChange={({ nativeEvent }) => {
if (nativeEvent.state === State.ACTIVE) {
callDismiss()
}
}}
>
<Animated.View
style={[styles.overlay, { backgroundColor: theme.backgroundOverlay }]}
>
<PanGestureHandler onGestureEvent={onGestureEvent}>
<Animated.View
style={[
styles.container,
styleTop,
{
backgroundColor: theme.background,
paddingBottom: insets.bottom || StyleConstants.Spacing.L
}
]}
>
<View
style={[
styles.handle,
{ backgroundColor: theme.primaryOverlay }
]}
/>
{children}
<Button
type='text'
content='取消'
onPress={() => {
analytics('bottomsheet_cancel')
handleDismiss()
}}
style={styles.button}
/>
</Animated.View>
</PanGestureHandler>
</Animated.View>
</TapGestureHandler>
</Modal>
)
}
const styles = StyleSheet.create({
overlay: {
flex: 1,
justifyContent: 'flex-end'
},
container: {
paddingTop: StyleConstants.Spacing.M
},
handle: {
alignSelf: 'center',
width: StyleConstants.Spacing.S * 8,
height: StyleConstants.Spacing.S / 2,
borderRadius: 100,
top: -StyleConstants.Spacing.M * 2
},
button: {
marginHorizontal: StyleConstants.Spacing.Global.PagePadding * 2
}
})
export default BottomSheet

View File

@ -9,9 +9,11 @@ import {
} from 'react-native' } from 'react-native'
import { Blurhash } from 'react-native-blurhash' import { Blurhash } from 'react-native-blurhash'
import FastImage from 'react-native-fast-image' import FastImage from 'react-native-fast-image'
import { SharedElement } from 'react-navigation-shared-element'
import { useTheme } from '@utils/styles/ThemeManager' import { useTheme } from '@utils/styles/ThemeManager'
export interface Props { export interface Props {
sharedElement?: string
hidden?: boolean hidden?: boolean
uri: { preview?: string; original: string; remote?: string } uri: { preview?: string; original: string; remote?: string }
blurhash?: string blurhash?: string
@ -22,6 +24,7 @@ export interface Props {
} }
const GracefullyImage: React.FC<Props> = ({ const GracefullyImage: React.FC<Props> = ({
sharedElement,
hidden = false, hidden = false,
uri, uri,
blurhash, blurhash,
@ -36,11 +39,21 @@ const GracefullyImage: React.FC<Props> = ({
const children = useCallback(() => { const children = useCallback(() => {
return ( return (
<> <>
<FastImage {sharedElement ? (
source={{ uri: uri.preview || uri.original || uri.remote }} <SharedElement id={`image.${sharedElement}`} style={[styles.image]}>
style={[styles.image, imageStyle]} <FastImage
onLoad={() => setImageLoaded(true)} source={{ uri: uri.preview || uri.original || uri.remote }}
/> style={[styles.image, imageStyle]}
onLoad={() => setImageLoaded(true)}
/>
</SharedElement>
) : (
<FastImage
source={{ uri: uri.preview || uri.original || uri.remote }}
style={[styles.image, imageStyle]}
onLoad={() => setImageLoaded(true)}
/>
)}
{blurhash && (hidden || !imageLoaded) ? ( {blurhash && (hidden || !imageLoaded) ? (
<Blurhash <Blurhash
decodeAsync decodeAsync

View File

@ -19,12 +19,12 @@ const ComponentHashtag: React.FC<Props> = ({
}) => { }) => {
const { theme } = useTheme() const { theme } = useTheme()
const navigation = useNavigation< const navigation = useNavigation<
StackNavigationProp<Nav.LocalStackParamList> StackNavigationProp<Nav.TabLocalStackParamList>
>() >()
const onPress = useCallback(() => { const onPress = useCallback(() => {
analytics('search_account_press', { page: origin }) analytics('search_account_press', { page: origin })
navigation.push('Screen-Shared-Hashtag', { hashtag: hashtag.name }) navigation.push('Tab-Shared-Hashtag', { hashtag: hashtag.name })
}, []) }, [])
return ( return (

View File

@ -7,11 +7,17 @@ import { Pressable, StyleSheet, Text } from 'react-native'
export interface Props { export interface Props {
type?: 'icon' | 'text' type?: 'icon' | 'text'
content?: string content?: string
native?: boolean
onPress: () => void onPress: () => void
} }
const HeaderLeft: React.FC<Props> = ({ type = 'icon', content, onPress }) => { const HeaderLeft: React.FC<Props> = ({
type = 'icon',
content,
native = true,
onPress
}) => {
const { theme } = useTheme() const { theme } = useTheme()
const children = useMemo(() => { const children = useMemo(() => {
@ -42,7 +48,11 @@ const HeaderLeft: React.FC<Props> = ({ type = 'icon', content, onPress }) => {
styles.base, styles.base,
{ {
backgroundColor: theme.backgroundGradientStart, backgroundColor: theme.backgroundGradientStart,
...(type === 'icon' && { height: 44, width: 44, marginLeft: -9 }) ...(type === 'icon' && {
height: 44,
width: 44,
marginLeft: native ? -9 : 9
})
} }
]} ]}
/> />

View File

@ -9,6 +9,7 @@ import { Chase } from 'react-native-animated-spinkit'
export interface Props { export interface Props {
type?: 'icon' | 'text' type?: 'icon' | 'text'
content: string content: string
native?: boolean
loading?: boolean loading?: boolean
disabled?: boolean disabled?: boolean
@ -19,6 +20,7 @@ export interface Props {
const HeaderRight: React.FC<Props> = ({ const HeaderRight: React.FC<Props> = ({
type = 'icon', type = 'icon',
content, content,
native = true,
loading, loading,
disabled, disabled,
onPress onPress
@ -88,7 +90,11 @@ const HeaderRight: React.FC<Props> = ({
styles.base, styles.base,
{ {
backgroundColor: theme.backgroundGradientStart, backgroundColor: theme.backgroundGradientStart,
...(type === 'icon' && { height: 44, width: 44, marginRight: -9 }) ...(type === 'icon' && {
height: 44,
width: 44,
marginRight: native ? -9 : 9
})
} }
]} ]}
/> />

View File

@ -230,12 +230,6 @@ const ComponentInstance: React.FC<Props> = ({
content={instanceQuery.data?.title || undefined} content={instanceQuery.data?.title || undefined}
potentialWidth={2} potentialWidth={2}
/> />
<InstanceInfo
visible={instanceQuery.data?.short_description !== undefined}
header={t('server.information.description.heading')}
content={instanceQuery.data?.short_description || undefined}
potentialLines={5}
/>
<View style={styles.instanceStats}> <View style={styles.instanceStats}>
<InstanceInfo <InstanceInfo
style={styles.stat1} style={styles.stat1}

View File

@ -58,7 +58,7 @@ const renderNode = ({
analytics('status_hashtag_press') analytics('status_hashtag_press')
!disableDetails && !disableDetails &&
differentTag && differentTag &&
navigation.push('Screen-Shared-Hashtag', { navigation.push('Tab-Shared-Hashtag', {
hashtag: tag[1] || tag[2] hashtag: tag[1] || tag[2]
}) })
}} }}
@ -86,7 +86,7 @@ const renderNode = ({
accountIndex !== -1 && accountIndex !== -1 &&
!disableDetails && !disableDetails &&
differentAccount && differentAccount &&
navigation.push('Screen-Shared-Account', { navigation.push('Tab-Shared-Account', {
account: mentions[accountIndex] account: mentions[accountIndex]
}) })
}} }}
@ -115,7 +115,7 @@ const renderNode = ({
analytics('status_link_press') analytics('status_link_press')
!disableDetails && !shouldBeTag !disableDetails && !shouldBeTag
? await openLink(href) ? await openLink(href)
: navigation.push('Screen-Shared-Hashtag', { : navigation.push('Tab-Shared-Hashtag', {
hashtag: content.substring(1) hashtag: content.substring(1)
}) })
}} }}

View File

@ -2,7 +2,7 @@ import { HeaderRight } from '@components/Header'
import Timeline from '@components/Timelines/Timeline' import Timeline from '@components/Timelines/Timeline'
import SegmentedControl from '@react-native-community/segmented-control' import SegmentedControl from '@react-native-community/segmented-control'
import { useNavigation } from '@react-navigation/native' import { useNavigation } from '@react-navigation/native'
import sharedScreens from '@screens/Shared/sharedScreens' import sharedScreens from '@screens/Tabs/Shared/sharedScreens'
import { getLocalActiveIndex } from '@utils/slices/instancesSlice' import { getLocalActiveIndex } from '@utils/slices/instancesSlice'
import { useTheme } from '@utils/styles/ThemeManager' import { useTheme } from '@utils/styles/ThemeManager'
import React, { useCallback, useMemo, useState } from 'react' import React, { useCallback, useMemo, useState } from 'react'
@ -14,7 +14,7 @@ import ViewPagerAdapter from 'react-native-tab-view-viewpager-adapter'
import { useSelector } from 'react-redux' import { useSelector } from 'react-redux'
import analytics from './analytics' import analytics from './analytics'
const Stack = createNativeStackNavigator<Nav.RemoteStackParamList>() const Stack = createNativeStackNavigator<Nav.TabPublicStackParamList>()
const Timelines: React.FC = () => { const Timelines: React.FC = () => {
const { t, i18n } = useTranslation() const { t, i18n } = useTranslation()
@ -28,7 +28,7 @@ const Timelines: React.FC = () => {
const onPressSearch = useCallback(() => { const onPressSearch = useCallback(() => {
analytics('search_tap', { page: pages[segment].page }) analytics('search_tap', { page: pages[segment].page })
navigation.navigate('Screen-Public', { screen: 'Screen-Shared-Search' }) navigation.navigate('Tab-Public', { screen: 'Tab-Shared-Search' })
}, []) }, [])
const routes = pages.map(p => ({ key: p.page })) const routes = pages.map(p => ({ key: p.page }))

View File

@ -18,7 +18,6 @@ import TimelineConversation from './Timeline/Conversation'
import TimelineDefault from './Timeline/Default' import TimelineDefault from './Timeline/Default'
import TimelineEmpty from './Timeline/Empty' import TimelineEmpty from './Timeline/Empty'
import TimelineEnd from './Timeline/End' import TimelineEnd from './Timeline/End'
import TimelineHeader from './Timeline/Header'
import TimelineNotifications from './Timeline/Notifications' import TimelineNotifications from './Timeline/Notifications'
export interface Props { export interface Props {
@ -155,7 +154,6 @@ const Timeline: React.FC<Props> = ({
() => !disableInfinity && !isFetchingNextPage && fetchNextPage(), () => !disableInfinity && !isFetchingNextPage && fetchNextPage(),
[isFetchingNextPage] [isFetchingNextPage]
) )
const ListHeaderComponent = useCallback(() => <TimelineHeader />, [])
const ListFooterComponent = useCallback( const ListFooterComponent = useCallback(
() => <TimelineEnd hasNextPage={!disableInfinity ? hasNextPage : false} />, () => <TimelineEnd hasNextPage={!disableInfinity ? hasNextPage : false} />,
[hasNextPage] [hasNextPage]

View File

@ -76,13 +76,13 @@ const TimelineConversation: React.FC<Props> = ({
}) })
const navigation = useNavigation< const navigation = useNavigation<
StackNavigationProp<Nav.LocalStackParamList> StackNavigationProp<Nav.TabLocalStackParamList>
>() >()
const onPress = useCallback(() => { const onPress = useCallback(() => {
analytics('timeline_conversation_press') analytics('timeline_conversation_press')
if (conversation.last_status) { if (conversation.last_status) {
conversation.unread && mutate() conversation.unread && mutate()
navigation.push('Screen-Shared-Toot', { navigation.push('Tab-Shared-Toot', {
toot: conversation.last_status toot: conversation.last_status
}) })
} }

View File

@ -36,7 +36,7 @@ const TimelineDefault: React.FC<Props> = ({
}) => { }) => {
const localAccount = useSelector(getLocalAccount) const localAccount = useSelector(getLocalAccount)
const navigation = useNavigation< const navigation = useNavigation<
StackNavigationProp<Nav.LocalStackParamList> StackNavigationProp<Nav.TabLocalStackParamList>
>() >()
let actualStatus = item.reblog ? item.reblog : item let actualStatus = item.reblog ? item.reblog : item
@ -47,7 +47,7 @@ const TimelineDefault: React.FC<Props> = ({
}) })
!disableOnPress && !disableOnPress &&
!highlighted && !highlighted &&
navigation.push('Screen-Shared-Toot', { navigation.push('Tab-Shared-Toot', {
toot: actualStatus toot: actualStatus
}) })
}, []) }, [])

View File

@ -1,61 +0,0 @@
import analytics from '@components/analytics'
import { useNavigation } from '@react-navigation/native'
import Icon from '@root/components/Icon'
import { StyleConstants } from '@root/utils/styles/constants'
import { useTheme } from '@root/utils/styles/ThemeManager'
import { updatePublicRemoteNotice } from '@utils/slices/contextsSlice'
import React from 'react'
import { useTranslation } from 'react-i18next'
import { StyleSheet, Text, View } from 'react-native'
import { useDispatch } from 'react-redux'
const TimelineHeader = React.memo(
() => {
const { t } = useTranslation('componentTimeline')
const dispatch = useDispatch()
const navigation = useNavigation()
const { theme } = useTheme()
return (
<View style={[styles.base, { borderColor: theme.border }]}>
<Text style={[styles.text, { color: theme.primary }]}>
{t('header.explanation')}
<Text
style={{ color: theme.blue }}
onPress={() => {
analytics('timeline_remote_header_press')
dispatch(updatePublicRemoteNotice(1))
navigation.navigate('Screen-Me', {
screen: 'Screen-Me-Root',
params: { navigateAway: 'Screen-Me-Settings-UpdateRemote' }
})
}}
>
{t('header.button')}
<Icon
name='ArrowRight'
size={StyleConstants.Font.Size.S}
color={theme.blue}
/>
</Text>
</Text>
</View>
)
},
() => true
)
const styles = StyleSheet.create({
base: {
margin: StyleConstants.Spacing.Global.PagePadding,
paddingHorizontal: StyleConstants.Spacing.M,
paddingVertical: StyleConstants.Spacing.S,
borderWidth: 1,
borderRadius: 6
},
text: {
...StyleConstants.FontStyle.S
}
})
export default TimelineHeader

View File

@ -29,7 +29,7 @@ const TimelineNotifications: React.FC<Props> = ({
}) => { }) => {
const localAccount = useSelector(getLocalAccount) const localAccount = useSelector(getLocalAccount)
const navigation = useNavigation< const navigation = useNavigation<
StackNavigationProp<Nav.LocalStackParamList> StackNavigationProp<Nav.TabLocalStackParamList>
>() >()
const actualAccount = notification.status const actualAccount = notification.status
? notification.status.account ? notification.status.account
@ -38,7 +38,7 @@ const TimelineNotifications: React.FC<Props> = ({
const onPress = useCallback(() => { const onPress = useCallback(() => {
analytics('timeline_notification_press') analytics('timeline_notification_press')
notification.status && notification.status &&
navigation.push('Screen-Shared-Toot', { navigation.push('Tab-Shared-Toot', {
toot: notification.status toot: notification.status
}) })
}, []) }, [])

View File

@ -23,7 +23,7 @@ const TimelineActioned: React.FC<Props> = ({
const { t } = useTranslation('componentTimeline') const { t } = useTranslation('componentTimeline')
const { theme } = useTheme() const { theme } = useTheme()
const navigation = useNavigation< const navigation = useNavigation<
StackNavigationProp<Nav.LocalStackParamList> StackNavigationProp<Nav.TabLocalStackParamList>
>() >()
const name = account.display_name || account.username const name = account.display_name || account.username
const iconColor = theme.primary const iconColor = theme.primary
@ -34,7 +34,7 @@ const TimelineActioned: React.FC<Props> = ({
const onPress = useCallback(() => { const onPress = useCallback(() => {
analytics('timeline_shared_actioned_press', { action }) analytics('timeline_shared_actioned_press', { action })
navigation.push('Screen-Shared-Account', { account }) navigation.push('Tab-Shared-Account', { account })
}, []) }, [])
const children = useMemo(() => { const children = useMemo(() => {

View File

@ -104,7 +104,7 @@ const TimelineActions: React.FC<Props> = ({
page: queryKey[1].page, page: queryKey[1].page,
count: status.replies_count count: status.replies_count
}) })
navigation.navigate('Screen-Shared-Compose', { navigation.navigate('Screen-Compose', {
type: 'reply', type: 'reply',
incomingStatus: status, incomingStatus: status,
accts, accts,

View File

@ -40,7 +40,7 @@ const TimelineAttachment: React.FC<Props> = ({ status }) => {
})[] = [] })[] = []
const navigation = useNavigation() const navigation = useNavigation()
const navigateToImagesViewer = (imageIndex: number) => const navigateToImagesViewer = (imageIndex: number) =>
navigation.navigate('Screen-Shared-ImagesViewer', { navigation.navigate('Screen-ImagesViewer', {
imageUrls, imageUrls,
imageIndex imageIndex
}) })

View File

@ -33,6 +33,7 @@ const AttachmentImage: React.FC<Props> = ({
original: image.url, original: image.url,
remote: image.remote_url remote: image.remote_url
}} }}
sharedElement={image.url}
blurhash={image.blurhash} blurhash={image.blurhash}
onPress={onPress} onPress={onPress}
style={[ style={[

View File

@ -13,12 +13,14 @@ export interface Props {
const TimelineAvatar: React.FC<Props> = ({ queryKey, account }) => { const TimelineAvatar: React.FC<Props> = ({ queryKey, account }) => {
const navigation = useNavigation< const navigation = useNavigation<
StackNavigationProp<Nav.LocalStackParamList> StackNavigationProp<Nav.TabLocalStackParamList>
>() >()
// Need to fix go back root // Need to fix go back root
const onPress = useCallback(() => { const onPress = useCallback(() => {
analytics('timeline_shared_avatar_press', { page: queryKey[1].page }) analytics('timeline_shared_avatar_press', {
queryKey && navigation.push('Screen-Shared-Account', { account }) page: queryKey && queryKey[1].page
})
queryKey && navigation.push('Tab-Shared-Account', { account })
}, []) }, [])
return ( return (

View File

@ -1,5 +1,5 @@
import analytics from '@components/analytics' import analytics from '@components/analytics'
import BottomSheet from '@components/BottomSheet' import BottomSheet from '@screens/Tabs/Shared/node_modules/@screens/Actions/BottomSheet'
import Icon from '@components/Icon' import Icon from '@components/Icon'
import { QueryKeyTimeline } from '@utils/queryHooks/timeline' import { QueryKeyTimeline } from '@utils/queryHooks/timeline'
import { getLocalAccount, getLocalUrl } from '@utils/slices/instancesSlice' import { getLocalAccount, getLocalUrl } from '@utils/slices/instancesSlice'

View File

@ -106,7 +106,7 @@ const HeaderActionsStatus: React.FC<Props> = ({
id: status.id id: status.id
}) })
if (res.id) { if (res.id) {
navigation.navigate('Screen-Shared-Compose', { navigation.navigate('Screen-Compose', {
type: 'edit', type: 'edit',
incomingStatus: res, incomingStatus: res,
queryKey queryKey

View File

@ -1,13 +1,15 @@
import { StyleConstants } from '@utils/styles/constants' import { StyleConstants } from '@utils/styles/constants'
import React from 'react' import React from 'react'
import { StyleSheet, View } from 'react-native' import { Pressable, StyleSheet, View } from 'react-native'
import HeaderSharedAccount from './HeaderShared/Account' import HeaderSharedAccount from './HeaderShared/Account'
import HeaderSharedApplication from './HeaderShared/Application' import HeaderSharedApplication from './HeaderShared/Application'
import HeaderSharedCreated from './HeaderShared/Created' import HeaderSharedCreated from './HeaderShared/Created'
import HeaderSharedVisibility from './HeaderShared/Visibility' import HeaderSharedVisibility from './HeaderShared/Visibility'
import { QueryKeyTimeline } from '@utils/queryHooks/timeline' import { QueryKeyTimeline } from '@utils/queryHooks/timeline'
import HeaderSharedMuted from './HeaderShared/Muted' import HeaderSharedMuted from './HeaderShared/Muted'
import HeaderActions from './HeaderActions/Root' import { useNavigation } from '@react-navigation/native'
import Icon from '@components/Icon'
import { useTheme } from '@utils/styles/ThemeManager'
export interface Props { export interface Props {
queryKey?: QueryKeyTimeline queryKey?: QueryKeyTimeline
@ -15,6 +17,9 @@ export interface Props {
} }
const TimelineHeaderDefault: React.FC<Props> = ({ queryKey, status }) => { const TimelineHeaderDefault: React.FC<Props> = ({ queryKey, status }) => {
const navigation = useNavigation()
const { theme } = useTheme()
return ( return (
<View style={styles.base}> <View style={styles.base}>
<View style={styles.accountAndMeta}> <View style={styles.accountAndMeta}>
@ -28,11 +33,28 @@ const TimelineHeaderDefault: React.FC<Props> = ({ queryKey, status }) => {
</View> </View>
{queryKey ? ( {queryKey ? (
<HeaderActions <Pressable
queryKey={queryKey} style={{
status={status} flex: 1,
url={status.url || status.uri} flexDirection: 'row',
type='status' justifyContent: 'center',
paddingBottom: StyleConstants.Spacing.S
}}
onPress={() =>
navigation.navigate('Screen-Actions', {
queryKey,
status,
url: status.url || status.uri,
type: 'status'
})
}
children={
<Icon
name='MoreHorizontal'
color={theme.secondary}
size={StyleConstants.Font.Size.L}
/>
}
/> />
) : null} ) : null}
</View> </View>

View File

@ -8,8 +8,8 @@ import HeaderSharedCreated from './HeaderShared/Created'
import HeaderSharedVisibility from './HeaderShared/Visibility' import HeaderSharedVisibility from './HeaderShared/Visibility'
import RelationshipIncoming from '@root/components/Relationship/Incoming' import RelationshipIncoming from '@root/components/Relationship/Incoming'
import HeaderSharedMuted from './HeaderShared/Muted' import HeaderSharedMuted from './HeaderShared/Muted'
import HeaderActions from './HeaderActions/Root'
import { QueryKeyTimeline } from '@utils/queryHooks/timeline' import { QueryKeyTimeline } from '@utils/queryHooks/timeline'
import ScreenActions from '@screens/Actions'
export interface Props { export interface Props {
queryKey: QueryKeyTimeline queryKey: QueryKeyTimeline
@ -28,7 +28,7 @@ const TimelineHeaderNotification: React.FC<Props> = ({
return <RelationshipIncoming id={notification.account.id} /> return <RelationshipIncoming id={notification.account.id} />
default: default:
return notification.status ? ( return notification.status ? (
<HeaderActions queryKey={queryKey} status={notification.status} /> <ScreenActions queryKey={queryKey} status={notification.status} />
) : null ) : null
} }
}, [notification.type]) }, [notification.type])

View File

@ -18,6 +18,7 @@ export default {
sharedAccount: require('./screens/sharedAccount').default, sharedAccount: require('./screens/sharedAccount').default,
sharedAnnouncements: require('./screens/sharedAnnouncements').default, sharedAnnouncements: require('./screens/sharedAnnouncements').default,
sharedAttachments: require('./screens/sharedAttachments').default,
sharedCompose: require('./screens/sharedCompose').default, sharedCompose: require('./screens/sharedCompose').default,
sharedRelationships: require('./screens/sharedRelationships').default, sharedRelationships: require('./screens/sharedRelationships').default,
sharedSearch: require('./screens/sharedSearch').default, sharedSearch: require('./screens/sharedSearch').default,

View File

@ -0,0 +1,3 @@
export default {
heading: '<0 />\'s <1>media</1>'
}

View File

@ -18,6 +18,7 @@ export default {
sharedAccount: require('./screens/sharedAccount').default, sharedAccount: require('./screens/sharedAccount').default,
sharedAnnouncements: require('./screens/sharedAnnouncements').default, sharedAnnouncements: require('./screens/sharedAnnouncements').default,
sharedAttachments: require('./screens/sharedAttachments').default,
sharedCompose: require('./screens/sharedCompose').default, sharedCompose: require('./screens/sharedCompose').default,
sharedRelationships: require('./screens/sharedRelationships').default, sharedRelationships: require('./screens/sharedRelationships').default,
sharedSearch: require('./screens/sharedSearch').default, sharedSearch: require('./screens/sharedSearch').default,

View File

@ -0,0 +1,3 @@
export default {
heading: '<0 /> <1>的媒体</1>'
}

175
src/screens/Actions.tsx Normal file
View File

@ -0,0 +1,175 @@
import { StackScreenProps } from '@react-navigation/stack'
import { getLocalAccount, getLocalUrl } from '@utils/slices/instancesSlice'
import { StyleConstants } from '@utils/styles/constants'
import { useTheme } from '@utils/styles/ThemeManager'
import React, { useCallback, useEffect } from 'react'
import { Dimensions, StyleSheet, View } from 'react-native'
import {
PanGestureHandler,
State,
TapGestureHandler
} from 'react-native-gesture-handler'
import Animated, {
Extrapolate,
interpolate,
runOnJS,
useAnimatedGestureHandler,
useAnimatedStyle,
useSharedValue,
withTiming
} from 'react-native-reanimated'
import { useSafeAreaInsets } from 'react-native-safe-area-context'
import { useSelector } from 'react-redux'
import ActionsAccount from './Actions/Account'
import ActionsDomain from './Actions/Domain'
import ActionsShare from './Actions/Share'
import ActionsStatus from './Actions/Status'
export type ScreenAccountProp = StackScreenProps<
Nav.RootStackParamList,
'Screen-Actions'
>
const ScreenActions = React.memo(
({
route: {
params: { queryKey, status, url, type }
},
navigation
}: ScreenAccountProp) => {
const localAccount = useSelector(getLocalAccount)
const sameAccount = localAccount?.id === status.account.id
const localDomain = useSelector(getLocalUrl)
const statusDomain = status.uri
? status.uri.split(new RegExp(/\/\/(.*?)\//))[1]
: ''
const sameDomain = localDomain === statusDomain
const { theme } = useTheme()
const insets = useSafeAreaInsets()
const DEFAULT_VALUE = 350
const screenHeight = Dimensions.get('screen').height
const panY = useSharedValue(DEFAULT_VALUE)
useEffect(() => {
panY.value = withTiming(0)
}, [])
const styleTop = useAnimatedStyle(() => {
return {
bottom: interpolate(
panY.value,
[0, screenHeight],
[0, -screenHeight],
Extrapolate.CLAMP
)
}
})
const dismiss = useCallback(() => {
panY.value = withTiming(DEFAULT_VALUE)
navigation.goBack()
}, [])
const onGestureEvent = useAnimatedGestureHandler({
onActive: ({ translationY }) => {
panY.value = translationY
},
onEnd: ({ velocityY }) => {
if (velocityY > 500) {
runOnJS(dismiss)()
} else {
panY.value = withTiming(0)
}
}
})
return (
<Animated.View style={{ flex: 1 }}>
<TapGestureHandler
onHandlerStateChange={({ nativeEvent }) => {
if (nativeEvent.state === State.ACTIVE) {
dismiss()
}
}}
>
<Animated.View
style={[
styles.overlay,
{ backgroundColor: theme.backgroundOverlay }
]}
>
<PanGestureHandler onGestureEvent={onGestureEvent}>
<Animated.View
style={[
styles.container,
styleTop,
{
backgroundColor: theme.background,
paddingBottom: insets.bottom || StyleConstants.Spacing.L
}
]}
>
<View
style={[
styles.handle,
{ backgroundColor: theme.primaryOverlay }
]}
/>
{!sameAccount && (
<ActionsAccount
queryKey={queryKey}
account={status.account}
dismiss={dismiss}
/>
)}
{sameAccount && (
<ActionsStatus
navigation={navigation}
queryKey={queryKey}
status={status}
dismiss={dismiss}
/>
)}
{!sameDomain && (
<ActionsDomain
queryKey={queryKey}
domain={statusDomain}
dismiss={dismiss}
/>
)}
{url && type ? (
<ActionsShare url={url} type={type} dismiss={dismiss} />
) : null}
</Animated.View>
</PanGestureHandler>
</Animated.View>
</TapGestureHandler>
</Animated.View>
)
},
() => true
)
const styles = StyleSheet.create({
overlay: {
flex: 1,
justifyContent: 'flex-end'
},
container: {
paddingTop: StyleConstants.Spacing.M
},
handle: {
alignSelf: 'center',
width: StyleConstants.Spacing.S * 8,
height: StyleConstants.Spacing.S / 2,
borderRadius: 100,
top: -StyleConstants.Spacing.M * 2
},
button: {
marginHorizontal: StyleConstants.Spacing.Global.PagePadding * 2
}
})
export default ScreenActions

View File

@ -0,0 +1,126 @@
import analytics from '@components/analytics'
import haptics from '@components/haptics'
import { MenuContainer, MenuHeader, MenuRow } from '@components/Menu'
import { toast } from '@components/toast'
import {
MutationVarsTimelineUpdateAccountProperty,
QueryKeyTimeline,
useTimelineMutation
} from '@utils/queryHooks/timeline'
import React from 'react'
import { useTranslation } from 'react-i18next'
import { useQueryClient } from 'react-query'
export interface Props {
queryKey?: QueryKeyTimeline
account: Mastodon.Account
dismiss: () => void
}
const ActionsAccount: React.FC<Props> = ({ queryKey, account, dismiss }) => {
const { t } = useTranslation('componentTimeline')
const queryClient = useQueryClient()
const mutateion = useTimelineMutation({
queryClient,
onSuccess: (_, params) => {
const theParams = params as MutationVarsTimelineUpdateAccountProperty
haptics('Success')
toast({
type: 'success',
message: t('common:toastMessage.success.message', {
function: t(
`shared.header.actions.account.${theParams.payload.property}.function`,
{
acct: account.acct
}
)
})
})
},
onError: (err: any, params) => {
const theParams = params as MutationVarsTimelineUpdateAccountProperty
haptics('Error')
toast({
type: 'error',
message: t('common:toastMessage.error.message', {
function: t(
`shared.header.actions.account.${theParams.payload.property}.function`
)
}),
...(err.status &&
typeof err.status === 'number' &&
err.data &&
err.data.error &&
typeof err.data.error === 'string' && {
description: err.data.error
})
})
},
onSettled: () => {
queryKey && queryClient.invalidateQueries(queryKey)
}
})
return (
<MenuContainer>
<MenuHeader heading={t('shared.header.actions.account.heading')} />
<MenuRow
onPress={() => {
analytics('timeline_shared_headeractions_account_mute_press', {
page: queryKey && queryKey[1].page
})
dismiss()
mutateion.mutate({
type: 'updateAccountProperty',
queryKey,
id: account.id,
payload: { property: 'mute' }
})
}}
iconFront='EyeOff'
title={t('shared.header.actions.account.mute.button', {
acct: account.acct
})}
/>
<MenuRow
onPress={() => {
analytics('timeline_shared_headeractions_account_block_press', {
page: queryKey && queryKey[1].page
})
dismiss()
mutateion.mutate({
type: 'updateAccountProperty',
queryKey,
id: account.id,
payload: { property: 'block' }
})
}}
iconFront='XCircle'
title={t('shared.header.actions.account.block.button', {
acct: account.acct
})}
/>
<MenuRow
onPress={() => {
analytics('timeline_shared_headeractions_account_reports_press', {
page: queryKey && queryKey[1].page
})
dismiss()
mutateion.mutate({
type: 'updateAccountProperty',
queryKey,
id: account.id,
payload: { property: 'reports' }
})
}}
iconFront='Flag'
title={t('shared.header.actions.account.reports.button', {
acct: account.acct
})}
/>
</MenuContainer>
)
}
export default ActionsAccount

View File

@ -0,0 +1,83 @@
import analytics from '@components/analytics'
import MenuContainer from '@components/Menu/Container'
import MenuHeader from '@components/Menu/Header'
import MenuRow from '@components/Menu/Row'
import { toast } from '@components/toast'
import {
QueryKeyTimeline,
useTimelineMutation
} from '@utils/queryHooks/timeline'
import React from 'react'
import { useTranslation } from 'react-i18next'
import { Alert } from 'react-native'
import { useQueryClient } from 'react-query'
export interface Props {
queryKey: QueryKeyTimeline
domain: string
dismiss: () => void
}
const ActionsDomain: React.FC<Props> = ({ queryKey, domain, dismiss }) => {
const { t } = useTranslation('componentTimeline')
const queryClient = useQueryClient()
const mutation = useTimelineMutation({
queryClient,
onSettled: () => {
toast({
type: 'success',
message: t('common:toastMessage.success.message', {
function: t(`shared.header.actions.domain.block.function`)
})
})
queryClient.invalidateQueries(queryKey)
}
})
return (
<MenuContainer>
<MenuHeader heading={t(`shared.header.actions.domain.heading`)} />
<MenuRow
onPress={() => {
analytics('timeline_shared_headeractions_domain_block_press', {
page: queryKey[1].page
})
Alert.alert(
t('shared.header.actions.domain.alert.title', { domain }),
t('shared.header.actions.domain.alert.message'),
[
{
text: t('shared.header.actions.domain.alert.buttons.cancel'),
style: 'cancel'
},
{
text: t('shared.header.actions.domain.alert.buttons.confirm'),
style: 'destructive',
onPress: () => {
analytics(
'timeline_shared_headeractions_domain_block_confirm',
{
page: queryKey && queryKey[1].page
}
)
dismiss()
mutation.mutate({
type: 'domainBlock',
queryKey,
domain: domain
})
}
}
]
)
}}
iconFront='CloudOff'
title={t(`shared.header.actions.domain.block.button`, {
domain
})}
/>
</MenuContainer>
)
}
export default ActionsDomain

View File

@ -0,0 +1,45 @@
import analytics from '@components/analytics'
import MenuContainer from '@components/Menu/Container'
import MenuHeader from '@components/Menu/Header'
import MenuRow from '@components/Menu/Row'
import React from 'react'
import { useTranslation } from 'react-i18next'
import { Platform, Share } from 'react-native'
export interface Props {
type: 'status' | 'account'
url: string
dismiss: () => void
}
const ActionsShare: React.FC<Props> = ({ type, url, dismiss }) => {
const { t } = useTranslation('componentTimeline')
return (
<MenuContainer>
<MenuHeader heading={t(`shared.header.actions.share.${type}.heading`)} />
<MenuRow
iconFront='Share2'
title={t(`shared.header.actions.share.${type}.button`)}
onPress={async () => {
analytics('timeline_shared_headeractions_share_press')
switch (Platform.OS) {
case 'ios':
await Share.share({
url
})
break
case 'android':
await Share.share({
message: url
})
break
}
dismiss()
}}
/>
</MenuContainer>
)
}
export default ActionsShare

View File

@ -0,0 +1,181 @@
import React from 'react'
import { useTranslation } from 'react-i18next'
import { Alert } from 'react-native'
import { useQueryClient } from 'react-query'
import { MenuContainer, MenuHeader, MenuRow } from '@components/Menu'
import { toast } from '@components/toast'
import {
MutationVarsTimelineUpdateStatusProperty,
QueryKeyTimeline,
useTimelineMutation
} from '@utils/queryHooks/timeline'
import analytics from '@components/analytics'
import { StackNavigationProp } from '@react-navigation/stack'
export interface Props {
navigation: StackNavigationProp<Nav.RootStackParamList, 'Screen-Actions'>
queryKey: QueryKeyTimeline
status: Mastodon.Status
dismiss: () => void
}
const ActionsStatus: React.FC<Props> = ({
navigation,
queryKey,
status,
dismiss
}) => {
const { t } = useTranslation('componentTimeline')
const queryClient = useQueryClient()
const mutation = useTimelineMutation({
queryClient,
onMutate: true,
onError: (err: any, params, oldData) => {
const theFunction = (params as MutationVarsTimelineUpdateStatusProperty)
.payload
? (params as MutationVarsTimelineUpdateStatusProperty).payload.property
: 'delete'
toast({
type: 'error',
message: t('common:toastMessage.error.message', {
function: t(`shared.header.actions.status.${theFunction}.function`)
}),
...(err.status &&
typeof err.status === 'number' &&
err.data &&
err.data.error &&
typeof err.data.error === 'string' && {
description: err.data.error
})
})
queryClient.setQueryData(queryKey, oldData)
}
})
return (
<MenuContainer>
<MenuHeader heading={t('shared.header.actions.status.heading')} />
<MenuRow
onPress={() => {
analytics('timeline_shared_headeractions_status_delete_press', {
page: queryKey && queryKey[1].page
})
dismiss()
mutation.mutate({
type: 'deleteItem',
source: 'statuses',
queryKey,
id: status.id
})
}}
iconFront='Trash'
title={t('shared.header.actions.status.delete.button')}
/>
<MenuRow
onPress={() => {
analytics('timeline_shared_headeractions_status_deleteedit_press', {
page: queryKey && queryKey[1].page
})
Alert.alert(
t('shared.header.actions.status.edit.alert.title'),
t('shared.header.actions.status.edit.alert.message'),
[
{
text: t(
'shared.header.actions.status.edit.alert.buttons.cancel'
),
style: 'cancel'
},
{
text: t(
'shared.header.actions.status.edit.alert.buttons.confirm'
),
style: 'destructive',
onPress: async () => {
analytics(
'timeline_shared_headeractions_status_deleteedit_confirm',
{
page: queryKey && queryKey[1].page
}
)
dismiss()
const res = (await mutation.mutateAsync({
type: 'deleteItem',
source: 'statuses',
queryKey,
id: status.id
})) as Mastodon.Status
if (res.id) {
navigation.navigate('Screen-Compose', {
type: 'edit',
incomingStatus: res,
queryKey
})
}
}
}
]
)
}}
iconFront='Edit'
title={t('shared.header.actions.status.edit.button')}
/>
<MenuRow
onPress={() => {
analytics('timeline_shared_headeractions_status_mute_press', {
page: queryKey && queryKey[1].page
})
dismiss()
mutation.mutate({
type: 'updateStatusProperty',
queryKey,
id: status.id,
payload: {
property: 'muted',
currentValue: status.muted,
propertyCount: undefined,
countValue: undefined
}
})
}}
iconFront='VolumeX'
title={
status.muted
? t('shared.header.actions.status.mute.button.negative')
: t('shared.header.actions.status.mute.button.positive')
}
/>
{/* Also note that reblogs cannot be pinned. */}
{(status.visibility === 'public' || status.visibility === 'unlisted') && (
<MenuRow
onPress={() => {
analytics('timeline_shared_headeractions_status_pin_press', {
page: queryKey && queryKey[1].page
})
dismiss()
mutation.mutate({
type: 'updateStatusProperty',
queryKey,
id: status.id,
payload: {
property: 'pinned',
currentValue: status.pinned,
propertyCount: undefined,
countValue: undefined
}
})
}}
iconFront='Anchor'
title={
status.pinned
? t('shared.header.actions.status.pin.button.negative')
: t('shared.header.actions.status.pin.button.positive')
}
/>
)}
</MenuContainer>
)
}
export default ActionsStatus

View File

@ -3,7 +3,8 @@ import Button from '@components/Button'
import haptics from '@components/haptics' import haptics from '@components/haptics'
import { ParseHTML } from '@components/Parse' import { ParseHTML } from '@components/Parse'
import RelativeTime from '@components/RelativeTime' import RelativeTime from '@components/RelativeTime'
import { useBottomTabBarHeight } from '@react-navigation/bottom-tabs' import { BlurView } from '@react-native-community/blur'
import { StackScreenProps } from '@react-navigation/stack'
import { import {
useAnnouncementMutation, useAnnouncementMutation,
useAnnouncementQuery useAnnouncementQuery
@ -23,16 +24,19 @@ import {
import { Chase } from 'react-native-animated-spinkit' import { Chase } from 'react-native-animated-spinkit'
import { FlatList, ScrollView } from 'react-native-gesture-handler' import { FlatList, ScrollView } from 'react-native-gesture-handler'
import { SafeAreaView } from 'react-native-safe-area-context' import { SafeAreaView } from 'react-native-safe-area-context'
import { SharedAnnouncementsProp } from './sharedScreens'
const ScreenSharedAnnouncements: React.FC<SharedAnnouncementsProp> = ({ export type ScreenAnnouncementsProp = StackScreenProps<
Nav.RootStackParamList,
'Screen-Announcements'
>
const ScreenAnnouncements: React.FC<ScreenAnnouncementsProp> = ({
route: { route: {
params: { showAll = false } params: { showAll = false }
}, },
navigation navigation
}) => { }) => {
const { theme } = useTheme() const { mode, theme } = useTheme()
const bottomTabBarHeight = useBottomTabBarHeight()
const [index, setIndex] = useState(0) const [index, setIndex] = useState(0)
const { t } = useTranslation('sharedAnnouncements') const { t } = useTranslation('sharedAnnouncements')
@ -60,13 +64,7 @@ const ScreenSharedAnnouncements: React.FC<SharedAnnouncementsProp> = ({
const renderItem = useCallback( const renderItem = useCallback(
({ item, index }: { item: Mastodon.Announcement; index: number }) => ( ({ item, index }: { item: Mastodon.Announcement; index: number }) => (
<View <View key={index} style={styles.announcementContainer}>
key={index}
style={[
styles.announcementContainer,
{ backgroundColor: theme.background }
]}
>
<Pressable <Pressable
style={styles.pressable} style={styles.pressable}
onPress={() => navigation.goBack()} onPress={() => navigation.goBack()}
@ -199,40 +197,44 @@ const ScreenSharedAnnouncements: React.FC<SharedAnnouncementsProp> = ({
}, []) }, [])
return ( return (
<SafeAreaView style={[styles.base, { backgroundColor: theme.background }]}> <BlurView
<View style={[styles.header, { height: bottomTabBarHeight }]}> blurType={mode}
<Text style={[styles.headerText, { color: theme.primary }]}></Text> blurAmount={20}
</View> style={styles.base}
<FlatList reducedTransparencyFallbackColor={theme.background}
horizontal >
data={query.data} <SafeAreaView style={styles.base}>
pagingEnabled <FlatList
renderItem={renderItem} horizontal
showsHorizontalScrollIndicator={false} data={query.data}
onMomentumScrollEnd={onMomentumScrollEnd} pagingEnabled
ListEmptyComponent={ListEmptyComponent} renderItem={renderItem}
/> showsHorizontalScrollIndicator={false}
<View style={[styles.indicators, { height: bottomTabBarHeight }]}> onMomentumScrollEnd={onMomentumScrollEnd}
{query.data && query.data.length > 1 ? ( ListEmptyComponent={ListEmptyComponent}
<> />
{query.data.map((d, i) => ( <View style={[styles.indicators]}>
<View {query.data && query.data.length > 1 ? (
key={i} <>
style={[ {query.data.map((d, i) => (
styles.indicator, <View
{ key={i}
borderColor: theme.primary, style={[
backgroundColor: i === index ? theme.primary : undefined, styles.indicator,
marginLeft: {
i === query.data.length ? 0 : StyleConstants.Spacing.S borderColor: theme.primary,
} backgroundColor: i === index ? theme.primary : undefined,
]} marginLeft:
/> i === query.data.length ? 0 : StyleConstants.Spacing.S
))} }
</> ]}
) : null} />
</View> ))}
</SafeAreaView> </>
) : null}
</View>
</SafeAreaView>
</BlurView>
) )
} }
@ -241,14 +243,6 @@ const styles = StyleSheet.create({
flex: 1 flex: 1
}, },
invisibleTextInput: { ...StyleSheet.absoluteFillObject }, invisibleTextInput: { ...StyleSheet.absoluteFillObject },
header: {
justifyContent: 'center',
alignItems: 'center'
},
headerText: {
...StyleConstants.FontStyle.L,
fontWeight: StyleConstants.Font.Weight.Bold
},
announcementContainer: { announcementContainer: {
width: Dimensions.get('screen').width, width: Dimensions.get('screen').width,
padding: StyleConstants.Spacing.Global.PagePadding, padding: StyleConstants.Spacing.Global.PagePadding,
@ -297,7 +291,8 @@ const styles = StyleSheet.create({
indicators: { indicators: {
flexDirection: 'row', flexDirection: 'row',
justifyContent: 'center', justifyContent: 'center',
alignItems: 'center' alignItems: 'center',
minHeight: 49
}, },
indicator: { indicator: {
width: StyleConstants.Spacing.S, width: StyleConstants.Spacing.S,
@ -307,4 +302,4 @@ const styles = StyleSheet.create({
} }
}) })
export default ScreenSharedAnnouncements export default ScreenAnnouncements

View File

@ -1,9 +1,10 @@
import analytics from '@components/analytics' import analytics from '@components/analytics'
import { HeaderLeft, HeaderRight } from '@components/Header' import { HeaderLeft, HeaderRight } from '@components/Header'
import { StackScreenProps } from '@react-navigation/stack'
import haptics from '@root/components/haptics' import haptics from '@root/components/haptics'
import { store } from '@root/store' import { store } from '@root/store'
import formatText from '@screens/Shared/Compose/formatText' import formatText from '@screens/Compose/formatText'
import ComposeRoot from '@screens/Shared/Compose/Root' import ComposeRoot from '@screens/Compose/Root'
import { QueryKeyTimeline } from '@utils/queryHooks/timeline' import { QueryKeyTimeline } from '@utils/queryHooks/timeline'
import { updateStoreReview } from '@utils/slices/contextsSlice' import { updateStoreReview } from '@utils/slices/contextsSlice'
import { getLocalAccount } from '@utils/slices/instancesSlice' import { getLocalAccount } from '@utils/slices/instancesSlice'
@ -30,11 +31,15 @@ import composeInitialState from './Compose/utils/initialState'
import composeParseState from './Compose/utils/parseState' import composeParseState from './Compose/utils/parseState'
import composePost from './Compose/utils/post' import composePost from './Compose/utils/post'
import composeReducer from './Compose/utils/reducer' import composeReducer from './Compose/utils/reducer'
import { SharedComposeProp } from './sharedScreens'
export type ScreenComposeProp = StackScreenProps<
Nav.RootStackParamList,
'Screen-Compose'
>
const Stack = createNativeStackNavigator() const Stack = createNativeStackNavigator()
const Compose: React.FC<SharedComposeProp> = ({ const ScreenCompose: React.FC<ScreenComposeProp> = ({
route: { params }, route: { params },
navigation navigation
}) => { }) => {
@ -224,22 +229,22 @@ const Compose: React.FC<SharedComposeProp> = ({
return ( return (
<KeyboardAvoidingView <KeyboardAvoidingView
style={styles.base}
behavior={Platform.OS === 'ios' ? 'padding' : 'height'} behavior={Platform.OS === 'ios' ? 'padding' : 'height'}
style={{ flex: 1 }}
> >
<SafeAreaView <SafeAreaView
style={{ flex: 1 }} style={styles.base}
edges={hasKeyboard ? ['left', 'right'] : ['left', 'right', 'bottom']} edges={hasKeyboard ? ['top'] : ['top', 'bottom']}
> >
<ComposeContext.Provider value={{ composeState, composeDispatch }}> <ComposeContext.Provider value={{ composeState, composeDispatch }}>
<Stack.Navigator screenOptions={{ headerTopInsetEnabled: false }}> <Stack.Navigator screenOptions={{ headerTopInsetEnabled: false }}>
<Stack.Screen <Stack.Screen
name='Screen-Shared-Compose-Root' name='Tab-Compose-Root'
component={ComposeRoot} component={ComposeRoot}
options={{ headerLeft, headerCenter, headerRight }} options={{ headerLeft, headerCenter, headerRight }}
/> />
<Stack.Screen <Stack.Screen
name='Screen-Shared-Compose-EditAttachment' name='Tab-Compose-EditAttachment'
component={ComposeEditAttachment} component={ComposeEditAttachment}
options={{ stackPresentation: 'modal', headerShown: false }} options={{ stackPresentation: 'modal', headerShown: false }}
/> />
@ -251,10 +256,11 @@ const Compose: React.FC<SharedComposeProp> = ({
} }
const styles = StyleSheet.create({ const styles = StyleSheet.create({
base: { flex: 1 },
count: { count: {
textAlign: 'center', textAlign: 'center',
...StyleConstants.FontStyle.M ...StyleConstants.FontStyle.M
} }
}) })
export default Compose export default ScreenCompose

View File

@ -168,7 +168,7 @@ const ComposeEditAttachment: React.FC<Props> = ({
<SafeAreaView style={{ flex: 1 }} edges={['left', 'right', 'bottom']}> <SafeAreaView style={{ flex: 1 }} edges={['left', 'right', 'bottom']}>
<Stack.Navigator screenOptions={{ headerTopInsetEnabled: false }}> <Stack.Navigator screenOptions={{ headerTopInsetEnabled: false }}>
<Stack.Screen <Stack.Screen
name='Screen-Shared-Compose-EditAttachment-Root' name='Screen-Compose-EditAttachment-Root'
children={children} children={children}
options={{ headerLeft, headerRight, headerCenter: () => null }} options={{ headerLeft, headerRight, headerCenter: () => null }}
/> />

View File

@ -1,9 +1,9 @@
import React, { useContext } from 'react' import React, { useContext } from 'react'
import ComposeAttachments from '@screens/Shared/Compose/Root/Footer/Attachments' import ComposeAttachments from '@screens/Compose/Root/Footer/Attachments'
import ComposeEmojis from '@screens/Shared/Compose/Root/Footer/Emojis' import ComposeEmojis from '@screens/Compose/Root/Footer/Emojis'
import ComposePoll from '@screens/Shared/Compose/Root/Footer/Poll' import ComposePoll from '@screens/Compose/Root/Footer/Poll'
import ComposeReply from '@screens/Shared/Compose/Root/Footer/Reply' import ComposeReply from '@screens/Compose/Root/Footer/Reply'
import ComposeContext from '@screens/Shared/Compose//utils/createContext' import ComposeContext from '@screens/Compose/utils/createContext'
const ComposeRootFooter: React.FC = () => { const ComposeRootFooter: React.FC = () => {
const { composeState } = useContext(ComposeContext) const { composeState } = useContext(ComposeContext)

View File

@ -177,7 +177,7 @@ const ComposeAttachments: React.FC = () => {
overlay overlay
onPress={() => { onPress={() => {
analytics('compose_attachment_edit') analytics('compose_attachment_edit')
navigation.navigate('Screen-Shared-Compose-EditAttachment', { navigation.navigate('Screen-Compose-EditAttachment', {
index index
}) })
}} }}

View File

@ -4,7 +4,7 @@ import composeInitialState from './initialState'
import { ComposeState } from './types' import { ComposeState } from './types'
const composeParseState = ( const composeParseState = (
params: NonNullable<Nav.SharedStackParamList['Screen-Shared-Compose']> params: NonNullable<Nav.SharedStackParamList['Screen-Compose']>
): ComposeState => { ): ComposeState => {
switch (params.type) { switch (params.type) {
case 'edit': case 'edit':

View File

@ -1,6 +1,6 @@
import client from '@root/api/client' import client from '@root/api/client'
import { ComposeState } from '@screens/Shared/Compose/utils/types' import { ComposeState } from '@screens/Compose/utils/types'
import { SharedComposeProp } from '@screens/Shared/sharedScreens' import { SharedComposeProp } from '@screens/Tabs/Shared/sharedScreens'
import * as Crypto from 'expo-crypto' import * as Crypto from 'expo-crypto'
const composePost = async ( const composePost = async (

View File

@ -0,0 +1,93 @@
import analytics from '@components/analytics'
import { HeaderRight } from '@components/Header'
import { StackScreenProps } from '@react-navigation/stack'
import { StyleConstants } from '@utils/styles/constants'
import { useTheme } from '@utils/styles/ThemeManager'
import { findIndex } from 'lodash'
import React, { useCallback, useLayoutEffect, useState } from 'react'
import { Platform, Share, StyleSheet, Text } from 'react-native'
import FastImage from 'react-native-fast-image'
import ImageViewer from 'react-native-image-zoom-viewer'
import { SharedElement } from 'react-navigation-shared-element'
export type ScreenImagesViewerProp = StackScreenProps<
Nav.RootStackParamList,
'Screen-ImagesViewer'
>
const ScreenImagesViewer = React.memo(
({
route: {
params: { imageUrls, imageIndex }
},
navigation
}: ScreenImagesViewerProp) => {
const { theme } = useTheme()
const [currentIndex, setCurrentIndex] = useState(
findIndex(imageUrls, ['imageIndex', imageIndex])
)
const onPress = useCallback(() => {
analytics('imageviewer_share_press')
switch (Platform.OS) {
case 'ios':
return Share.share({ url: imageUrls[currentIndex].url })
case 'android':
return Share.share({ message: imageUrls[currentIndex].url })
}
}, [currentIndex])
useLayoutEffect(
() =>
navigation.setOptions({
headerTitle: () => (
<Text
style={[styles.headerCenter, { color: theme.primaryOverlay }]}
>
{currentIndex + 1} / {imageUrls.length}
</Text>
),
headerRight: () => (
<HeaderRight content='Share' native={false} onPress={onPress} />
)
}),
[currentIndex]
)
const renderImage = useCallback(
prop => (
<SharedElement id={`image.${imageUrls[imageIndex].url}`}>
<FastImage {...prop} resizeMode={'contain'} />
</SharedElement>
),
[]
)
return (
<ImageViewer
index={currentIndex}
imageUrls={imageUrls}
pageAnimateTime={250}
enableSwipeDown
useNativeDriver
swipeDownThreshold={100}
renderIndicator={() => <></>}
saveToLocalByLongPress={false}
onSwipeDown={() => navigation.goBack()}
style={{ flex: 1 }}
onChange={index => index !== undefined && setCurrentIndex(index)}
renderImage={renderImage}
/>
)
},
() => true
)
const styles = StyleSheet.create({
headerCenter: {
color: 'white',
...StyleConstants.FontStyle.M
}
})
export default ScreenImagesViewer

View File

@ -1,99 +0,0 @@
import analytics from '@components/analytics'
import { HeaderLeft, HeaderRight } from '@components/Header'
import { StyleConstants } from '@utils/styles/constants'
import { findIndex } from 'lodash'
import React, { useCallback, useState } from 'react'
import {
Image,
Platform,
Share,
StatusBar,
StyleSheet,
Text
} from 'react-native'
import ImageViewer from 'react-native-image-zoom-viewer'
import { useSafeAreaInsets } from 'react-native-safe-area-context'
import { createNativeStackNavigator } from 'react-native-screens/native-stack'
import { SharedImagesViewerProp } from './sharedScreens'
const Stack = createNativeStackNavigator()
const ScreenSharedImagesViewer: React.FC<SharedImagesViewerProp> = ({
route: {
params: { imageUrls, imageIndex }
},
navigation
}) => {
const safeAreaInsets = useSafeAreaInsets()
const initialIndex = findIndex(imageUrls, ['imageIndex', imageIndex])
const [currentIndex, setCurrentIndex] = useState(initialIndex)
const component = useCallback(
() => (
<>
<StatusBar barStyle='light-content' />
<ImageViewer
index={initialIndex}
imageUrls={imageUrls}
pageAnimateTime={250}
enableSwipeDown
useNativeDriver
swipeDownThreshold={100}
renderIndicator={() => <></>}
saveToLocalByLongPress={false}
onSwipeDown={() => navigation.goBack()}
style={{ flex: 1, marginBottom: 44 + safeAreaInsets.bottom }}
onChange={index => index !== undefined && setCurrentIndex(index)}
renderImage={prop => {
return <Image {...prop} resizeMode={'contain'} />
}}
/>
</>
),
[]
)
const onPress = useCallback(() => {
analytics('imageviewer_share_press')
switch (Platform.OS) {
case 'ios':
return Share.share({ url: imageUrls[currentIndex].url })
case 'android':
return Share.share({ message: imageUrls[currentIndex].url })
}
}, [currentIndex])
return (
<Stack.Navigator
screenOptions={{ headerHideShadow: true, headerTopInsetEnabled: false }}
>
<Stack.Screen
name='Screen-Shared-ImagesViewer-Root'
component={component}
options={{
contentStyle: { backgroundColor: 'black' },
headerStyle: { backgroundColor: 'black' },
headerLeft: () => (
<HeaderLeft content='X' onPress={() => navigation.goBack()} />
),
headerCenter: () => (
<Text style={styles.headerCenter}>
{currentIndex + 1} / {imageUrls.length}
</Text>
),
headerRight: () => <HeaderRight content='Share' onPress={onPress} />
}}
/>
</Stack.Navigator>
)
}
const styles = StyleSheet.create({
headerCenter: {
color: 'white',
...StyleConstants.FontStyle.M
}
})
export default React.memo(ScreenSharedImagesViewer, () => true)

View File

@ -1,73 +0,0 @@
import SegmentedControl from '@react-native-community/segmented-control'
import { useNavigation } from '@react-navigation/native'
import { useTheme } from '@utils/styles/ThemeManager'
import React, { useEffect, useState } from 'react'
import { useTranslation } from 'react-i18next'
import { Dimensions, StyleSheet, View } from 'react-native'
import { TabView } from 'react-native-tab-view'
import RelationshipsList from './Relationships/List'
import { SharedRelationshipsProp } from './sharedScreens'
const ScreenSharedRelationships: React.FC<SharedRelationshipsProp> = ({
route: {
params: { account, initialType }
}
}) => {
const { t } = useTranslation('sharedRelationships')
const { mode } = useTheme()
const navigation = useNavigation()
const [segment, setSegment] = useState(initialType === 'following' ? 0 : 1)
useEffect(() => {
const updateHeaderRight = () =>
navigation.setOptions({
headerCenter: () => (
<View style={styles.segmentsContainer}>
<SegmentedControl
appearance={mode}
values={[t('heading.segments.left'), t('heading.segments.right')]}
selectedIndex={segment}
onChange={({ nativeEvent }) =>
setSegment(nativeEvent.selectedSegmentIndex)
}
/>
</View>
)
})
return updateHeaderRight()
}, [segment, mode])
const routes: {
key: SharedRelationshipsProp['route']['params']['initialType']
}[] = [{ key: 'following' }, { key: 'followers' }]
const renderScene = ({
route
}: {
route: {
key: SharedRelationshipsProp['route']['params']['initialType']
}
}) => {
return <RelationshipsList id={account.id} type={route.key} />
}
return (
<TabView
lazy
swipeEnabled
renderScene={renderScene}
renderTabBar={() => null}
onIndexChange={index => setSegment(index)}
navigationState={{ index: segment, routes }}
initialLayout={{ width: Dimensions.get('screen').width }}
/>
)
}
const styles = StyleSheet.create({
segmentsContainer: {
flexBasis: '60%'
}
})
export default React.memo(ScreenSharedRelationships, () => true)

213
src/screens/Tabs.tsx Normal file
View File

@ -0,0 +1,213 @@
import haptics from '@components/haptics'
import Icon from '@components/Icon'
import {
BottomTabNavigationOptions,
createBottomTabNavigator
} from '@react-navigation/bottom-tabs'
import { NavigatorScreenParams } from '@react-navigation/native'
import { StackScreenProps } from '@react-navigation/stack'
import { useTimelineQuery } from '@utils/queryHooks/timeline'
import {
getLocalAccount,
getLocalActiveIndex,
getLocalNotification,
localUpdateNotification
} from '@utils/slices/instancesSlice'
import { useTheme } from '@utils/styles/ThemeManager'
import React, { useCallback, useEffect, useMemo } from 'react'
import { Platform } from 'react-native'
import FastImage from 'react-native-fast-image'
import { useDispatch, useSelector } from 'react-redux'
import TabLocal from './Tabs/Local'
import TabMe from './Tabs/Me'
import TabNotifications from './Tabs/Notifications'
import TabPublic from './Tabs/Public'
export type ScreenTabsParamList = {
'Tab-Local': NavigatorScreenParams<Nav.TabLocalStackParamList>
'Tab-Public': NavigatorScreenParams<Nav.TabPublicStackParamList>
'Tab-Compose': NavigatorScreenParams<Nav.TabComposeStackParamList>
'Tab-Notifications': NavigatorScreenParams<Nav.TabNotificationsStackParamList>
'Tab-Me': NavigatorScreenParams<Nav.TabMeStackParamList>
}
export type ScreenTabsProp = StackScreenProps<
Nav.RootStackParamList,
'Screen-Tabs'
>
const Tab = createBottomTabNavigator<Nav.ScreenTabsStackParamList>()
const ScreenTabs: React.FC<ScreenTabsProp> = ({ navigation }) => {
const { theme } = useTheme()
const dispatch = useDispatch()
const localActiveIndex = useSelector(getLocalActiveIndex)
const localAccount = useSelector(getLocalAccount)
const screenOptions = useCallback(
({ route }): BottomTabNavigationOptions => ({
tabBarIcon: ({
focused,
color,
size
}: {
focused: boolean
color: string
size: number
}) => {
switch (route.name) {
case 'Tab-Local':
return <Icon name='Home' size={size} color={color} />
case 'Tab-Public':
return <Icon name='Globe' size={size} color={color} />
case 'Tab-Compose':
return <Icon name='Plus' size={size} color={color} />
case 'Tab-Notifications':
return <Icon name='Bell' size={size} color={color} />
case 'Tab-Me':
return localActiveIndex !== null ? (
<FastImage
source={{ uri: localAccount?.avatarStatic }}
style={{
width: size,
height: size,
borderRadius: size,
borderWidth: focused ? 2 : 0,
borderColor: focused ? theme.secondary : color
}}
/>
) : (
<Icon
name={focused ? 'Meh' : 'Smile'}
size={size}
color={!focused ? theme.secondary : color}
/>
)
default:
return <Icon name='AlertOctagon' size={size} color={color} />
}
}
}),
[localActiveIndex, localAccount]
)
const tabNavigatorTabBarOptions = useMemo(
() => ({
activeTintColor: theme.primary,
inactiveTintColor:
localActiveIndex !== null ? theme.secondary : theme.disabled,
showLabel: false,
...(Platform.OS === 'android' && { keyboardHidesTabBar: true })
}),
[theme, localActiveIndex]
)
const tabScreenLocalListeners = useCallback(
() => ({
tabPress: (e: any) => {
if (!(localActiveIndex !== null)) {
e.preventDefault()
}
}
}),
[localActiveIndex]
)
const tabScreenComposeListeners = useMemo(
() => ({
tabPress: (e: any) => {
e.preventDefault()
if (localActiveIndex !== null) {
haptics('Light')
navigation.navigate('Screen-Compose')
}
}
}),
[localActiveIndex]
)
const tabScreenComposeComponent = useCallback(() => null, [])
const tabScreenNotificationsListeners = useCallback(
() => ({
tabPress: (e: any) => {
if (!(localActiveIndex !== null)) {
e.preventDefault()
}
}
}),
[localActiveIndex]
)
// On launch check if there is any unread noficiations
const queryNotification = useTimelineQuery({
page: 'Notifications',
options: {
enabled: localActiveIndex !== null ? true : false,
refetchInterval: 1000 * 60,
refetchIntervalInBackground: true
}
})
const prevNotification = useSelector(getLocalNotification)
useEffect(() => {
if (queryNotification.data?.pages) {
const flattenData = queryNotification.data.pages.flatMap(d => [...d])
const latestNotificationTime = flattenData.length
? (flattenData[0] as Mastodon.Notification).created_at
: undefined
if (!prevNotification || !prevNotification.latestTime) {
dispatch(localUpdateNotification({ unread: false }))
} else if (
latestNotificationTime &&
new Date(prevNotification.latestTime) < new Date(latestNotificationTime)
) {
dispatch(
localUpdateNotification({
unread: true,
latestTime: latestNotificationTime
})
)
}
}
}, [queryNotification.data?.pages])
return (
<Tab.Navigator
initialRouteName={localActiveIndex !== null ? 'Tab-Local' : 'Tab-Me'}
screenOptions={screenOptions}
tabBarOptions={tabNavigatorTabBarOptions}
>
<Tab.Screen
name='Tab-Local'
component={TabLocal}
listeners={tabScreenLocalListeners}
/>
<Tab.Screen name='Tab-Public' component={TabPublic} />
<Tab.Screen
name='Tab-Compose'
component={tabScreenComposeComponent}
listeners={tabScreenComposeListeners}
/>
<Tab.Screen
name='Tab-Notifications'
component={TabNotifications}
listeners={tabScreenNotificationsListeners}
options={
prevNotification && prevNotification.unread
? {
tabBarBadge: '',
tabBarBadgeStyle: {
transform: [{ scale: 0.5 }],
backgroundColor: theme.red
}
}
: {
tabBarBadgeStyle: {
transform: [{ scale: 0.5 }],
backgroundColor: theme.red
}
}
}
/>
<Tab.Screen name='Tab-Me' component={TabMe} />
</Tab.Navigator>
)
}
export default ScreenTabs

View File

@ -1,7 +1,8 @@
import analytics from '@components/analytics' import analytics from '@components/analytics'
import { HeaderCenter, HeaderRight } from '@components/Header' import { HeaderCenter, HeaderRight } from '@components/Header'
import Timeline from '@components/Timelines/Timeline' import Timeline from '@components/Timelines/Timeline'
import { useNavigation } from '@react-navigation/native' import { BottomTabScreenProps } from '@react-navigation/bottom-tabs'
import { ScreenTabsParamList } from '@screens/Tabs'
import { getLocalActiveIndex } from '@utils/slices/instancesSlice' import { getLocalActiveIndex } from '@utils/slices/instancesSlice'
import React, { useCallback } from 'react' import React, { useCallback } from 'react'
import { useTranslation } from 'react-i18next' import { useTranslation } from 'react-i18next'
@ -10,26 +11,27 @@ import { createNativeStackNavigator } from 'react-native-screens/native-stack'
import { useSelector } from 'react-redux' import { useSelector } from 'react-redux'
import sharedScreens from './Shared/sharedScreens' import sharedScreens from './Shared/sharedScreens'
const Stack = createNativeStackNavigator<Nav.LocalStackParamList>() export type TabLocalProp = BottomTabScreenProps<
ScreenTabsParamList,
'Tab-Local'
>
const ScreenLocal = React.memo( const Stack = createNativeStackNavigator<Nav.TabLocalStackParamList>()
() => {
const TabLocal = React.memo(
({ navigation }: TabLocalProp) => {
const { t } = useTranslation('local') const { t } = useTranslation('local')
const navigation = useNavigation()
const localActiveIndex = useSelector(getLocalActiveIndex) const localActiveIndex = useSelector(getLocalActiveIndex)
const onPressSearch = useCallback(() => { const onPressSearch = useCallback(() => {
analytics('search_tap', { page: 'Local' }) analytics('search_tap', { page: 'Local' })
navigation.navigate('Screen-Local', { screen: 'Screen-Shared-Search' }) navigation.navigate('Tab-Local', { screen: 'Tab-Shared-Search' })
}, []) }, [])
return ( return (
<Stack.Navigator <Stack.Navigator
screenOptions={{ screenOptions={{
headerLeft: () => null, headerLeft: () => null,
headerRight: () => (
<HeaderRight content='Search' onPress={onPressSearch} />
),
headerTitle: t('heading'), headerTitle: t('heading'),
...(Platform.OS === 'android' && { ...(Platform.OS === 'android' && {
headerCenter: () => <HeaderCenter content={t('heading')} /> headerCenter: () => <HeaderCenter content={t('heading')} />
@ -38,7 +40,14 @@ const ScreenLocal = React.memo(
headerTopInsetEnabled: false headerTopInsetEnabled: false
}} }}
> >
<Stack.Screen name='Screen-Local-Root'> <Stack.Screen
name='Tab-Local-Root'
options={{
headerRight: () => (
<HeaderRight content='Search' onPress={onPressSearch} />
)
}}
>
{() => {() =>
localActiveIndex !== null ? <Timeline page='Following' /> : null localActiveIndex !== null ? <Timeline page='Following' /> : null
} }
@ -51,4 +60,4 @@ const ScreenLocal = React.memo(
() => true () => true
) )
export default ScreenLocal export default TabLocal

View File

@ -1,21 +1,21 @@
import { HeaderCenter, HeaderLeft } from '@components/Header' import { HeaderCenter, HeaderLeft } from '@components/Header'
import ScreenMeBookmarks from '@screens/Me/Bookmarks' import ScreenMeBookmarks from '@screens/Tabs/Me/Bookmarks'
import ScreenMeConversations from '@screens/Me/Cconversations' import ScreenMeConversations from '@screens/Tabs/Me/Cconversations'
import ScreenMeFavourites from '@screens/Me/Favourites' import ScreenMeFavourites from '@screens/Tabs/Me/Favourites'
import ScreenMeLists from '@screens/Me/Lists' import ScreenMeLists from '@screens/Tabs/Me/Lists'
import ScreenMeRoot from '@screens/Me/Root' import ScreenMeRoot from '@screens/Tabs/Me/Root'
import ScreenMeListsList from '@screens/Me/Root/Lists/List' import ScreenMeListsList from '@screens/Tabs/Me/Root/Lists/List'
import ScreenMeSettings from '@screens/Me/Settings' import ScreenMeSettings from '@screens/Tabs/Me/Settings'
import ScreenMeSwitch from '@screens/Me/Switch' import ScreenMeSwitch from '@screens/Tabs/Me/Switch'
import sharedScreens from '@screens/Shared/sharedScreens' import sharedScreens from '@screens/Tabs/Shared/sharedScreens'
import React from 'react' import React from 'react'
import { useTranslation } from 'react-i18next' import { useTranslation } from 'react-i18next'
import { Platform } from 'react-native' import { Platform } from 'react-native'
import { createNativeStackNavigator } from 'react-native-screens/native-stack' import { createNativeStackNavigator } from 'react-native-screens/native-stack'
const Stack = createNativeStackNavigator<Nav.MeStackParamList>() const Stack = createNativeStackNavigator<Nav.TabMeStackParamList>()
const ScreenMe: React.FC = () => { const TabMe: React.FC = () => {
const { t } = useTranslation() const { t } = useTranslation()
return ( return (
@ -23,7 +23,7 @@ const ScreenMe: React.FC = () => {
screenOptions={{ headerHideShadow: true, headerTopInsetEnabled: false }} screenOptions={{ headerHideShadow: true, headerTopInsetEnabled: false }}
> >
<Stack.Screen <Stack.Screen
name='Screen-Me-Root' name='Tab-Me-Root'
component={ScreenMeRoot} component={ScreenMeRoot}
options={{ options={{
headerTranslucent: true, headerTranslucent: true,
@ -32,7 +32,7 @@ const ScreenMe: React.FC = () => {
}} }}
/> />
<Stack.Screen <Stack.Screen
name='Screen-Me-Bookmarks' name='Tab-Me-Bookmarks'
component={ScreenMeBookmarks} component={ScreenMeBookmarks}
options={({ navigation }: any) => ({ options={({ navigation }: any) => ({
headerTitle: t('meBookmarks:heading'), headerTitle: t('meBookmarks:heading'),
@ -45,7 +45,7 @@ const ScreenMe: React.FC = () => {
})} })}
/> />
<Stack.Screen <Stack.Screen
name='Screen-Me-Conversations' name='Tab-Me-Conversations'
component={ScreenMeConversations} component={ScreenMeConversations}
options={({ navigation }: any) => ({ options={({ navigation }: any) => ({
headerTitle: t('meConversations:heading'), headerTitle: t('meConversations:heading'),
@ -58,7 +58,7 @@ const ScreenMe: React.FC = () => {
})} })}
/> />
<Stack.Screen <Stack.Screen
name='Screen-Me-Favourites' name='Tab-Me-Favourites'
component={ScreenMeFavourites} component={ScreenMeFavourites}
options={({ navigation }: any) => ({ options={({ navigation }: any) => ({
headerTitle: t('meFavourites:heading'), headerTitle: t('meFavourites:heading'),
@ -71,7 +71,7 @@ const ScreenMe: React.FC = () => {
})} })}
/> />
<Stack.Screen <Stack.Screen
name='Screen-Me-Lists' name='Tab-Me-Lists'
component={ScreenMeLists} component={ScreenMeLists}
options={({ navigation }: any) => ({ options={({ navigation }: any) => ({
headerTitle: t('meLists:heading'), headerTitle: t('meLists:heading'),
@ -82,7 +82,7 @@ const ScreenMe: React.FC = () => {
})} })}
/> />
<Stack.Screen <Stack.Screen
name='Screen-Me-Lists-List' name='Tab-Me-Lists-List'
component={ScreenMeListsList} component={ScreenMeListsList}
options={({ route, navigation }: any) => ({ options={({ route, navigation }: any) => ({
headerTitle: t('meListsList:heading', { list: route.params.title }), headerTitle: t('meListsList:heading', { list: route.params.title }),
@ -97,7 +97,7 @@ const ScreenMe: React.FC = () => {
})} })}
/> />
<Stack.Screen <Stack.Screen
name='Screen-Me-Settings' name='Tab-Me-Settings'
component={ScreenMeSettings} component={ScreenMeSettings}
options={({ navigation }: any) => ({ options={({ navigation }: any) => ({
headerTitle: t('meSettings:heading'), headerTitle: t('meSettings:heading'),
@ -110,10 +110,10 @@ const ScreenMe: React.FC = () => {
})} })}
/> />
<Stack.Screen <Stack.Screen
name='Screen-Me-Switch' name='Tab-Me-Switch'
component={ScreenMeSwitch} component={ScreenMeSwitch}
options={({ navigation }: any) => ({ options={({ navigation }: any) => ({
stackPresentation: 'fullScreenModal', stackPresentation: 'modal',
headerShown: false, headerShown: false,
headerLeft: () => <HeaderLeft onPress={() => navigation.pop(1)} /> headerLeft: () => <HeaderLeft onPress={() => navigation.pop(1)} />
})} })}
@ -124,4 +124,4 @@ const ScreenMe: React.FC = () => {
) )
} }
export default ScreenMe export default TabMe

View File

@ -1,11 +1,13 @@
import { MenuRow } from '@components/Menu' import { MenuRow } from '@components/Menu'
import { useNavigation } from '@react-navigation/native' import { StackScreenProps } from '@react-navigation/stack'
import TimelineEmpty from '@root/components/Timelines/Timeline/Empty' import TimelineEmpty from '@root/components/Timelines/Timeline/Empty'
import { useListsQuery } from '@utils/queryHooks/lists' import { useListsQuery } from '@utils/queryHooks/lists'
import React, { useMemo } from 'react' import React, { useMemo } from 'react'
const ScreenMeLists: React.FC = () => { const ScreenMeLists: React.FC<StackScreenProps<
const navigation = useNavigation() Nav.TabMeStackParamList,
'Tab-Me-Switch'
>> = ({ navigation }) => {
const { status, data, refetch } = useListsQuery({}) const { status, data, refetch } = useListsQuery({})
const children = useMemo(() => { const children = useMemo(() => {
@ -16,7 +18,7 @@ const ScreenMeLists: React.FC = () => {
iconFront='List' iconFront='List'
title={d.title} title={d.title}
onPress={() => onPress={() =>
navigation.navigate('Screen-Me-Lists-List', { navigation.navigate('Tab-Me-Lists-List', {
list: d.id, list: d.id,
title: d.title title: d.title
}) })

View File

@ -1,31 +1,22 @@
import ComponentInstance from '@components/Instance'
import { useScrollToTop } from '@react-navigation/native' import { useScrollToTop } from '@react-navigation/native'
import Collections from '@screens/Me/Root/Collections' import Collections from '@screens/Tabs/Me/Root/Collections'
import MyInfo from '@screens/Me/Root/MyInfo' import Logout from '@screens/Tabs/Me/Root/Logout'
import Settings from '@screens/Me/Root/Settings' import MyInfo from '@screens/Tabs/Me/Root/MyInfo'
import Logout from '@screens/Me/Root/Logout' import Settings from '@screens/Tabs/Me/Root/Settings'
import AccountNav from '@screens/Shared/Account/Nav' import AccountNav from '@screens/Tabs/Shared/Account/Nav'
import accountReducer from '@screens/Shared/Account/utils/reducer' import AccountContext from '@screens/Tabs/Shared/Account/utils/createContext'
import accountInitialState from '@screens/Shared/Account/utils/initialState' import accountInitialState from '@screens/Tabs/Shared/Account/utils/initialState'
import AccountContext from '@screens/Shared/Account/utils/createContext' import accountReducer from '@screens/Tabs/Shared/Account/utils/reducer'
import { getLocalActiveIndex } from '@utils/slices/instancesSlice' import { getLocalActiveIndex } from '@utils/slices/instancesSlice'
import React, { useEffect, useReducer, useRef, useState } from 'react' import React, { useReducer, useRef, useState } from 'react'
import { useSelector } from 'react-redux'
import Animated, { import Animated, {
useAnimatedScrollHandler, useAnimatedScrollHandler,
useSharedValue useSharedValue
} from 'react-native-reanimated' } from 'react-native-reanimated'
import ComponentInstance from '@components/Instance' import { useSelector } from 'react-redux'
import { StackScreenProps } from '@react-navigation/stack'
const ScreenMeRoot: React.FC<StackScreenProps< const ScreenMeRoot: React.FC = () => {
Nav.MeStackParamList,
'Screen-Me-Root'
>> = ({ route: { params }, navigation }) => {
useEffect(() => {
if (params && params.navigateAway) {
navigation.navigate(params.navigateAway)
}
}, [params])
const localActiveIndex = useSelector(getLocalActiveIndex) const localActiveIndex = useSelector(getLocalActiveIndex)
const scrollRef = useRef<Animated.ScrollView>(null) const scrollRef = useRef<Animated.ScrollView>(null)

View File

@ -27,25 +27,25 @@ const Collections: React.FC = () => {
iconFront='Mail' iconFront='Mail'
iconBack='ChevronRight' iconBack='ChevronRight'
title={t('content.collections.conversations')} title={t('content.collections.conversations')}
onPress={() => navigation.navigate('Screen-Me-Conversations')} onPress={() => navigation.navigate('Tab-Me-Conversations')}
/> />
<MenuRow <MenuRow
iconFront='Bookmark' iconFront='Bookmark'
iconBack='ChevronRight' iconBack='ChevronRight'
title={t('content.collections.bookmarks')} title={t('content.collections.bookmarks')}
onPress={() => navigation.navigate('Screen-Me-Bookmarks')} onPress={() => navigation.navigate('Tab-Me-Bookmarks')}
/> />
<MenuRow <MenuRow
iconFront='Star' iconFront='Star'
iconBack='ChevronRight' iconBack='ChevronRight'
title={t('content.collections.favourites')} title={t('content.collections.favourites')}
onPress={() => navigation.navigate('Screen-Me-Favourites')} onPress={() => navigation.navigate('Tab-Me-Favourites')}
/> />
<MenuRow <MenuRow
iconFront='List' iconFront='List'
iconBack='ChevronRight' iconBack='ChevronRight'
title={t('content.collections.lists')} title={t('content.collections.lists')}
onPress={() => navigation.navigate('Screen-Me-Lists')} onPress={() => navigation.navigate('Tab-Me-Lists')}
/> />
<MenuRow <MenuRow
iconFront='Clipboard' iconFront='Clipboard'
@ -56,7 +56,7 @@ const Collections: React.FC = () => {
onPress={() => onPress={() =>
data && data &&
data.length && data.length &&
navigation.navigate('Screen-Shared-Announcements', { showAll: true }) navigation.navigate('Screen-Announcements', { showAll: true })
} }
/> />
</MenuContainer> </MenuContainer>

View File

@ -1,5 +1,5 @@
import AccountHeader from '@screens/Shared/Account/Header' import AccountHeader from '@screens/Tabs/Shared/Account/Header'
import AccountInformation from '@screens/Shared/Account/Information' import AccountInformation from '@screens/Tabs/Shared/Account/Information'
import { useAccountQuery } from '@utils/queryHooks/account' import { useAccountQuery } from '@utils/queryHooks/account'
import { getLocalAccount } from '@utils/slices/instancesSlice' import { getLocalAccount } from '@utils/slices/instancesSlice'
import React, { useEffect } from 'react' import React, { useEffect } from 'react'

View File

@ -21,7 +21,7 @@ const Settings: React.FC = () => {
iconFront='Settings' iconFront='Settings'
iconBack='ChevronRight' iconBack='ChevronRight'
title={t('content.settings')} title={t('content.settings')}
onPress={() => navigation.navigate('Screen-Me-Settings')} onPress={() => navigation.navigate('Tab-Me-Settings')}
/> />
</MenuContainer> </MenuContainer>
) )

View File

@ -68,7 +68,7 @@ const SettingsTooot: React.FC = () => {
account => account.acct === 'tooot@xmflsct.com' account => account.acct === 'tooot@xmflsct.com'
) )
if (foundAccounts?.length === 1) { if (foundAccounts?.length === 1) {
navigation.navigate('Screen-Shared-Compose', { navigation.navigate('Screen-Compose', {
type: 'conversation', type: 'conversation',
accts: [foundAccounts[0].acct] accts: [foundAccounts[0].acct]
}) })

View File

@ -1,6 +1,6 @@
import { HeaderCenter } from '@components/Header' import { HeaderCenter } from '@components/Header'
import Timeline from '@components/Timelines/Timeline' import Timeline from '@components/Timelines/Timeline'
import sharedScreens from '@screens/Shared/sharedScreens' import sharedScreens from '@screens/Tabs/Shared/sharedScreens'
import { getLocalActiveIndex } from '@utils/slices/instancesSlice' import { getLocalActiveIndex } from '@utils/slices/instancesSlice'
import React from 'react' import React from 'react'
import { useTranslation } from 'react-i18next' import { useTranslation } from 'react-i18next'
@ -8,9 +8,9 @@ import { Platform } from 'react-native'
import { createNativeStackNavigator } from 'react-native-screens/native-stack' import { createNativeStackNavigator } from 'react-native-screens/native-stack'
import { useSelector } from 'react-redux' import { useSelector } from 'react-redux'
const Stack = createNativeStackNavigator<Nav.NotificationsStackParamList>() const Stack = createNativeStackNavigator<Nav.TabNotificationsStackParamList>()
const ScreenNotifications: React.FC = () => { const TabNotifications: React.FC = () => {
const { t } = useTranslation() const { t } = useTranslation()
const localActiveIndex = useSelector(getLocalActiveIndex) const localActiveIndex = useSelector(getLocalActiveIndex)
@ -28,7 +28,7 @@ const ScreenNotifications: React.FC = () => {
headerTopInsetEnabled: false headerTopInsetEnabled: false
}} }}
> >
<Stack.Screen name='Screen-Notifications-Root'> <Stack.Screen name='Tab-Notifications-Root'>
{() => {() =>
localActiveIndex !== null ? <Timeline page='Notifications' /> : null localActiveIndex !== null ? <Timeline page='Notifications' /> : null
} }
@ -39,4 +39,4 @@ const ScreenNotifications: React.FC = () => {
) )
} }
export default ScreenNotifications export default TabNotifications

View File

@ -1,11 +1,11 @@
import Timelines from '@components/Timelines' import Timelines from '@components/Timelines'
import React from 'react' import React from 'react'
const ScreenPublic = React.memo( const TabPublic = React.memo(
() => { () => {
return <Timelines /> return <Timelines />
}, },
() => true () => true
) )
export default ScreenPublic export default TabPublic

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