Эх сурвалжийг харах

feat: 补充 web 的部分回调

陈文艺 3 сар өмнө
parent
commit
52cc8fd0d5

+ 3 - 2
Lanu.xcodeproj/project.pbxproj

@@ -270,6 +270,7 @@
 				Views/Settings/LNAboutViewController.swift,
 				Views/Settings/LNHelpCenterViewController.swift,
 				Views/Settings/LNLanguageSettingPanel.swift,
+				Views/Settings/LNNewVersionView.swift,
 				Views/Settings/LNSettingsViewController.swift,
 				Views/Wallet/Coin/LNCoinToDiamondPanel.swift,
 				Views/Wallet/Coin/LNCoinViewController.swift,
@@ -524,7 +525,7 @@
 				ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
 				CLANG_ENABLE_MODULES = YES;
 				CODE_SIGN_STYLE = Automatic;
-				CURRENT_PROJECT_VERSION = 9;
+				CURRENT_PROJECT_VERSION = 10;
 				DEVELOPMENT_TEAM = 5H8D98R72W;
 				ENABLE_USER_SCRIPT_SANDBOXING = NO;
 				GENERATE_INFOPLIST_FILE = YES;
@@ -567,7 +568,7 @@
 				ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
 				CLANG_ENABLE_MODULES = YES;
 				CODE_SIGN_STYLE = Automatic;
-				CURRENT_PROJECT_VERSION = 9;
+				CURRENT_PROJECT_VERSION = 10;
 				DEVELOPMENT_TEAM = 5H8D98R72W;
 				ENABLE_USER_SCRIPT_SANDBOXING = NO;
 				GENERATE_INFOPLIST_FILE = YES;

+ 1 - 0
Lanu/AppDelegate.swift

