Skip to content

Commit 6c2bc66

Browse files
committed
feat(mobile): enhance web view navigation and header interactions
- Add ModalWebViewController and WebViewController for improved web browsing - Implement WebViewManager method to push modal web views - Create UINavigationHeaderActionButton for consistent header button styling - Update header components to use new action button and back button components - Refactor navigation header and scroll view interactions Signed-off-by: Innei <tukon479@gmail.com>
1 parent 97dda3e commit 6c2bc66

File tree

11 files changed

+244
-95
lines changed

11 files changed

+244
-95
lines changed

apps/mobile/native/ios/SharedWebView/ModalWebViewController.swift renamed to apps/mobile/native/ios/Controllers/ModalWebViewController.swift

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,13 @@ class ModalWebViewController: UIViewController {
2727
setupNavigationBar()
2828
setupWebView()
2929
loadContent()
30+
setupInteractivePopGesture()
31+
32+
}
33+
34+
private func setupInteractivePopGesture() {
35+
navigationController?.interactivePopGestureRecognizer?.delegate = self
36+
navigationController?.interactivePopGestureRecognizer?.isEnabled = true
3037
}
3138

3239
private func setupNavigationBar() {
@@ -55,6 +62,7 @@ class ModalWebViewController: UIViewController {
5562

5663
private func setupWebView() {
5764
view.addSubview(webView)
65+
view.backgroundColor = .systemBackground
5866
webView.snp.makeConstraints { make in
5967
make.top.equalTo(view.safeAreaLayoutGuide)
6068
make.left.right.bottom.equalToSuperview()
@@ -81,3 +89,10 @@ extension ModalWebViewController: WKNavigationDelegate {
8189
navigationItem.title = webView.title
8290
}
8391
}
92+
93+
// MARK: - UIGestureRecognizerDelegate
94+
extension ModalWebViewController: UIGestureRecognizerDelegate {
95+
func gestureRecognizerShouldBegin(_ gestureRecognizer: UIGestureRecognizer) -> Bool {
96+
return true
97+
}
98+
}
Lines changed: 150 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,150 @@
1+
//
2+
// WebViewController.swift
3+
// FollowNative
4+
//
5+
// Created by Innei on 2025/2/27.
6+
//
7+
8+
import Foundation
9+
import SnapKit
10+
import UIKit
11+
import WebKit
12+
13+
class WebViewController: UIViewController {
14+
private let webView: WKWebView
15+
private let url: URL
16+
17+
init(url: URL) {
18+
self.url = url
19+
20+
let configuration = WKWebViewConfiguration()
21+
22+
self.webView = WKWebView(frame: .zero, configuration: configuration)
23+
super.init(nibName: nil, bundle: nil)
24+
25+
webView.navigationDelegate = self
26+
}
27+
28+
required init?(coder: NSCoder) {
29+
fatalError("init(coder:) has not been implemented")
30+
}
31+
32+
override func viewDidLoad() {
33+
super.viewDidLoad()
34+
35+
setupWebView()
36+
setupToolbar()
37+
loadContent()
38+
setupInteractivePopGesture()
39+
}
40+
41+
private func setupInteractivePopGesture() {
42+
navigationController?.interactivePopGestureRecognizer?.delegate = self
43+
navigationController?.interactivePopGestureRecognizer?.isEnabled = true
44+
}
45+
46+
private func setupWebView() {
47+
view.addSubview(webView)
48+
view.backgroundColor = .systemBackground
49+
webView.snp.makeConstraints { make in
50+
make.top.equalTo(view.safeAreaLayoutGuide)
51+
make.left.right.bottom.equalToSuperview()
52+
}
53+
}
54+
55+
private func setupToolbar() {
56+
// 确保导航控制器显示工具栏
57+
navigationController?.isToolbarHidden = false
58+
59+
// 创建工具栏按钮
60+
let backButton = UIBarButtonItem(
61+
image: UIImage(systemName: "chevron.backward"), style: .plain, target: self,
62+
action: #selector(goBack))
63+
backButton.tintColor = Utils.accentColor
64+
65+
let forwardButton = UIBarButtonItem(
66+
image: UIImage(systemName: "chevron.forward"), style: .plain, target: self,
67+
action: #selector(goForward))
68+
forwardButton.tintColor = Utils.accentColor
69+
70+
let refreshButton = UIBarButtonItem(
71+
barButtonSystemItem: .refresh, target: self, action: #selector(refreshPage))
72+
refreshButton.tintColor = Utils.accentColor
73+
74+
let safariButton = UIBarButtonItem(
75+
image: UIImage(systemName: "safari"), style: .plain, target: self,
76+
action: #selector(openInSafari))
77+
safariButton.tintColor = Utils.accentColor
78+
79+
// 添加弹性空间使按钮均匀分布
80+
let flexSpace = UIBarButtonItem(barButtonSystemItem: .flexibleSpace, target: nil, action: nil)
81+
82+
// 设置工具栏项目
83+
toolbarItems = [
84+
flexSpace, backButton, flexSpace, forwardButton, flexSpace, refreshButton, flexSpace,
85+
safariButton, flexSpace,
86+
]
87+
}
88+
89+
private func loadContent() {
90+
let request = URLRequest(url: url)
91+
webView.load(request)
92+
}
93+
94+
@objc private func openInSafari() {
95+
UIApplication.shared.open(url)
96+
}
97+
98+
@objc private func goBack() {
99+
if webView.canGoBack {
100+
webView.goBack()
101+
}
102+
}
103+
104+
@objc private func goForward() {
105+
if webView.canGoForward {
106+
webView.goForward()
107+
}
108+
}
109+
110+
@objc private func refreshPage() {
111+
webView.reload()
112+
}
113+
}
114+
115+
// MARK: - WKNavigationDelegate
116+
extension WebViewController: WKNavigationDelegate {
117+
func webView(_ webView: WKWebView, didFinish navigation: WKNavigation!) {
118+
navigationItem.title = webView.title
119+
120+
// 更新后退/前进按钮状态
121+
updateToolbarButtonsState()
122+
}
123+
124+
func webView(_ webView: WKWebView, didStartProvisionalNavigation navigation: WKNavigation!) {
125+
// 更新后退/前进按钮状态
126+
updateToolbarButtonsState()
127+
}
128+
129+
private func updateToolbarButtonsState() {
130+
// 找到后退和前进按钮并更新它们的启用状态
131+
if let items = toolbarItems {
132+
// 后退按钮在索引1
133+
if let backButton = items[1] as? UIBarButtonItem {
134+
backButton.isEnabled = webView.canGoBack
135+
}
136+
137+
// 前进按钮在索引3
138+
if let forwardButton = items[3] as? UIBarButtonItem {
139+
forwardButton.isEnabled = webView.canGoForward
140+
}
141+
}
142+
}
143+
}
144+
145+
// MARK: - UIGestureRecognizerDelegate
146+
extension WebViewController: UIGestureRecognizerDelegate {
147+
func gestureRecognizerShouldBegin(_ gestureRecognizer: UIGestureRecognizer) -> Bool {
148+
return true
149+
}
150+
}

apps/mobile/native/ios/Helper/HelperModule.swift

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,8 +16,8 @@ public class HelperModule: Module {
1616
return
1717
}
1818
DispatchQueue.main.async {
19-
guard let rootVC = UIApplication.shared.windows.first?.rootViewController else { return }
20-
WebViewManager.presentModalWebView(url: url, from: rootVC)
19+
// guard let rootVC = UIApplication.shared.windows.first?.rootViewController else { return }
20+
// WebViewManager.pushModalWebView(url: url, from: rootVC)
2121
}
2222
}
2323

apps/mobile/native/ios/SharedWebView/WebViewManager.swift

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -175,6 +175,11 @@ enum WebViewManager {
175175
navController.modalPresentationStyle = .fullScreen
176176
viewController.present(navController, animated: true)
177177
}
178+
179+
static func pushModalWebView(url: URL, from navigationController: UINavigationController) {
180+
let modalVC = ModalWebViewController(url: url)
181+
navigationController.pushViewController(modalVC, animated: true)
182+
}
178183
}
179184

180185
private class WebViewDelegate: NSObject, WKNavigationDelegate, WKScriptMessageHandler, WKUIDelegate

apps/mobile/src/components/layouts/header/NavigationHeader.tsx

Lines changed: 21 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -147,10 +147,12 @@ export const NavigationHeader = ({
147147
const setHeaderHeight = useContext(SetNavigationHeaderHeightContext)
148148

149149
useEffect(() => {
150-
const id = scrollY.addListener(({ value }) => {
150+
const handler = ({ value }: { value: number }) => {
151151
opacityAnimated.value = Math.max(0, Math.min(1, (value + blurThreshold) / 10))
152-
})
152+
}
153+
const id = scrollY.addListener(handler)
153154

155+
handler({ value: (scrollY as any)._value })
154156
return () => {
155157
scrollY.removeListener(id)
156158
}
@@ -191,7 +193,7 @@ export const NavigationHeader = ({
191193
}
192194
}, [navigation, title])
193195

194-
const HeaderLeft = headerLeft ?? DefaultHeaderLeft
196+
const HeaderLeft = headerLeft ?? DefaultHeaderBackButton
195197

196198
const renderTitle = customHeaderTitle ?? HeaderTitle
197199
const headerTitle =
@@ -306,14 +308,27 @@ export const NavigationHeader = ({
306308
)
307309
}
308310

309-
const DefaultHeaderLeft = ({ canGoBack }: { canGoBack: boolean }) => {
311+
export const DefaultHeaderBackButton = ({ canGoBack }: { canGoBack: boolean }) => {
310312
const label = useColor("label")
311313
if (!canGoBack) return null
312314
return (
313-
<TouchableOpacity hitSlop={10} onPress={() => router.back()}>
315+
<UINavigationHeaderActionButton onPress={() => router.back()}>
314316
<MingcuteLeftLineIcon height={20} width={20} color={label} />
315-
</TouchableOpacity>
317+
</UINavigationHeaderActionButton>
316318
)
317319
}
318320

321+
export const UINavigationHeaderActionButton = ({
322+
children,
323+
onPress,
324+
}: {
325+
children: ReactNode
326+
onPress?: () => void
327+
}) => {
328+
return (
329+
<TouchableOpacity hitSlop={5} className="p-2" onPress={onPress}>
330+
{children}
331+
</TouchableOpacity>
332+
)
333+
}
319334
const Noop = () => null

apps/mobile/src/components/layouts/views/SafeNavigationScrollView.tsx

Lines changed: 3 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,19 @@
11
import { useTypeScriptHappyCallback } from "@follow/hooks"
22
import { getDefaultHeaderHeight } from "@react-navigation/elements"
33
import type { NativeStackNavigationOptions } from "@react-navigation/native-stack"
4-
import { router, Stack, useNavigation } from "expo-router"
4+
import { Stack } from "expo-router"
55
import type { FC, PropsWithChildren } from "react"
66
import { useContext, useEffect, useMemo, useRef, useState } from "react"
77
import type { ScrollView, ScrollViewProps } from "react-native"
8-
import { Animated as RNAnimated, TouchableOpacity, useAnimatedValue, View } from "react-native"
8+
import { Animated as RNAnimated, useAnimatedValue, View } from "react-native"
99
import type { ReanimatedScrollEvent } from "react-native-reanimated/lib/typescript/hook/commonTypes"
1010
import { useSafeAreaFrame, useSafeAreaInsets } from "react-native-safe-area-context"
11-
import { useColor } from "react-native-uikit-colors"
1211

1312
import {
1413
AttachNavigationScrollViewContext,
1514
SetAttachNavigationScrollViewContext,
1615
} from "@/src/components/layouts/tabbar/contexts/AttachNavigationScrollViewContext"
1716
import { useBottomTabBarHeight } from "@/src/components/layouts/tabbar/hooks"
18-
import { MingcuteLeftLineIcon } from "@/src/icons/mingcute_left_line"
1917

2018
import { AnimatedScrollView } from "../../common/AnimatedComponents"
2119
import { NavigationHeader } from "../header/NavigationHeader"
@@ -104,8 +102,6 @@ export const NavigationBlurEffectHeader = ({
104102
headerHideableBottom?: () => React.ReactNode
105103
headerTitleAbsolute?: boolean
106104
}) => {
107-
const canBack = useNavigation().canGoBack()
108-
109105
const navigationContext = useContext(NavigationContext)!
110106

111107
const setHeaderHeight = useContext(SetNavigationHeaderHeightContext)
@@ -119,7 +115,7 @@ export const NavigationBlurEffectHeader = ({
119115
headerTransparent: true,
120116

121117
headerShown: true,
122-
headerLeft: headerLeft ?? (canBack ? () => <NavigationHeaderBackButton /> : undefined),
118+
headerLeft,
123119

124120
header: useTypeScriptHappyCallback(
125121
({ options }) => (
@@ -147,15 +143,3 @@ export const NavigationBlurEffectHeader = ({
147143
/>
148144
)
149145
}
150-
151-
export const NavigationHeaderBackButton = () => {
152-
return <NavigationHeaderBackButtonImpl />
153-
}
154-
const NavigationHeaderBackButtonImpl = () => {
155-
const label = useColor("label")
156-
return (
157-
<TouchableOpacity hitSlop={10} onPress={() => router.back()}>
158-
<MingcuteLeftLineIcon height={20} width={20} color={label} />
159-
</TouchableOpacity>
160-
)
161-
}

apps/mobile/src/modules/entry-content/EntryTitle.tsx

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -11,14 +11,13 @@ import Animated, {
1111
useSharedValue,
1212
withTiming,
1313
} from "react-native-reanimated"
14-
import { useColor } from "react-native-uikit-colors"
1514

1615
import { useUISettingKey } from "@/src/atoms/settings/ui"
16+
import { DefaultHeaderBackButton } from "@/src/components/layouts/header/NavigationHeader"
1717
import { NavigationContext } from "@/src/components/layouts/views/NavigationContext"
1818
import { NavigationBlurEffectHeader } from "@/src/components/layouts/views/SafeNavigationScrollView"
1919
import { UserAvatar } from "@/src/components/ui/avatar/UserAvatar"
2020
import { FeedIcon } from "@/src/components/ui/icon/feed-icon"
21-
import { MingcuteLeftLineIcon } from "@/src/icons/mingcute_left_line"
2221
import { apiClient } from "@/src/lib/api-fetch"
2322
import { EntryContentContext, useEntryContentContext } from "@/src/modules/entry-content/ctx"
2423
import { EntryContentHeaderRightActions } from "@/src/modules/entry-content/EntryContentHeaderRightActions"
@@ -197,8 +196,6 @@ interface EntryLeftGroupProps {
197196
}
198197

199198
const EntryLeftGroup = ({ canGoBack, entryId, titleOpacityShareValue }: EntryLeftGroupProps) => {
200-
const label = useColor("label")
201-
202199
const hideRecentReader = useUISettingKey("hideRecentReader")
203200
const animatedOpacity = useAnimatedStyle(() => {
204201
return {
@@ -208,7 +205,7 @@ const EntryLeftGroup = ({ canGoBack, entryId, titleOpacityShareValue }: EntryLef
208205
return (
209206
<View className="flex-row items-center justify-center">
210207
<TouchableOpacity hitSlop={10} onPress={() => router.back()}>
211-
{canGoBack && <MingcuteLeftLineIcon height={20} width={20} color={label} />}
208+
<DefaultHeaderBackButton canGoBack={canGoBack} />
212209
</TouchableOpacity>
213210

214211
{!hideRecentReader && (

apps/mobile/src/modules/settings/routes/Lists.tsx

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,12 @@
11
import { router } from "expo-router"
22
import { createContext, createElement, useCallback, useContext, useMemo } from "react"
33
import type { ListRenderItem } from "react-native"
4-
import { ActivityIndicator, Image, StyleSheet, Text, TouchableOpacity, View } from "react-native"
4+
import { ActivityIndicator, Image, StyleSheet, Text, View } from "react-native"
55
import Animated, { LinearTransition } from "react-native-reanimated"
66
import { useColor } from "react-native-uikit-colors"
77

88
import { Balance } from "@/src/components/common/Balance"
9+
import { UINavigationHeaderActionButton } from "@/src/components/layouts/header/NavigationHeader"
910
import {
1011
NavigationBlurEffectHeader,
1112
SafeNavigationScrollView,
@@ -99,9 +100,9 @@ export const ListsScreen = () => {
99100
const AddListButton = () => {
100101
const labelColor = useColor("label")
101102
return (
102-
<TouchableOpacity hitSlop={10} onPress={() => router.push("/list")}>
103+
<UINavigationHeaderActionButton onPress={() => router.push("/list")}>
103104
<AddCuteReIcon height={20} width={20} color={labelColor} />
104-
</TouchableOpacity>
105+
</UINavigationHeaderActionButton>
105106
)
106107
}
107108

0 commit comments

Comments
 (0)