Skip to content

Conversation

@graycreate
Copy link
Member

Summary

  • Add URL interception logic similar to Android version for internal V2EX links
  • Implement builtin browser (InAppBrowserView) with WKWebView for external links
  • Add settings toggle for enabling/disabling builtin browser
  • Use NavigationLink instead of fullScreenCover to workaround iOS 26 WKWebView blank screen bug

Changes

URL Routing

  • Add LinkHandler class in URLRouter.swift to determine appropriate action for URLs
  • Internal links (/t/, /member/, /go/) navigate to native pages (FeedDetailPage, UserDetailPage, TagDetailPage)
  • External links open in builtin browser or SafariViewController based on user preference

InAppBrowserView

  • Full-featured in-app browser with WKWebView
  • Translucent navigation bar with blur effect
  • Full-screen content that extends under navigation bar
  • Support for back/forward navigation, refresh, share, and open in Safari
  • Handle target="_blank" links via WKUIDelegate

Settings

  • Add "内置浏览器" toggle in OtherSettingsView
  • Persist preference in UserDefaults

iOS 26 Bug Workaround

  • Use NavigationLink instead of fullScreenCover/sheet for presenting WebViews
  • This fixes the blank white screen issue in iOS 26 beta

Test plan

  • Test internal links (topic, user, node) navigate to native pages
  • Test external links open in builtin browser when enabled
  • Test external links open in SafariViewController when builtin browser disabled
  • Test target="_blank" links work in builtin browser
  • Verify full-screen scrolling effect with translucent navigation bar

🤖 Generated with Claude Code

- Add URLRouter with LinkHandler for URL parsing and routing
- Implement internal link interception for topics, users, and nodes
- Add InAppBrowserView with WKWebView for builtin browser
- Add useBuiltinBrowser setting toggle in OtherSettingsView
- Use NavigationLink instead of fullScreenCover (iOS 26 bug workaround)
- Support full-screen WebView with translucent navigation bar
- Handle target="_blank" links in WKWebView
- Add SFSafariViewControllerDelegate for proper dismiss handling

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
Copilot AI review requested due to automatic review settings December 28, 2025 12:54
Copy link

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR adds URL interception and a builtin in-app browser to improve link handling within the V2EX client app. Internal V2EX links now navigate to native pages while external links can open in either a builtin browser or SafariViewController based on user preference.

Key Changes:

  • Implemented LinkHandler class to route URLs to appropriate destinations (native pages vs browsers)
  • Added InAppBrowserView with WKWebView for full-featured in-app browsing with navigation controls
  • Added user setting toggle for enabling/disabling the builtin browser with state persistence

Reviewed changes

Copilot reviewed 10 out of 10 changed files in this pull request and generated 7 comments.

Show a summary per file
File Description
V2er/View/InAppBrowserView.swift New full-featured in-app browser with WKWebView, navigation controls, and share functionality
V2er/General/URLRouter.swift Extended with LinkHandler class to determine navigation actions based on URL type and user settings
V2er/View/Settings/OtherSettingsView.swift Added toggle UI for builtin browser preference
V2er/View/FeedDetail/ReplyItemView.swift Refactored link handling to use LinkHandler and support both browser types via NavigationLink
V2er/View/FeedDetail/NewsContentView.swift Refactored link handling to use LinkHandler and support both browser types via NavigationLink
V2er/State/DataFlow/State/SettingState.swift Added useBuiltinBrowser state property and ToggleBuiltinBrowserAction
V2er/State/DataFlow/Reducers/SettingReducer.swift Added reducer logic for builtin browser toggle with UserDefaults persistence
V2er/Sources/RichView/Views/RichContentView.swift Added OpenURLAction environment to intercept link taps in markdown content
V2er/General/Extentions.swift Enhanced SafariView with delegate to handle dismissal properly
V2er.xcodeproj/project.pbxproj Added InAppBrowserView.swift and URLRouter.swift to project build phases

// V2er
//
// Created by Claude on 2025/1/28.
// Copyright © 2025 lessmore.io. All rights reserved.
Copy link

Copilot AI Dec 28, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The copyright date "2025/1/28" is in the future (current date is December 28, 2025, but the month/day format used here suggests January 28, 2025 which would be in the past). However, this appears to be using a YYYY/M/D format which would make this January 28, 2025. For consistency with other files in the project that use "Copyright © 2021", consider using the same year format.

Suggested change
// Copyright © 2025 lessmore.io. All rights reserved.
// Copyright © 2021 lessmore.io. All rights reserved.

Copilot uses AI. Check for mistakes.
Comment on lines +180 to 201
static func action(for url: URL, useBuiltinBrowser: Bool) -> LinkAction {
let result = URLRouter.parse(url.absoluteString)

switch result {
case .external(let externalUrl):
// Open external URLs in Safari
open(externalUrl, options: [:], completionHandler: completion)

case .webview(let webviewUrl):
// For now, open in Safari
// TODO: Implement in-app webview
open(webviewUrl, options: [:], completionHandler: completion)

default:
// For topic, node, member URLs - should be handled by navigation
// Fall back to Safari if not handled
open(url, options: [:], completionHandler: completion)
case .topic(let id):
return .navigateToTopic(id: id)
case .member(let username):
return .navigateToUser(username: username)
case .node(let name):
return .navigateToNode(name: name)
case .external(let externalUrl), .webview(let externalUrl):
// When builtin browser is enabled, use InAppBrowser
// When disabled, use SafariViewController to stay in app
if useBuiltinBrowser {
return .openInAppBrowser(url: externalUrl)
} else {
return .openInSafariViewController(url: externalUrl)
}
case .invalid:
return .openInSafariViewController(url: url)
}
}
Copy link