@@ -26,6 +26,7 @@ class AppDelegate: UIResponder, UIApplicationDelegate {
         _ = RechargeManager.shared
         _ = LNLocationManager.shared
         _ = LNRelationManager.shared
+        _ = LNDeeplinkManager.shared
         
         LNEventDeliver.notifyAppLaunchFinished()
         

+ 22 - 0
Lanu/Assets.xcassets/Profile/Settings/ic_delete.imageset/Contents.json

@@ -0,0 +1,22 @@
+{
+  "images" : [
+    {
+      "idiom" : "universal",
+      "scale" : "1x"
+    },
+    {
+      "filename" : "ic_delete@2x.png",
+      "idiom" : "universal",
+      "scale" : "2x"
+    },
+    {
+      "filename" : "ic_delete@3x.png",
+      "idiom" : "universal",
+      "scale" : "3x"
+    }
+  ],
+  "info" : {
+    "author" : "xcode",
+    "version" : 1
+  }
+}

BIN
Lanu/Assets.xcassets/Profile/Settings/ic_delete.imageset/ic_delete@2x.png


BIN
Lanu/Assets.xcassets/Profile/Settings/ic_delete.imageset/ic_delete@3x.png


+ 54 - 3
Lanu/Common/Config/LNAppConfig.swift

@@ -53,6 +53,9 @@ enum LNAppLanguage: Codable {
 class LNAppConfig {
     static var shared = LNAppConfig()
     
+    @Published
+    private(set) var hasNewVersion = false
+    
     var curEnv: LNAppEnvType = LNUserDefaults[.appEnv, .official] {
         didSet {
             LNUserDefaults[.appEnv] = curEnv
@@ -61,10 +64,9 @@ class LNAppConfig {
     
     var curLang: LNAppLanguage = LNUserDefaults[.appLanguage, .english] {
         didSet {
+            guard oldValue != curLang else { return }
             LNUserDefaults[.appLanguage] = curLang
-            if oldValue != curLang {
-                notifyLanguageChanged()
-            }
+            notifyLanguageChanged()
         }
     }
     
@@ -82,6 +84,55 @@ class LNAppConfig {
     var languageBundle: Bundle {
         languageBundleMap[curLang] ?? Bundle.main
     }
+    
+    init() {
+        checkNewVersion()
+        LNEventDeliver.addObserver(self)
+    }
+}
+
+extension LNAppConfig {
+    func jumpToAppStore() {
+        let appStoreJumpURL = "https://apps.apple.com/us/app/gamigo/id6756352551?action=write-review"
+        guard let url = URL(string: appStoreJumpURL) else { return }
+        
+        if UIApplication.shared.canOpenURL(url) {
+            UIApplication.shared.open(url, options: [:], completionHandler: nil)
+        }
+    }
+    
+    private func checkNewVersion() {
+        let appStoreLookupURL = "https://itunes.apple.com/lookup?bundleId=\(curAppBundleIdentifier)"
+        guard let url = URL(string: appStoreLookupURL) else {
+            return
+        }
+            
+        let task = URLSession.shared.dataTask(with: url)
+        { [weak self] data, response,
+            error in
+            guard let self = self else { return }
+               
+            guard error == nil,
+                  let data else {
+                return
+            }
+            
+            if let json = try? JSONSerialization.jsonObject(with: data) as? [String: Any],
+               let results = json["results"] as? [[String: Any]],
+               let appStoreVersion = results.first?["version"] as? String {
+                hasNewVersion = curAppVersion != appStoreVersion
+            }
+        }
+        task.resume()
+    }
+}
+
+extension LNAppConfig: LNNetworkMonitorNotify {
+    func onNetworkStateChanged(state: LNNetworkState) {
+        if case .available = state {
+            checkNewVersion()
+        }
+    }
 }
 
 extension LNAppConfig {

+ 10 - 6
Lanu/Common/Config/String+Urls.swift

@@ -12,13 +12,17 @@ extension String {
     static var webUrlHost: String {
         LNAppConfig.shared.curEnv == .test ? "https://test-web.gami.vip" : "https://web.gami.vip"
     }
-    static var privacyUrl: String {
+    static var privacyUrl: String = {
         "\(webUrlHost)/about/privacyPolicy"
-    }
-    static var serviceUrl: String {
+    }()
+    static var serviceUrl: String = {
         "\(webUrlHost)/about/termsOfService"
-    }
-    static var communityUrl: String {
+    }()
+    static var communityUrl: String = {
         "\(webUrlHost)/about/communityGuideline"
-    }
+    }()
+    
+    static var deleteAccountUrl: String = {
+        "\(webUrlHost)/mine/cancellation"
+    }()
 }

+ 0 - 19
Lanu/Common/Extension/NSObject+Extension.swift

@@ -15,25 +15,6 @@ private final class CancellableBagContainer {
     var cancellables: Set<AnyCancellable> = []
     /// 读写锁(避免多线程操作Set崩溃)
     let lock = NSLock()
-    
-    func insert(_ cancellable: AnyCancellable) {
-        lock.lock()
-        cancellables.insert(cancellable)
-        lock.unlock()
-    }
-    
-    func removeAll() {
-        lock.lock()
-        cancellables.removeAll()
-        lock.unlock()
-    }
-    
-    var count: Int {
-        lock.lock()
-        let count = cancellables.count
-        lock.unlock()
-        return count
-    }
 }
 
 // 2. 关联对象Key(私有静态变量)

+ 5 - 0
Lanu/Common/Voice/LNVoicePlayer.swift

@@ -15,6 +15,11 @@ protocol LNVoicePlayerNotify {
     func onAudioUpdateDuration(path: String, cur: TimeInterval)
     func onAudioStopPlay(path: String)
 }
+extension LNVoicePlayerNotify {
+    func onAudioStartPlay(path: String) { }
+    func onAudioUpdateDuration(path: String, cur: TimeInterval) { }
+    func onAudioStopPlay(path: String) { }
+}
 
 
 enum LNVoiceSource: Codable {

+ 36 - 2
Lanu/Manager/Deeplink/LNDeeplinkManager.swift

@@ -8,12 +8,46 @@
 import Foundation
 
 
+struct LNDeeplinkUrls {
+    static let appScheme = "gami"
+    
+    enum web: String {
+        case closeWindow
+        case userCancellation
+    }
+}
+
+extension RawRepresentable where RawValue == String {
+    var deeplinkPath: String {
+        let fullTypeName = String(reflecting: Self.self)
+        let components = fullTypeName.components(separatedBy: CharacterSet(charactersIn: "<>[]()")).first!
+        
+        var nestedComponents = components.components(separatedBy: ".")
+        if nestedComponents.count > 1 {
+            // 移除最外层 lanu
+            nestedComponents.removeFirst()
+        }
+        if nestedComponents.count > 1 {
+            // 移除 LNDeeplinkUrls
+            nestedComponents.removeFirst()
+        }
+        nestedComponents.append(rawValue)
+        return nestedComponents.joined(separator: "/")
+    }
+}
+
+
 class LNDeeplinkManager {
     static let shared = LNDeeplinkManager()
+    typealias LNDeeplinkHandler = ([String: Any]) -> Void
     
-    private init() { }
+    private var routerMap: [String: LNDeeplinkHandler] = [:]
     
-    func handleDeepLink(_ url: String) {
+    private init() {
         
     }
+    
+    func handleDeepLink(_ url: String) {
+        routerMap[url]?([:])
+    }
 }

+ 3 - 1
Lanu/SceneDelegate.swift

@@ -76,7 +76,9 @@ extension SceneDelegate: LNNetworkMonitorNotify {
 
 extension SceneDelegate: LNAppMainEvent {
     func onAppLanguageChanged(newLanguage: LNAppLanguage) {
-        window?.rootViewController = LNNavigationController(rootViewController: LNMainViewController())
+        let nav = LNNavigationController(rootViewController: LNMainViewController())
+        window?.rootViewController = nav
+        nav.viewControllers.append(LNSettingsViewController())
     }
 }
 

+ 4 - 1
Lanu/Views/Game/MateList/LNGameMateListCell.swift

@@ -120,7 +120,10 @@ extension LNGameMateListCell {
         container.onTap { [weak self] in
             guard let self else { return }
             guard let curItem else { return }
-            pushToSkillDetail(curItem.id)
+//            pushToSkillDetail(curItem.id)
+            var name = Mirror(reflecting: LNDeeplinkUrls.self)
+//            var test = 
+//            var i = 0
         }
         contentView.addSubview(container)
         container.snp.makeConstraints { make in

+ 1 - 0
Lanu/Views/Game/Skill/LNSkillBottomMenuView.swift

@@ -30,6 +30,7 @@ class LNSkillBottomMenuView: UIView {
         priceLabel.attributedText = attrStr
         
         curDetail = detail
+        isHidden = detail.userNo.isMyUid
     }
     
     required init?(coder: NSCoder) {

+ 18 - 5
Lanu/Views/Game/Skill/LNSkillVoiceBarView.swift

@@ -13,11 +13,13 @@ import SnapKit
 class LNSkillVoiceBarView: UIView {
     private let durationLabel = UILabel()
     private var curUrl: String?
+    private let playStateIc = UIImageView()
     
     override init(frame: CGRect) {
         super.init(frame: frame)
         
         setupViews()
+        LNEventDeliver.addObserver(self)
     }
     
     func setVoice(_ url: String) {
@@ -38,6 +40,18 @@ class LNSkillVoiceBarView: UIView {
     }
 }
 
+extension LNSkillVoiceBarView: LNVoicePlayerNotify {
+    func onAudioStartPlay(path: String) {
+        guard path == curUrl else { return }
+        playStateIc.image = .init(named: "ic_voice_pause")
+    }
+    
+    func onAudioStopPlay(path: String) {
+        guard path == curUrl else { return }
+        playStateIc.image = .init(named: "ic_voice_play")
+    }
+}
+
 extension LNSkillVoiceBarView {
     private func setupViews() {
         isHidden = true
@@ -65,10 +79,9 @@ extension LNSkillVoiceBarView {
             make.directionalHorizontalEdges.equalToSuperview().inset(8)
         }
         
-        let play = UIImageView()
-        play.image = .init(named: "ic_voice_play")
-        container.addSubview(play)
-        play.snp.makeConstraints { make in
+        playStateIc.image = .init(named: "ic_voice_play")
+        container.addSubview(playStateIc)
+        playStateIc.snp.makeConstraints { make in
             make.leading.equalToSuperview()
             make.verticalEdges.equalToSuperview()
         }
@@ -78,7 +91,7 @@ extension LNSkillVoiceBarView {
         container.addSubview(voice)
         voice.snp.makeConstraints { make in
             make.centerY.equalToSuperview()
-            make.leading.equalTo(play.snp.trailing).offset(6)
+            make.leading.equalTo(playStateIc.snp.trailing).offset(6)
         }
         
         durationLabel.font = .heading_h5

+ 0 - 1
Lanu/Views/IM/Chat/GameMate/LNIMChatGameMateSkillCell.swift

@@ -127,7 +127,6 @@ extension LNIMChatGameMateSkillCell {
             make.trailing.lessThanOrEqualTo(order.snp.leading).offset(-16)
         }
         
-        gameNameLabel.text = "123"
         gameNameLabel.font = .heading_h4
         gameNameLabel.textColor = .text_5
         gameNameLabel.setContentHuggingPriority(.defaultLow, for: .horizontal)

+ 1 - 3
Lanu/Views/IM/Chat/GameMate/LNIMChatGameMateSkillView.swift

@@ -59,9 +59,7 @@ class LNIMChatGameMateSkillView: UIView {
 
 extension LNIMChatGameMateSkillView: LNIMChatGameMateSkillCellDelegate {
     func onIMChatGameMateSkillCell(cell: LNIMChatGameMateSkillCell, didClickOrder skill: LNGameMateSkillVO) {
-        let panel = LNCreateOrderFromSkillListPanel()
-        panel.update(skills, selected: skill)
-        panel.showIn(self)
+        pushToCreateOrder(skill.id)
     }
 }
 

+ 10 - 5
Lanu/Views/Profile/Profile/LNProfileScoreFloatingView.swift

@@ -12,6 +12,7 @@ import SnapKit
 
 class LNProfileScoreFloatingView: UIView {
     private var curDetail: LNUserProfileVO?
+    private let background = UIImageView()
     
     override init(frame: CGRect) {
         super.init(frame: frame)
@@ -25,6 +26,11 @@ class LNProfileScoreFloatingView: UIView {
         isHidden = !detail.playmate || detail.rated || detail.userNo.isMyUid
     }
     
+    override func layoutSubviews() {
+        super.layoutSubviews()
+        background.layer.cornerRadius = background.bounds.height * 0.5
+    }
+    
     required init?(coder: NSCoder) {
         fatalError("init(coder:) has not been implemented")
     }
@@ -78,7 +84,6 @@ extension LNProfileScoreFloatingView {
             make.leading.lessThanOrEqualToSuperview()
         }
         
-        let background = UIImageView()
         background.image = .primary_7
         background.layer.cornerRadius = 9.5
         background.clipsToBounds = true
@@ -87,8 +92,7 @@ extension LNProfileScoreFloatingView {
             make.directionalHorizontalEdges.equalToSuperview()
             make.bottom.equalToSuperview()
             make.bottom.equalTo(ic.snp.bottom).offset(8)
-            make.width.equalTo(56)
-            make.height.equalTo(19)
+            make.width.lessThanOrEqualTo(74)
         }
         
         let label = UILabel()
@@ -96,10 +100,11 @@ extension LNProfileScoreFloatingView {
         label.font = .heading_h5
         label.textColor = .text_1
         label.textAlignment = .center
+        label.numberOfLines = 0
         background.addSubview(label)
         label.snp.makeConstraints { make in
-            make.center.equalToSuperview()
-            make.leading.greaterThanOrEqualToSuperview()
+            make.verticalEdges.equalToSuperview().inset(4)
+            make.directionalHorizontalEdges.equalToSuperview().inset(6)
         }
     }
     

+ 8 - 1
Lanu/Views/Settings/LNAboutViewController.swift

@@ -98,7 +98,14 @@ extension LNAboutViewController {
         }
         stackView.addArrangedSubview(social)
         
-        let version = buildFunctionItem(title: .init(key: "版本更新"), infoView: nil)
+        let newVersion = LNNewVersionView()
+        newVersion.isHidden = !LNAppConfig.shared.hasNewVersion
+        let version = buildFunctionItem(title: .init(key: "版本更新"), infoView: newVersion)
+        if LNAppConfig.shared.hasNewVersion {
+            version.onTap {
+                LNAppConfig.shared.jumpToAppStore()
+            }
+        }
         stackView.addArrangedSubview(version)
     }
     

+ 42 - 0
Lanu/Views/Settings/LNNewVersionView.swift

@@ -0,0 +1,42 @@
+//
+//  LNNewVersionView.swift
+//  Lanu
+//
+//  Created by OneeChan on 2026/1/4.
+//
+
+import Foundation
+import UIKit
+import SnapKit
+
+
+class LNNewVersionView: UIView {
+    override init(frame: CGRect) {
+        super.init(frame: frame)
+        
+        let background = UIImageView()
+        background.layer.cornerRadius = 8
+        background.image = .primary_7
+        background.clipsToBounds = true
+        background.isUserInteractionEnabled = false
+        addSubview(background)
+        background.snp.makeConstraints { make in
+            make.directionalEdges.equalToSuperview()
+            make.height.equalTo(16)
+        }
+        
+        let newLabel = UILabel()
+        newLabel.font = .body_s
+        newLabel.textColor = .text_1
+        newLabel.text = .init(key: "New")
+        addSubview(newLabel)
+        newLabel.snp.makeConstraints { make in
+            make.center.equalToSuperview()
+            make.leading.equalToSuperview().offset(7)
+        }
+    }
+    
+    required init?(coder: NSCoder) {
+        fatalError("init(coder:) has not been implemented")
+    }
+}

+ 9 - 17
Lanu/Views/Settings/LNSettingsViewController.swift

@@ -21,7 +21,6 @@ extension UIView {
 class LNSettingsViewController: LNViewController {
     private let curLanguageLabel = UILabel()
     private let cacheCountLabel = UILabel()
-    private let newVersionTag = UIImageView()
     
     override func viewDidLoad() {
         super.viewDidLoad()
@@ -68,22 +67,8 @@ extension LNSettingsViewController {
         cacheCountLabel.text = "100MB"
         stackView.addArrangedSubview(cleanCache)
         
-        newVersionTag.layer.cornerRadius = 8
-        newVersionTag.image = .primary_7
-        newVersionTag.clipsToBounds = true
-        newVersionTag.isUserInteractionEnabled = false
-        newVersionTag.snp.makeConstraints { make in
-            make.height.equalTo(16)
-        }
-        let newLabel = UILabel()
-        newLabel.font = .body_s
-        newLabel.textColor = .text_1
-        newLabel.text = .init(key: "New")
-        newVersionTag.addSubview(newLabel)
-        newLabel.snp.makeConstraints { make in
-            make.center.equalToSuperview()
-            make.leading.equalToSuperview().offset(7)
-        }
+        let newVersionTag = LNNewVersionView()
+        newVersionTag.isHidden = !LNAppConfig.shared.hasNewVersion
         let about = buildFunctionItem(icName: "ic_about", title: .init(key: "关于Gami"), infoView: newVersionTag)
         about.onTap { [weak self] in
             guard let self else { return }
@@ -91,6 +76,13 @@ extension LNSettingsViewController {
         }
         stackView.addArrangedSubview(about)
         
+        let delete = buildFunctionItem(icName: "ic_delete", title: .init(key: "删除账号"), infoView: nil)
+        delete.onTap { [weak self] in
+            guard let self else { return }
+            view.pushToWebView(.init(url: .deleteAccountUrl))
+        }
+        stackView.addArrangedSubview(delete)
+        
         let logoutView = UIView()
         logoutView.backgroundColor = .fill
         logoutView.layer.cornerRadius = 12

+ 24 - 0
Lanu/Views/Web/LNWebViewController.swift

@@ -62,7 +62,31 @@ extension LNWebViewController {
 }
 
 extension LNWebViewController: WKNavigationDelegate {
+    func webView(_ webView: WKWebView, decidePolicyFor navigationAction: WKNavigationAction,
+                 decisionHandler: @escaping @MainActor (WKNavigationActionPolicy) -> Void) {
+        guard let url = navigationAction.request.url,
+              let scheme = url.scheme else {
+            decisionHandler(.allow) // 无 URL/Schema,允许默认行为
+            return
+        }
+        
+        guard scheme.lowercased() == LNDeeplinkUrls.appScheme else {
+            decisionHandler(.allow)
+            return
+        }
+        let fullPath = if let host = url.host {
+            "\(host)\(url.path)"
+        } else {
+            url.path
+        }
+        if fullPath == LNDeeplinkUrls.web.closeWindow.deeplinkPath {
+            navigationController?.popViewController(animated: true)
+        } else if fullPath == LNDeeplinkUrls.web.userCancellation.deeplinkPath {
+            LNAccountManager.shared.logout()
+        }
         
+        decisionHandler(.cancel)
+    }
 }
 
 extension LNWebViewController {