diff --git a/.kilo/package-lock.json b/.kilo/package-lock.json index 86ea34f..edda722 100644 --- a/.kilo/package-lock.json +++ b/.kilo/package-lock.json @@ -5,21 +5,22 @@ "packages": { "": { "dependencies": { - "@kilocode/plugin": "7.2.10" + "@kilocode/plugin": "7.2.31" } }, "node_modules/@kilocode/plugin": { - "version": "7.2.10", - "resolved": "https://registry.npmjs.org/@kilocode/plugin/-/plugin-7.2.10.tgz", - "integrity": "sha512-VJPhJC+E5WWu7XgEJzrVOxKJlwJ+OATwxEzgjqEPj8KN5N38YxUPBY/rzUTjv90x7nkzyk1rFGfCVqXdA/Koug==", + "version": "7.2.31", + "resolved": "https://registry.npmmirror.com/@kilocode/plugin/-/plugin-7.2.31.tgz", + "integrity": "sha512-KmKTTIly7hRlJdXKhqZ/j/brvTPh0z0UTjWSjJWq5fqf4pATgYGn7G0g3ZjILnN7MUkkZXuljgqExTEeQJHGkQ==", "license": "MIT", "dependencies": { - "@kilocode/sdk": "7.2.10", + "@kilocode/sdk": "7.2.31", + "effect": "4.0.0-beta.48", "zod": "4.1.8" }, "peerDependencies": { - "@opentui/core": ">=0.1.97", - "@opentui/solid": ">=0.1.97" + "@opentui/core": ">=0.1.100", + "@opentui/solid": ">=0.1.100" }, "peerDependenciesMeta": { "@opentui/core": { @@ -31,17 +32,101 @@ } }, "node_modules/@kilocode/sdk": { - "version": "7.2.10", - "resolved": "https://registry.npmjs.org/@kilocode/sdk/-/sdk-7.2.10.tgz", - "integrity": "sha512-H6jGXYAhN/yjOGX3MRZ0OxyEAuRGY3VOwDbLTh4O6ljpgutFHaLvomDZ82qNVy7gl7AjJgi3SAQAt9UQpeGl/w==", + "version": "7.2.31", + "resolved": "https://registry.npmmirror.com/@kilocode/sdk/-/sdk-7.2.31.tgz", + "integrity": "sha512-Sx05yv+3TIlc6M4Ze+YGgKCLoIg8B0WRE15JXDriVncT+wz7M6+e+4mjNWkwfsuywdeOTYPfeFViqX8iO7ckKA==", "license": "MIT", "dependencies": { "cross-spawn": "7.0.6" } }, + "node_modules/@msgpackr-extract/msgpackr-extract-darwin-arm64": { + "version": "3.0.3", + "resolved": "https://registry.npmmirror.com/@msgpackr-extract/msgpackr-extract-darwin-arm64/-/msgpackr-extract-darwin-arm64-3.0.3.tgz", + "integrity": "sha512-QZHtlVgbAdy2zAqNA9Gu1UpIuI8Xvsd1v8ic6B2pZmeFnFcMWiPLfWXh7TVw4eGEZ/C9TH281KwhVoeQUKbyjw==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@msgpackr-extract/msgpackr-extract-darwin-x64": { + "version": "3.0.3", + "resolved": "https://registry.npmmirror.com/@msgpackr-extract/msgpackr-extract-darwin-x64/-/msgpackr-extract-darwin-x64-3.0.3.tgz", + "integrity": "sha512-mdzd3AVzYKuUmiWOQ8GNhl64/IoFGol569zNRdkLReh6LRLHOXxU4U8eq0JwaD8iFHdVGqSy4IjFL4reoWCDFw==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@msgpackr-extract/msgpackr-extract-linux-arm": { + "version": "3.0.3", + "resolved": "https://registry.npmmirror.com/@msgpackr-extract/msgpackr-extract-linux-arm/-/msgpackr-extract-linux-arm-3.0.3.tgz", + "integrity": "sha512-fg0uy/dG/nZEXfYilKoRe7yALaNmHoYeIoJuJ7KJ+YyU2bvY8vPv27f7UKhGRpY6euFYqEVhxCFZgAUNQBM3nw==", + "cpu": [ + "arm" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@msgpackr-extract/msgpackr-extract-linux-arm64": { + "version": "3.0.3", + "resolved": "https://registry.npmmirror.com/@msgpackr-extract/msgpackr-extract-linux-arm64/-/msgpackr-extract-linux-arm64-3.0.3.tgz", + "integrity": "sha512-YxQL+ax0XqBJDZiKimS2XQaf+2wDGVa1enVRGzEvLLVFeqa5kx2bWbtcSXgsxjQB7nRqqIGFIcLteF/sHeVtQg==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@msgpackr-extract/msgpackr-extract-linux-x64": { + "version": "3.0.3", + "resolved": "https://registry.npmmirror.com/@msgpackr-extract/msgpackr-extract-linux-x64/-/msgpackr-extract-linux-x64-3.0.3.tgz", + "integrity": "sha512-cvwNfbP07pKUfq1uH+S6KJ7dT9K8WOE4ZiAcsrSes+UY55E/0jLYc+vq+DO7jlmqRb5zAggExKm0H7O/CBaesg==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@msgpackr-extract/msgpackr-extract-win32-x64": { + "version": "3.0.3", + "resolved": "https://registry.npmmirror.com/@msgpackr-extract/msgpackr-extract-win32-x64/-/msgpackr-extract-win32-x64-3.0.3.tgz", + "integrity": "sha512-x0fWaQtYp4E6sktbsdAqnehxDgEc/VwM7uLsRCYWaiGu0ykYdZPiS8zCWdnjHwyiumousxfBm4SO31eXqwEZhQ==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@standard-schema/spec": { + "version": "1.1.0", + "resolved": "https://registry.npmmirror.com/@standard-schema/spec/-/spec-1.1.0.tgz", + "integrity": "sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w==", + "license": "MIT" + }, "node_modules/cross-spawn": { "version": "7.0.6", - "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", + "resolved": "https://registry.npmmirror.com/cross-spawn/-/cross-spawn-7.0.6.tgz", "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", "license": "MIT", "dependencies": { @@ -53,24 +138,163 @@ "node": ">= 8" } }, + "node_modules/detect-libc": { + "version": "2.1.2", + "resolved": "https://registry.npmmirror.com/detect-libc/-/detect-libc-2.1.2.tgz", + "integrity": "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==", + "license": "Apache-2.0", + "optional": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/effect": { + "version": "4.0.0-beta.48", + "resolved": "https://registry.npmmirror.com/effect/-/effect-4.0.0-beta.48.tgz", + "integrity": "sha512-MMAM/ZabuNdNmgXiin+BAanQXK7qM8mlt7nfXDoJ/Gn9V8i89JlCq+2N0AiWmqFLXjGLA0u3FjiOjSOYQk5uMw==", + "license": "MIT", + "dependencies": { + "@standard-schema/spec": "^1.1.0", + "fast-check": "^4.6.0", + "find-my-way-ts": "^0.1.6", + "ini": "^6.0.0", + "kubernetes-types": "^1.30.0", + "msgpackr": "^1.11.9", + "multipasta": "^0.2.7", + "toml": "^4.1.1", + "uuid": "^13.0.0", + "yaml": "^2.8.3" + } + }, + "node_modules/fast-check": { + "version": "4.7.0", + "resolved": "https://registry.npmmirror.com/fast-check/-/fast-check-4.7.0.tgz", + "integrity": "sha512-NsZRtqvSSoCP0HbNjUD+r1JH8zqZalyp6gLY9e7OYs7NK9b6AHOs2baBFeBG7bVNsuoukh89x2Yg3rPsul8ziQ==", + "funding": [ + { + "type": "individual", + "url": "https://github.com/sponsors/dubzzz" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fast-check" + } + ], + "license": "MIT", + "dependencies": { + "pure-rand": "^8.0.0" + }, + "engines": { + "node": ">=12.17.0" + } + }, + "node_modules/find-my-way-ts": { + "version": "0.1.6", + "resolved": "https://registry.npmmirror.com/find-my-way-ts/-/find-my-way-ts-0.1.6.tgz", + "integrity": "sha512-a85L9ZoXtNAey3Y6Z+eBWW658kO/MwR7zIafkIUPUMf3isZG0NCs2pjW2wtjxAKuJPxMAsHUIP4ZPGv0o5gyTA==", + "license": "MIT" + }, + "node_modules/ini": { + "version": "6.0.0", + "resolved": "https://registry.npmmirror.com/ini/-/ini-6.0.0.tgz", + "integrity": "sha512-IBTdIkzZNOpqm7q3dRqJvMaldXjDHWkEDfrwGEQTs5eaQMWV+djAhR+wahyNNMAa+qpbDUhBMVt4ZKNwpPm7xQ==", + "license": "ISC", + "engines": { + "node": "^20.17.0 || >=22.9.0" + } + }, "node_modules/isexe": { "version": "2.0.0", - "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "resolved": "https://registry.npmmirror.com/isexe/-/isexe-2.0.0.tgz", "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", "license": "ISC" }, + "node_modules/kubernetes-types": { + "version": "1.30.0", + "resolved": "https://registry.npmmirror.com/kubernetes-types/-/kubernetes-types-1.30.0.tgz", + "integrity": "sha512-Dew1okvhM/SQcIa2rcgujNndZwU8VnSapDgdxlYoB84ZlpAD43U6KLAFqYo17ykSFGHNPrg0qry0bP+GJd9v7Q==", + "license": "Apache-2.0" + }, + "node_modules/msgpackr": { + "version": "1.11.12", + "resolved": "https://registry.npmmirror.com/msgpackr/-/msgpackr-1.11.12.tgz", + "integrity": "sha512-RBdJ1Un7yGlXWajrkxcSa93nvQ0w4zBf60c0yYv7YtBelP8H2FA7XsfBbMHtXKXUMUxH7zV3Zuozh+kUQWhHvg==", + "license": "MIT", + "optionalDependencies": { + "msgpackr-extract": "^3.0.2" + } + }, + "node_modules/msgpackr-extract": { + "version": "3.0.3", + "resolved": "https://registry.npmmirror.com/msgpackr-extract/-/msgpackr-extract-3.0.3.tgz", + "integrity": "sha512-P0efT1C9jIdVRefqjzOQ9Xml57zpOXnIuS+csaB4MdZbTdmGDLo8XhzBG1N7aO11gKDDkJvBLULeFTo46wwreA==", + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "dependencies": { + "node-gyp-build-optional-packages": "5.2.2" + }, + "bin": { + "download-msgpackr-prebuilds": "bin/download-prebuilds.js" + }, + "optionalDependencies": { + "@msgpackr-extract/msgpackr-extract-darwin-arm64": "3.0.3", + "@msgpackr-extract/msgpackr-extract-darwin-x64": "3.0.3", + "@msgpackr-extract/msgpackr-extract-linux-arm": "3.0.3", + "@msgpackr-extract/msgpackr-extract-linux-arm64": "3.0.3", + "@msgpackr-extract/msgpackr-extract-linux-x64": "3.0.3", + "@msgpackr-extract/msgpackr-extract-win32-x64": "3.0.3" + } + }, + "node_modules/multipasta": { + "version": "0.2.7", + "resolved": "https://registry.npmmirror.com/multipasta/-/multipasta-0.2.7.tgz", + "integrity": "sha512-KPA58d68KgGil15oDqXjkUBEBYc00XvbPj5/X+dyzeo/lWm9Nc25pQRlf1D+gv4OpK7NM0J1odrbu9JNNGvynA==", + "license": "MIT" + }, + "node_modules/node-gyp-build-optional-packages": { + "version": "5.2.2", + "resolved": "https://registry.npmmirror.com/node-gyp-build-optional-packages/-/node-gyp-build-optional-packages-5.2.2.tgz", + "integrity": "sha512-s+w+rBWnpTMwSFbaE0UXsRlg7hU4FjekKU4eyAih5T8nJuNZT1nNsskXpxmeqSK9UzkBl6UgRlnKc8hz8IEqOw==", + "license": "MIT", + "optional": true, + "dependencies": { + "detect-libc": "^2.0.1" + }, + "bin": { + "node-gyp-build-optional-packages": "bin.js", + "node-gyp-build-optional-packages-optional": "optional.js", + "node-gyp-build-optional-packages-test": "build-test.js" + } + }, "node_modules/path-key": { "version": "3.1.1", - "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", + "resolved": "https://registry.npmmirror.com/path-key/-/path-key-3.1.1.tgz", "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", "license": "MIT", "engines": { "node": ">=8" } }, + "node_modules/pure-rand": { + "version": "8.4.0", + "resolved": "https://registry.npmmirror.com/pure-rand/-/pure-rand-8.4.0.tgz", + "integrity": "sha512-IoM8YF/jY0hiugFo/wOWqfmarlE6J0wc6fDK1PhftMk7MGhVZl88sZimmqBBFomLOCSmcCCpsfj7wXASCpvK9A==", + "funding": [ + { + "type": "individual", + "url": "https://github.com/sponsors/dubzzz" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fast-check" + } + ], + "license": "MIT" + }, "node_modules/shebang-command": { "version": "2.0.0", - "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", + "resolved": "https://registry.npmmirror.com/shebang-command/-/shebang-command-2.0.0.tgz", "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", "license": "MIT", "dependencies": { @@ -82,16 +306,38 @@ }, "node_modules/shebang-regex": { "version": "3.0.0", - "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", + "resolved": "https://registry.npmmirror.com/shebang-regex/-/shebang-regex-3.0.0.tgz", "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", "license": "MIT", "engines": { "node": ">=8" } }, + "node_modules/toml": { + "version": "4.1.1", + "resolved": "https://registry.npmmirror.com/toml/-/toml-4.1.1.tgz", + "integrity": "sha512-EBJnVBr3dTXdA89WVFoAIPUqkBjxPMwRqsfuo1r240tKFHXv3zgca4+NJib/h6TyvGF7vOawz0jGuryJCdNHrw==", + "license": "MIT", + "engines": { + "node": ">=20" + } + }, + "node_modules/uuid": { + "version": "13.0.1", + "resolved": "https://registry.npmmirror.com/uuid/-/uuid-13.0.1.tgz", + "integrity": "sha512-9ezox2roIft6ExBVTVqibSd5dc5/47Sw/uY6b4SjQUT2TzQ0tltNquWA46y4xPQmdZYqvnio22SgWd41M86+jw==", + "funding": [ + "https://github.com/sponsors/broofa", + "https://github.com/sponsors/ctavan" + ], + "license": "MIT", + "bin": { + "uuid": "dist-node/bin/uuid" + } + }, "node_modules/which": { "version": "2.0.2", - "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "resolved": "https://registry.npmmirror.com/which/-/which-2.0.2.tgz", "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", "license": "ISC", "dependencies": { @@ -104,6 +350,21 @@ "node": ">= 8" } }, + "node_modules/yaml": { + "version": "2.8.4", + "resolved": "https://registry.npmmirror.com/yaml/-/yaml-2.8.4.tgz", + "integrity": "sha512-ml/JPOj9fOQK8RNnWojA67GbZ0ApXAUlN2UQclwv2eVgTgn7O9gg9o7paZWKMp4g0H3nTLtS9LVzhkpOFIKzog==", + "license": "ISC", + "bin": { + "yaml": "bin.mjs" + }, + "engines": { + "node": ">= 14.6" + }, + "funding": { + "url": "https://github.com/sponsors/eemeli" + } + }, "node_modules/zod": { "version": "4.1.8", "license": "MIT", diff --git a/app/(app)/(tabs)/profile/_layout.tsx b/app/(app)/(tabs)/profile/_layout.tsx index 78a343b..59c4f68 100644 --- a/app/(app)/(tabs)/profile/_layout.tsx +++ b/app/(app)/(tabs)/profile/_layout.tsx @@ -1,49 +1,28 @@ -import { useMemo } from 'react'; import { Stack } from 'expo-router'; -import { useRouter } from 'expo-router'; - -import { AppBackButton } from '../../../../src/components/common'; -import { useAppColors } from '../../../../src/theme'; export default function ProfileStackLayout() { - const router = useRouter(); - const colors = useAppColors(); - - const headerOptions = useMemo( - () => ({ - headerStyle: { backgroundColor: colors.background.paper }, - headerTintColor: colors.text.primary, - headerTitleStyle: { fontWeight: '600' as const }, - headerShadowVisible: false, - headerBackTitle: '', - }), - [colors] - ); - return ( router.back()} />, + headerShown: false, }} > - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + ); } diff --git a/app/_layout.tsx b/app/_layout.tsx index 8d4f537..e854b2b 100644 --- a/app/_layout.tsx +++ b/app/_layout.tsx @@ -19,7 +19,7 @@ import { usePaperThemeFromStore, useResolvedColorScheme, } from '../src/theme'; -import { AppBackButton } from '../src/components/common'; + import AppPromptBar from '../src/components/common/AppPromptBar'; import AppDialogHost from '../src/components/common/AppDialogHost'; import { installAlertOverride } from '@/services/ui'; @@ -232,7 +232,6 @@ function APKUpdateBootstrap() { function ThemedStack() { const router = useRouter(); - const colors = useAppColors(); const resolved = useResolvedColorScheme(); return ( @@ -250,49 +249,25 @@ function ThemedStack() { router.back()} />, + headerShown: false, }} /> router.back()} />, + headerShown: false, }} /> router.back()} />, + headerShown: false, }} /> router.back()} />, + headerShown: false, }} /> diff --git a/src/components/common/AppBackButton.tsx b/src/components/common/AppBackButton.tsx index f1d357f..53ae537 100644 --- a/src/components/common/AppBackButton.tsx +++ b/src/components/common/AppBackButton.tsx @@ -50,6 +50,7 @@ const styles = StyleSheet.create({ marginLeft: -12, marginRight: 0, padding: 0, + backgroundColor: 'transparent', }, }); diff --git a/src/components/common/SimpleHeader.tsx b/src/components/common/SimpleHeader.tsx new file mode 100644 index 0000000..902bd9f --- /dev/null +++ b/src/components/common/SimpleHeader.tsx @@ -0,0 +1,55 @@ +import React from 'react'; +import { View, StyleSheet } from 'react-native'; +import { useAppColors, spacing } from '../../theme'; +import { AppBackButton, Text } from './index'; + +interface SimpleHeaderProps { + title: string; + onBack: () => void; + rightComponent?: React.ReactNode; +} + +export const SimpleHeader: React.FC = ({ title, onBack, rightComponent }) => { + const colors = useAppColors(); + + return ( + + + + {title} + + {rightComponent ? ( + + {rightComponent} + + ) : ( + + )} + + ); +}; + +const styles = StyleSheet.create({ + header: { + flexDirection: 'row', + alignItems: 'center', + height: 52, + paddingHorizontal: spacing.xs, + }, + title: { + flex: 1, + fontSize: 17, + fontWeight: '600', + marginLeft: spacing.xs, + }, + rightContainer: { + flexDirection: 'row', + alignItems: 'center', + }, + rightPlaceholder: { + width: 40, + }, + backButton: { + marginLeft: 0, + }, +}); diff --git a/src/components/common/index.ts b/src/components/common/index.ts index 5b0bbc6..5bd7d6f 100644 --- a/src/components/common/index.ts +++ b/src/components/common/index.ts @@ -18,6 +18,7 @@ export { default as ResponsiveGrid } from './ResponsiveGrid'; export { default as ResponsiveStack, HStack, VStack } from './ResponsiveStack'; export { default as AdaptiveLayout, SidebarLayout } from './AdaptiveLayout'; export { default as AppBackButton } from './AppBackButton'; +export { SimpleHeader } from './SimpleHeader'; export { default as PagerView } from './PagerView'; // 图片相关组件 diff --git a/src/screens/home/PostDetailScreen.tsx b/src/screens/home/PostDetailScreen.tsx index c3ca3a3..9b21c17 100644 --- a/src/screens/home/PostDetailScreen.tsx +++ b/src/screens/home/PostDetailScreen.tsx @@ -23,8 +23,9 @@ import { Dimensions, } from 'react-native'; import { FlashList, ListRenderItem, FlashListRef } from '@shopify/flash-list'; -import { useSafeAreaInsets } from 'react-native-safe-area-context'; +import { SafeAreaView, useSafeAreaInsets } from 'react-native-safe-area-context'; import { useNavigation, useRouter, useLocalSearchParams } from 'expo-router'; +import { StatusBar } from 'expo-status-bar'; import { MaterialCommunityIcons } from '@expo/vector-icons'; import * as ImagePicker from 'expo-image-picker'; import { @@ -131,6 +132,7 @@ export const PostDetailScreen: React.FC = () => { postId?: string; scrollToComments?: string; }>(); + const postId = postIdParam || ''; const shouldScrollToComments = scrollParam === '1' || scrollParam === 'true'; @@ -367,58 +369,43 @@ export const PostDetailScreen: React.FC = () => { } }, [shouldScrollToComments, isPostInitialLoading, comments?.length]); - // 动态设置导航头部 - useEffect(() => { - if (!post?.author) return; - - const author = post.author; - const handleBackPress = () => { - if (router.canGoBack()) { - router.back(); - return; - } - router.replace(hrefs.hrefHome()); - }; - - navigation.setOptions({ - // 与页面容器 background.default 一致,并去掉原生 header 底部分隔阴影,避免「一条线」 - headerStyle: { backgroundColor: colors.background.default }, - headerShadowVisible: false, - headerBackVisible: false, - headerTitleAlign: 'left', - headerLeft: () => ( - - ), - headerTitle: () => ( + const handleBackPress = useCallback(() => { + if (router.canGoBack()) { + router.back(); + return; + } + router.replace(hrefs.hrefHome()); + }, [router]); + + const renderCustomHeader = () => ( + + + {post?.author ? ( handleUserPress(author.id)} + onPress={() => handleUserPress(post.author!.id)} style={styles.headerAvatarWrapper} > - + - handleUserPress(author.id)}> + handleUserPress(post.author!.id)}> - {author.nickname && author.nickname.length > 10 - ? `${author.nickname.slice(0, 10)}...` - : author.nickname} + {post.author.nickname && post.author.nickname.length > 10 + ? `${post.author.nickname.slice(0, 10)}...` + : post.author.nickname} - ), - headerRight: () => ( - - {renderFollowButton()} - - ), - }); - }, [post?.author, isFollowing, isFollowingMe, isFollowLoading, navigation, colors.background.default, styles]); + ) : ( + + )} + + {renderFollowButton()} + + + ); // 监听键盘事件 useEffect(() => { @@ -1076,12 +1063,15 @@ export const PostDetailScreen: React.FC = () => { } }; - // 渲染关注按钮 - const renderFollowButton = () => { - if (!post?.author?.id || currentUser?.id === post.author.id) return null; - + // 渲染关注按钮(始终返回同尺寸占位,避免 header 右侧宽度跳动) + const renderFollowButton = useCallback(() => { + const shouldShow = post?.author?.id && currentUser?.id !== post.author.id; + if (!shouldShow) { + return ; + } + const config = getFollowButtonConfig(); - + return ( { ); - }; + }, [post?.author?.id, currentUser?.id, isFollowing, isFollowingMe, isFollowLoading, styles]); // 使用 useMemo 缓存图片数据,避免输入时重新创建数组导致图片重新加载 const postImages = useMemo(() => { @@ -1712,7 +1702,9 @@ export const PostDetailScreen: React.FC = () => { // 桌面端双栏布局 if (useDualColumnLayout) { return ( - + + + {renderCustomHeader()} { onClose={() => setShowImageModal(false)} enableSave /> - + ); } // 移动端单栏布局 return ( - + + + {renderCustomHeader()} { } }} /> - + ); }; @@ -1909,6 +1903,16 @@ function createPostDetailStyles(colors: AppColors) { flex: 1, backgroundColor: colors.background.default, }, + customHeader: { + flexDirection: 'row', + alignItems: 'center', + backgroundColor: colors.background.default, + height: 52, + paddingRight: spacing.sm, + }, + headerPlaceholder: { + flex: 1, + }, // Header 样式 headerContainer: { flexDirection: 'row', @@ -1932,10 +1936,12 @@ function createPostDetailStyles(colors: AppColors) { flexDirection: 'row', alignItems: 'center', marginRight: spacing.sm, + backgroundColor: 'transparent', }, headerBackButton: { marginLeft: 0, marginRight: 0, + backgroundColor: 'transparent', }, // 帖子容器 postContainer: { @@ -1950,6 +1956,7 @@ function createPostDetailStyles(colors: AppColors) { minWidth: 64, alignItems: 'center', justifyContent: 'center', + backgroundColor: 'transparent', }, followButtonPrimary: { backgroundColor: colors.primary.main, @@ -1967,6 +1974,12 @@ function createPostDetailStyles(colors: AppColors) { followButtonLoading: { opacity: 0.7, }, + followButtonPlaceholder: { + minWidth: 64, + paddingHorizontal: spacing.md, + paddingVertical: 4, + backgroundColor: 'transparent', + }, followButtonText: { fontSize: fontSizes.sm, fontWeight: '600', diff --git a/src/screens/profile/AboutScreen.tsx b/src/screens/profile/AboutScreen.tsx index f6a9ef2..40b18d6 100644 --- a/src/screens/profile/AboutScreen.tsx +++ b/src/screens/profile/AboutScreen.tsx @@ -18,6 +18,7 @@ import { import { SafeAreaView, useSafeAreaInsets } from 'react-native-safe-area-context'; import { MaterialCommunityIcons } from '@expo/vector-icons'; import Constants from 'expo-constants'; +import { StatusBar } from 'expo-status-bar'; import { useRouter } from 'expo-router'; import { useAppColors, @@ -26,7 +27,7 @@ import { borderRadius, type AppColors, } from '../../theme'; -import { Text } from '../../components/common'; +import { Text, SimpleHeader } from '../../components/common'; import { useResponsive, useResponsiveSpacing } from '../../hooks'; import { checkForAPKUpdate, type VersionCheckResult } from '@/services/platform'; import * as hrefs from '../../navigation/hrefs'; @@ -517,7 +518,9 @@ export const AboutScreen: React.FC = () => { ); return ( - + + + router.back()} /> { if (loading) { return ( - + @@ -284,7 +285,9 @@ export const AccountDeletionScreen: React.FC = () => { } return ( - + + + router.back()} /> {status?.is_pending_deletion ? ( diff --git a/src/screens/profile/AccountSecurityScreen.tsx b/src/screens/profile/AccountSecurityScreen.tsx index 20555a7..11909f4 100644 --- a/src/screens/profile/AccountSecurityScreen.tsx +++ b/src/screens/profile/AccountSecurityScreen.tsx @@ -7,10 +7,12 @@ import { ActivityIndicator, ScrollView, } from 'react-native'; +import { StatusBar } from 'expo-status-bar'; +import { useRouter } from 'expo-router'; import { SafeAreaView, useSafeAreaInsets } from 'react-native-safe-area-context'; import { MaterialCommunityIcons } from '@expo/vector-icons'; import { spacing, fontSizes, borderRadius, useAppColors, type AppColors } from '../../theme'; -import { Text } from '../../components/common'; +import { Text, SimpleHeader } from '../../components/common'; import { useResponsive, useResponsiveSpacing } from '../../hooks'; // 内容最大宽度 @@ -21,6 +23,7 @@ import { showPrompt } from '@/services/ui'; import { useAuthStore } from '../../stores'; export const AccountSecurityScreen: React.FC = () => { + const router = useRouter(); const colors = useAppColors(); const styles = useMemo(() => createAccountSecurityStyles(colors), [colors]); const { isWideScreen, isMobile } = useResponsive(); @@ -202,7 +205,9 @@ export const AccountSecurityScreen: React.FC = () => { const responsivePadding = useResponsiveSpacing({ xs: 8, sm: 12, md: 16, lg: 24, xl: 32 }); return ( - + + + router.back()} /> {/* 邮箱验证分组 */} diff --git a/src/screens/profile/BlockedUsersScreen.tsx b/src/screens/profile/BlockedUsersScreen.tsx index 2f05eb1..5226065 100644 --- a/src/screens/profile/BlockedUsersScreen.tsx +++ b/src/screens/profile/BlockedUsersScreen.tsx @@ -10,7 +10,8 @@ import { } from 'react-native'; import { SafeAreaView, useSafeAreaInsets } from 'react-native-safe-area-context'; import { useRouter } from 'expo-router'; -import { Avatar, Button, EmptyState, ResponsiveContainer, Text } from '../../components/common'; +import { StatusBar } from 'expo-status-bar'; +import { Avatar, Button, EmptyState, ResponsiveContainer, SimpleHeader, Text } from '../../components/common'; import { authService } from '../../services'; import { spacing, borderRadius, shadows, useAppColors, type AppColors } from '../../theme'; import { User } from '../../types'; @@ -140,7 +141,8 @@ export const BlockedUsersScreen: React.FC = () => { if (loading) { return ( - + + @@ -149,7 +151,9 @@ export const BlockedUsersScreen: React.FC = () => { } return ( - + + + router.back()} /> { + const router = useRouter(); const colors = useAppColors(); const styles = useMemo(() => createStyles(colors), [colors]); const responsivePadding = useResponsiveSpacing({ xs: 8, sm: 12, md: 16, lg: 24, xl: 32 }); @@ -379,7 +382,9 @@ export const ChatSettingsScreen: React.FC = () => { ); return ( - + + + router.back()} /> { + const router = useRouter(); const colors = useAppColors(); const styles = useMemo(() => createDataStorageStyles(colors), [colors]); const { isMobile } = useResponsive(); @@ -372,7 +375,9 @@ export const DataStorageScreen: React.FC = () => { }; return ( - + + + router.back()} /> { const colors = useAppColors(); const styles = useMemo(() => createEditProfileStyles(colors), [colors]); const navigation = useNavigation(); + const router = useRouter(); const { currentUser, updateUser } = useAuthStore(); const { isWideScreen, isMobile, width } = useResponsive(); const responsivePadding = useResponsiveSpacing({ xs: 0, sm: 0, md: 0, lg: 24, xl: 32 }); @@ -596,7 +599,9 @@ export const EditProfileScreen: React.FC = () => { ); return ( - + + + router.back()} /> { + const router = useRouter(); const colors = useAppColors(); const styles = useMemo(() => createNotificationSettingsStyles(colors), [colors]); const [vibrationEnabled, setVibrationEnabledState] = useState(true); @@ -316,7 +319,9 @@ export const NotificationSettingsScreen: React.FC = () => { const responsivePadding = useResponsiveSpacing({ xs: 8, sm: 12, md: 16, lg: 24, xl: 32 }); return ( - + + + router.back()} /> {renderContent()} diff --git a/src/screens/profile/PrivacyPolicyScreen.tsx b/src/screens/profile/PrivacyPolicyScreen.tsx index 5e83d8d..97aa167 100644 --- a/src/screens/profile/PrivacyPolicyScreen.tsx +++ b/src/screens/profile/PrivacyPolicyScreen.tsx @@ -10,13 +10,15 @@ import { StyleSheet, ScrollView, } from 'react-native'; +import { StatusBar } from 'expo-status-bar'; +import { useRouter } from 'expo-router'; import { SafeAreaView, useSafeAreaInsets } from 'react-native-safe-area-context'; import { useAppColors, spacing, type AppColors, } from '../../theme'; -import { Text } from '../../components/common'; +import { Text, SimpleHeader } from '../../components/common'; import { useResponsive, useResponsiveSpacing } from '../../hooks'; // 内容最大宽度 @@ -278,6 +280,7 @@ export const PrivacyPolicyScreen: React.FC = () => { const colors = useAppColors(); const styles = useMemo(() => createPrivacyStyles(colors), [colors]); const insets = useSafeAreaInsets(); + const router = useRouter(); const { isMobile } = useResponsive(); const responsivePadding = useResponsiveSpacing({ xs: 20, sm: 24, md: 28, lg: 32, xl: 40 }); @@ -285,7 +288,9 @@ export const PrivacyPolicyScreen: React.FC = () => { const scrollBottomInset = isMobile ? 64 + 24 + insets.bottom + spacing.md : spacing.lg; return ( - + + + router.back()} /> { if (loading) { return ( - + + @@ -209,7 +211,9 @@ export const PrivacySettingsScreen: React.FC = () => { } return ( - + + + router.back()} /> diff --git a/src/screens/profile/SettingsScreen.tsx b/src/screens/profile/SettingsScreen.tsx index 805209d..2075247 100644 --- a/src/screens/profile/SettingsScreen.tsx +++ b/src/screens/profile/SettingsScreen.tsx @@ -13,6 +13,7 @@ import { ScrollView, } from 'react-native'; import { SafeAreaView } from 'react-native-safe-area-context'; +import { StatusBar } from 'expo-status-bar'; import { useRouter } from 'expo-router'; import { MaterialCommunityIcons } from '@expo/vector-icons'; import Constants from 'expo-constants'; @@ -26,7 +27,7 @@ import { type AppColors, } from '../../theme'; import { useAuthStore } from '../../stores'; -import { Text } from '../../components/common'; +import { Text, SimpleHeader } from '../../components/common'; import { useResponsive, useResponsiveSpacing } from '../../hooks'; import { useSafeAreaInsets } from 'react-native-safe-area-context'; import * as hrefs from '../../navigation/hrefs'; @@ -365,7 +366,9 @@ export const SettingsScreen: React.FC = () => { ); return ( - + + + router.back()} /> {renderContent()} diff --git a/src/screens/profile/TermsOfServiceScreen.tsx b/src/screens/profile/TermsOfServiceScreen.tsx index 50940ab..1b7ea04 100644 --- a/src/screens/profile/TermsOfServiceScreen.tsx +++ b/src/screens/profile/TermsOfServiceScreen.tsx @@ -10,13 +10,15 @@ import { StyleSheet, ScrollView, } from 'react-native'; +import { StatusBar } from 'expo-status-bar'; import { SafeAreaView, useSafeAreaInsets } from 'react-native-safe-area-context'; +import { useRouter } from 'expo-router'; import { useAppColors, spacing, type AppColors, } from '../../theme'; -import { Text } from '../../components/common'; +import { Text, SimpleHeader } from '../../components/common'; import { useResponsive, useResponsiveSpacing } from '../../hooks'; // 内容最大宽度 @@ -213,12 +215,15 @@ export const TermsOfServiceScreen: React.FC = () => { const insets = useSafeAreaInsets(); const { isMobile } = useResponsive(); const responsivePadding = useResponsiveSpacing({ xs: 20, sm: 24, md: 28, lg: 32, xl: 40 }); + const router = useRouter(); // 底部间距,避免被 TabBar 遮挡 const scrollBottomInset = isMobile ? 64 + 24 + insets.bottom + spacing.md : spacing.lg; return ( - + + + router.back()} /> { const { userId: userIdParam } = useLocalSearchParams<{ userId?: string }>(); + const router = useRouter(); const userId = userIdParam || ''; const currentUser = useCurrentUser(); const isSelfProfile = !!currentUser?.id && currentUser.id === userId; - + return ( - + + router.back()} /> + + ); }; diff --git a/src/screens/profile/VerificationSettingsScreen.tsx b/src/screens/profile/VerificationSettingsScreen.tsx index fe6476e..91924f6 100644 --- a/src/screens/profile/VerificationSettingsScreen.tsx +++ b/src/screens/profile/VerificationSettingsScreen.tsx @@ -14,8 +14,10 @@ import { Alert, ActivityIndicator, } from 'react-native'; +import { StatusBar } from 'expo-status-bar'; import { SafeAreaView } from 'react-native-safe-area-context'; import { useRouter } from 'expo-router'; +import { SimpleHeader } from '../../components/common'; import { MaterialCommunityIcons } from '@expo/vector-icons'; import { useAppColors, type AppColors } from '../../theme'; import { @@ -91,7 +93,9 @@ export const VerificationSettingsScreen: React.FC = () => { if (loading && !status) { return ( - + + + router.back()} /> @@ -104,7 +108,9 @@ export const VerificationSettingsScreen: React.FC = () => { const identityText = IDENTITY_TEXT[status?.identity || 'general']; return ( - + + + router.back()} />