Copilot AI Dec 28, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The new LinkHandler.action method lacks test coverage. While URLRouter.parse is well-tested, the LinkHandler logic that determines whether to use the builtin browser vs SafariViewController based on user settings should have tests to verify the correct LinkAction is returned for different combinations of URL types and useBuiltinBrowser settings.

Copilot uses AI. Check for mistakes.
Comment on lines +135 to +138
struct ToggleBuiltinBrowserAction: Action {
var target: Reducer = R
let enabled: Bool
}
Copy link

Copilot AI Dec 28, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The new ToggleBuiltinBrowserAction and its reducer implementation lack test coverage. The setting persistence to UserDefaults and state updates should be tested to ensure the preference is correctly saved and loaded.

Copilot uses AI. Check for mistakes.
isa = PBXGroup;
children = (
934528345C354D5F9B11C816 /* InAppBrowserView.swift */,
A1B2C3D4E5F60001 /* InAppBrowserView.swift */,
Copy link

Copilot AI Dec 28, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The View group contains a duplicate reference to InAppBrowserView.swift with two different UUIDs (934528345C354D5F9B11C816 and A1B2C3D4E5F60001). This will cause build errors or unpredictable behavior. Only one file reference should be present.

Suggested change
A1B2C3D4E5F60001 /* InAppBrowserView.swift */,

Copilot uses AI. Check for mistakes.
Comment on lines +359 to +360
E43141D64D5C4A65B1700BF8 /* PostscriptItemView.swift */ = {isa = PBXFileReference;
A1B2C3D4E5F60001 /* InAppBrowserView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InAppBrowserView.swift; sourceTree = "<group>"; }; lastKnownFileType = sourcecode.swift; path = PostscriptItemView.swift; sourceTree = "<group>"; };
Copy link

Copilot AI Dec 28, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The PBXFileReference entry is malformed with content split across multiple lines and duplicated. The file reference UUID A1B2C3D4E5F60001 appears to be duplicated with both the intended InAppBrowserView.swift content and the PostscriptItemView.swift content on the same line.

Suggested change
E43141D64D5C4A65B1700BF8 /* PostscriptItemView.swift */ = {isa = PBXFileReference;
A1B2C3D4E5F60001 /* InAppBrowserView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InAppBrowserView.swift; sourceTree = "<group>"; }; lastKnownFileType = sourcecode.swift; path = PostscriptItemView.swift; sourceTree = "<group>"; };
E43141D64D5C4A65B1700BF8 /* PostscriptItemView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PostscriptItemView.swift; sourceTree = "<group>"; };
A1B2C3D4E5F60001 /* InAppBrowserView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InAppBrowserView.swift; sourceTree = "<group>"; };

Copilot uses AI. Check for mistakes.
buildConfigurations = (
5D43701624791C2D00FFA37E /* Debug */,
5D43701724791C2D00FFA37E /* Release */,
A1B2C3D4E5F60002 /* InAppBrowserView.swift in Sources */,
Copy link

Copilot AI Dec 28, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

An InAppBrowserView.swift source file entry is incorrectly placed inside the XCConfigurationList section for UITests. This entry should be in the PBXSourcesBuildPhase section instead. This will cause the project file to be invalid.

Suggested change
A1B2C3D4E5F60002 /* InAppBrowserView.swift in Sources */,

Copilot uses AI. Check for mistakes.
Comment on lines +172 to +173
F090B4D9D3B115551BEF05B4 /* AsyncImageAttachment.swift in Sources */ = {isa = PBXBuildFile;
A1B2C3D4E5F60002 /* InAppBrowserView.swift in Sources */ = {isa = PBXBuildFile; fileRef = A1B2C3D4E5F60001 /* InAppBrowserView.swift */; }; fileRef = 8E707C08835B71223A7A3359 /* AsyncImageAttachment.swift */; };
Copy link

Copilot AI Dec 28, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The project file contains malformed entries with duplicated file references and corrupted syntax. Line 172-173 has "fileRef" appearing twice in the same entry, and line 359-360 has a PBXFileReference split across lines with duplicated content. Additionally, lines 753-754 show the same file "InAppBrowserView.swift" referenced twice with different UUIDs (934528345C354D5F9B11C816 and A1B2C3D4E5F60001), and line 1464 incorrectly places a source file entry inside an XCConfigurationList section.

Suggested change
F090B4D9D3B115551BEF05B4 /* AsyncImageAttachment.swift in Sources */ = {isa = PBXBuildFile;
A1B2C3D4E5F60002 /* InAppBrowserView.swift in Sources */ = {isa = PBXBuildFile; fileRef = A1B2C3D4E5F60001 /* InAppBrowserView.swift */; }; fileRef = 8E707C08835B71223A7A3359 /* AsyncImageAttachment.swift */; };
F090B4D9D3B115551BEF05B4 /* AsyncImageAttachment.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8E707C08835B71223A7A3359 /* AsyncImageAttachment.swift */; };

Copilot uses AI. Check for mistakes.
🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
@graycreate graycreate merged commit 30c2c3d into main Dec 28, 2025
@graycreate graycreate deleted the feature/url-interception-and-builtin-browser branch December 28, 2025 13:03
@github-actions github-actions bot added size/XL and removed size/XL labels Dec 28, 2025
@github-actions
Copy link

Code Coverage Report ❌

Current coverage: 33.83%

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants