-
Notifications
You must be signed in to change notification settings - Fork 48
feat: add URL interception and builtin browser support #131
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
feat: add URL interception and builtin browser support #131
Conversation
- 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>
There was a problem hiding this 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
LinkHandlerclass to route URLs to appropriate destinations (native pages vs browsers) - Added
InAppBrowserViewwith 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. |
Copilot
AI
Dec 28, 2025
There was a problem hiding this comment.
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.
| // Copyright © 2025 lessmore.io. All rights reserved. | |
| // Copyright © 2021 lessmore.io. All rights reserved. |
| 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) | ||
| } | ||
| } |
Copilot
AI
Dec 28, 2025
There was a problem hiding this comment.
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.
| struct ToggleBuiltinBrowserAction: Action { | ||
| var target: Reducer = R | ||
| let enabled: Bool | ||
| } |
Copilot
AI
Dec 28, 2025
There was a problem hiding this comment.
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.
| isa = PBXGroup; | ||
| children = ( | ||
| 934528345C354D5F9B11C816 /* InAppBrowserView.swift */, | ||
| A1B2C3D4E5F60001 /* InAppBrowserView.swift */, |
Copilot
AI
Dec 28, 2025
There was a problem hiding this comment.
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.
| A1B2C3D4E5F60001 /* InAppBrowserView.swift */, |
| 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>"; }; |
Copilot
AI
Dec 28, 2025
There was a problem hiding this comment.
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.
| 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>"; }; |
| buildConfigurations = ( | ||
| 5D43701624791C2D00FFA37E /* Debug */, | ||
| 5D43701724791C2D00FFA37E /* Release */, | ||
| A1B2C3D4E5F60002 /* InAppBrowserView.swift in Sources */, |
Copilot
AI
Dec 28, 2025
There was a problem hiding this comment.
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.
| A1B2C3D4E5F60002 /* InAppBrowserView.swift in Sources */, |
| F090B4D9D3B115551BEF05B4 /* AsyncImageAttachment.swift in Sources */ = {isa = PBXBuildFile; | ||
| A1B2C3D4E5F60002 /* InAppBrowserView.swift in Sources */ = {isa = PBXBuildFile; fileRef = A1B2C3D4E5F60001 /* InAppBrowserView.swift */; }; fileRef = 8E707C08835B71223A7A3359 /* AsyncImageAttachment.swift */; }; |
Copilot
AI
Dec 28, 2025
There was a problem hiding this comment.
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.
| 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 */; }; |
🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
Code Coverage Report ❌Current coverage: 33.83% |
Summary
Changes
URL Routing
LinkHandlerclass inURLRouter.swiftto determine appropriate action for URLsInAppBrowserView
target="_blank"links via WKUIDelegateSettings
iOS 26 Bug Workaround
Test plan
target="_blank"links work in builtin browser🤖 Generated with Claude Code