Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
43 changes: 29 additions & 14 deletions WLocApp/WLocAppMac/AppWLocPACManager.swift
Original file line number Diff line number Diff line change
Expand Up @@ -89,11 +89,9 @@ final class AppWLocPACManager {
try ensureHelper()

let defaults = UserDefaults.standard
if let data = defaults.data(forKey: savedSettingsKey),
let staleSettings = try? JSONDecoder().decode([String: SavedPACSetting].self, from: data),
!staleSettings.isEmpty {
restorePAC(staleSettings)
defaults.removeObject(forKey: savedSettingsKey)
try loadSavedSettings()
if let error = restorePAC(previousSettings) {
throw error
}

let services = try activeNetworkServices()
Expand Down Expand Up @@ -153,12 +151,11 @@ final class AppWLocPACManager {
@discardableResult
private func stop() -> Error? {
var restoreError: Error?
if !previousSettings.isEmpty {
do {
try loadSavedSettings()
restoreError = restorePAC(previousSettings)
previousSettings.removeAll()
if restoreError == nil {
UserDefaults.standard.removeObject(forKey: savedSettingsKey)
}
} catch {
restoreError = error
}
if pacServer.isRunning { pacServer.stop() }
proxyServer.stop()
Expand All @@ -167,22 +164,40 @@ final class AppWLocPACManager {
return restoreError
}

private func loadSavedSettings() throws {
guard previousSettings.isEmpty,
let data = UserDefaults.standard.data(forKey: savedSettingsKey) else { return }
previousSettings = try JSONDecoder().decode([String: SavedPACSetting].self, from: data)
}

@discardableResult
private func restorePAC(_ settings: [String: SavedPACSetting]) -> Error? {
var restoreError: Error?
guard !settings.isEmpty else { return nil }
var failures: [String] = []
var remaining = settings
for (service, setting) in settings {
do {
try setPAC([AppWLocPACNetworkSetting(
service: service,
url: setting.url,
enabled: setting.enabled
)])
remaining.removeValue(forKey: service)
} catch {
restoreError = error
AppWLocUtils.debugLog("PAC 忽略无法恢复的网络服务 \(service):\(error.localizedDescription)")
failures.append("\(service):\(error.localizedDescription)")
}
}
return restoreError
previousSettings = remaining
if remaining.isEmpty {
UserDefaults.standard.removeObject(forKey: savedSettingsKey)
} else {
do {
UserDefaults.standard.set(try JSONEncoder().encode(remaining), forKey: savedSettingsKey)
} catch {
return error
}
}
return failures.isEmpty ? nil : AppWLocPACError.commandFailed(failures.sorted().joined(separator: "\n"))
}

private func ensureHelper() throws {
Expand Down
84 changes: 69 additions & 15 deletions WLocApp/WLocAppMac/WLocMacMapViewController.swift
Original file line number Diff line number Diff line change
Expand Up @@ -90,16 +90,16 @@ private final class WLocMacLinkButton: NSButton {
}

private func updateAppearance() {
let alpha: CGFloat = isPointerInside ? 0.92 : 0.78
let alpha: CGFloat = isPointerInside ? 0.14 : 0.04
layer?.backgroundColor = baseColor.withAlphaComponent(alpha).cgColor
attributedTitle = NSAttributedString(
string: title,
attributes: [
.foregroundColor: NSColor.white,
.foregroundColor: NSColor.secondaryLabelColor,
.font: NSFont.systemFont(ofSize: 13, weight: .semibold)
]
)
contentTintColor = .white
contentTintColor = .secondaryLabelColor
}
}

Expand All @@ -126,6 +126,7 @@ final class WLocMacMapViewController: NSViewController {
private let detailLabel = NSTextField.wlocLabel("")
private let coordinateLabel = NSTextField.wlocLabel("")
private let lockButton = NSButton.wlocButton("锁定位置")
private let restoreButton = NSButton.wlocButton("恢复定位")
private let favoriteButton = NSButton.wlocButton("加入收藏")
private let coordinateInputButton = NSButton.wlocButton("经纬度选点")
private let tutorialButton = NSButton.wlocButton("教程与证书")
Expand Down Expand Up @@ -156,8 +157,11 @@ final class WLocMacMapViewController: NSViewController {
private var availableUpdate: AppWLocAvailableUpdate?
private var shouldSelectNextLocationUpdate = false
private var isRefreshingLocationAfterLock = false
private var isChangingLocation = false
private var locationRefreshWorkItem: DispatchWorkItem?

deinit {
locationRefreshWorkItem?.cancel()
if let outsideSearchClickMonitor {
NSEvent.removeMonitor(outsideSearchClickMonitor)
}
Expand Down Expand Up @@ -268,7 +272,7 @@ final class WLocMacMapViewController: NSViewController {
favoriteMenu.addItem(deleteItem)
favoritesTable.menu = favoriteMenu

[lockButton, favoriteButton, coordinateInputButton, tutorialButton, telegramButton, githubButton].forEach {
[lockButton, restoreButton, favoriteButton, coordinateInputButton, tutorialButton, telegramButton, githubButton].forEach {
$0.bezelStyle = .rounded
$0.controlSize = .regular
}
Expand All @@ -285,6 +289,8 @@ final class WLocMacMapViewController: NSViewController {
button.font = .systemFont(ofSize: 18, weight: .semibold)
}

restoreButton.target = self
restoreButton.action = #selector(restoreLocation)
lockButton.target = self
lockButton.action = #selector(lockCurrentPlace)
favoriteButton.target = self
Expand Down Expand Up @@ -383,12 +389,18 @@ final class WLocMacMapViewController: NSViewController {
favoritesScroll.hasVerticalScroller = true
favoritesScroll.borderType = .noBorder

let actionStack = NSStackView(views: [lockButton, favoriteButton, coordinateInputButton])
let actionStack = NSStackView(views: [lockButton, restoreButton])
actionStack.orientation = .horizontal
actionStack.spacing = 10
actionStack.distribution = .fillEqually

let secondaryActionStack = NSStackView(views: [tutorialButton])
lockButton.bezelColor = .controlAccentColor
let selectionStack = NSStackView(views: [favoriteButton, coordinateInputButton])
selectionStack.orientation = .horizontal
selectionStack.spacing = 10
selectionStack.distribution = .fillEqually

let secondaryActionStack = NSStackView(views: [selectionStack, tutorialButton])
secondaryActionStack.orientation = .vertical
secondaryActionStack.spacing = 15
secondaryActionStack.distribution = .fillEqually
Expand All @@ -397,19 +409,17 @@ final class WLocMacMapViewController: NSViewController {
externalLinkStack.orientation = .horizontal
externalLinkStack.spacing = 10
externalLinkStack.distribution = .fillEqually
secondaryActionStack.addArrangedSubview(externalLinkStack)

externalLinkStack.snp.makeConstraints { make in
make.width.equalTo(secondaryActionStack).offset(-36)
selectionStack.snp.makeConstraints { make in
make.width.equalTo(secondaryActionStack)
}

[lockButton, favoriteButton, coordinateInputButton, tutorialButton, telegramButton, githubButton].forEach { button in
[lockButton, restoreButton, favoriteButton, coordinateInputButton, tutorialButton, telegramButton, githubButton].forEach { button in
button.snp.makeConstraints { make in
make.height.equalTo(36)
}
}

[appNameLabel, versionLabel, updateButton, searchField, titleLabel, detailLabel, coordinateLabel, actionStack, secondaryActionStack, favoriteTitle, favoritesScroll].forEach {
[appNameLabel, versionLabel, updateButton, searchField, titleLabel, detailLabel, coordinateLabel, actionStack, secondaryActionStack, favoriteTitle, favoritesScroll, externalLinkStack].forEach {
sidebar.contentView.addSubview($0)
}

Expand Down Expand Up @@ -450,7 +460,7 @@ final class WLocMacMapViewController: NSViewController {
}
secondaryActionStack.snp.makeConstraints { make in
make.top.equalTo(actionStack.snp.bottom).offset(10)
make.leading.trailing.equalToSuperview()
make.leading.trailing.equalTo(searchField)
}
favoriteTitle.snp.makeConstraints { make in
make.top.equalTo(secondaryActionStack.snp.bottom).offset(20)
Expand All @@ -459,7 +469,11 @@ final class WLocMacMapViewController: NSViewController {
favoritesScroll.snp.makeConstraints { make in
make.top.equalTo(favoriteTitle.snp.bottom).offset(8)
make.leading.trailing.equalTo(searchField)
make.bottom.equalToSuperview().inset(16)
make.bottom.equalTo(externalLinkStack.snp.top).offset(-12)
}
externalLinkStack.snp.makeConstraints { make in
make.leading.trailing.equalTo(searchField)
make.bottom.equalToSuperview().inset(12)
}

searchResultsPanel.snp.makeConstraints { make in
Expand Down Expand Up @@ -672,18 +686,26 @@ final class WLocMacMapViewController: NSViewController {
}

private func lock(_ place: AppWLocPlace, successMessage: String) {
guard !isChangingLocation else { return }
isChangingLocation = true
locationRefreshWorkItem?.cancel()
restoreButton.isEnabled = false
lockButton.isEnabled = false
lockButton.title = "锁定中..."
pacManager.lock(to: place) { [weak self] result in
AppWLocUtils.mainThread {
guard let self else { return }
self.isChangingLocation = false
self.restoreButton.isEnabled = true
self.lockButton.isEnabled = true
switch result {
case .success:
self.lockButton.title = "锁定位置"
AppWLocUtils.mainThreadAfter(2.0) { [weak self] in
let refresh = DispatchWorkItem { [weak self] in
self?.startSystemLocationRefresh(selectResult: false)
}
self.locationRefreshWorkItem = refresh
DispatchQueue.main.asyncAfter(deadline: .now() + 2, execute: refresh)
self.showAlert(title: "已锁定", message: successMessage)
case .failure(let error):
self.lockButton.title = "锁定位置"
Expand All @@ -693,6 +715,33 @@ final class WLocMacMapViewController: NSViewController {
}
}

@objc private func restoreLocation() {
guard !isChangingLocation else { return }
isChangingLocation = true
locationRefreshWorkItem?.cancel()
locationRefreshWorkItem = nil
shouldSelectNextLocationUpdate = false
isRefreshingLocationAfterLock = false
locationManager.stopUpdatingLocation()
restoreButton.isEnabled = false
lockButton.isEnabled = false
restoreButton.title = "恢复中…"

pacManager.stop(clearState: true) { [weak self] error in
guard let self else { return }
self.isChangingLocation = false
self.restoreButton.isEnabled = true
self.lockButton.isEnabled = true
if let error {
self.restoreButton.title = "重试恢复"
self.showAlert(title: "恢复失败", message: "原代理设置尚未全部恢复,请重试。\n\n\(error.localizedDescription)")
} else {
self.restoreButton.title = "恢复定位"
self.showAlert(title: "已停止修改定位", message: "已恢复原代理设置。请关闭系统定位服务,等待两秒后重新开启,以刷新实际位置。")
}
}
}

@discardableResult
func handleDeepLink(_ url: URL) -> Bool {
do {
Expand All @@ -712,6 +761,7 @@ final class WLocMacMapViewController: NSViewController {
}

private func applyExternalLocation(_ place: AppWLocPlace) {
guard !isChangingLocation else { return }
view.window?.makeFirstResponder(nil)
reverseGeocodeWorkItem?.cancel()
geocoder.cancelGeocode()
Expand Down Expand Up @@ -933,6 +983,7 @@ final class WLocMacMapViewController: NSViewController {
}

private func startSystemLocationRefresh(selectResult: Bool) {
guard !isChangingLocation else { return }
shouldSelectNextLocationUpdate = selectResult
isRefreshingLocationAfterLock = !selectResult
mapView.showsUserLocation = true
Expand Down Expand Up @@ -962,6 +1013,7 @@ final class WLocMacMapViewController: NSViewController {
}

private func handleLocationAuthorizationChange(_ status: CLAuthorizationStatus) {
guard !isChangingLocation else { return }
let hasLockedState = AppWLocStateStore.shared.load() != nil
AppWLocUtils.debugLog(
"\(AppWLocConfig.displayName) macOS 定位授权变化 status=\(authorizationStatusDescription(status)),afterLock=\(isRefreshingLocationAfterLock),selectNext=\(shouldSelectNextLocationUpdate),locked=\(hasLockedState)"
Expand Down Expand Up @@ -1148,6 +1200,7 @@ extension WLocMacMapViewController: CLLocationManagerDelegate {
}

func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
guard !isChangingLocation else { return }
guard let location = locations.last else { return }
manager.stopUpdatingLocation()
AppWLocUtils.debugLog(
Expand All @@ -1162,6 +1215,7 @@ extension WLocMacMapViewController: CLLocationManagerDelegate {
}

func locationManager(_ manager: CLLocationManager, didFailWithError error: Error) {
guard !isChangingLocation else { return }
manager.stopUpdatingLocation()
AppWLocUtils.debugLog("\(AppWLocConfig.displayName) macOS 定位刷新失败:\(error.localizedDescription)")
let shouldShowError = shouldSelectNextLocationUpdate || !isRefreshingLocationAfterLock
Expand Down
4 changes: 2 additions & 2 deletions WLocApp/WLocAppMac/WLocMacTutorialWindowController.swift
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,7 @@ final class WLocMacTutorialViewController: NSViewController {

addTitle("二、锁定位置")
addStep("1. 在主界面搜索地点,或拖动地图到目标位置。")
addStep("2. 点击“锁定位置”,系统会自动添加或启动 \(AppWLocConfig.displayName) VPN。")
addStep("2. 点击“锁定位置”,应用会启用本地定位代理,并配置系统 PAC。")
addStep("3. 打开系统定位服务,关闭后等待两秒再打开。")

let line = NSView()
Expand All @@ -91,7 +91,7 @@ final class WLocMacTutorialViewController: NSViewController {
}

addTitle("三、恢复原始位置")
addStep("1. 退出应用会自动断开 \(AppWLocConfig.displayName) VPN。")
addStep("1. 点击主界面的“恢复定位”恢复原代理设置;若提示失败,请点击“重试恢复”。退出应用也会尝试恢复。")
addStep("2. 再次关开定位服务。如未恢复,重启电脑后再试。")
}

Expand Down
Loading