From: liangzhenying <867174124@qq.com> Date: Thu, 26 Feb 2026 07:16:57 +0000 (+0800) Subject: 运动播报修改 X-Git-Url: http://git.snicker.com.cn/?a=commitdiff_plain;h=770db3a97899425704ffe927c2dbe2224dafd5c4;p=mirrors%2FShenYin.git 运动播报修改 --- diff --git a/ShenYin/DeepChordsSdk/DCTts.swift b/ShenYin/DeepChordsSdk/DCTts.swift new file mode 100644 index 0000000..acf2de2 --- /dev/null +++ b/ShenYin/DeepChordsSdk/DCTts.swift @@ -0,0 +1,46 @@ +// +// DCTts.swift +// ShenYin +// +// Created by liangshi on 2026/2/26. +// +import StarburstSdk +public class DCTts:NSObject{ + public static let shared = DCTts() + + /// + var ttsCallBack:((_ stream:Data)->Void)? + + /// 文本合成语音 + /// - Parameters: + /// - text: 文本 + /// - callback: 音频流回调 + public func tts(text:String,callback: @escaping ((Data))->Void){ + self.ttsCallBack = callback + TextToSpeechManager.sharedInstance().textToSpeech( + companyId: "2101982539", + text: text, + reqId: UUID().uuidString, + speedRatio: 1.0, + voiceType: "zh_female_qingchezizi_moon_bigtts", + encoding: "pcm", + callback: self + ) + } +} +extension DCTts: TextToSpeechCallback { + public func onResult(_ response: String) { + guard let jsonObj = try? JSONSerialization.jsonObject(with: response.data(using: .utf8)!, options: []) as? [String:Any], + let respObj = jsonObj["resp"] as? [String:Any], + let base64Data = respObj["data"] as? String, + let pcmBytes = Data(base64Encoded: base64Data) else { + DCLogger.debug("解析PCM失败\n") + return + } + ttsCallBack?(pcmBytes) + } + + public func onError(_ code: Int, message: String) { + DCLogger.debug("合成失败:\(message)\n") + } +} diff --git a/ShenYin/Podfile.lock b/ShenYin/Podfile.lock index 7d0c168..9df3bbc 100644 --- a/ShenYin/Podfile.lock +++ b/ShenYin/Podfile.lock @@ -1,4 +1,11 @@ PODS: + - AMap3DMap-NO-IDFA (10.0.700): + - AMapFoundation-NO-IDFA (>= 1.8.0) + - AMapFoundation-NO-IDFA (1.8.2) + - AMapLocation-NO-IDFA (2.10.0): + - AMapFoundation-NO-IDFA (>= 1.8.0) + - AMapSearch-NO-IDFA (9.7.0): + - AMapFoundation-NO-IDFA (>= 1.8.0) - CryptoSwift (1.8.4) - DeviceKit (5.4.0) - DGActivityIndicatorView (2.1.1) @@ -70,6 +77,9 @@ PODS: - ZipArchive (1.4.0) DEPENDENCIES: + - AMap3DMap-NO-IDFA (= 10.0.700) + - AMapLocation-NO-IDFA + - AMapSearch-NO-IDFA (= 9.7.0) - CryptoSwift - DeviceKit (~> 5.4.0) - DGActivityIndicatorView @@ -81,7 +91,6 @@ DEPENDENCIES: - JXPagingView/Paging - JXSegmentedView - Kingfisher - - Kingfisher (~> 6.0) - MBProgressHUD - MJRefresh - SnapKit @@ -98,6 +107,10 @@ DEPENDENCIES: SPEC REPOS: https://gitee.com/mirrors/CocoaPods-Specs.git: + - AMap3DMap-NO-IDFA + - AMapFoundation-NO-IDFA + - AMapLocation-NO-IDFA + - AMapSearch-NO-IDFA - CryptoSwift - DeviceKit - DGActivityIndicatorView @@ -131,6 +144,10 @@ SPEC REPOS: - ZipArchive SPEC CHECKSUMS: + AMap3DMap-NO-IDFA: e029d65ea35d3e4c0a948e1dab9e77109bf0e86a + AMapFoundation-NO-IDFA: 6ce0ef596d4eb8d934ff498e56747b6de1247b05 + AMapLocation-NO-IDFA: 735791aeb1ba52dff666e4158b86caf4b68b5be7 + AMapSearch-NO-IDFA: 749f73d5a773758f01a24eb8872f8798c8b47cb4 CryptoSwift: e64e11850ede528a02a0f3e768cec8e9d92ecb90 DeviceKit: bd34bb1766d926c3cc5a5f6e3e9a6dd10148b805 DGActivityIndicatorView: ff2c76073d79692f724863c8dd38685866a03cb6 @@ -163,6 +180,6 @@ SPEC CHECKSUMS: YYText: 5c461d709e24d55a182d1441c41dc639a18a4849 ZipArchive: e25a4373192673e3229ac8d6e9f64a3e5713c966 -PODFILE CHECKSUM: d3adf5685042140a1efe3de6a225914ce9255e5c +PODFILE CHECKSUM: 6c1824ce6ff4ee834e74a20980186ec7c76f1d1c COCOAPODS: 1.16.2 diff --git a/ShenYin/ShenYin.xcodeproj/project.pbxproj b/ShenYin/ShenYin.xcodeproj/project.pbxproj index 4db690f..75fb146 100644 --- a/ShenYin/ShenYin.xcodeproj/project.pbxproj +++ b/ShenYin/ShenYin.xcodeproj/project.pbxproj @@ -782,6 +782,7 @@ 3F887B9A2E38D8DC00C98658 /* Resources */, 5A934A35F54A5620CFD3C08A /* [CP] Embed Pods Frameworks */, 3FEFE9092F1B252000D82F17 /* Embed Frameworks */, + 169D8C02ED63ABC1BD3F8125 /* [CP] Copy Pods Resources */, ); buildRules = ( ); @@ -894,6 +895,27 @@ /* End PBXResourcesBuildPhase section */ /* Begin PBXShellScriptBuildPhase section */ + 169D8C02ED63ABC1BD3F8125 /* [CP] Copy Pods Resources */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + "${PODS_ROOT}/Target Support Files/Pods-ShenYin/Pods-ShenYin-resources-${CONFIGURATION}-input-files.xcfilelist", + ); + inputPaths = ( + ); + name = "[CP] Copy Pods Resources"; + outputFileListPaths = ( + "${PODS_ROOT}/Target Support Files/Pods-ShenYin/Pods-ShenYin-resources-${CONFIGURATION}-output-files.xcfilelist", + ); + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-ShenYin/Pods-ShenYin-resources.sh\"\n"; + showEnvVarsInLog = 0; + }; 3FD1DFE62F0E00BF00A62733 /* [CP] Check Pods Manifest.lock */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; @@ -1279,7 +1301,7 @@ CODE_SIGN_ENTITLEMENTS = ShenYin/ShenYin.entitlements; CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 100041; + CURRENT_PROJECT_VERSION = 202602241648; DEVELOPMENT_TEAM = MAC46RQ4KH; FRAMEWORK_SEARCH_PATHS = ( "$(inherited)", @@ -1334,7 +1356,7 @@ CODE_SIGN_ENTITLEMENTS = ShenYin/ShenYin.entitlements; CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 100041; + CURRENT_PROJECT_VERSION = 202602241648; DEVELOPMENT_TEAM = MAC46RQ4KH; FRAMEWORK_SEARCH_PATHS = ( "$(inherited)", diff --git a/ShenYin/ShenYin/Assets.xcassets/sport/sport_call.imageset/Contents.json b/ShenYin/ShenYin/Assets.xcassets/sport/sport_call.imageset/Contents.json new file mode 100644 index 0000000..2942dce --- /dev/null +++ b/ShenYin/ShenYin/Assets.xcassets/sport/sport_call.imageset/Contents.json @@ -0,0 +1,22 @@ +{ + "images" : [ + { + "idiom" : "universal", + "scale" : "1x" + }, + { + "filename" : "Frame_2255@2x.png", + "idiom" : "universal", + "scale" : "2x" + }, + { + "filename" : "Frame_2255@3x.png", + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/ShenYin/ShenYin/Assets.xcassets/sport/sport_call.imageset/Frame_2255@2x.png b/ShenYin/ShenYin/Assets.xcassets/sport/sport_call.imageset/Frame_2255@2x.png new file mode 100644 index 0000000..fe1f134 Binary files /dev/null and b/ShenYin/ShenYin/Assets.xcassets/sport/sport_call.imageset/Frame_2255@2x.png differ diff --git a/ShenYin/ShenYin/Assets.xcassets/sport/sport_call.imageset/Frame_2255@3x.png b/ShenYin/ShenYin/Assets.xcassets/sport/sport_call.imageset/Frame_2255@3x.png new file mode 100644 index 0000000..842ec4f Binary files /dev/null and b/ShenYin/ShenYin/Assets.xcassets/sport/sport_call.imageset/Frame_2255@3x.png differ diff --git a/ShenYin/ShenYin/Assets.xcassets/sport/sport_music_collection.imageset/Contents.json b/ShenYin/ShenYin/Assets.xcassets/sport/sport_music_collection.imageset/Contents.json new file mode 100644 index 0000000..2942dce --- /dev/null +++ b/ShenYin/ShenYin/Assets.xcassets/sport/sport_music_collection.imageset/Contents.json @@ -0,0 +1,22 @@ +{ + "images" : [ + { + "idiom" : "universal", + "scale" : "1x" + }, + { + "filename" : "Frame_2255@2x.png", + "idiom" : "universal", + "scale" : "2x" + }, + { + "filename" : "Frame_2255@3x.png", + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/ShenYin/ShenYin/Assets.xcassets/sport/sport_music_collection.imageset/Frame_2255@2x.png b/ShenYin/ShenYin/Assets.xcassets/sport/sport_music_collection.imageset/Frame_2255@2x.png new file mode 100644 index 0000000..45eb136 Binary files /dev/null and b/ShenYin/ShenYin/Assets.xcassets/sport/sport_music_collection.imageset/Frame_2255@2x.png differ diff --git a/ShenYin/ShenYin/Assets.xcassets/sport/sport_music_collection.imageset/Frame_2255@3x.png b/ShenYin/ShenYin/Assets.xcassets/sport/sport_music_collection.imageset/Frame_2255@3x.png new file mode 100644 index 0000000..c0c4c74 Binary files /dev/null and b/ShenYin/ShenYin/Assets.xcassets/sport/sport_music_collection.imageset/Frame_2255@3x.png differ diff --git a/ShenYin/ShenYin/Assets.xcassets/sport/sport_music_list.imageset/Contents.json b/ShenYin/ShenYin/Assets.xcassets/sport/sport_music_list.imageset/Contents.json new file mode 100644 index 0000000..2942dce --- /dev/null +++ b/ShenYin/ShenYin/Assets.xcassets/sport/sport_music_list.imageset/Contents.json @@ -0,0 +1,22 @@ +{ + "images" : [ + { + "idiom" : "universal", + "scale" : "1x" + }, + { + "filename" : "Frame_2255@2x.png", + "idiom" : "universal", + "scale" : "2x" + }, + { + "filename" : "Frame_2255@3x.png", + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/ShenYin/ShenYin/Assets.xcassets/sport/sport_music_list.imageset/Frame_2255@2x.png b/ShenYin/ShenYin/Assets.xcassets/sport/sport_music_list.imageset/Frame_2255@2x.png new file mode 100644 index 0000000..1da43ce Binary files /dev/null and b/ShenYin/ShenYin/Assets.xcassets/sport/sport_music_list.imageset/Frame_2255@2x.png differ diff --git a/ShenYin/ShenYin/Assets.xcassets/sport/sport_music_list.imageset/Frame_2255@3x.png b/ShenYin/ShenYin/Assets.xcassets/sport/sport_music_list.imageset/Frame_2255@3x.png new file mode 100644 index 0000000..3baf0a4 Binary files /dev/null and b/ShenYin/ShenYin/Assets.xcassets/sport/sport_music_list.imageset/Frame_2255@3x.png differ diff --git a/ShenYin/ShenYin/Assets.xcassets/sport/sport_record.imageset/Contents.json b/ShenYin/ShenYin/Assets.xcassets/sport/sport_record.imageset/Contents.json new file mode 100644 index 0000000..2942dce --- /dev/null +++ b/ShenYin/ShenYin/Assets.xcassets/sport/sport_record.imageset/Contents.json @@ -0,0 +1,22 @@ +{ + "images" : [ + { + "idiom" : "universal", + "scale" : "1x" + }, + { + "filename" : "Frame_2255@2x.png", + "idiom" : "universal", + "scale" : "2x" + }, + { + "filename" : "Frame_2255@3x.png", + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/ShenYin/ShenYin/Assets.xcassets/sport/sport_record.imageset/Frame_2255@2x.png b/ShenYin/ShenYin/Assets.xcassets/sport/sport_record.imageset/Frame_2255@2x.png new file mode 100644 index 0000000..629ea8e Binary files /dev/null and b/ShenYin/ShenYin/Assets.xcassets/sport/sport_record.imageset/Frame_2255@2x.png differ diff --git a/ShenYin/ShenYin/Assets.xcassets/sport/sport_record.imageset/Frame_2255@3x.png b/ShenYin/ShenYin/Assets.xcassets/sport/sport_record.imageset/Frame_2255@3x.png new file mode 100644 index 0000000..9baf1e0 Binary files /dev/null and b/ShenYin/ShenYin/Assets.xcassets/sport/sport_record.imageset/Frame_2255@3x.png differ diff --git a/ShenYin/ShenYin/Sdk/LogManager.swift b/ShenYin/ShenYin/Sdk/LogManager.swift index f61e054..8735333 100644 --- a/ShenYin/ShenYin/Sdk/LogManager.swift +++ b/ShenYin/ShenYin/Sdk/LogManager.swift @@ -9,7 +9,7 @@ import Foundation /// 日记管理 class Logger { - static var isDebug: Bool = false + static var isDebug: Bool = true fileprivate static let shared = Logger() private var fileHandle:FileHandle? diff --git a/ShenYin/ShenYin/UI/Base/BaseTC.swift b/ShenYin/ShenYin/UI/Base/BaseTC.swift index 907eba5..f1a8f9f 100644 --- a/ShenYin/ShenYin/UI/Base/BaseTC.swift +++ b/ShenYin/ShenYin/UI/Base/BaseTC.swift @@ -20,8 +20,8 @@ class BaseTC:UITabBarController{ func setupUI(){ if CCDevice.isJoy{ - let homeVC = HomeDeviceVC() -// let homeVC = SportHomeVC() +// let homeVC = HomeDeviceVC() + let homeVC = SportHomeVC() let modeVC = ModeVC() let sleepVC = SleepVC() diff --git a/ShenYin/ShenYin/UI/Sport/Music/MusicRootView.swift b/ShenYin/ShenYin/UI/Sport/Music/MusicRootView.swift new file mode 100644 index 0000000..a74cc6c --- /dev/null +++ b/ShenYin/ShenYin/UI/Sport/Music/MusicRootView.swift @@ -0,0 +1,53 @@ +// +// MusicVC.swift +// ShenYin +// +// Created by liangshi on 2026/1/20. +// + + +import UIKit +import JXSegmentedView + +class MusicRootView: BaseView{ + let content = SprotRocrodView() + + override func setupViews() { + addSubviews([content]) + content.do { v in + v.header.titleLabel.text = "音乐".l10n + v.header.detailLabel.isHidden = true + v.header.icon.isHidden = true + v.item1.titleLabel.text = "我的歌单".l10n + v.item1.icon.image = UIImage(named: "sport_music_list") + v.item2.titleLabel.text = "我的收藏".l10n + v.item2.icon.image = UIImage(named: "sport_music_collection") +// v.header.yc.addTap(target: self, action: #selector(historyAction)) + v.item1.yc.addTap(target: self, action: #selector(recordAction)) + v.item2.yc.addTap(target: self, action: #selector(callAction)) + } + } + + override func setupLayout() { + content.snp.makeConstraints { make in + make.left.right.top.equalToSuperview().inset(18) + make.height.equalTo(209) + } + } +// @objc func historyAction(){ +// App.shared.goToPage(HistoryRecordVC()) +// } + @objc func recordAction(){ + App.shared.goToPage(HistoryRecordVC()) + } + @objc func callAction(){ + App.shared.goToPage(HistoryRecordVC()) + } +} + +extension MusicRootView: JXSegmentedListContainerViewListDelegate { + func listView() -> UIView { + return self + } +} + diff --git a/ShenYin/ShenYin/UI/Sport/Record/RecordHeadItem.swift b/ShenYin/ShenYin/UI/Sport/Record/RecordHeadItem.swift new file mode 100644 index 0000000..51f4273 --- /dev/null +++ b/ShenYin/ShenYin/UI/Sport/Record/RecordHeadItem.swift @@ -0,0 +1,117 @@ +// +// Untitled.swift +// ShenYin +// +// Created by liangshi on 2026/2/25. +// +class SprotRocrodView: BaseView { + let header = RecordheadItem() + let line = UIView() + let item1 = SportRecordItem() + let item2 = SportRecordItem() + + override func setupViews() { + yc.setCornerRadius(12) + backgroundColor = .color(.cellBg) + addSubviews([header,line,item1,item2]) + line.backgroundColor = .color(.separateLine) + } + + override func setupLayout() { + header.snp.makeConstraints { make in + make.left.right.top.equalToSuperview() + make.height.equalTo(51) + } + item1.snp.makeConstraints { make in + make.left.right.equalToSuperview().inset(15) + make.height.equalTo(60) + make.top.equalToSuperview().offset(61) + } + item2.snp.makeConstraints { make in + make.left.right.equalToSuperview().inset(15) + make.height.equalTo(60) + make.top.equalTo(item1.snp.bottom).offset(14) + } + line.snp.makeConstraints { make in + make.left.right.equalToSuperview().inset(14) + make.height.equalTo(1) + make.top.equalToSuperview().inset(47) + } + } +} +class SportRecordItem: BaseView { + let icon = UIImageView() + let titleLabel = UILabel() + + override func setupViews() { + yc.setCornerRadius(12) + backgroundColor = .color(.bgColor) + addSubviews([icon,titleLabel]) + + titleLabel.do { v in + v.textColor = .color(.first) + v.font = .medium(14) + v.text = "".l10n + } + icon.do { v in + v.image = UIImage(named: "arrow_2") + } + } + + override func setupLayout() { + + titleLabel.snp.makeConstraints { make in + make.centerY.equalToSuperview() + make.left.equalToSuperview().inset(60) + } + icon.snp.makeConstraints { make in + make.size.equalTo(32) + make.centerY.equalToSuperview() + make.left.equalToSuperview().inset(14) + } + } +} + +class RecordheadItem: BaseView { + let icon = UIImageView() + let detailLabel = UILabel() + let titleLabel = UILabel() + + override func setupViews() { + yc.setCornerRadius(12) + addSubviews([icon,titleLabel,detailLabel]) + + titleLabel.do { v in + v.textColor = .color(.first) + v.font = .medium(14) + v.text = "".l10n + } + detailLabel.do { v in + v.textColor = .color(.secondary) + v.font = .regular(12) + v.text = "历史记录".l10n + } + icon.do { v in + v.image = UIImage(named: "arrow_2") + } + backgroundColor = .white + } + + override func setupLayout() { + + titleLabel.snp.makeConstraints { make in + make.centerY.equalToSuperview() + make.left.equalToSuperview().inset(14) + } + detailLabel.snp.makeConstraints { make in + make.centerY.equalToSuperview() + make.right.equalToSuperview().inset(27) + } + icon.snp.makeConstraints { make in + make.width.equalTo(7) + make.height.equalTo(13) + make.centerY.equalToSuperview() + make.right.equalToSuperview().inset(12) + } + } +} diff --git a/ShenYin/ShenYin/UI/Sport/Record/RecordRootView.swift b/ShenYin/ShenYin/UI/Sport/Record/RecordRootView.swift new file mode 100644 index 0000000..e1cbc9a --- /dev/null +++ b/ShenYin/ShenYin/UI/Sport/Record/RecordRootView.swift @@ -0,0 +1,50 @@ +// +// RecordView.swift +// ShenYin +// +// Created by liangshi on 2026/1/20. +// + +import UIKit +import JXSegmentedView + +class RecordRootView: BaseView{ + let content = SprotRocrodView() + + override func setupViews() { + addSubviews([content]) + content.do { v in + v.header.titleLabel.text = "录音".l10n + v.item1.titleLabel.text = "现场录音".l10n + v.item1.icon.image = UIImage(named: "sport_record") + v.item2.titleLabel.text = "通话录音".l10n + v.item2.icon.image = UIImage(named: "sport_call") + v.header.yc.addTap(target: self, action: #selector(historyAction)) + v.item1.yc.addTap(target: self, action: #selector(recordAction)) + v.item2.yc.addTap(target: self, action: #selector(callAction)) + } + } + + override func setupLayout() { + content.snp.makeConstraints { make in + make.left.right.top.equalToSuperview().inset(18) + make.height.equalTo(209) + } + } + @objc func historyAction(){ + App.shared.goToPage(HistoryRecordVC()) + } + @objc func recordAction(){ + CCPage.gotoFieldRecord() + } + @objc func callAction(){ + CCPage.gotoCallRecord() + } +} + +extension RecordRootView: JXSegmentedListContainerViewListDelegate { + func listView() -> UIView { + return self + } +} + diff --git a/ShenYin/ShenYin/UI/Sport/Sport/Group/Create/AddGroupHeader.swift b/ShenYin/ShenYin/UI/Sport/Sport/Group/Create/AddGroupHeader.swift new file mode 100644 index 0000000..f9912ab --- /dev/null +++ b/ShenYin/ShenYin/UI/Sport/Sport/Group/Create/AddGroupHeader.swift @@ -0,0 +1,40 @@ +// +// AddGroupHeader.swift +// ShenYin +// +// Created by liangshi on 2026/2/6. +// + +class AddGroupHeader: BaseView { + let icon = UIImageView() + let titleLabel = UILabel() + + override func setupViews() { + addSubviews([icon,titleLabel]) + + titleLabel.do { v in + v.textColor = .color(.secondary) + v.font = .regular(10) + v.text = "设置团队头像".l10n + } + + icon.do { v in + v.image = UIImage(named: "group_avatar") + yc.setCornerRadius(12) + + } + } + + override func setupLayout() { + icon.snp.makeConstraints { make in + make.size.equalTo(90) + make.centerX.equalToSuperview() + make.top.equalToSuperview() + } + titleLabel.snp.makeConstraints { make in + make.centerX.equalToSuperview() + make.top.equalTo(icon.snp.bottom).offset(6) + } + + } +} diff --git a/ShenYin/ShenYin/UI/Sport/Sport/Group/Create/CreateGroupVC.swift b/ShenYin/ShenYin/UI/Sport/Sport/Group/Create/CreateGroupVC.swift new file mode 100644 index 0000000..c77bec8 --- /dev/null +++ b/ShenYin/ShenYin/UI/Sport/Sport/Group/Create/CreateGroupVC.swift @@ -0,0 +1,144 @@ +// +// CreateGroupVC.swift +// ShenYin +// +// Created by liangshi on 2026/2/5. +// + +import UIKit + +class CreateGroupVC: BaseVC{ + let headerView = AddGroupHeader() + let nameView = SportInputItem() + let typeView = SportSelectItem() + let desView = SportInputItem() + let lightView = SportSelectItem() + let verifyView = SportSwitchItem() + let okBtn = CustomButton() + var sportType:SportType = .walk + + override func setupViews() { + navBar.do { v in + v.title = "创建团队".l10n + v.rightView.setImage(UIImage(named: "help_icon"), for: .normal) + v.rightView.isHidden = false + } + view.addSubviews([headerView,nameView,typeView,desView,lightView,verifyView,okBtn]) + nameView.do { v in + v.yc.setCornerRadius(12) + v.titleLabel.text = "团队名称".l10n + v.textField.text = "111" + v.textField.attributedPlaceholder = NSAttributedString(string: "给团队起个名字".l10n, attributes: [NSAttributedString.Key.foregroundColor: UIColor.color(.placeholder)]) + + } + typeView.do { v in + v.yc.setCornerRadius(12) + v.titleLabel.text = "团队类型".l10n + v.detailLabel.text = sportType.name + v.yc.addTap(target: self, action: #selector(typeAction)) + } + desView.do { v in + v.yc.setCornerRadius(12) + v.titleLabel.text = "简介".l10n + v.textField.attributedPlaceholder = NSAttributedString(string: "描述一下您的团".l10n, attributes: [NSAttributedString.Key.foregroundColor: UIColor.color(.placeholder)]) + } + lightView.do { v in + v.yc.setCornerRadius(12) + v.titleLabel.text = "灯带设置".l10n + v.yc.addTap(target: self, action: #selector(lightAction)) + + } + verifyView.do { v in + v.yc.setCornerRadius(12) + v.titleLabel.text = "加入验证".l10n + v.detailLabel.text = "开启后,成员需要申请才能加入".l10n + } + okBtn.set(title: "完成创建".l10n,style: .theme1, target: self, action: #selector(okAction)) + } + override func setupLayout() { + headerView.snp.makeConstraints { make in + make.left.right.equalToSuperview() + make.top.equalToSuperview().inset(kNavBtm + 20) + make.height.equalTo(120) + } + nameView.snp.makeConstraints { make in + make.left.right.equalToSuperview().inset(18) + make.top.equalTo(headerView.snp.bottom).offset(10) + make.height.equalTo(51) + } + typeView.snp.makeConstraints { make in + make.left.right.equalToSuperview().inset(18) + make.top.equalTo(nameView.snp.bottom).offset(10) + make.height.equalTo(51) + } + desView.snp.makeConstraints { make in + make.left.right.equalToSuperview().inset(18) + make.top.equalTo(typeView.snp.bottom).offset(10) + make.height.equalTo(65) + } + lightView.snp.makeConstraints { make in + make.left.right.equalToSuperview().inset(18) + make.top.equalTo(desView.snp.bottom).offset(10) + make.height.equalTo(51) + } + verifyView.snp.makeConstraints { make in + make.left.right.equalToSuperview().inset(18) + make.top.equalTo(lightView.snp.bottom).offset(10) + make.height.equalTo(65) + } + okBtn.snp.makeConstraints { make in + make.bottom.equalToSuperview().inset(80) + make.left.right.equalToSuperview().inset(51) + make.height.equalTo(46) + } + } + override func navRightViewTapped() { + let tip = GroupDesPopView() + tip.show() + } + @objc func typeAction(){ + let vc = GroupTypeVC() + vc.didSelected = {[weak self] value in + self?.sportType = value + self?.typeView.detailLabel.text = value.name + } + App.shared.goToPage(vc) + } + @objc func lightAction(){ + App.shared.goToPage(LightSetVC()) + } + @objc func okAction(){ + if nameView.isEmpty { + YLprogressHUD.showAutoHiden("团队名称不能为空".l10n) + return + } + + YLprogressHUD.showLoading() + var params:[String:Any] = [:] + // 团队名称,必须 + params["name"] = nameView.value + // 头像 +// params["icon"] = "" + // 运动类型:running | cycling | walking,必须 + params["type"] = sportType.rawValue + // 团队简介,非必须 +// params["introduction"] = "" + // 1加入审核,0不需要审核,非必须,默认0 + params["joinReview"] = verifyView.iSwitch.isOn ? "1":"0" + let api = CommomApi.createSportGroup(param: params) + HttpClient.request(api){ (model:CommonDataModel, res) in + + if model.isSuccess{ + YLprogressHUD.showSuccess { + App.shared.popPage(animated: false) + let vc = GroupDetailVC(groupId:model.data ) + App.shared.goToPage(vc) + } + } + else{ + YLprogressHUD.showAutoHiden("请求失败".l10n) + } + } + } +} + diff --git a/ShenYin/ShenYin/UI/Sport/Sport/Group/Create/SportInputItem.swift b/ShenYin/ShenYin/UI/Sport/Sport/Group/Create/SportInputItem.swift new file mode 100644 index 0000000..4c10566 --- /dev/null +++ b/ShenYin/ShenYin/UI/Sport/Sport/Group/Create/SportInputItem.swift @@ -0,0 +1,136 @@ +// +// SportInputItem.swift +// ShenYin +// +// Created by liangshi on 2026/2/6. +// + +class SportInputItem: BaseView,UITextFieldDelegate { + let valueLabel = UILabel() + let textField = UITextField() + let titleLabel = UILabel() + var maxLength = 10 + + var isEmpty:Bool{ + if let text = textField.text,text.isNotEmpty { + return false + } + return true + } + var value:String{ + return textField.text ?? "" + } + + override func setupViews() { + yc.setCornerRadius(12) + addSubviews([valueLabel,titleLabel,textField]) + + titleLabel.do { v in + v.textColor = .color(.first) + v.font = .medium(14) + } + textField.do { v in + v.textColor = .color(.first) + v.keyboardType = .default + v.returnKeyType = .done + v.textAlignment = .left + v.addTarget(self, action: #selector(textFieldValueChange), for: .valueChanged) +// v.backgroundColor = .red + v.delegate = self + } + valueLabel.do { v in + v.textColor = .color(.placeholder) + v.font = .regular(10) + v.textAlignment = .right + v.text = "0/\(maxLength)" + } + backgroundColor = .white + } + + override func setupLayout() { + + titleLabel.snp.makeConstraints { make in + make.centerY.equalToSuperview() + make.left.equalToSuperview().inset(14) + } + textField.snp.makeConstraints { make in + make.left.equalTo(titleLabel.snp.right).offset(10).priority(.high) + make.centerY.height.equalToSuperview() + make.right.equalToSuperview().inset(50).priority(.low) + } + valueLabel.snp.makeConstraints { make in + make.width.equalTo(38) + make.height.equalToSuperview() + make.centerY.equalToSuperview() + make.right.equalToSuperview().inset(12) + } + } + @objc func textFieldValueChange(){ + valueLabel.text = "\(textField.text?.count ?? 0)/\(maxLength)" + } + func textFieldShouldReturn(_ textField: UITextField) -> Bool { + textField.resignFirstResponder() + return false + } +} + +class SportTextViewItem: BaseView,UITextViewDelegate { + let valueLabel = UILabel() + let textView = UITextView() + let titleLabel = UILabel() + var maxLength = 10 + + override func setupViews() { + yc.setCornerRadius(12) + addSubviews([valueLabel,titleLabel,textView]) + + titleLabel.do { v in + v.textColor = .color(.first) + v.font = .medium(14) + v.text = "简介".l10n + } + textView.do { v in + v.textColor = .color(.first) + v.keyboardType = .default + v.returnKeyType = .done + v.textAlignment = .left + v.delegate = self + } + valueLabel.do { v in + v.textColor = .color(.placeholder) + v.font = .regular(10) + v.textAlignment = .right + v.text = "0/\(maxLength)" + } + backgroundColor = .white + } + + override func setupLayout() { + + titleLabel.snp.makeConstraints { make in + make.centerY.equalToSuperview() + make.left.equalToSuperview().inset(14) + } + textView.snp.makeConstraints { make in + make.left.equalTo(titleLabel.snp.right).offset(10).priority(.high) + make.centerY.height.equalToSuperview() + make.right.equalToSuperview().inset(50).priority(.low) + } + valueLabel.snp.makeConstraints { make in + make.width.equalTo(38) + make.height.equalToSuperview() + make.centerY.equalToSuperview() + make.right.equalToSuperview().inset(12) + } + } + + func textViewDidChange(_ textView: UITextView) { + valueLabel.text = "\(textView.text?.count ?? 0)/\(maxLength)" + } + + func textView(_ textView: UITextView, shouldChangeTextInRanges ranges: [NSValue], replacementText text: String) -> Bool { + textView.resignFirstResponder() + + return false + } +} diff --git a/ShenYin/ShenYin/UI/Sport/Sport/Group/Create/SportSelectItem.swift b/ShenYin/ShenYin/UI/Sport/Sport/Group/Create/SportSelectItem.swift new file mode 100644 index 0000000..51d8041 --- /dev/null +++ b/ShenYin/ShenYin/UI/Sport/Sport/Group/Create/SportSelectItem.swift @@ -0,0 +1,48 @@ +// +// SportItem.swift +// ShenYin +// +// Created by liangshi on 2026/2/6. +// + +class SportSelectItem: BaseView { + let icon = UIImageView() + let detailLabel = UILabel() + let titleLabel = UILabel() + + override func setupViews() { + yc.setCornerRadius(12) + addSubviews([icon,titleLabel,detailLabel]) + + titleLabel.do { v in + v.textColor = .color(.first) + v.font = .medium(14) + } + detailLabel.do { v in + v.textColor = .color(.secondary) + v.font = .regular(12) + } + icon.do { v in + v.image = UIImage(named: "arrow_2") + } + backgroundColor = .white + } + + override func setupLayout() { + + titleLabel.snp.makeConstraints { make in + make.centerY.equalToSuperview() + make.left.equalToSuperview().inset(14) + } + detailLabel.snp.makeConstraints { make in + make.centerY.equalToSuperview() + make.right.equalToSuperview().inset(27) + } + icon.snp.makeConstraints { make in + make.width.equalTo(7) + make.height.equalTo(13) + make.centerY.equalToSuperview() + make.right.equalToSuperview().inset(12) + } + } +} diff --git a/ShenYin/ShenYin/UI/Sport/Sport/Group/Create/SportSwitchItem.swift b/ShenYin/ShenYin/UI/Sport/Sport/Group/Create/SportSwitchItem.swift new file mode 100644 index 0000000..4f5423a --- /dev/null +++ b/ShenYin/ShenYin/UI/Sport/Sport/Group/Create/SportSwitchItem.swift @@ -0,0 +1,51 @@ +// +// SportSwitchItem.swift +// ShenYin +// +// Created by liangshi on 2026/2/6. +// + +class SportSwitchItem: BaseTableViewCell { + let titleLabel = UILabel() + let detailLabel = UILabel() + let iSwitch = UISwitch() + + + override func setupViews() { + addSubviews([titleLabel,detailLabel,iSwitch]) + + backgroundColor = .color(.cellBg) + titleLabel.do { (v) in + v.textColor = .color(.first) + v.font = .bold(14) + v.textAlignment = .left + } + detailLabel.do { (v) in + v.textColor = .color(.secondary) + v.font = .regular(10) + v.textAlignment = .left + } + iSwitch.do { v in + v.addTarget(self, action: #selector(swicthAction), for: .valueChanged) + } + } + + override func setupLayout() { + titleLabel.snp.makeConstraints { (m) in + m.top.equalToSuperview().inset(16) + m.left.equalToSuperview().offset(16) + } + detailLabel.snp.makeConstraints { (m) in + m.bottom.equalToSuperview().inset(16) + m.left.equalToSuperview().offset(16) + } + iSwitch.snp.makeConstraints { (m) in + m.right.equalToSuperview().inset(15) + m.centerY.equalToSuperview() + } + } + + + @objc func swicthAction(){ + } +} diff --git a/ShenYin/ShenYin/UI/Sport/Sport/Group/Detail/GroupDetailVC.swift b/ShenYin/ShenYin/UI/Sport/Sport/Group/Detail/GroupDetailVC.swift new file mode 100644 index 0000000..bbfc751 --- /dev/null +++ b/ShenYin/ShenYin/UI/Sport/Sport/Group/Detail/GroupDetailVC.swift @@ -0,0 +1,29 @@ +// +// GroupDetailVC.swift +// ShenYin +// +// Created by liangshi on 2026/2/25. +// + +import UIKit + +class GroupDetailVC: BaseVC{ + var groupId = "" + + + init(groupId:String) { + self.groupId = groupId + super.init() + } + + override func setupViews() { + navBar.do { v in + v.title = "团队详情".l10n + } + + } + override func setupLayout() { + + } + +} diff --git a/ShenYin/ShenYin/UI/Sport/Sport/Group/Member/GroupMemberCell.swift b/ShenYin/ShenYin/UI/Sport/Sport/Group/Member/GroupMemberCell.swift new file mode 100644 index 0000000..048f84e --- /dev/null +++ b/ShenYin/ShenYin/UI/Sport/Sport/Group/Member/GroupMemberCell.swift @@ -0,0 +1,161 @@ +// +// GroupMemberCell.swift +// ShenYin +// +// Created by liangshi on 2026/2/12. +// + +class GroupMemberCell: BaseTableViewCell { + let bgView = UIView() + let icon = UIImageView() + let nameLabel = UILabel() + let roleType = UIImageView() + let roleLabel = UILabel() + let moreIcon = UIImageView() + let line = UIView() + var model:GroupMemberModel!{ + didSet{ + icon.kf.setImage(with: model.avatar.yc.asUrl) + nameLabel.text = model.userName + roleLabel.text = model.role.name + roleType.backgroundColor = model.role.color +// moreIcon.isHidden = model.userId != CacheManager.shared.user.info.userId + } + } + + + override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) { + super.init(style: style, reuseIdentifier: reuseIdentifier) + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + override func setupViews() { + contentView.addSubview(bgView) + bgView.addSubviews([icon,nameLabel,roleLabel,roleType,moreIcon,line]) + + backgroundColor = .color(.bgColor) + contentView.backgroundColor = .clear + selectionStyle = .none + bgView.do { v in + v.backgroundColor = .color(.cellBg) +// v.yc.setCornerRadius(18) + } + nameLabel.do { (v) in + v.textColor = .color(.first) + v.font = .medium(12) + v.textAlignment = .left + v.lineBreakMode = .byTruncatingTail + } + roleLabel.do { v in + v.textColor = .color(.secondary) + v.font = .regular(10) + v.textAlignment = .left + v.lineBreakMode = .byTruncatingTail + } + roleType.do { v in + v.yc.setCornerRadius(3) + } + icon.do { v in + v.yc.setCornerRadius(18) + } + moreIcon.do { v in + v.image = UIImage(named: "more") + v.contentMode = .center + v.yc.addTap(target: self, action: #selector(moreAction)) + } + line.do { v in + v.backgroundColor = .color(.separateLine) + } + } + + override func setupLayout() { + bgView.snp.makeConstraints { (m) in + m.left.right.equalToSuperview().inset(18) + m.top.equalToSuperview() + m.bottom.equalToSuperview() + } + + icon.snp.makeConstraints { (m) in + m.size.equalTo(36) + m.left.equalToSuperview().inset(15) + m.top.equalToSuperview().inset(15) + } + nameLabel.snp.makeConstraints { (m) in + m.top.equalToSuperview().inset(16) + m.left.equalToSuperview().offset(60) + m.right.lessThanOrEqualToSuperview().inset(16) + } + roleType.snp.makeConstraints { make in + make.left.equalTo(nameLabel) + make.bottom.equalToSuperview().inset(16) + make.width.equalTo(6) + make.height.equalTo(10) + } + roleLabel.snp.makeConstraints { (m) in + m.bottom.equalToSuperview().inset(16) + m.left.equalToSuperview().offset(77) + m.right.lessThanOrEqualToSuperview().inset(16) + } + moreIcon.snp.makeConstraints { make in + make.right.equalToSuperview() + make.centerY.equalToSuperview() + make.width.equalTo(18+18+5) + make.height.equalToSuperview() + } + line.snp.makeConstraints { make in + make.left.right.equalToSuperview().inset(18) + make.bottom.equalToSuperview() + make.height.equalTo(0.4) + } + } + + @objc func moreAction(){ + let vc = UIAlertController(title: nil, message: nil, preferredStyle: .actionSheet) + let action0 = UIAlertAction(title: "团长转让".l10n, style: .default) {[weak self] action in + self?.transferGroup() + } + let action1 = UIAlertAction(title: "移除团队".l10n, style: .default) {[weak self] action in + self?.removeFromGroup() + } + let action2 = UIAlertAction(title: "取消".l10n, style: .cancel) { action in + + } + vc.addAction(action0) + vc.addAction(action1) + vc.addAction(action2) + App.shared.topVC().present(vc, animated: true) + } + @objc func removeFromGroup(){ + let vc = UIAlertController(title: "提示".l10n, message: "确认要把TA移出团队吗?".l10n, preferredStyle: .alert) + let action0 = UIAlertAction(title: "确定".l10n, style: .destructive) {[weak self] action in + self?.removeFromGroup2() + } + let action1 = UIAlertAction(title: "取消".l10n, style: .cancel) {[weak self] action in + } + + vc.addAction(action0) + vc.addAction(action1) + App.shared.topVC().present(vc, animated: true) + } + @objc func removeFromGroup2(){ + + } + + @objc func transferGroup(){ + let vc = UIAlertController(title: "提示".l10n, message: "确认要把团长转让给TA吗?".l10n, preferredStyle: .alert) + let action0 = UIAlertAction(title: "确定".l10n, style: .destructive) {[weak self] action in + self?.transferGroup2() + } + let action1 = UIAlertAction(title: "取消".l10n, style: .cancel) {[weak self] action in + } + + vc.addAction(action0) + vc.addAction(action1) + App.shared.topVC().present(vc, animated: true) + } + @objc func transferGroup2(){ + + } +} diff --git a/ShenYin/ShenYin/UI/Sport/Sport/Group/Member/GroupMemberModel.swift b/ShenYin/ShenYin/UI/Sport/Sport/Group/Member/GroupMemberModel.swift new file mode 100644 index 0000000..6552a48 --- /dev/null +++ b/ShenYin/ShenYin/UI/Sport/Sport/Group/Member/GroupMemberModel.swift @@ -0,0 +1,21 @@ +// +// GroupMemberModel.swift +// ShenYin +// +// Created by liangshi on 2026/2/12. +// + +import HandyJSON + +class GroupMemberModelRoot:ResponseBaseModel { + var data = [GroupMemberModel]() +} + +class GroupMemberModel:HandyJSON { + var userId = ""/// 用户ID + var userName = "" /// 用户名 + var role:GroupRoleType = .bjy /// 角色名 + var avatar = "" /// 头像 + + required public init() { } +} diff --git a/ShenYin/ShenYin/UI/Sport/Sport/Group/Member/GroupMemberVC.swift b/ShenYin/ShenYin/UI/Sport/Sport/Group/Member/GroupMemberVC.swift new file mode 100644 index 0000000..74b7740 --- /dev/null +++ b/ShenYin/ShenYin/UI/Sport/Sport/Group/Member/GroupMemberVC.swift @@ -0,0 +1,114 @@ +// +// GroupMemberVC.swift +// ShenYin +// +// Created by liangshi on 2026/2/12. +// + +import UIKit + +class GroupMemberVC: BaseVC{ + let tableView = UITableView(frame: .zero, style: .plain) + var list:[GroupMemberModel] = [] + var currentType = GroupRoleType.none + let btn = CustomButton() + var groupId = "" + + + init(groupId:String) { + self.groupId = groupId + super.init() + } + + override func viewDidLoad() { + super.viewDidLoad() + // Do any additional setup after loading the view. + } + override func setupViews() { + navBar.do { v in + v.title = "成员管理".l10n +// v.rightView.setImage(UIImage(named: "help_icon"), for: .normal) +// v.rightView.isHidden = false + } + + tableView.do { v in + v.backgroundColor = .clear + v.delegate = self + v.dataSource = self + v.rowHeight = 68 + v.separatorStyle = .none + v.contentInset = UIEdgeInsets(top: 10, left: 0, bottom: 0, right: 0) + v.register(GroupMemberCell.self, forCellReuseIdentifier: GroupMemberCell.reuseID) + + let header = UIView(frame: CGRect(x: 0, y: 0, width: kScreenWid, height: 40)) + let label = UILabel(frame: CGRect(x: 18, y: 0, width: kScreenWid - 36, height: 40)) + label.do { v in + v.text = "成员列表".l10n + v.font = .regular(14) + v.textColor = .color(.secondary) + } + header.addSubview(label) + v.tableHeaderView = header + } + view.addSubviews([tableView,btn]) + + btn.set(title: "邀请".l10n, target: self, action: #selector(btnAction)) + } + override func setupLayout() { + tableView.snp.makeConstraints { make in + make.left.right.bottom.equalToSuperview() + make.top.equalTo(kNavBtm) + } + btn.snp.makeConstraints { make in + make.width.equalTo(272) + make.height.equalTo(46) + make.centerX.equalToSuperview() + make.bottom.equalToSuperview().inset(82) + } + } + override func setupData() { + let api = CommomApi.getGroupMembers(groupId: self.groupId) + HttpClient.request(api) {[weak self] (model:GroupMemberModelRoot,res) in + guard let self = self else {return} + if model.isSuccess{ + list = model.data + } + tableView.reloadData() + tableView.mj_header?.endRefreshing() + } + } + @objc func btnAction(){ + + } + override func navRightViewTapped() { + + } +} + +extension GroupMemberVC:UITableViewDelegate,UITableViewDataSource { + func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { + return list.count + } + + func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { + let cell = GroupMemberCell.cell(withTableView: tableView, indexPath: indexPath) + cell.model = list[indexPath.row] + let rect = CGRect(x: 0, y: 0, width: kScreenWid - 36, height: 68) + if indexPath.row == 0 { + cell.bgView.yc.setCorners(withRoundedRect: rect, roundingCorners: [.topLeft,.topRight], cornerRadii: CGSize(width: 8, height: 8)) + } + else if indexPath.row == list.count - 1{ + cell.bgView.yc.setCorners(withRoundedRect: rect, roundingCorners: [.bottomLeft,.bottomRight], cornerRadii: CGSize(width: 8, height: 8)) + } + else{ + cell.bgView.yc.setCorners(withRoundedRect: rect, roundingCorners: [.allCorners], cornerRadii: CGSize(width: 0, height: 0)) + } + if list.count == 1 { + cell.bgView.yc.setCorners(withRoundedRect: rect, roundingCorners: [.allCorners], cornerRadii: CGSize(width: 8, height: 8)) + } + return cell + } + func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { + + } +} diff --git a/ShenYin/ShenYin/UI/Sport/Sport/Group/MyGroup/MyGroupCell.swift b/ShenYin/ShenYin/UI/Sport/Sport/Group/MyGroup/MyGroupCell.swift new file mode 100644 index 0000000..3174168 --- /dev/null +++ b/ShenYin/ShenYin/UI/Sport/Sport/Group/MyGroup/MyGroupCell.swift @@ -0,0 +1,90 @@ +// +// MyGroupCell.swift +// ShenYin +// +// Created by liangshi on 2026/2/6. +// + + class MyGroupCell: BaseTableViewCell { + let bgView = UIView() + let icon = UIImageView() + let nameLabel = UILabel() + let contentLabel = UILabel() + + var model:MyGroupModel!{ + didSet{ + if model.icon.isEmpty{ + icon.image = UIImage(named: "group_avatar") + } + else{ + icon.kf.setImage(with: model.icon.yc.asUrl) + } + nameLabel.text = model.name + contentLabel.text = model.type.name + ".\(model.userCount)" + "人已加入".l10n + } + } + + + override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) { + super.init(style: style, reuseIdentifier: reuseIdentifier) + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + override func setupViews() { + contentView.addSubview(bgView) + bgView.addSubviews([icon,nameLabel,contentLabel]) + + backgroundColor = .color(.bgColor) + contentView.backgroundColor = .clear + selectionStyle = .none + bgView.do { v in + v.backgroundColor = .color(.cellBg) + v.yc.setCornerRadius(8) + } + nameLabel.do { (v) in + v.textColor = .color(.first) + v.font = .medium(14) + v.textAlignment = .left + v.lineBreakMode = .byTruncatingTail + } + contentLabel.do { v in + v.textColor = .color(.secondary) + v.font = .regular(11) + v.textAlignment = .left + v.lineBreakMode = .byTruncatingTail + } + + icon.do { v in + v.yc.setCornerRadius(8) + } + } + + override func setupLayout() { + bgView.snp.makeConstraints { (m) in + m.left.right.equalToSuperview().inset(18) + m.top.equalToSuperview().inset(6) + m.bottom.equalToSuperview() + } + + icon.snp.makeConstraints { (m) in + m.size.equalTo(36) + m.left.equalToSuperview().inset(15) + m.top.equalToSuperview().inset(15) + } + nameLabel.snp.makeConstraints { (m) in + m.top.equalToSuperview().inset(16) + m.left.equalToSuperview().offset(60) + m.right.lessThanOrEqualToSuperview().inset(16) + } + contentLabel.snp.makeConstraints { (m) in + m.bottom.equalToSuperview().inset(16) + m.left.equalToSuperview().offset(60) + m.right.lessThanOrEqualToSuperview().inset(16) + } + + } + + +} diff --git a/ShenYin/ShenYin/UI/Sport/Sport/Group/MyGroup/MyGroupModel.swift b/ShenYin/ShenYin/UI/Sport/Sport/Group/MyGroup/MyGroupModel.swift new file mode 100644 index 0000000..703df34 --- /dev/null +++ b/ShenYin/ShenYin/UI/Sport/Sport/Group/MyGroup/MyGroupModel.swift @@ -0,0 +1,31 @@ +// +// Untitled.swift +// ShenYin +// +// Created by liangshi on 2026/2/6. +// + +import HandyJSON + +class MyGroupModelRoot:ResponseBaseModel { + var data = [MyGroupModel]() +} + +class MyGroupModel:HandyJSON { + var id = "" // 团队ID + var userId = ""// 用户ID,创建者的ID + var creatorNickname = "" // 创建者昵称 + var creatorAvatar = "" // https://example.com/avatar.jpg", // 创建者头像 + var icon = "" // https://example.com/icon.jpg", // 图标 + var state = "" // 状态:1正常,0解散 + var name = "" // 团队名称 + var type:SportType = SportType.walk // 运动类型:running | cycling | walking + var introduction = "" // 团队简介 + var joinReview = ""// 1加入审核,0不需要审核 + var userCount = 0 //加入人数 + var totalDistance = "" // 总距离,单位米 + var totalTime = "" // 总运动时长,单位秒 + var createTime = "" // 创建时间 + var updateTime = ""// 更新时间 + required public init() { } +} diff --git a/ShenYin/ShenYin/UI/Sport/Sport/Group/MyGroup/MyGroupVC.swift b/ShenYin/ShenYin/UI/Sport/Sport/Group/MyGroup/MyGroupVC.swift new file mode 100644 index 0000000..4b34e04 --- /dev/null +++ b/ShenYin/ShenYin/UI/Sport/Sport/Group/MyGroup/MyGroupVC.swift @@ -0,0 +1,118 @@ +// +// MyGroupVC.swift +// ShenYin +// +// Created by liangshi on 2026/2/6. +// + +import JXSegmentedView + + +enum MyGroupType{ + case create + case add + + var name:String{ + switch self { + case .create: + return "我管理的" + case .add: + return "我加入的" + } + } + var value:String{ + switch self { + case .create: + return "1" + case .add: + return "0" + } + } + static var all:[MyGroupType] = [.create,.add] +} + +class MyGroupVC: BaseVC { + private let segmentedDataSource = JXSegmentedTitleDataSource() + private let segmentedView = JXSegmentedView() + private lazy var listContainerView: JXSegmentedListContainerView! = { + let view = JXSegmentedListContainerView(dataSource: self, type: .scrollView) + return view + }() + + var list:[MyGroupType] = MyGroupType.all + override func viewDidLoad() { + super.viewDidLoad() + + // AudioPlayView.shared.show() + self.view.addSubview(AudioPlayView.shared) + MapManager.shared.setupSdk() + } + + override func setupLayout(){ + + segmentedView.snp.makeConstraints { (m) in + m.left.right.equalToSuperview() + m.height.equalTo(44.fitR) + m.top.equalToSuperview().inset(kNavBtm) + } + listContainerView.snp.makeConstraints { (m) in + m.width.left.equalToSuperview() + m.top.equalTo(segmentedView.snp.bottom) + m.bottom.equalToSuperview() + } + } + override func setupViews(){ + navBar.do { v in + v.title = "我的团队" + } + view.addSubview(segmentedView) + view.addSubview(listContainerView) + + view.backgroundColor = .color(.bgColor) + segmentedView.do { (v) in + segmentedDataSource.titleNormalColor = .color(.secondary) + segmentedDataSource.titleSelectedColor = .color(.first) + segmentedDataSource.titleNormalFont = .regular(14) + segmentedDataSource.titleSelectedFont = .bold(16) + segmentedDataSource.isTitleZoomEnabled = true + // segmentedDataSource.titleSelectedZoomScale = 1 + segmentedDataSource.isTitleStrokeWidthEnabled = true + segmentedDataSource.isItemSpacingAverageEnabled = true + segmentedDataSource.isTitleColorGradientEnabled = true + segmentedDataSource.titles = list.map{"\($0.name)"} + segmentedView.backgroundColor = .clear + + //配置指示器 + let indicator = JXSegmentedIndicatorLineView() + indicator.indicatorWidth = 20 + indicator.indicatorHeight = 4 + indicator.verticalOffset = 6 + indicator.indicatorColor = .color(.theme) + segmentedView.indicators = [indicator] + + v.dataSource = segmentedDataSource + v.listContainer = listContainerView + } + } +} + +extension MyGroupVC: JXSegmentedListContainerViewListDelegate { + func listView() -> UIView { + return view + } +} + +extension MyGroupVC: JXSegmentedListContainerViewDataSource { + func numberOfLists(in listContainerView: JXSegmentedListContainerView) -> Int { + if let titleDataSource = segmentedView.dataSource as? JXSegmentedBaseDataSource { + return titleDataSource.dataSource.count + } + return 0 + } + + func listContainerView(_ listContainerView: JXSegmentedListContainerView, initListAt index: Int) -> JXSegmentedListContainerViewListDelegate { + let type = list[index] + return MyGroupView(selectType: type) + + } +} diff --git a/ShenYin/ShenYin/UI/Sport/Sport/Group/MyGroup/MyGroupView.swift b/ShenYin/ShenYin/UI/Sport/Sport/Group/MyGroup/MyGroupView.swift new file mode 100644 index 0000000..8cb5dea --- /dev/null +++ b/ShenYin/ShenYin/UI/Sport/Sport/Group/MyGroup/MyGroupView.swift @@ -0,0 +1,84 @@ +// +// GroupListView.swift +// ShenYin +// +// Created by liangshi on 2026/2/6. +// + +import UIKit +import JXSegmentedView +import MJRefresh + + +class MyGroupView: BaseView,UITableViewDelegate,UITableViewDataSource { + let tableView = UITableView(frame: .zero, style: .plain) + var list = [MyGroupModel]() + var type = MyGroupType.add + + init(selectType type:MyGroupType){ + super.init(frame: .zero) + self.type = type + tableView.mj_header?.beginRefreshing() + } + + override func setupViews() { + addSubview(tableView) + tableView.do { v in + v.backgroundColor = .color(.bgColor) + v.delegate = self + v.dataSource = self + v.rowHeight = 74 + v.separatorStyle = .none + v.register(MyGroupCell.self, forCellReuseIdentifier: MyGroupCell.reuseID) + v.mj_header = MJRefreshNormalHeader(refreshingTarget: self, refreshingAction: #selector(refreshAction)) + } + } + + override func setupLayout() { + tableView.snp.makeConstraints { (m) in + m.top.equalToSuperview().inset(10) + m.left.right.bottom.equalToSuperview() + } + } + + @objc func refreshAction(){ + var params:[String:Any] = [:] + params["type"] = type.value + + let api = CommomApi.getSportGroupList(param: params) + HttpClient.request(api) {[weak self] (model:MyGroupModelRoot,res) in + guard let self = self else {return} + if model.isSuccess{ + list = model.data + } + tableView.reloadData() + tableView.mj_header?.endRefreshing() + } + + } + + + @MainActor required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { + return list.count + } + func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { + let cell = MyGroupCell.cell(withTableView: tableView, indexPath: indexPath) + cell.model = list[indexPath.row] + return cell + } + func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { + let vc = GroupDetailVC(groupId: list[indexPath.row].id) + App.shared.goToPage(vc) + } + +} + +extension MyGroupView: JXSegmentedListContainerViewListDelegate { + func listView() -> UIView { + return self + } +} diff --git a/ShenYin/ShenYin/UI/Sport/Sport/Group/Other/GroupRoleVC.swift b/ShenYin/ShenYin/UI/Sport/Sport/Group/Other/GroupRoleVC.swift new file mode 100644 index 0000000..613860a --- /dev/null +++ b/ShenYin/ShenYin/UI/Sport/Sport/Group/Other/GroupRoleVC.swift @@ -0,0 +1,241 @@ +import UIKit +import HandyJSON + +enum GroupRoleType:String,HandyJSONEnum{ + case none = "none" + case pfs = "pfs" + case lpz = "lpz" + case gpz = "gpz" + case ccs = "ccs" + case psy = "psy" + case bjy = "bjy" + case qns = "qns" + case lqz = "lqz" + case gqz = "gqz" + case pps = "pps" + + + var name:String{ + switch self { + case .none: + return "无" + case .pfs: + return "破风手" + case .lpz: + return "领跑者" + case .gpz: + return "跟跑者" + case .ccs: + return "冲刺手" + case .psy: + return "配速员" + case .bjy: + return "补给员" + case .qns: + return "全能手" + case .lqz: + return "领骑者" + case .gqz: + return "跟骑者" + case .pps: + return "爬坡手" + } + } + var color:UIColor{ + switch self { + case .none: + return .rgb(0xD9D9D9) + case .pfs: + return .rgb(0xFF9232) + case .lpz: + return .rgb(0x7C2CED) + case .gpz: + return .rgb(0x21B7F8) + case .ccs: + return .rgb(0xFF583B) + case .psy: + return .rgb(0x4252FF) + case .bjy: + return .rgb(0x13D598) + case .qns: + return .rgb(0xF1BF18) + case .lqz: + return .rgb(0x7C2CED) + case .gqz: + return .rgb(0xF1BF18) + case .pps: + return .rgb(0xF1BF18) + } + } + + static var all:[GroupRoleType] = [.none,.pfs,.lpz,.gpz,.ccs,.psy,.bjy,.qns] +} + +class GroupRoleVC: BaseVC{ + let tableView = UITableView(frame: .zero, style: .plain) + var list:[GroupRoleType] = GroupRoleType.all + var currentType = GroupRoleType.none + let btn = CustomButton() + var didSelected:((GroupRoleType)->Void)? + + override func viewDidLoad() { + super.viewDidLoad() + // Do any additional setup after loading the view. + } + override func setupViews() { + navBar.do { v in + v.title = "我的团队角色".l10n + v.rightView.setImage(UIImage(named: "help_icon"), for: .normal) + v.rightView.isHidden = false + } + + tableView.do { v in + v.backgroundColor = .color(.bgColor) + v.delegate = self + v.dataSource = self + v.rowHeight = 52 + v.separatorStyle = .none + v.contentInset = UIEdgeInsets(top: 10, left: 0, bottom: 0, right: 0) + v.register(RoleCell.self, forCellReuseIdentifier: RoleCell.reuseID) + + let header = UIView(frame: CGRect(x: 0, y: 0, width: kScreenWid, height: 40)) + let label = UILabel(frame: CGRect(x: 18, y: 0, width: kScreenWid - 36, height: 40)) + label.do { v in + v.text = "我的团队-团队设置-骑行团队角色".l10n + v.font = .regular(14) + v.textColor = .color(.secondary) + } + header.addSubview(label) + v.tableHeaderView = header + } + view.addSubviews([tableView,btn]) + + btn.set(title: "确定".l10n, target: self, action: #selector(btnAction)) + } + override func setupLayout() { + tableView.snp.makeConstraints { make in + make.left.right.bottom.equalToSuperview() + make.top.equalTo(kNavBtm) + } + btn.snp.makeConstraints { make in + make.width.equalTo(272) + make.height.equalTo(46) + make.centerX.equalToSuperview() + make.bottom.equalToSuperview().inset(82) + } + } + @objc func btnAction(){ + didSelected?(currentType) + self.navigationController?.popViewController(animated: true) + } + override func navRightViewTapped() { + let tip = GroupDesPopView() + tip.titleLabel.text = "团队角色说明".l10n + tip.titleLabel1.text = "团队角色是什么?".l10n + tip.desLabel2.text = "团队运动中各角色是分工明确、协同发力的核心单元,通过精准配合最大化团队运动效果;".l10n + tip.titleLabel2.text = "团队角色说明".l10n + tip.desLabel2.text = "破风手:以 “挡风减阻” 为核心,为后方队友节省体能;\n领骑者:把控整体节奏,稳定配速避免失衡;\n跟骑者:借助风阻优势储备体力,为关键阶段蓄力;\n冲刺手:聚焦终点前爆发,锁定最终成绩;\n配速员:作为 “节奏标杆”,助力队友校准目标; \n补给员:保障物资供应,维持运动续航; \n全能手:灵活补位,适配破风、带速等多元需求。".l10n + tip.tipsIcon.image = UIImage(named: "role_des") + tip.show() + } +} + +extension GroupRoleVC:UITableViewDelegate,UITableViewDataSource { + func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { + return list.count + } + + func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { + let cell = RoleCell.cell(withTableView: tableView, indexPath: indexPath) + cell.titleLabel.text = list[indexPath.row].name + cell.type.backgroundColor = list[indexPath.row].color + let rect = CGRect(x: 0, y: 0, width: kScreenWid - 36, height: 52) + if indexPath.row == 0 { + cell.bgView.yc.setCorners(withRoundedRect: rect, roundingCorners: [.topLeft,.topRight], cornerRadii: CGSize(width: 8, height: 8)) + } + else if indexPath.row == list.count - 1{ + cell.bgView.yc.setCorners(withRoundedRect: rect, roundingCorners: [.bottomLeft,.bottomRight], cornerRadii: CGSize(width: 8, height: 8)) + } + else{ + cell.bgView.yc.setCorners(withRoundedRect: rect, roundingCorners: [.allCorners], cornerRadii: CGSize(width: 0, height: 0)) + } + cell.selectedIcon.isHidden = list[indexPath.row] != currentType + return cell + } + func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { + let data = list[indexPath.row] + currentType = data + tableView.reloadData() + } +} +class RoleCell: BaseTableViewCell { + let bgView = UIView() + let type = UIView() + let titleLabel = UILabel() + let selectedIcon = UIImageView() + let line = UIView() + + + override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) { + super.init(style: style, reuseIdentifier: reuseIdentifier) + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + override func setupViews() { + contentView.addSubview(bgView) + bgView.addSubviews([type,titleLabel,selectedIcon,line]) + + backgroundColor = .color(.bgColor) + contentView.backgroundColor = .clear + selectionStyle = .none + bgView.do { v in + v.backgroundColor = .color(.cellBg) + } + type.do { v in + v.yc.setCornerRadius(3) + v.backgroundColor = .red + } + titleLabel.do { (v) in + v.textColor = .color(.first) + v.font = .bold(14) + v.textAlignment = .left + } + + selectedIcon.image = UIImage(named: "cell_selected") + line.do { v in + v.backgroundColor = .color(.separateLine) + } + } + + override func setupLayout() { + bgView.snp.makeConstraints { (m) in + m.left.right.equalToSuperview().inset(18) + m.top.equalToSuperview() + m.bottom.equalToSuperview() + } + type.snp.makeConstraints { make in + make.left.equalToSuperview().inset(16) + make.centerY.equalToSuperview() + make.width.equalTo(6) + make.height.equalTo(16) + } + selectedIcon.snp.makeConstraints { (m) in + m.width.equalTo(21) + m.height.equalTo(16) + m.right.equalToSuperview().inset(22) + m.centerY.equalToSuperview() + } + titleLabel.snp.makeConstraints { (m) in + m.centerY.equalToSuperview() + m.left.equalToSuperview().offset(32) + m.height.equalToSuperview() + } + line.snp.makeConstraints { make in + make.left.right.equalToSuperview().inset(18) + make.bottom.equalToSuperview() + make.height.equalTo(0.4) + } + } +} diff --git a/ShenYin/ShenYin/UI/Sport/Sport/Group/Other/GroupTypeVC.swift b/ShenYin/ShenYin/UI/Sport/Sport/Group/Other/GroupTypeVC.swift new file mode 100644 index 0000000..bb7a0e6 --- /dev/null +++ b/ShenYin/ShenYin/UI/Sport/Sport/Group/Other/GroupTypeVC.swift @@ -0,0 +1,80 @@ +// +// GroupTypeVC.swift +// ShenYin +// +// Created by liangshi on 2026/2/6. +// + +import UIKit + +class GroupTypeVC: BaseVC{ + let tableView = UITableView(frame: .zero, style: .plain) + var list:[SportType] = SportType.all + var currentType = SportType.walk + let btn = CustomButton() + var didSelected:((SportType)->Void)? + + override func viewDidLoad() { + super.viewDidLoad() + // Do any additional setup after loading the view. + } + override func setupViews() { + self.navBar.title = "团队类型".l10n + tableView.do { v in + v.backgroundColor = .color(.bgColor) + v.delegate = self + v.dataSource = self + v.rowHeight = 52 + v.separatorStyle = .none + v.contentInset = UIEdgeInsets(top: 10, left: 0, bottom: 0, right: 0) + v.register(SelectCell.self, forCellReuseIdentifier: SelectCell.reuseID) + } + view.addSubviews([tableView,btn]) + + btn.set(title: "确定".l10n, target: self, action: #selector(btnAction)) + } + override func setupLayout() { + tableView.snp.makeConstraints { make in + make.left.right.bottom.equalToSuperview() + make.top.equalTo(kNavBtm) + } + btn.snp.makeConstraints { make in + make.width.equalTo(272) + make.height.equalTo(46) + make.centerX.equalToSuperview() + make.bottom.equalToSuperview().inset(82) + } + } + @objc func btnAction(){ + didSelected?(currentType) + self.navigationController?.popViewController(animated: true) + } +} + +extension GroupTypeVC:UITableViewDelegate,UITableViewDataSource { + func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { + return list.count + } + + func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { + let cell = SelectCell.cell(withTableView: tableView, indexPath: indexPath) + cell.titleLabel.text = list[indexPath.row].name + let rect = CGRect(x: 0, y: 0, width: kScreenWid - 36, height: 52) + if indexPath.row == 0 { + cell.bgView.yc.setCorners(withRoundedRect: rect, roundingCorners: [.topLeft,.topRight], cornerRadii: CGSize(width: 8, height: 8)) + } + else if indexPath.row == list.count - 1{ + cell.bgView.yc.setCorners(withRoundedRect: rect, roundingCorners: [.bottomLeft,.bottomRight], cornerRadii: CGSize(width: 8, height: 8)) + } + else{ + cell.bgView.yc.setCorners(withRoundedRect: rect, roundingCorners: [.allCorners], cornerRadii: CGSize(width: 0, height: 0)) + } + cell.selectedIcon.isHidden = list[indexPath.row] != currentType + return cell + } + func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { + let data = list[indexPath.row] + currentType = data + tableView.reloadData() + } +} diff --git a/ShenYin/ShenYin/UI/Sport/Sport/Group/add/AddGroupVC.swift b/ShenYin/ShenYin/UI/Sport/Sport/Group/add/AddGroupVC.swift new file mode 100644 index 0000000..a403111 --- /dev/null +++ b/ShenYin/ShenYin/UI/Sport/Sport/Group/add/AddGroupVC.swift @@ -0,0 +1,106 @@ +// +// AddGrpupVC.swift +// ShenYin +// +// Created by liangshi on 2026/2/6. +// + +import UIKit + +class AddGroupVC: BaseVC{ + let nameView = SportInputItem() + let typeView = SportSelectItem() + let desView = SportInputItem() + let okBtn = CustomButton() + var sportType:GroupRoleType = .none + + override func setupViews() { + navBar.do { v in + v.title = "加入团队".l10n + v.rightView.setImage(UIImage(named: "help_icon"), for: .normal) + v.rightView.isHidden = false + } + view.addSubviews([nameView,typeView,desView,okBtn]) + nameView.do { v in + v.yc.setCornerRadius(12) + v.titleLabel.text = "团队号".l10n + v.textField.text = "" + v.textField.attributedPlaceholder = NSAttributedString(string: "请输入团队号".l10n, attributes: [NSAttributedString.Key.foregroundColor: UIColor.color(.placeholder)]) + + } + typeView.do { v in + v.yc.setCornerRadius(12) + v.titleLabel.text = "团队角色".l10n + v.detailLabel.text = sportType.name + v.yc.addTap(target: self, action: #selector(typeAction)) + } + desView.do { v in + v.yc.setCornerRadius(12) + v.titleLabel.text = "打招呼内容".l10n + v.textField.attributedPlaceholder = NSAttributedString(string: "描述一下您的团".l10n, attributes: [NSAttributedString.Key.foregroundColor: UIColor.color(.placeholder)]) + v.maxLength = 50 + } + okBtn.set(title: "加入".l10n,style: .theme1, target: self, action: #selector(okAction)) + } + override func setupLayout() { + nameView.snp.makeConstraints { make in + make.left.right.equalToSuperview().inset(18) + make.top.equalToSuperview().inset(kNavBtm + 20) + make.height.equalTo(51) + } + + desView.snp.makeConstraints { make in + make.left.right.equalToSuperview().inset(18) + make.top.equalTo(nameView.snp.bottom).offset(10) + make.height.equalTo(65) + } + typeView.snp.makeConstraints { make in + make.left.right.equalToSuperview().inset(18) + make.top.equalTo(desView.snp.bottom).offset(10) + make.height.equalTo(51) + } + okBtn.snp.makeConstraints { make in + make.bottom.equalToSuperview().inset(80) + make.left.right.equalToSuperview().inset(51) + make.height.equalTo(46) + } + } + override func navRightViewTapped() { + let tip = GroupDesPopView() + tip.show() + } + @objc func typeAction(){ + let vc = GroupRoleVC() + vc.didSelected = {[weak self] value in + self?.sportType = value + self?.typeView.detailLabel.text = value.name + } + App.shared.goToPage(vc) + } + @objc func lightAction(){ + App.shared.goToPage(LightSetVC()) + } + @objc func okAction(){ + if nameView.isEmpty { + YLprogressHUD.showAutoHiden("团队名称不能为空".l10n) + return + } + + YLprogressHUD.showLoading() + var params:[String:Any] = [:] + params["id"] = nameView.value + params["requestText"] = desView.value + + let api = CommomApi.applyJoinGroup(param: params) + HttpClient.request(api){ (model:ResponseBaseModel, res) in + + if model.isSuccess{ + YLprogressHUD.hidden() + } + else{ + YLprogressHUD.showAutoHiden(model.msg) + } + } + } +} + diff --git a/ShenYin/ShenYin/UI/Sport/Sport/History/SportHistoryVC.swift b/ShenYin/ShenYin/UI/Sport/Sport/History/SportHistoryVC.swift new file mode 100644 index 0000000..d64dece --- /dev/null +++ b/ShenYin/ShenYin/UI/Sport/Sport/History/SportHistoryVC.swift @@ -0,0 +1,124 @@ +// +// History.swift +// ShenYin +// +// Created by liangshi on 2026/2/25. +// + +import JXSegmentedView + + +enum SportHistoryType{ + case week + case month + case year + case all + + var name:String{ + switch self { + case .week: + return "周" + case .month: + return "月" + case .year: + return "å¹´" + case .all: + return "总" + } + } + static var allData:[SportHistoryType] = [.week,.month,.year,.all] +} + +class SportHistoryVC: BaseVC { + private let segmentedDataSource = JXSegmentedTitleDataSource() + private let segmentedView = JXSegmentedView() + private lazy var listContainerView: JXSegmentedListContainerView! = { + let view = JXSegmentedListContainerView(dataSource: self, type: .scrollView) + return view + }() + + var list:[SportHistoryType] = SportHistoryType.allData + override func viewDidLoad() { + super.viewDidLoad() + + // AudioPlayView.shared.show() + self.view.addSubview(AudioPlayView.shared) + MapManager.shared.setupSdk() + } + + override func setupLayout(){ + segmentedView.snp.makeConstraints { (m) in + m.left.right.equalToSuperview() + m.height.equalTo(44.fitR) + m.top.equalToSuperview().inset(kNavBtm) + } + listContainerView.snp.makeConstraints { (m) in + m.width.left.equalToSuperview() + m.top.equalTo(segmentedView.snp.bottom) + m.bottom.equalToSuperview() + } + } + override func setupViews(){ + navBar.do { v in + v.title = "运动记录" + } + view.addSubview(segmentedView) + view.addSubview(listContainerView) + + view.backgroundColor = .color(.bgColor) + segmentedView.do { (v) in + segmentedDataSource.titleNormalColor = .color(.secondary) + segmentedDataSource.titleSelectedColor = .color(.first) + segmentedDataSource.titleNormalFont = .regular(14) + segmentedDataSource.titleSelectedFont = .bold(16) + segmentedDataSource.isTitleZoomEnabled = true + // segmentedDataSource.titleSelectedZoomScale = 1 + segmentedDataSource.isTitleStrokeWidthEnabled = true + segmentedDataSource.isItemSpacingAverageEnabled = true + segmentedDataSource.isTitleColorGradientEnabled = true + segmentedDataSource.titles = list.map{"\($0.name)"} + segmentedView.backgroundColor = .clear + + //配置指示器 + let indicator = JXSegmentedIndicatorLineView() + indicator.indicatorWidth = 20 + indicator.indicatorHeight = 4 + indicator.verticalOffset = 6 + indicator.indicatorColor = .color(.theme) + segmentedView.indicators = [indicator] + + v.dataSource = segmentedDataSource + v.listContainer = listContainerView + } + } +} + +extension SportHistoryVC: JXSegmentedListContainerViewListDelegate { + func listView() -> UIView { + return view + } +} + +extension SportHistoryVC: JXSegmentedListContainerViewDataSource { + func numberOfLists(in listContainerView: JXSegmentedListContainerView) -> Int { + if let titleDataSource = segmentedView.dataSource as? JXSegmentedBaseDataSource { + return titleDataSource.dataSource.count + } + return 0 + } + + func listContainerView(_ listContainerView: JXSegmentedListContainerView, initListAt index: Int) -> JXSegmentedListContainerViewListDelegate { + let type = list[index] + switch type { + case .week: + return SprotHistoryView() + case .month: + return SprotHistoryView() + case .year: + return SprotHistoryView() + case .all: + return SprotHistoryView() + } + + } +} diff --git a/ShenYin/ShenYin/UI/Sport/Sport/History/SprotHistoryView.swift b/ShenYin/ShenYin/UI/Sport/Sport/History/SprotHistoryView.swift new file mode 100644 index 0000000..bd99048 --- /dev/null +++ b/ShenYin/ShenYin/UI/Sport/Sport/History/SprotHistoryView.swift @@ -0,0 +1,25 @@ +// +// SprotHistoryView.swift +// ShenYin +// +// Created by liangshi on 2026/2/25. +// + +import UIKit +import JXSegmentedView + +class SprotHistoryView: BaseView{ + + override func setupViews() { + + } + override func setupLayout() { + + } +} + +extension SprotHistoryView: JXSegmentedListContainerViewListDelegate { + func listView() -> UIView { + return self + } +} diff --git a/ShenYin/ShenYin/UI/Sport/Sport/Light/BrightnessView.swift b/ShenYin/ShenYin/UI/Sport/Sport/Light/BrightnessView.swift new file mode 100644 index 0000000..48f4d23 --- /dev/null +++ b/ShenYin/ShenYin/UI/Sport/Sport/Light/BrightnessView.swift @@ -0,0 +1,78 @@ +// +// LightView.swift +// ShenYin +// +// Created by liangshi on 2026/2/2. +// + +extension LightSetVC{ + class BrightnessView: BaseView { + let bgView = UIView() + let titleLabel = UILabel() + let btn = UIButton() + let line = UIView() + let slider = UISlider() + + override func setupViews() { + addSubviews([titleLabel,slider,btn,line]) + + titleLabel.do { (v) in + v.textColor = .white + v.font = .bold(14) + v.textAlignment = .left + v.text = "亮度".l10n + } + + btn.do { v in + v.setImage(UIImage(named: "tips_2"), for: .normal) + v.addTarget(self, action: #selector(tipsAction), for: .touchUpInside) + } + + line.do { v in + v.backgroundColor = .color(.separateLine) + } + slider.do { v in + // v.backgroundColor = .red + v.minimumValue = 0 + v.maximumValue = 100 + v.value = 50 + v.minimumTrackTintColor = .white + v.maximumTrackTintColor = .white.withAlphaComponent(0.2) + v.setThumbImage(UIColor.white.imageCircle(with: CGSize(width: 20, height: 20)), for: .normal) + v.addTarget(self, action: #selector(sliderChanged(_:)), for: .valueChanged) // continuous changes + + } + } + + override func setupLayout() { + titleLabel.snp.makeConstraints { (m) in + m.top.equalToSuperview().inset(16) + m.left.equalToSuperview().offset(32) + } + slider.snp.makeConstraints { make in + make.height.equalTo(40) + make.centerX.equalToSuperview() + make.left.right.equalToSuperview().inset(32) + make.top.equalToSuperview().inset(40) + } + btn.snp.makeConstraints { (m) in + m.size.equalTo(30) + m.right.equalToSuperview().inset(32) + m.centerY.equalTo(titleLabel.snp.centerY) + } + + line.snp.makeConstraints { make in + make.left.right.equalToSuperview().inset(28) + make.bottom.equalToSuperview() + make.height.equalTo(0.4) + } + } + + @objc func tipsAction(){ + SportPopView().show() + } + @objc func sliderChanged(_ slider:UISlider){ + + } + } +} diff --git a/ShenYin/ShenYin/UI/Sport/Sport/Light/LightSetVC.swift b/ShenYin/ShenYin/UI/Sport/Sport/Light/LightSetVC.swift new file mode 100644 index 0000000..2fb42fd --- /dev/null +++ b/ShenYin/ShenYin/UI/Sport/Sport/Light/LightSetVC.swift @@ -0,0 +1,103 @@ +// +// LightSetVC.swift +// ShenYin +// +// Created by liangshi on 2026/2/2. +// + +import UIKit +class LightSetVC: BaseVC{ + let bgView = UIScrollView() + let icon = UIImageView() + let stateView = StateView() + let modelView = ModeView() + let brightnessView = BrightnessView() + let sysView = StateView() + + override func viewDidLoad() { + super.viewDidLoad() + // Do any additional setup after loading the view. + + } + open override var preferredStatusBarStyle: UIStatusBarStyle { + return .lightContent + } + override func setupViews() { + navBar.do { v in + v.title = "灯带设置".l10n + v.titleLabel.textColor = .white + v.backgroundColor = .clear + v.leftView.setImage(UIImage(named: "arrow_back_white"), for: .normal) + } + view.backgroundColor = .rgb(0x1F1F1F) + view.addSubviews([bgView]) + bgView.addSubviews([icon,stateView,modelView,brightnessView,sysView]) + + bgView.do { v in + v.backgroundColor = .clear + } + icon.do { v in + v.image = UIImage(named: "guchuandao_icon") + } + stateView.do { v in + v.iSwitch.addTarget(self, action: #selector(stateAction), for: .valueChanged) + v.titleLabel.text = "灯带".l10n + } + modelView.do { v in + } + brightnessView.do { v in +// v.backgroundColor = .yellow + } + sysView.do { v in + v.line.isHidden = true + v.titleLabel.text = "同步至队员".l10n + } + stateAction() + } + override func setupLayout() { + + bgView.snp.makeConstraints { make in + make.left.equalToSuperview() + make.width.equalTo(kScreenWid) + make.top.equalToSuperview().inset(kNavBtm) + make.height.equalTo(kScreenHei - kNavBtm) + } + icon.snp.makeConstraints { make in + make.height.equalTo(130.fitR) + make.width.equalTo(220.fitR) + make.centerX.equalToSuperview() + make.top.equalToSuperview().inset(40) + } + stateView.snp.makeConstraints { make in + make.width.equalTo(kScreenWid) + make.height.equalTo(60) + make.top.equalTo(icon.snp.bottom).offset(35) + make.left.equalToSuperview() + } + modelView.snp.makeConstraints { make in + make.width.equalTo(kScreenWid) + make.height.equalTo(103) + make.top.equalTo(stateView.snp.bottom).offset(0) + make.left.equalToSuperview() + } + brightnessView.snp.makeConstraints { make in + make.width.equalTo(kScreenWid) + make.height.equalTo(90) + make.top.equalTo(modelView.snp.bottom).offset(0) + make.left.equalToSuperview() + } + sysView.snp.makeConstraints { make in + make.width.equalTo(kScreenWid) + make.height.equalTo(60) + make.top.equalTo(brightnessView.snp.bottom).offset(0) + make.left.equalToSuperview() + make.bottom.equalToSuperview() + } + } + + @objc func stateAction(){ + modelView.isHidden = !stateView.iSwitch.isOn + brightnessView.isHidden = !stateView.iSwitch.isOn + sysView.isHidden = !stateView.iSwitch.isOn + } +} diff --git a/ShenYin/ShenYin/UI/Sport/Sport/Light/ModeView.swift b/ShenYin/ShenYin/UI/Sport/Sport/Light/ModeView.swift new file mode 100644 index 0000000..f6f5a06 --- /dev/null +++ b/ShenYin/ShenYin/UI/Sport/Sport/Light/ModeView.swift @@ -0,0 +1,165 @@ +// +// ModeView.swift +// ShenYin +// +// Created by liangshi on 2026/2/2. +// +enum LightMode { + case normal + case breath + case flash + + var name:String{ + switch self { + case .normal: + return "正常".l10n + case .breath: + return "呼吸".l10n + case .flash: + return "闪烁".l10n + } + } +} +extension LightSetVC{ + + + class ModeView: BaseView { + let titleLabel = UILabel() + let normalItem = SelectedTypeItem() + let breathItem = SelectedTypeItem() + let flashItem = SelectedTypeItem() + let line = UIView() + + var isOk:Bool{ + return normalItem.isSelected || breathItem.isSelected || flashItem.isSelected + } + var mode:LightMode?{ + if normalItem.isSelected { + return .normal + } + if breathItem.isSelected { + return .breath + } + if flashItem.isSelected { + return .flash + } + return nil + } + + + override func setupViews() { + // backgroundColor = .red + addSubviews([titleLabel,normalItem,breathItem,flashItem,line]) + + titleLabel.do { v in + v.textAlignment = .center + v.font = .bold(14) + v.textColor = .white + v.text = "模式".l10n + } + normalItem.do { v in + v.mode = .normal + v.yc.setCornerRadius(8) + v.yc.addTap(target: self, action: #selector(normalAction)) + v.isSelected = true + } + breathItem.do { v in + v.mode = .breath + v.yc.setCornerRadius(8) + v.yc.addTap(target: self, action: #selector(breathAction)) + } + flashItem.do { v in + v.mode = .flash + v.yc.setCornerRadius(8) + v.yc.addTap(target: self, action: #selector(flashAction)) + } + line.do { v in + v.backgroundColor = .color(.separateLine) + } + } + + override func setupLayout() { + titleLabel.snp.makeConstraints { make in + make.top.equalToSuperview().inset(20) + make.left.equalToSuperview().inset(32) + } + normalItem.snp.makeConstraints { make in + make.left.equalToSuperview().inset(32) + make.top.equalTo(titleLabel.snp.bottom).offset(10) + make.width.equalTo(80.fitR) + make.height.equalTo(32) + } + breathItem.snp.makeConstraints { make in + make.centerX.equalToSuperview() + make.top.width.height.equalTo(normalItem) + } + flashItem.snp.makeConstraints { make in + make.right.equalToSuperview().inset(32) + make.top.width.height.equalTo(normalItem) + } + line.snp.makeConstraints { make in + make.left.right.equalToSuperview().inset(28) + make.bottom.equalToSuperview() + make.height.equalTo(0.4) + } + } + + @objc func normalAction(){ + normalItem.isSelected = true + breathItem.isSelected = false + flashItem.isSelected = false + } + @objc func breathAction(){ + normalItem.isSelected = false + breathItem.isSelected = true + flashItem.isSelected = false + } + @objc func flashAction(){ + normalItem.isSelected = false + breathItem.isSelected = false + flashItem.isSelected = true + } + + + class SelectedTypeItem: BaseView { + let titleLabel = UILabel() + var mode:LightMode = .normal{ + didSet{ + titleLabel.text = mode.name + } + } + + var isSelected:Bool = false{ + didSet{ + if isSelected { + titleLabel.textColor = .color(.first) + backgroundColor = .color(.bgColor) + } + else{ + backgroundColor = .clear + titleLabel.textColor = .color(.secondary) + yc.setBorder(width: 1, color: .color(.secondary)) + } + } + } + + override func setupViews() { + backgroundColor = .color(.bgColor) + addSubviews([titleLabel]) + titleLabel.do { v in + v.textAlignment = .center + v.adjustsFontSizeToFitWidth = true + v.font = .regular(14) + } + self.isSelected = false + } + + override func setupLayout() { + titleLabel.snp.makeConstraints { make in + make.edges.equalToSuperview() + } + } + } + } + +} diff --git a/ShenYin/ShenYin/UI/Sport/Sport/Light/StateView.swift b/ShenYin/ShenYin/UI/Sport/Sport/Light/StateView.swift new file mode 100644 index 0000000..66a1d90 --- /dev/null +++ b/ShenYin/ShenYin/UI/Sport/Sport/Light/StateView.swift @@ -0,0 +1,52 @@ +// +// StateView.swift +// ShenYin +// +// Created by liangshi on 2026/2/2. +// + +extension LightSetVC{ + class StateView: BaseView { + let titleLabel = UILabel() + let iSwitch = UISwitch() + let line = UIView() + weak var delegate:SportSetCellDelegate? + + override func setupViews() { + addSubviews([titleLabel,iSwitch,line]) + + titleLabel.do { (v) in + v.textColor = .white + v.font = .bold(14) + v.textAlignment = .left + } + + iSwitch.do { v in + // v.backgroundColor = .red + v.isOn = true + } + + line.do { v in + v.backgroundColor = .color(.separateLine) + } + } + + override func setupLayout() { + titleLabel.snp.makeConstraints { (m) in + m.centerY.equalToSuperview() + m.left.equalToSuperview().offset(32) + } + + iSwitch.snp.makeConstraints { (m) in + m.right.equalToSuperview().inset(32) + m.centerY.equalToSuperview() + } + + line.snp.makeConstraints { make in + make.left.right.equalToSuperview().inset(28) + make.bottom.equalToSuperview() + make.height.equalTo(0.4) + } + } + } +} diff --git a/ShenYin/ShenYin/UI/Sport/Sport/Ready/CountdownView.swift b/ShenYin/ShenYin/UI/Sport/Sport/Ready/CountdownView.swift new file mode 100644 index 0000000..5afe77e --- /dev/null +++ b/ShenYin/ShenYin/UI/Sport/Sport/Ready/CountdownView.swift @@ -0,0 +1,76 @@ +// +// Untitled.swift +// ShenYin +// +// Created by liangshi on 2026/1/21. +// + +extension ReadyVC{ + class CountdownView: BaseView { + let label = UILabel() + let skip = UILabel() + var timer:Timer! + var count = 3 + + override func setupViews() { + addSubviews([label,skip]) + label.do { v in + v.textColor = .color(.first) + v.font = .bold(210) + v.textAlignment = .center + v.text = "\(count)" + } + skip.do { v in + v.textColor = .color(.first) + v.font = .regular(14) + v.textAlignment = .center + v.text = "直接开始".l10n + v.yc.addTap(target: self, action: #selector(skipAction)) + + let line = UIView() + line.backgroundColor = .color(.first) + v.addSubview(line) + line.snp.makeConstraints { make in + make.height.equalTo(1) + make.left.right.equalToSuperview() + make.bottom.equalToSuperview().offset(4) + } + } + backgroundColor = .white + } + + override func setupLayout() { + label.snp.makeConstraints { make in + make.center.equalToSuperview() + } + skip.snp.makeConstraints { make in + make.bottom.equalToSuperview().inset(100) + make.centerX.equalToSuperview() + } + } + override func setupData() { + timer = Timer.scheduledTimer(timeInterval: 1, target: self, selector: #selector(timerAction), userInfo: nil, repeats: true) + } + @objc func timerAction(){ + count -= 1 + label.text = "\(count)" + print(count) + if count == 0 { + skipAction() + } + } + static func show(in view:UIView){ + let countView = CountdownView() + view.addSubview(countView) + countView.snp.makeConstraints { make in + make.edges.equalToSuperview() + } + } + @objc func skipAction(){ + timer.invalidate() + App.shared.goToPage(SportingVC()) + removeFromSuperview() + } + } + +} diff --git a/ShenYin/ShenYin/UI/Sport/Sport/Ready/CustomMapView.swift b/ShenYin/ShenYin/UI/Sport/Sport/Ready/CustomMapView.swift new file mode 100644 index 0000000..fbc7567 --- /dev/null +++ b/ShenYin/ShenYin/UI/Sport/Sport/Ready/CustomMapView.swift @@ -0,0 +1,12 @@ +// +// Untitled.swift +// ShenYin +// +// Created by liangshi on 2026/1/21. +// + +class CustomMapView: BaseView { + + + +} diff --git a/ShenYin/ShenYin/UI/Sport/Sport/Ready/DeviceStateView.swift b/ShenYin/ShenYin/UI/Sport/Sport/Ready/DeviceStateView.swift new file mode 100644 index 0000000..06be847 --- /dev/null +++ b/ShenYin/ShenYin/UI/Sport/Sport/Ready/DeviceStateView.swift @@ -0,0 +1,57 @@ +// +// Untitled.swift +// ShenYin +// +// Created by liangshi on 2026/2/25. +// + +class DeviceStateView: BaseView { + let deviceLabel = UILabel() + let deviceBg = UIView() + let stateLabel = UIView() + + + var value:String = ""{ + didSet{ + deviceLabel.text = value + } + } + + override func setupViews() { + addSubviews([deviceBg]) + deviceBg.addSubviews([stateLabel,deviceLabel]) + deviceBg.do { v in + v.backgroundColor = .rgb(0xF7F7F7 ) + v.yc.setCornerRadius(13) + } + stateLabel.do { v in + v.backgroundColor = .rgb(0x2BD84E ) + v.yc.setCornerRadius(2) + } + deviceLabel.do { v in + v.textColor = .color(.secondary) + v.font = .regular(10) + v.textAlignment = .center + v.text = "Deep Lite2 Ultra" + } + } + + override func setupLayout() { + deviceBg.snp.makeConstraints { make in + make.height.equalTo(26) + make.top.equalToSuperview() + make.left.right.equalToSuperview() + } + stateLabel.snp.makeConstraints { make in + make.size.equalTo(4) + make.centerY.equalToSuperview() + make.left.equalToSuperview().inset(9) + } + deviceLabel.snp.makeConstraints { make in + make.centerY.equalToSuperview() + make.left.equalToSuperview().inset(18) + make.right.equalToSuperview().inset(8) + make.height.equalToSuperview() + } + } + } diff --git a/ShenYin/ShenYin/UI/Sport/Sport/Ready/FooterView.swift b/ShenYin/ShenYin/UI/Sport/Sport/Ready/FooterView.swift new file mode 100644 index 0000000..78b2034 --- /dev/null +++ b/ShenYin/ShenYin/UI/Sport/Sport/Ready/FooterView.swift @@ -0,0 +1,106 @@ +// +// FooterView.swift +// ShenYin +// +// Created by liangshi on 2026/1/21. +// + +extension ReadyVC{ + class FooterView: BaseView { + let targetBtn = ButtonItem() + let routeBtn = ButtonItem() + let goBtn = UIImageView() + + override func setupViews() { + addSubviews([targetBtn,goBtn,routeBtn]) + targetBtn.do { v in + v.titleLabel.text = "目标".l10n + v.icon.image = UIImage(named: "ready_target") + v.yc.setCornerRadius(18) + v.yc.addTap(target: self, action: #selector(targetAction)) + } + goBtn.do { v in + v.image = UIImage(named: "ready_go") + v.yc.addTap(target: self, action: #selector(goAction)) + } + routeBtn.do { v in + v.yc.setCornerRadius(18) + v.titleLabel.text = "路线".l10n + v.icon.image = UIImage(named: "ready_route") + v.yc.addTap(target: self, action: #selector(routeAction)) + } + } + + override func setupLayout() { + goBtn.snp.makeConstraints { make in + make.size.equalTo(90) + make.centerY.equalToSuperview() + make.centerX.equalTo(kScreenWid/2) + } + + targetBtn.snp.makeConstraints { make in + make.width.equalTo(90) + make.height.equalTo(36) + make.centerY.equalToSuperview() + make.right.equalTo(goBtn.snp.left).offset(-27) + } + routeBtn.snp.makeConstraints { make in + make.width.equalTo(90) + make.height.equalTo(36) + make.centerY.equalToSuperview() + make.left.equalTo(goBtn.snp.right).offset(27) + } + + } + + @objc func targetAction(){ + App.shared.goToPage(SportTargetVC()) + } + @objc func goAction(){ + if false { + CustomAlert.showWith(title: "完善数据".l10n, messgage: "完善你的身体档案,让运动数据更准确。".l10n,ok: "前往".l10n) { + + } okCallback: { + App.shared.goToPage(UserInfoVC()) + } + } + else{ + CountdownView.show(in: App.shared.window!) + } + + } + @objc func routeAction(){ + + } + + class ButtonItem: BaseView { + let icon = UIImageView() + let titleLabel = UILabel() + + override func setupViews() { + addSubviews([titleLabel,icon]) + icon.do { v in + } + titleLabel.do { v in + v.textColor = .color(.first) + v.font = .regular(14) + } + backgroundColor = .white + } + + override func setupLayout() { + icon.snp.makeConstraints { make in + make.size.equalTo(24) + make.centerY.equalToSuperview() + make.left.equalToSuperview().inset(16) + } + titleLabel.snp.makeConstraints { make in + make.centerY.equalToSuperview() + make.left.equalTo(43) + } + } + } + + } + +} diff --git a/ShenYin/ShenYin/UI/Sport/Sport/Ready/Navbar.swift b/ShenYin/ShenYin/UI/Sport/Sport/Ready/Navbar.swift new file mode 100644 index 0000000..d076f38 --- /dev/null +++ b/ShenYin/ShenYin/UI/Sport/Sport/Ready/Navbar.swift @@ -0,0 +1,110 @@ +// +// Navbar.swift +// ShenYin +// +// Created by liangshi on 2026/2/3. +// + +import UIKit + +extension ReadyVC{ + class Navbar: BaseView { + let backItem = BackItem() + let icon = UIImageView() + let groupBtn = UIButton() + let setBtn = UIButton() + + // + + override func setupViews() { + backgroundColor = .color(.bgColor) + addSubviews([backItem,icon,groupBtn,setBtn]) + backItem.do { v in + v.titleLabel.text = SportManager.shared.sportType.name + v.yc.addTap(target: self, action: #selector(backAction)) + } + icon.do { v in + v.image = UIImage(named: "sport_ai") + } + groupBtn.do { v in + v.setBackgroundImage(UIImage(named: "ready_group"), for: .normal) + v.setTitle("组队".l10n, for: .normal) + v.setTitleColor(.color(.first), for: .normal) + v.titleLabel?.font = .medium(14) + v.addTarget(self, action: #selector(groupAction), for: .touchUpInside) + } + setBtn.do { v in + v.setImage(UIImage(named: "ready_set"), for: .normal) + v.addTarget(self, action: #selector(groupAction), for: .touchUpInside) + } + } + + override func setupLayout() { + icon.snp.makeConstraints { make in + make.size.equalTo(32) + make.bottom.equalToSuperview().inset(10) + make.centerX.equalToSuperview() + } + setBtn.snp.makeConstraints { make in + make.width.equalTo(32) + make.height.equalTo(32) + make.centerY.equalTo(icon) + make.right.equalToSuperview().inset(10) + } + groupBtn.snp.makeConstraints { make in + make.width.equalTo(60) + make.height.equalTo(24) + make.centerY.equalTo(icon) + make.right.equalTo(setBtn.snp.left).offset(-10) + } + backItem.snp.makeConstraints { make in + make.left.equalToSuperview() + make.centerY.equalTo(icon) + make.height.equalTo(32) + make.width.equalTo(100) + } + } + @objc func backAction(){ + App.shared.popPage() + } + @objc func groupAction(){ + App.shared.goToPage(SportSetVC()) + } + @objc func setAction(){ + App.shared.goToPage(SportSetVC()) + } + + class BackItem: BaseView { + let icon = UIImageView() + let titleLabel = UILabel() + + override func setupViews() { + addSubviews([titleLabel,icon]) + icon.do { v in + v.image = UIImage(named: "arrow_back") + } + titleLabel.do { v in + v.textColor = .color(.first) + v.font = .bold(16) + } + } + + override func setupLayout() { + icon.snp.makeConstraints { make in + make.width.equalTo(10) + make.height.equalTo(19) + make.left.equalToSuperview().inset(18) + make.centerY.equalToSuperview() + } + titleLabel.snp.makeConstraints { make in + make.centerY.equalToSuperview() + make.left.equalToSuperview().inset(42) + } + } + } + } + + +} + + diff --git a/ShenYin/ShenYin/UI/Sport/Sport/Ready/ReadyHeader.swift b/ShenYin/ShenYin/UI/Sport/Sport/Ready/ReadyHeader.swift new file mode 100644 index 0000000..a73da88 --- /dev/null +++ b/ShenYin/ShenYin/UI/Sport/Sport/Ready/ReadyHeader.swift @@ -0,0 +1,107 @@ +// +// ReadyHeader.swift +// ShenYin +// +// Created by liangshi on 2026/2/3. +// + +extension ReadyVC{ + class ReadyHeader: BaseView { + + let typeLabel = UILabel() + let totalLabel = UILabel() + let unitLabel = UILabel() + let aiBtn = ButtonItem() + + + override func setupViews() { + addSubviews([typeLabel,totalLabel,unitLabel,aiBtn]) + + typeLabel.do { v in + v.textColor = .color(.secondary) + v.font = .regular(12) + v.text = SportManager.shared.sportType.readyText + v.textAlignment = .left + } + totalLabel.do { v in + v.textColor = .color(.first) + v.font = .bold(20) + v.text = "00" + v.textAlignment = .left + } + unitLabel.do { v in + v.textColor = .color(.first) + v.font = .medium(14) + v.text = "公里" + v.textAlignment = .left + } + aiBtn.do { v in + v.yc.setBorder(width: 1, color: .color(.secondary), radius: 18) + v.yc.addTap(target: self, action: #selector(aiAction)) + } + } + + override func setupLayout() { + typeLabel.snp.makeConstraints { make in + make.left.equalToSuperview().inset(32) + make.top.equalToSuperview().inset(25) + + } + totalLabel.snp.makeConstraints { make in + make.left.equalTo(typeLabel) + make.bottom.equalToSuperview().inset(27) + + } + unitLabel.snp.makeConstraints { make in + make.left.equalTo(totalLabel.snp.right).offset(2) + make.bottom.equalToSuperview().inset(30) + + } + + aiBtn.snp.makeConstraints { make in + make.size.equalTo(105) + make.height.equalTo(36) + make.centerY.equalToSuperview() + make.right.equalToSuperview().inset(18) + } + } + + @objc func aiAction(){ +// App.shared.goToPage(SportTargetVC()) + } + + + class ButtonItem: BaseView { + let aiLabel = UILabel() + let titleLabel = UILabel() + + override func setupViews() { + addSubviews([titleLabel,aiLabel]) + aiLabel.do { v in + v.textColor = .color(.secondary) + v.font = .bold(14) + v.text = "AI" + } + titleLabel.do { v in + v.textColor = .color(.secondary) + v.font = .regular(14) + v.text = "语音陪练".l10n + } + backgroundColor = .white + } + + override func setupLayout() { + aiLabel.snp.makeConstraints { make in + make.left.equalToSuperview().inset(12) + make.centerY.equalToSuperview() + } + titleLabel.snp.makeConstraints { make in + make.centerY.equalToSuperview() + make.right.equalToSuperview().inset(12) + } + } + } + + } + +} diff --git a/ShenYin/ShenYin/UI/Sport/Sport/Ready/ReadyVC.swift b/ShenYin/ShenYin/UI/Sport/Sport/Ready/ReadyVC.swift new file mode 100644 index 0000000..a50f35c --- /dev/null +++ b/ShenYin/ShenYin/UI/Sport/Sport/Ready/ReadyVC.swift @@ -0,0 +1,76 @@ +// +// Untitled.swift +// ShenYin +// +// Created by liangshi on 2026/1/21. +// +import MAMapKit + +class ReadyVC: BaseVC { + let bar = Navbar() + let headerView = ReadyHeader() + var mapView:MAMapView! + let footerView = FooterView() + let deviceView = DeviceStateView() + + override func setupViews() { + navBar.do { v in + v.addSubview(bar) + } + + mapView = MAMapView(frame: .zero) + mapView.do { v in + v.delegate = self + v.showsUserLocation = true + v.zoomLevel = 16 + v.userTrackingMode = .followWithHeading + } + + + view.addSubviews([headerView,mapView,footerView]) + mapView.addSubview(deviceView) + + headerView.do { v in + v.backgroundColor = .clear + } + mapView.do { v in + v.yc.setCornerRadius(12) + } + footerView.do { v in + } + } + override func setupLayout() { + bar.snp.makeConstraints { make in + make.edges.equalToSuperview() + } + headerView.snp.makeConstraints { make in + make.left.right.equalToSuperview() + make.top.equalToSuperview().inset(kNavBtm) + make.height.equalTo(100) + } + footerView.snp.makeConstraints { make in + make.left.right.equalToSuperview() + make.bottom.equalToSuperview() + make.height.equalTo(200) + } + mapView.snp.makeConstraints { make in + make.left.right.equalToSuperview().inset(18) + make.top.equalTo(headerView.snp.bottom) + make.bottom.equalTo(footerView.snp.top) + } + deviceView.snp.makeConstraints { make in + make.top.right.equalToSuperview().inset(12) + make.height.equalTo(26) + } + } + override func setupData() { + + } + +} + +extension ReadyVC:MAMapViewDelegate{ + func mapViewRequireLocationAuth(_ locationManager: CLLocationManager!) { +// locationManager.requestAlwaysAuthorization() + } +} diff --git a/ShenYin/ShenYin/UI/Sport/Sport/Root/DataView.swift b/ShenYin/ShenYin/UI/Sport/Sport/Root/DataView.swift new file mode 100644 index 0000000..1b2c5c5 --- /dev/null +++ b/ShenYin/ShenYin/UI/Sport/Sport/Root/DataView.swift @@ -0,0 +1,136 @@ +// +// DataView.swift +// ShenYin +// +// Created by liangshi on 2026/1/21. +// + +extension SportRootView{ + class DataView: BaseView { + let header = headItem() + let line = UIView() + let lastView = ItemView() + + override func setupViews() { + backgroundColor = .color(.cellBg) + addSubviews([header,lastView,line]) + line.backgroundColor = .color(.separateLine) + header.do { v in + v.yc.addTap(target: self, action: #selector(allAction)) + } + lastView.do { v in + v.yc.addTap(target: self, action: #selector(lastAction)) + } + } + + override func setupLayout() { + header.snp.makeConstraints { make in + make.left.right.top.equalToSuperview() + make.height.equalTo(51) + } + lastView.snp.makeConstraints { make in + make.left.right.bottom.equalToSuperview() + make.height.equalTo(72) + } + line.snp.makeConstraints { make in + make.left.right.equalToSuperview().inset(14) + make.height.equalTo(1) + make.bottom.equalTo(header) + } + } + @objc func allAction(){ + App.shared.goToPage(SportHistoryVC()) + } + @objc func lastAction(){ + App.shared.goToPage(SportDetailVC()) + + } + + class headItem: BaseView { + let icon = UIImageView() + let detailLabel = UILabel() + let titleLabel = UILabel() + + override func setupViews() { + yc.setCornerRadius(12) + addSubviews([icon,titleLabel,detailLabel]) + + titleLabel.do { v in + v.textColor = .color(.first) + v.font = .medium(14) + v.text = "运动数据".l10n + } + detailLabel.do { v in + v.textColor = .color(.secondary) + v.font = .regular(12) + v.text = "全部".l10n + } + icon.do { v in + v.image = UIImage(named: "arrow_2") + } + backgroundColor = .white + } + + override func setupLayout() { + + titleLabel.snp.makeConstraints { make in + make.centerY.equalToSuperview() + make.left.equalToSuperview().inset(14) + } + detailLabel.snp.makeConstraints { make in + make.centerY.equalToSuperview() + make.right.equalToSuperview().inset(27) + } + icon.snp.makeConstraints { make in + make.width.equalTo(7) + make.height.equalTo(13) + make.centerY.equalToSuperview() + make.right.equalToSuperview().inset(12) + } + } + } + + class ItemView: BaseView { + let icon = UIImageView() + let detailLabel = UILabel() + let titleLabel = UILabel() + + override func setupViews() { + yc.setCornerRadius(12) + addSubviews([icon,titleLabel,detailLabel]) + + titleLabel.do { v in + v.textColor = .color(.first) + v.font = .medium(14) + v.text = "户外跑步 6.21公里" + } + detailLabel.do { v in + v.textColor = .color(.secondary) + v.font = .regular(10) + v.text = "30分26秒" + } + icon.do { v in + v.image = UIImage(named: "add_2") + } + backgroundColor = .white + } + + override func setupLayout() { + + titleLabel.snp.makeConstraints { make in + make.top.equalToSuperview().inset(16) + make.left.equalToSuperview().inset(64) + } + detailLabel.snp.makeConstraints { make in + make.bottom.equalToSuperview().inset(16) + make.left.equalTo(titleLabel) + } + icon.snp.makeConstraints { make in + make.size.equalTo(40) + make.centerY.equalToSuperview() + make.left.equalToSuperview().inset(14) + } + } + } + } +} diff --git a/ShenYin/ShenYin/UI/Sport/Sport/Root/GroupView.swift b/ShenYin/ShenYin/UI/Sport/Sport/Root/GroupView.swift new file mode 100644 index 0000000..620dc8a --- /dev/null +++ b/ShenYin/ShenYin/UI/Sport/Sport/Root/GroupView.swift @@ -0,0 +1,102 @@ +// +// GroupView.swift +// ShenYin +// +// Created by liangshi on 2026/1/21. +// + +extension SportRootView{ + class GroupView: BaseView { + let creatGroup = ItemView() + let addGroup = ItemView() + let myGroup = ItemView() + + override func setupViews() { + addSubviews([creatGroup,addGroup,myGroup]) + creatGroup.do { v in + v.titleLabel.text = "创建团队".l10n + v.detailLabel.text = "创建我的团队".l10n + v.yc.addTap(target: self, action: #selector(creatAction)) + } + addGroup.do { v in + v.titleLabel.text = "加入团队".l10n + v.detailLabel.text = "加入已创建的团队".l10n + v.yc.addTap(target: self, action: #selector(joinAction)) + + } + myGroup.do { v in + v.titleLabel.text = "我的团队".l10n + v.detailLabel.text = "我创建和已加入的团队".l10n + v.icon.isHidden = true + v.yc.addTap(target: self, action: #selector(myAction)) + } + } + + override func setupLayout() { + creatGroup.snp.makeConstraints { make in + make.left.top.right.equalToSuperview() + make.height.equalTo(65) + } + addGroup.snp.makeConstraints { make in + make.left.bottom.equalToSuperview() + make.height.equalTo(65) + make.width.equalTo((kScreenWid - 36 - 10)/2) + } + myGroup.snp.makeConstraints { make in + make.right.bottom.equalToSuperview() + make.height.equalTo(addGroup) + make.left.equalTo(addGroup.snp.right).offset(10) + } + } + @objc func creatAction(){ + App.shared.goToPage(CreateGroupVC()) + } + @objc func joinAction(){ + App.shared.goToPage(AddGroupVC()) + } + @objc func myAction(){ + App.shared.goToPage(MyGroupVC()) + } + } + class ItemView: BaseView { + let icon = UIImageView() + let detailLabel = UILabel() + let titleLabel = UILabel() + + override func setupViews() { + backgroundColor = .color(.cellBg) + yc.setCornerRadius(12) + addSubviews([icon,titleLabel,detailLabel]) + + titleLabel.do { v in + v.textColor = .color(.first) + v.font = .medium(14) + } + detailLabel.do { v in + v.textColor = .color(.secondary) + v.font = .regular(10) + } + icon.do { v in + v.image = UIImage(named: "add_2") + } + backgroundColor = .white + } + + override func setupLayout() { + + titleLabel.snp.makeConstraints { make in + make.top.equalToSuperview().inset(16) + make.left.equalToSuperview().inset(14) + } + detailLabel.snp.makeConstraints { make in + make.bottom.equalToSuperview().inset(16) + make.left.equalTo(titleLabel) + } + icon.snp.makeConstraints { make in + make.size.equalTo(36) + make.centerY.equalToSuperview() + make.right.equalToSuperview().inset(12) + } + } + } +} diff --git a/ShenYin/ShenYin/UI/Sport/Sport/Root/PlanView.swift b/ShenYin/ShenYin/UI/Sport/Sport/Root/PlanView.swift new file mode 100644 index 0000000..d29ffed --- /dev/null +++ b/ShenYin/ShenYin/UI/Sport/Sport/Root/PlanView.swift @@ -0,0 +1,26 @@ +// +// PlanView.swift +// ShenYin +// +// Created by liangshi on 2026/1/21. +// + +extension SportRootView{ + class PlanView: BaseView { + let icon = ItemView() + + override func setupViews() { + addSubviews([icon]) + icon.do { v in + v.titleLabel.text = "智能运动计划建议".l10n + v.detailLabel.text = "AI规划个人定制运动计划".l10n + } + } + + override func setupLayout() { + icon.snp.makeConstraints { make in + make.edges.equalToSuperview() + } + } + } +} diff --git a/ShenYin/ShenYin/UI/Sport/Sport/Root/RouteView.swift b/ShenYin/ShenYin/UI/Sport/Sport/Root/RouteView.swift new file mode 100644 index 0000000..dadc7a4 --- /dev/null +++ b/ShenYin/ShenYin/UI/Sport/Sport/Root/RouteView.swift @@ -0,0 +1,98 @@ +// +// RouteView.swift +// ShenYin +// +// Created by liangshi on 2026/1/21. +// + +extension SportRootView{ + class RouteView: BaseView { + let header = headItem() + let line = UIView() + let routeView1 = UIView() + let routeView2 = UIView() + + + override func setupViews() { + backgroundColor = .color(.cellBg) + addSubviews([header,routeView1,routeView2,line]) + line.backgroundColor = .color(.separateLine) + + routeView1.do { v in + v.backgroundColor = .lightGray + v.yc.setCornerRadius(12) + } + routeView2.do { v in + v.backgroundColor = .lightGray + v.yc.setCornerRadius(12) + } + } + + override func setupLayout() { + header.snp.makeConstraints { make in + make.left.right.top.equalToSuperview() + make.height.equalTo(51) + } + routeView1.snp.makeConstraints { make in + make.left.bottom.equalToSuperview().inset(14) + make.height.equalTo(96) + make.width.equalTo(((kScreenWid - 64 - 10)/2)) + } + routeView2.snp.makeConstraints { make in + make.right.bottom.equalToSuperview().inset(14) + make.height.equalTo(96) + make.width.equalTo(((kScreenWid - 64 - 10)/2)) + } + line.snp.makeConstraints { make in + make.left.right.equalToSuperview().inset(14) + make.height.equalTo(1) + make.bottom.equalTo(header) + } + } + class headItem: BaseView { + let icon = UIImageView() + let detailLabel = UILabel() + let titleLabel = UILabel() + + override func setupViews() { + yc.setCornerRadius(12) + addSubviews([icon,titleLabel,detailLabel]) + + titleLabel.do { v in + v.textColor = .color(.first) + v.font = .medium(14) + v.text = "路线探索".l10n + } + detailLabel.do { v in + v.textColor = .color(.secondary) + v.font = .regular(12) + v.text = "全部".l10n + } + icon.do { v in + v.image = UIImage(named: "arrow_2") + } + backgroundColor = .white + } + + override func setupLayout() { + + titleLabel.snp.makeConstraints { make in + make.centerY.equalToSuperview() + make.left.equalToSuperview().inset(14) + } + detailLabel.snp.makeConstraints { make in + make.centerY.equalToSuperview() + make.right.equalToSuperview().inset(27) + } + icon.snp.makeConstraints { make in + make.width.equalTo(7) + make.height.equalTo(13) + make.centerY.equalToSuperview() + make.right.equalToSuperview().inset(12) + } + } + } + + + } +} diff --git a/ShenYin/ShenYin/UI/Sport/Sport/Root/SportRootView.swift b/ShenYin/ShenYin/UI/Sport/Sport/Root/SportRootView.swift new file mode 100644 index 0000000..29ea350 --- /dev/null +++ b/ShenYin/ShenYin/UI/Sport/Sport/Root/SportRootView.swift @@ -0,0 +1,66 @@ +// +// SportVC.swift +// ShenYin +// +// Created by liangshi on 2026/1/20. +// + +import UIKit +import JXSegmentedView + +class SportRootView: BaseView{ + let sportType = SportTypeView() + let dataView = DataView() + let groupView = GroupView() + let planView = PlanView() + let routeView = RouteView() + + override func setupViews() { + addSubviews([sportType,dataView,groupView,planView,routeView]) + dataView.do { v in + v.yc.setCornerRadius(12) + } + groupView.do { v in + v.yc.setCornerRadius(12) + } + planView.do { v in + v.yc.setCornerRadius(12) + } + routeView.do { v in + v.yc.setCornerRadius(12) + } + } + override func setupLayout() { + sportType.snp.makeConstraints { make in + make.left.right.equalToSuperview() + make.top.equalToSuperview().inset(20) + make.height.equalTo(80) + } + dataView.snp.makeConstraints { make in + make.left.right.equalToSuperview().inset(18) + make.top.equalTo(sportType.snp.bottom).offset(10) + make.height.equalTo(123) + } + groupView.snp.makeConstraints { make in + make.left.right.equalToSuperview().inset(18) + make.top.equalTo(dataView.snp.bottom).offset(10) + make.height.equalTo(140) + } + planView.snp.makeConstraints { make in + make.left.right.equalToSuperview().inset(18) + make.top.equalTo(groupView.snp.bottom).offset(10) + make.height.equalTo(65) + } + routeView.snp.makeConstraints { make in + make.left.right.equalToSuperview().inset(18) + make.top.equalTo(planView.snp.bottom).offset(10) + make.height.equalTo(180) + } + } +} + +extension SportRootView: JXSegmentedListContainerViewListDelegate { + func listView() -> UIView { + return self + } +} diff --git a/ShenYin/ShenYin/UI/Sport/Sport/Root/SportTypeView.swift b/ShenYin/ShenYin/UI/Sport/Sport/Root/SportTypeView.swift new file mode 100644 index 0000000..3ca5d5f --- /dev/null +++ b/ShenYin/ShenYin/UI/Sport/Sport/Root/SportTypeView.swift @@ -0,0 +1,134 @@ +// +// SportTypeView.swift +// ShenYin +// +// Created by liangshi on 2026/1/20. +// +import HandyJSON + +enum SportType:String,HandyJSONEnum{ + case run = "running" + case ride = "cycling" + case walk = "walking" + + var name:String{ + switch self { + case .run: + return "户外跑步" + case .ride: + return "户外骑行" + case .walk: + return "健走" + } + } + var icon:String{ + switch self { + case .run: + return "sport_run" + case .ride: + return "sport_ride" + case .walk: + return "sport_walk" + } + } + var readyText:String{ + switch self { + case .run: + return "累积跑步距离".l10n + case .ride: + return "累积骑行距离".l10n + case .walk: + return "累积健走距离".l10n + } + } + + static var all:[SportType] = [.run,.ride,.walk] +} + +class SportTypeView: BaseView { + let run = SportTypeItem() + let ride = SportTypeItem() + let walk = SportTypeItem() + + override func setupViews() { + addSubviews([run,ride,walk]) + run.do { v in + v.type = .run + v.yc.addTap(target: self, action: #selector(runAction)) + } + ride.do { v in + v.type = .ride + v.yc.addTap(target: self, action: #selector(rideAction)) + } + walk.do { v in + v.type = .walk + v.yc.addTap(target: self, action: #selector(walkAction)) + } + } + + override func setupLayout() { + run.snp.makeConstraints { make in + make.left.equalToSuperview().inset(38) + make.width.equalTo(50) + make.height.equalTo(80) + make.top.equalToSuperview() + } + ride.snp.makeConstraints { make in + make.left.equalTo(run.snp.right).offset(32) + make.width.equalTo(50) + make.height.equalTo(80) + make.top.equalToSuperview() + } + walk.snp.makeConstraints { make in + make.left.equalTo(ride.snp.right).offset(32) + make.width.equalTo(50) + make.height.equalTo(80) + make.top.equalToSuperview() + } + } + @objc func runAction(){ + SportManager.shared.sportType = .run + App.shared.goToPage(ReadyVC()) + } + @objc func rideAction(){ + SportManager.shared.sportType = .ride + App.shared.goToPage(ReadyVC()) + } + @objc func walkAction(){ + SportManager.shared.sportType = .walk + App.shared.goToPage(ReadyVC()) + } +} + +class SportTypeItem: BaseView { + private let icon = UIImageView() + private let titleLabel = UILabel() + var type:SportType = .run{ + didSet{ + titleLabel.text = type.name + icon.image = UIImage(named: type.icon) + } + } + override func setupViews() { + addSubviews([titleLabel,icon]) + icon.do { v in + + } + titleLabel.do { v in + v.textColor = .color(.first) + v.font = .regular(10) + } + } + + override func setupLayout() { + icon.snp.makeConstraints { make in + make.size.equalTo(50) + make.top.equalToSuperview() + make.centerX.equalToSuperview() + } + titleLabel.snp.makeConstraints { make in + make.top.equalTo(icon.snp.bottom).offset(5) + make.centerX.equalTo(icon) + } + } +} diff --git a/ShenYin/ShenYin/UI/Sport/Sport/Set/SportSetCell.swift b/ShenYin/ShenYin/UI/Sport/Sport/Set/SportSetCell.swift new file mode 100644 index 0000000..ce6aeb3 --- /dev/null +++ b/ShenYin/ShenYin/UI/Sport/Sport/Set/SportSetCell.swift @@ -0,0 +1,321 @@ +// +// Untitled.swift +// ShenYin +// +// Created by liangshi on 2026/1/23. +// + + +public enum SportSetCellType: Int { + case light + case voiceReport + case lcReport + case lcReportRate + case xlfdReport + case guideReport + case guideReportSet + case metronome + case metronomeRate + case metronomeAudio + case background + case permisson + case realData + + var title:String{ + switch self { + case .light: + return "灯带设置".l10n + case .voiceReport: + return "语音播报".l10n + case .lcReport: + return "里程播报".l10n + case .lcReportRate: + return "里程播报频次".l10n + case .xlfdReport: + return "训练分段播报".l10n + case .guideReport: + return "指导播报".l10n + case .guideReportSet: + return "指导语音设置".l10n + case .metronome: + return "节拍器".l10n + case .metronomeRate: + return "节拍器频率".l10n + case .metronomeAudio: + return "节拍器声音".l10n + case .background: + return "后台保护".l10n + case .permisson: + return "运动权限".l10n + case .realData: + return "运动实况".l10n + } + } + var detail:String{ + switch self { + case .lcReport: + return "当前已跑里程播报".l10n + case .xlfdReport: + return "训练分段开始和结束提示音".l10n + case .guideReport: + return "跑步过程中的指导语音".l10n + case .realData: + return "组内成员基本运动信息实时显示".l10n + default: + return "" + } + } + var showSwitch:Bool{ + switch self { + case .voiceReport,.lcReport,.xlfdReport,.guideReport,.metronome,.background,.realData: + return true + default: + return false + } + } +} + +protocol SportSetCellDelegate:NSObject{ + func sportSetCellSwitchAction(cell:SportSetCell) +} +class SportSetCell: BaseTableViewCell { + let bgView = UIView() + let titleLabel = UILabel() + let detailLabel = UILabel() + let valueLabel = UILabel() + let iSwitch = UISwitch() + let nextIcon = UIImageView(image: UIImage(named: "arrow_next")) + let line = UIView() + weak var delegate:SportSetCellDelegate? + var model:SportSetModel!{ + didSet{ + switch type { + case .light: + break + case .voiceReport: + iSwitch.isOn = model.voiceReport + case .lcReport: + iSwitch.isOn = model.lcReport + + case .lcReportRate: + valueLabel.text = model.lcReportRate + case .xlfdReport: + iSwitch.isOn = model.xlfdReport + case .guideReport: + iSwitch.isOn = model.guideReport + case .guideReportSet: + valueLabel.text = model.guideReportSet + case .metronome: + iSwitch.isOn = model.metronome + case .metronomeRate: + valueLabel.text = model.metronomeRate + case .metronomeAudio: + valueLabel.text = model.metronomeAudio + case .background: + iSwitch.isOn = model.background + case .permisson: + break + case .realData: + iSwitch.isOn = model.realData + } + + if type == .lcReport { + line.isHidden = model.lcReport + } + else if type == .guideReport { + line.isHidden = model.guideReport + } + else if type == .metronome { + line.isHidden = model.metronome + } + else if type == .permisson || type == .realData || type == .metronomeRate{ + line.isHidden = true + } + else{ + line.isHidden = false + } + } + } + + var type:SportSetCellType = .light{ + didSet{ + titleLabel.text = type.title + detailLabel.text = type.detail + iSwitch.isHidden = !type.showSwitch + nextIcon.isHidden = !iSwitch.isHidden + + if type.detail.isEmpty { + titleLabel.snp.remakeConstraints { (m) in + m.centerY.equalToSuperview() + m.left.equalToSuperview().offset(16) + } + } + else{ + titleLabel.snp.remakeConstraints { (m) in + m.top.equalToSuperview().inset(16) + m.left.equalToSuperview().offset(16) + } + } + + if type == .metronomeRate || type == .metronomeAudio { + valueLabel.isHidden = false + } + else{ + valueLabel.isHidden = true + } + } + } + + + override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) { + super.init(style: style, reuseIdentifier: reuseIdentifier) + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + override func setupViews() { + contentView.addSubview(bgView) + bgView.addSubviews([titleLabel,detailLabel,iSwitch,nextIcon,valueLabel,line]) + + backgroundColor = .color(.bgColor) + contentView.backgroundColor = .clear + selectionStyle = .none + bgView.do { v in + v.backgroundColor = .color(.cellBg) + v.yc.addTap(target: self, action: #selector(tapAction)) + } + titleLabel.do { (v) in + v.textColor = .color(.first) + v.font = .bold(14) + v.textAlignment = .left + } + detailLabel.do { (v) in + v.textColor = .color(.secondary) + v.font = .regular(10) + v.textAlignment = .left + } + iSwitch.do { v in +// v.backgroundColor = .red + v.addTarget(self, action: #selector(swicthAction), for: .valueChanged) + } + valueLabel.do { v in + v.textColor = .color(.secondary) + v.font = .regular(14) + v.textAlignment = .right + } + line.do { v in + v.backgroundColor = .color(.separateLine) + } + } + + override func setupLayout() { + bgView.snp.makeConstraints { (m) in + m.left.right.equalToSuperview().inset(18) + m.top.equalToSuperview() + m.bottom.equalToSuperview() + } + titleLabel.snp.makeConstraints { (m) in + m.top.equalToSuperview().inset(16) + m.left.equalToSuperview().offset(16) + } + detailLabel.snp.makeConstraints { (m) in + m.bottom.equalToSuperview().inset(16) + m.left.equalToSuperview().offset(16) + } + iSwitch.snp.makeConstraints { (m) in + m.right.equalToSuperview().inset(15) + m.centerY.equalToSuperview() + } + nextIcon.snp.makeConstraints { (m) in + m.width.equalTo(8) + m.height.equalTo(12) + m.right.equalToSuperview().inset(15) + m.centerY.equalToSuperview() + } + valueLabel.snp.makeConstraints { (m) in + m.centerY.equalToSuperview() + m.right.equalToSuperview().inset(38) + m.height.equalToSuperview() + m.left.equalToSuperview().inset(90) + } + line.snp.makeConstraints { make in + make.left.right.equalToSuperview().inset(18) + make.bottom.equalToSuperview() + make.height.equalTo(0.4) + } + } + @objc func tapAction(){ + switch type { + case .light: + App.shared.goToPage(LightSetVC()) + case .lcReportRate: + lcReportAction() + break + case .guideReportSet: + guideReportSetAction() + break + case .metronomeRate: + metronomeRateAction() + break + case .metronomeAudio: + metronomeAudioAction() + break + + case .background: + backgroundAction() + break + default: + break + } + } + func lcReportAction(){ + let list = Array(stride(from: 500, to: 1500, by: 500)).map { String($0) + "m" } + let v = CustomPicker2(title: "选择播报间距".l10n, list:list ) + v.selectedIndex = 50 + v.show() + v.didSelected = {[weak self] (index,value) in + guard let self = self else { return } + + } + } + func guideReportSetAction(){ + let list = [ + CustomPickerModel(title: "详细".l10n, detail: "1公里配速、总平均配速、1公里用时、总运动时长".l10n), + CustomPickerModel(title: "简洁".l10n, detail: "1公里配速、1公里用时".l10n), + CustomPickerModel(title: "极简".l10n, detail: "总距离、总时长".l10n)] + let v = CustomPicker4(title: "指导语音密度".l10n, list:list ) + v.selectedIndex = 0 + v.show() + v.didSelected = {[weak self] (index,value) in + guard let self = self else { return } + + } + } + func metronomeRateAction(){ + let list = Array(stride(from: 100, to: 250, by: 1)).map { String($0) + "次/分钟".l10n } + let v = CustomPicker2(title: "节拍器频率".l10n, list:list ) + v.selectedIndex = 50 + v.show() + v.didSelected = {[weak self] (index,value) in + guard let self = self else { return } + + } + } + func metronomeAudioAction(){ + let list = ["默认".l10n,"清脆".l10n,"重击".l10n] + let v = CustomPicker2(title: "节拍器声音".l10n, list:list ) + v.selectedIndex = 50 + v.show() + v.didSelected = {[weak self] (index,value) in + guard let self = self else { return } + + } + } + func backgroundAction(){ + + } + @objc func swicthAction(){ + delegate?.sportSetCellSwitchAction(cell: self) + } +} diff --git a/ShenYin/ShenYin/UI/Sport/Sport/Set/SportSetModel.swift b/ShenYin/ShenYin/UI/Sport/Sport/Set/SportSetModel.swift new file mode 100644 index 0000000..0310fa4 --- /dev/null +++ b/ShenYin/ShenYin/UI/Sport/Sport/Set/SportSetModel.swift @@ -0,0 +1,22 @@ +// +// SportSetModel.swift +// ShenYin +// +// Created by liangshi on 2026/1/30. +// + +class SportSetModel{ + var light = "" + var voiceReport = true + var lcReport = false + var lcReportRate = "" + var xlfdReport = false + var guideReport = false + var guideReportSet = "" + var metronome = false + var metronomeRate = "" + var metronomeAudio = "" + var background = false + var permisson = "" + var realData = false +} diff --git a/ShenYin/ShenYin/UI/Sport/Sport/Set/SportSetVC.swift b/ShenYin/ShenYin/UI/Sport/Sport/Set/SportSetVC.swift new file mode 100644 index 0000000..4347064 --- /dev/null +++ b/ShenYin/ShenYin/UI/Sport/Sport/Set/SportSetVC.swift @@ -0,0 +1,149 @@ +// +// SportSetVC.swift +// ShenYin +// +// Created by liangshi on 2026/1/23. +//,, + + +import UIKit +class SportSetVC: BaseVC{ + let tableView = UITableView(frame: .zero, style: .plain) + var list:[[SportSetCellType]] = [[.light],[.voiceReport],[.lcReport],[.xlfdReport],[.guideReport],[.metronome],[.background],[.permisson],[.realData]] + let model = SportSetModel() + + override func viewDidLoad() { + super.viewDidLoad() + // Do any additional setup after loading the view. + } + override func setupViews() { + self.navBar.title = "设置".l10n + tableView.do { v in + v.backgroundColor = .color(.bgColor) + v.delegate = self + v.dataSource = self + v.rowHeight = 65 + v.sectionHeaderHeight = 52 + v.separatorStyle = .none + v.register(SportSetCell.self, forCellReuseIdentifier: SportSetCell.reuseID) + v.register(CustomSectionHeader.self, forHeaderFooterViewReuseIdentifier: CustomSectionHeader.reuseID) + if #available(iOS 15.0, *) { + tableView.sectionHeaderTopPadding = 0 + } + } + + view.addSubview(tableView) + } + override func setupLayout() { + tableView.snp.makeConstraints { make in + make.left.right.bottom.equalToSuperview() + make.top.equalTo(kNavBtm) + } + } + override func viewWillAppear(_ animated: Bool) { + super.viewWillAppear(animated) +// CacheManager.shared.user.tokenIsValid(){[weak self] sucess in +// self?.header.updateUI() +// } + } +} + +extension SportSetVC:UITableViewDelegate,UITableViewDataSource { + func numberOfSections(in tableView: UITableView) -> Int { + return list.count + } + func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { + return list[section].count + } + func tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? { + if section == list.count - 1 { + let view = CustomSectionHeader.header(withTableView: tableView) + view.titleLabel.text = "组队设置".l10n + view.titleLabel.textColor = .color(.secondary) + return view + } + else{ + return nil + } + } + func tableView(_ tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat { + if section == list.count - 1 { + return 52 + } + else{ + return 0 + } + } + func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { + let cell = SportSetCell.cell(withTableView: tableView, indexPath: indexPath) + cell.type = list[indexPath.section][indexPath.row] + cell.model = model + cell.delegate = self + let rect = CGRect(x: 0, y: 0, width: kScreenWid - 36, height: 65) + if cell.type == .light { + cell.bgView.yc.setCorners(withRoundedRect: rect, roundingCorners: [.topLeft,.topRight], cornerRadii: CGSize(width: 12, height: 12)) + } + else { + if cell.type == .realData{ + cell.bgView.yc.setCorners(withRoundedRect: rect, roundingCorners: [.allCorners], cornerRadii: CGSize(width: 12, height: 12)) + } + else if cell.type == .permisson { + cell.bgView.yc.setCorners(withRoundedRect: rect, roundingCorners: [.bottomLeft,.bottomRight], cornerRadii: CGSize(width: 12, height: 12)) + } + else{ + cell.bgView.yc.setCorners(withRoundedRect: rect, roundingCorners: [.allCorners], cornerRadii: CGSize(width: 0, height: 0)) + } + } + + return cell + } +// func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { +// let data = list[indexPath.section][indexPath.row] +// switch data { +// case .light: +// +// default: +// break +// } +// } +} +extension SportSetVC:SportSetCellDelegate { + func sportSetCellSwitchAction(cell:SportSetCell){ + let state = cell.iSwitch.isOn + switch cell.type { + case .voiceReport: + model.voiceReport = state + tableView.reloadData() + break + case .lcReport: + model.lcReport = state + list[2] = state ? [.lcReport,.lcReportRate]:[.lcReport] + tableView.reloadData() + break + case .xlfdReport: + model.xlfdReport = state + tableView.reloadData() + break + case .guideReport: + model.guideReport = state + list[4] = state ? [.guideReport,.guideReportSet]:[.guideReport] + tableView.reloadData() + break + case .metronome: + model.metronome = state + list[5] = state ? [.metronome,.metronomeRate,.metronomeAudio]:[.metronome] + tableView.reloadData() + break + case .background: + model.background = state + tableView.reloadData() + break + case .realData: + model.realData = state + tableView.reloadData() + break + default: + break + } + } +} diff --git a/ShenYin/ShenYin/UI/Sport/Sport/SportDetail/CadenceView.swift b/ShenYin/ShenYin/UI/Sport/Sport/SportDetail/CadenceView.swift new file mode 100644 index 0000000..8e41a47 --- /dev/null +++ b/ShenYin/ShenYin/UI/Sport/Sport/SportDetail/CadenceView.swift @@ -0,0 +1,7 @@ +// +// CadenceView.swift +// ShenYin +// +// Created by liangshi on 2026/2/10. +// + diff --git a/ShenYin/ShenYin/UI/Sport/Sport/SportDetail/ElevationView.swift b/ShenYin/ShenYin/UI/Sport/Sport/SportDetail/ElevationView.swift new file mode 100644 index 0000000..d323eea --- /dev/null +++ b/ShenYin/ShenYin/UI/Sport/Sport/SportDetail/ElevationView.swift @@ -0,0 +1,7 @@ +// +// elevationView.swift +// ShenYin +// +// Created by liangshi on 2026/2/10. +// + diff --git a/ShenYin/ShenYin/UI/Sport/Sport/SportDetail/FeelView.swift b/ShenYin/ShenYin/UI/Sport/Sport/SportDetail/FeelView.swift new file mode 100644 index 0000000..0d7a8ed --- /dev/null +++ b/ShenYin/ShenYin/UI/Sport/Sport/SportDetail/FeelView.swift @@ -0,0 +1,57 @@ +// +// FeelView.swift +// ShenYin +// +// Created by liangshi on 2026/2/10. +// + +class FeelView: BaseView { + let header = SportDetailTitleView() + + override func setupViews() { + backgroundColor = .white + addSubviews([header]) + header.do { v in + v.titleLabel.text = "运动感受".l10n + v.icon.image = UIImage(named: "sport_feel") + } + } + + override func setupLayout() { + header.snp.makeConstraints { make in + make.centerY.equalToSuperview() + make.height.equalTo(55) + make.left.right.equalToSuperview() + } + + } +} +class SportDetailTitleView: BaseView { + let icon = UIImageView() + let titleLabel = UILabel() + + override func setupViews() { + addSubviews([icon,titleLabel]) + + titleLabel.do { v in + v.textColor = .color(.first) + v.font = .medium(16) + } + icon.do { v in + v.image = UIImage(named: "") + } + } + + override func setupLayout() { + + titleLabel.snp.makeConstraints { make in + make.centerY.equalToSuperview() + make.left.equalToSuperview().inset(48) + } + icon.snp.makeConstraints { make in + make.size.equalTo(24) + make.centerY.equalToSuperview() + make.left.equalToSuperview().inset(18) + } + } +} diff --git a/ShenYin/ShenYin/UI/Sport/Sport/SportDetail/PacePerView.swift b/ShenYin/ShenYin/UI/Sport/Sport/SportDetail/PacePerView.swift new file mode 100644 index 0000000..7945489 --- /dev/null +++ b/ShenYin/ShenYin/UI/Sport/Sport/SportDetail/PacePerView.swift @@ -0,0 +1,7 @@ +// +// PacePerView.swift +// ShenYin +// +// Created by liangshi on 2026/2/10. +// + diff --git a/ShenYin/ShenYin/UI/Sport/Sport/SportDetail/PaceView.swift b/ShenYin/ShenYin/UI/Sport/Sport/SportDetail/PaceView.swift new file mode 100644 index 0000000..9c69b83 --- /dev/null +++ b/ShenYin/ShenYin/UI/Sport/Sport/SportDetail/PaceView.swift @@ -0,0 +1,7 @@ +// +// SpeepView.swift +// ShenYin +// +// Created by liangshi on 2026/2/10. +// + diff --git a/ShenYin/ShenYin/UI/Sport/Sport/SportDetail/SportDetailBoard.swift b/ShenYin/ShenYin/UI/Sport/Sport/SportDetail/SportDetailBoard.swift new file mode 100644 index 0000000..0b88799 --- /dev/null +++ b/ShenYin/ShenYin/UI/Sport/Sport/SportDetail/SportDetailBoard.swift @@ -0,0 +1,309 @@ +//// +//// Untitled.swift +//// ShenYin +//// +//// Created by liangshi on 2026/2/5. +//// +// +import UIKit + +extension SportDetailVC{ + class SportDetailBoard: BaseView { + let topIcon = UIImageView() + let header = UILabel() + let distanceItem = DistanceItem() + let item1 = LabelItem() + let item2 = LabelItem() + let item3 = LabelItem() + let item4 = LabelItem() + let item5 = LabelItem() + let item6 = LabelItem() + let item7 = LabelItem() + let item8 = LabelItem() + let item9 = LabelItem() + func updateUI(){ + if SportManager.shared.sportType == .walk{ + header.do { v in + v.text = "健走".l10n + } + distanceItem.do { v in + v.title = SportManager.shared.distanceStr + } + item1.do { v in + v.title = SportManager.shared.durationStr + v.detail = "时长".l10n + } + item2.do { v in + v.title = SportManager.shared.peisuStr + v.detail = "平均配速".l10n + } + item3.do { v in + v.title = SportManager.shared.calorieStr + v.detail = "运动消耗".l10n + } + item4.do { v in + v.title = SportManager.shared.bushuStr + v.detail = "步数".l10n + } + item5.do { v in + v.title = SportManager.shared.avgBuFuStr + v.detail = "平均步幅".l10n + } + item6.do { v in + v.title = SportManager.shared.avgBuPinStr + v.detail = "平均步频".l10n + } + item7.do { v in + v.title = SportManager.shared.upStr + v.detail = "积累爬升".l10n + } + item8.do { v in + v.title = SportManager.shared.downStr + v.detail = "积累下降".l10n + } + + } + else if SportManager.shared.sportType == .run{ + header.do { v in + v.text = "户外跑步".l10n + } + distanceItem.do { v in + v.title = SportManager.shared.distanceStr + } + item1.do { v in + v.title = SportManager.shared.durationStr + v.detail = "时长".l10n + } + item2.do { v in + v.title = SportManager.shared.peisuStr + v.detail = "平均配速".l10n + } + item3.do { v in + v.title = SportManager.shared.calorieStr + v.detail = "运动消耗".l10n + } + item4.do { v in + v.title = SportManager.shared.bushuStr + v.detail = "步数".l10n + } + item5.do { v in + v.title = SportManager.shared.avgBuFuStr + v.detail = "平均步幅".l10n + } + item6.do { v in + v.title = SportManager.shared.avgBuPinStr + v.detail = "平均步频".l10n + } + item7.do { v in + v.title = SportManager.shared.upStr + v.detail = "积累爬升".l10n + } + item8.do { v in + v.title = SportManager.shared.downStr + v.detail = "积累下降".l10n + } + + } + else if SportManager.shared.sportType == .ride{ + header.do { v in + v.text = "户外骑行".l10n + } + distanceItem.do { v in + v.title = SportManager.shared.distanceStr + } + item1.do { v in + v.title = SportManager.shared.durationStr + v.detail = "时长".l10n + } + item2.do { v in + v.title = SportManager.shared.peisuStr + v.detail = "平均配速".l10n + } + item3.do { v in + v.title = SportManager.shared.calorieStr + v.detail = "运动消耗".l10n + } + item4.do { v in + v.title = SportManager.shared.bushuStr + v.detail = "步数".l10n + } + item5.do { v in + v.title = SportManager.shared.avgBuFuStr + v.detail = "平均步幅".l10n + } + item6.do { v in + v.title = SportManager.shared.avgBuPinStr + v.detail = "平均步频".l10n + } + item7.do { v in + v.title = SportManager.shared.upStr + v.detail = "积累爬升".l10n + } + item8.do { v in + v.title = SportManager.shared.downStr + v.detail = "积累下降".l10n + } + + } + } + override func setupViews() { + backgroundColor = .white + addSubviews([topIcon,header,distanceItem,item1,item2,item3,item4,item5,item6,item7,item8,item9]) + topIcon.do { v in + v.backgroundColor = .color(.secondary) + v.yc.setCornerRadius(12) + } + header.do { v in + v.font = .medium(16) + v.textColor = .color(.first) + } + item9.do { v in + v.isHidden = true + } + updateUI() + } + + override func setupLayout() { + topIcon.snp.makeConstraints { make in + make.width.equalTo(42) + make.height.equalTo(42) + make.top.equalToSuperview().inset(18) + make.right.equalToSuperview().inset(18) + } + header.snp.makeConstraints { make in + make.top.leading.equalToSuperview().inset(18) + } + distanceItem.snp.makeConstraints { make in + make.left.equalToSuperview().inset(18) + make.top.equalToSuperview().inset(50) +// make.height.equalTo(64) + } + item1.snp.makeConstraints { make in + make.left.equalToSuperview().inset(18) + make.top.equalToSuperview().inset(130) + make.height.equalTo(50) + make.width.equalTo(kScreenWid/3) + } + item2.snp.makeConstraints { make in + make.centerX.equalToSuperview().offset(20) + make.width.top.height.equalTo(item1) + } + item3.snp.makeConstraints { make in + make.right.equalToSuperview() + make.width.top.height.equalTo(item1) + } + item4.snp.makeConstraints { make in + make.top.equalToSuperview().inset(200) + make.width.left.height.equalTo(item1) + } + item5.snp.makeConstraints { make in + make.centerX.equalTo(item2) + make.width.top.height.equalTo(item4) + } + item6.snp.makeConstraints { make in + make.right.equalToSuperview() + make.width.height.equalTo(item1) + make.top.equalTo(item4) + } + item7.snp.makeConstraints { make in + make.top.equalToSuperview().inset(270) + make.width.left.height.equalTo(item1) + } + item8.snp.makeConstraints { make in + make.centerX.equalTo(item2) + make.width.top.height.equalTo(item7) + } + item9.snp.makeConstraints { make in + make.right.equalToSuperview() + make.width.height.equalTo(item1) + make.top.equalTo(item7) + } + } + + class LabelItem: BaseView { + let detailLabel = UILabel() + let titleLabel = UILabel() + + var title:String = ""{ + didSet{ + titleLabel.text = title + } + } + var detail:String = ""{ + didSet{ + detailLabel.text = detail + } + } + + override func setupViews() { + addSubviews([titleLabel,detailLabel]) + titleLabel.do { v in + v.textColor = .color(.first) + v.font = .semiBold(24) + v.textAlignment = .left + } + detailLabel.do { v in + v.textColor = .color(.secondary) + v.font = .regular(12) + v.textAlignment = .left + } + } + + override func setupLayout() { + titleLabel.snp.makeConstraints { make in + make.top.equalToSuperview().inset(0) + make.left.right.equalToSuperview() + } + detailLabel.snp.makeConstraints { make in + make.bottom.equalToSuperview().inset(0) + make.left.right.equalToSuperview() + } + } + } + class DistanceItem: BaseView { + let detailLabel = UILabel() + let titleLabel = UILabel() + + var title:String = ""{ + didSet{ + titleLabel.text = title + } + } + var detail:String = ""{ + didSet{ + detailLabel.text = detail + } + } + + override func setupViews() { + addSubviews([titleLabel,detailLabel]) + titleLabel.do { v in + v.textColor = .color(.first) + v.font = .bold(48) + v.textAlignment = .left + } + detailLabel.do { v in + v.textColor = .color(.first) + v.font = .bold(20) + v.textAlignment = .left + v.text = "公里".l10n + } + } + + override func setupLayout() { + titleLabel.snp.makeConstraints { make in + make.top.equalToSuperview().inset(0) + make.left.equalToSuperview() + } + detailLabel.snp.makeConstraints { make in + make.bottom.equalTo(titleLabel.snp.bottom).inset(8) + make.left.equalTo(titleLabel.snp.right).offset(10) + } + } + } + } + + + +} + diff --git a/ShenYin/ShenYin/UI/Sport/Sport/SportDetail/SportDetailVC.swift b/ShenYin/ShenYin/UI/Sport/Sport/SportDetail/SportDetailVC.swift new file mode 100644 index 0000000..3c758ef --- /dev/null +++ b/ShenYin/ShenYin/UI/Sport/Sport/SportDetail/SportDetailVC.swift @@ -0,0 +1,99 @@ +// +// SportDetailVC.swift +// ShenYin +// +// Created by liangshi on 2026/2/5. +// + +import MAMapKit + +class SportDetailVC: BaseVC { + let scrollView = UIScrollView() + var mapView:MAMapView! + let detailView = SportDetailBoard() + let pace = FeelView() + let pacePer = FeelView() + let cadence = FeelView() + let elevation = FeelView() + let feel = FeelView() + + + override func setupViews() { + MapManager.shared.setupSdk() + navBar.do { v in + v.title = "运动数据".l10n + } + + + mapView = MAMapView(frame: .zero) + mapView.do { v in + v.delegate = self + v.showsUserLocation = true + v.zoomLevel = 16 + v.userTrackingMode = .followWithHeading + } + view.addSubview(scrollView) + scrollView.addSubviews([mapView,detailView,pace,pacePer,cadence,elevation,feel]) + + scrollView.do { v in + + } + detailView.do { v in + v.yc.setCorners(withRoundedRect: CGRect(x: 0, y: 0, width: kScreenWid, height: 460), roundingCorners: [.topLeft,.topRight], cornerRadii: CGSize(width: 20, height: 20)) + } + } + override func setupLayout() { + scrollView.snp.makeConstraints { make in + make.top.equalToSuperview().inset(kNavBtm) + make.left.equalToSuperview() + make.width.equalTo(kScreenWid) + make.height.equalTo(kScreenHei - kNavBtm) + } + mapView.snp.makeConstraints { make in + make.width.equalTo(kScreenWid) + make.left.top.equalToSuperview() + make.height.equalTo(300) + } + detailView.snp.makeConstraints { make in + make.left.equalToSuperview() + make.width.equalTo(kScreenWid) + make.top.equalTo(mapView.snp.bottom) + make.height.equalTo(342) + } + pace.snp.makeConstraints { make in + make.left.width.equalTo(detailView) + make.top.equalTo(detailView.snp.bottom) + make.height.equalTo(100) + } + pacePer.snp.makeConstraints { make in + make.left.width.equalTo(detailView) + make.top.equalTo(pace.snp.bottom) + make.height.equalTo(100) + } + cadence.snp.makeConstraints { make in + make.left.width.equalTo(detailView) + make.top.equalTo(pacePer.snp.bottom) + make.height.equalTo(100) + } + elevation.snp.makeConstraints { make in + make.left.width.equalTo(detailView) + make.top.equalTo(cadence.snp.bottom) + make.height.equalTo(100) + } + feel.snp.makeConstraints { make in + make.left.width.equalTo(detailView) + make.top.equalTo(elevation.snp.bottom) + make.height.equalTo(100) + make.bottom.equalToSuperview() + } + } + override func setupData() { + + } +} + +extension SportDetailVC:MAMapViewDelegate{ + func mapViewRequireLocationAuth(_ locationManager: CLLocationManager!) { + // locationManager.requestAlwaysAuthorization() + } +} diff --git a/ShenYin/ShenYin/UI/Sport/Sport/SportDetail/StepRateView.swift b/ShenYin/ShenYin/UI/Sport/Sport/SportDetail/StepRateView.swift new file mode 100644 index 0000000..7913a83 --- /dev/null +++ b/ShenYin/ShenYin/UI/Sport/Sport/SportDetail/StepRateView.swift @@ -0,0 +1,7 @@ +// +// stepRateView.swift +// ShenYin +// +// Created by liangshi on 2026/2/10. +// + diff --git a/ShenYin/ShenYin/UI/Sport/Sport/SportDetail/StrideLengthView.swift b/ShenYin/ShenYin/UI/Sport/Sport/SportDetail/StrideLengthView.swift new file mode 100644 index 0000000..3266d83 --- /dev/null +++ b/ShenYin/ShenYin/UI/Sport/Sport/SportDetail/StrideLengthView.swift @@ -0,0 +1,7 @@ +// +// strideLengthView.swift +// ShenYin +// +// Created by liangshi on 2026/2/10. +// + diff --git a/ShenYin/ShenYin/UI/Sport/Sport/Sporting/DetailBoard.swift b/ShenYin/ShenYin/UI/Sport/Sport/Sporting/DetailBoard.swift new file mode 100644 index 0000000..74d6349 --- /dev/null +++ b/ShenYin/ShenYin/UI/Sport/Sport/Sporting/DetailBoard.swift @@ -0,0 +1,377 @@ +// +// Untitled.swift +// ShenYin +// +// Created by liangshi on 2026/2/3. +// + +import UIKit + +extension SportingVC{ + class DetailBoard: BaseView { + let topIcon = UIImageView() + let header = headerItem() + let distanceItem = DistanceItem() + let item1 = LabelItem() + let item2 = LabelItem() + let item3 = LabelItem() + let item4 = LabelItem() + let btnView = ButtonView() + func updateUI(){ + if SportManager.shared.sportType == .walk{ + header.do { v in + v.title = "健走".l10n + } + distanceItem.do { v in + v.title = SportManager.shared.distanceStr + } + item1.do { v in + v.title = SportManager.shared.durationStr + v.detail = "时长".l10n + } + item2.do { v in + v.title = SportManager.shared.peisuStr + v.detail = "配速".l10n + } + item3.do { v in + v.title = SportManager.shared.calorieStr + v.detail = "卡路里".l10n + } + item4.do { v in + v.title = SportManager.shared.bushuStr + v.detail = "步数".l10n + } + } + else if SportManager.shared.sportType == .run{ + header.do { v in + v.title = "户外跑步".l10n + } + distanceItem.do { v in + v.title = SportManager.shared.distanceStr + } + item1.do { v in + v.title = SportManager.shared.durationStr + v.detail = "时长".l10n + } + item2.do { v in + v.title = SportManager.shared.peisuStr + v.detail = "配速".l10n + } + item3.do { v in + v.title = SportManager.shared.calorieStr + v.detail = "卡路里".l10n + } + item4.do { v in + v.title = SportManager.shared.bushuStr + v.detail = "步数".l10n + } + } + else if SportManager.shared.sportType == .ride{ + header.do { v in + v.title = "户外骑行".l10n + } + distanceItem.do { v in + v.title = SportManager.shared.distanceStr + } + item1.do { v in + v.title = SportManager.shared.durationStr + v.detail = "时长".l10n + } + item2.do { v in + v.title = SportManager.shared.speedStr + v.detail = "时速".l10n + } + item3.do { v in + v.title = SportManager.shared.calorieStr + v.detail = "卡路里".l10n + } + item4.do { v in + + } + } + } + override func setupViews() { + backgroundColor = .white + addSubviews([topIcon,header,distanceItem,item1,item2,item3,item4,btnView]) + topIcon.do { v in + v.backgroundColor = .color(.secondary) + v.yc.setCornerRadius(2) + } + updateUI() + } + + override func setupLayout() { + topIcon.snp.makeConstraints { make in + make.width.equalTo(50) + make.height.equalTo(4) + make.top.equalToSuperview().inset(6) + make.centerX.equalToSuperview() + } + header.snp.makeConstraints { make in + make.top.equalToSuperview().inset(28) + make.left.right.equalToSuperview() + make.height.equalTo(25) + } + distanceItem.snp.makeConstraints { make in + make.left.equalToSuperview().inset(18) + make.top.equalToSuperview().inset(100) + make.height.equalTo(50) + } + item1.snp.makeConstraints { make in + make.left.equalToSuperview().inset(18) + make.top.equalToSuperview().inset(180) + make.height.equalTo(50) + } + item2.snp.makeConstraints { make in + make.left.equalToSuperview().inset(kScreenWid/2 + 18) + make.top.equalTo(item1.snp.top) + make.height.equalTo(50) + } + item3.snp.makeConstraints { make in + make.left.equalToSuperview().inset(18) + make.top.equalToSuperview().inset(250) + make.height.equalTo(50) + } + item4.snp.makeConstraints { make in + make.left.equalToSuperview().inset(kScreenWid/2 + 18) + make.top.equalTo(item3.snp.top) + make.height.equalTo(50) + } + btnView.snp.makeConstraints { make in + make.left.right.bottom.equalToSuperview() + make.height.equalTo(140) + } + } + + + class ButtonView: BaseView { + let okBtn = UIButton(type: .custom) + let lockBtn = UIButton(type: .custom) + let setBtn = UIButton(type: .custom) + + let lockedBtn = UIButton(type: .custom) + let stopBtn = UIButton(type: .custom) + let continueBtn = UIButton(type: .custom) + + override func setupViews() { +// backgroundColor = .red + addSubviews([okBtn,lockBtn,setBtn,lockedBtn,stopBtn,continueBtn]) + okBtn.do { v in + v.setImage(UIImage(named: "btn_pause"), for: .normal) + v.addTarget(self, action: #selector(okAction), for: .touchUpInside) + } + lockBtn.do { v in + v.setImage(UIImage(named: "btn_lock"), for: .normal) + v.addTarget(self, action: #selector(lockAction), for: .touchUpInside) + } + setBtn.do { v in + v.setImage(UIImage(named: "btn_set"), for: .normal) + v.addTarget(self, action: #selector(setAction), for: .touchUpInside) + } + + lockedBtn.do { v in + v.setImage(UIImage(named: "btn_locked"), for: .normal) + v.addTarget(self, action: #selector(lockedAction), for: .touchUpInside) + v.isHidden = true + } + stopBtn.do { v in + v.setImage(UIImage(named: "btn_stop"), for: .normal) + v.addTarget(self, action: #selector(stopAction), for: .touchUpInside) + v.isHidden = true + } + continueBtn.do { v in + v.setImage(UIImage(named: "btn_continue"), for: .normal) + v.addTarget(self, action: #selector(continueAction), for: .touchUpInside) + v.isHidden = true + } + } + + override func setupLayout() { + okBtn.snp.makeConstraints { make in + make.centerX.equalToSuperview() + make.bottom.equalToSuperview().inset(24) + make.size.equalTo(90) + } + lockBtn.snp.makeConstraints { make in + make.left.equalToSuperview().inset(46) + make.centerY.equalTo(okBtn) + make.size.equalTo(50) + } + setBtn.snp.makeConstraints { make in + make.right.equalToSuperview().inset(46) + make.centerY.equalTo(okBtn) + make.size.equalTo(50) + } + + lockedBtn.snp.makeConstraints { make in + make.centerX.equalToSuperview() + make.bottom.equalToSuperview().inset(24) + make.size.equalTo(90) + } + continueBtn.snp.makeConstraints { make in + make.left.equalToSuperview().inset(55) + make.bottom.equalToSuperview().inset(24) + make.size.equalTo(90) + } + stopBtn.snp.makeConstraints { make in + make.right.equalToSuperview().inset(55) + make.bottom.equalToSuperview().inset(24) + make.size.equalTo(90) + } + } + @objc func okAction(){ + SportManager.shared.pause() + continueBtn.isHidden = false + stopBtn.isHidden = false + + okBtn.isHidden = true + setBtn.isHidden = true + lockBtn.isHidden = true + } + @objc func lockAction(){ + okBtn.isHidden = true + setBtn.isHidden = true + lockBtn.isHidden = true + lockedBtn.isHidden = false + } + @objc func setAction(){ + + } + @objc func lockedAction(){ + okBtn.isHidden = false + setBtn.isHidden = false + lockBtn.isHidden = false + lockedBtn.isHidden = true + } + @objc func stopAction(){ + SportManager.shared.stop() + App.shared.goToPage(SportDetailVC()) + } + @objc func continueAction(){ + SportManager.shared.resume() + okBtn.isHidden = false + setBtn.isHidden = false + lockBtn.isHidden = false + + continueBtn.isHidden = true + stopBtn.isHidden = true + } + } + class headerItem: BaseView { + let deviceView = DeviceStateView() + let titleLabel = UILabel() + + var title:String = ""{ + didSet{ + titleLabel.text = title + } + } + override func setupViews() { + addSubviews([titleLabel,deviceView]) + titleLabel.do { v in + v.textColor = .color(.first) + v.font = .bold(16) + v.textAlignment = .left + } + } + + override func setupLayout() { + titleLabel.snp.makeConstraints { make in + make.centerY.equalToSuperview().inset(0) + make.left.equalToSuperview().inset(18) + } + deviceView.snp.makeConstraints { make in + make.height.equalTo(26) + make.left.equalToSuperview().inset(94) + make.centerY.equalToSuperview() + } + } + } + + + class LabelItem: BaseView { + let detailLabel = UILabel() + let titleLabel = UILabel() + + var title:String = ""{ + didSet{ + titleLabel.text = title + } + } + var detail:String = ""{ + didSet{ + detailLabel.text = detail + } + } + + override func setupViews() { + addSubviews([titleLabel,detailLabel]) + titleLabel.do { v in + v.textColor = .color(.first) + v.font = .bold(24) + v.textAlignment = .left + } + detailLabel.do { v in + v.textColor = .color(.secondary) + v.font = .regular(12) + v.textAlignment = .left + } + } + + override func setupLayout() { + titleLabel.snp.makeConstraints { make in + make.top.equalToSuperview().inset(0) + make.left.right.equalToSuperview() + } + detailLabel.snp.makeConstraints { make in + make.bottom.equalToSuperview().inset(0) + make.left.right.equalToSuperview() + } + } + } + class DistanceItem: BaseView { + let detailLabel = UILabel() + let titleLabel = UILabel() + + var title:String = ""{ + didSet{ + titleLabel.text = title + } + } + var detail:String = ""{ + didSet{ + detailLabel.text = detail + } + } + + override func setupViews() { + addSubviews([titleLabel,detailLabel]) + titleLabel.do { v in + v.textColor = .color(.first) + v.font = .bold(48) + v.textAlignment = .left + } + detailLabel.do { v in + v.textColor = .color(.first) + v.font = .bold(20) + v.textAlignment = .left + v.text = "公里".l10n + } + } + + override func setupLayout() { + titleLabel.snp.makeConstraints { make in + make.top.equalToSuperview().inset(0) + make.left.equalToSuperview() + } + detailLabel.snp.makeConstraints { make in + make.bottom.equalToSuperview().inset(0) + make.left.equalTo(titleLabel.snp.right).offset(10) + } + } + } + } + + + +} diff --git a/ShenYin/ShenYin/UI/Sport/Sport/Sporting/SimpleBoard.swift b/ShenYin/ShenYin/UI/Sport/Sport/Sporting/SimpleBoard.swift new file mode 100644 index 0000000..1fb9a22 --- /dev/null +++ b/ShenYin/ShenYin/UI/Sport/Sport/Sporting/SimpleBoard.swift @@ -0,0 +1,90 @@ +// +// Untitled.swift +// ShenYin +// +// Created by liangshi on 2026/2/3. +// + +import UIKit + +extension SportingVC{ + class SimpleBoard: BaseView { + let topIcon = UIImageView() + let distanceItem = LabelItem() + let durationItem = LabelItem() + + func updateUI(){ + distanceItem.titleLabel.text = SportManager.shared.distanceStr + durationItem.titleLabel.text = SportManager.shared.durationStr + } + + override func setupViews() { + backgroundColor = .white + addSubviews([topIcon,distanceItem,durationItem]) + topIcon.do { v in + v.backgroundColor = .color(.secondary) + v.yc.setCornerRadius(2) + } + distanceItem.do { v in + v.titleLabel.text = "0" + v.detailLabel.text = "总距离".l10n + } + durationItem.do { v in + v.titleLabel.text = "00:00:00" + v.detailLabel.text = "总时长".l10n + } + } + + override func setupLayout() { + topIcon.snp.makeConstraints { make in + make.width.equalTo(50) + make.height.equalTo(4) + make.top.equalToSuperview().inset(6) + make.centerX.equalToSuperview() + } + distanceItem.snp.makeConstraints { make in + make.left.equalToSuperview().inset(18) + make.top.equalToSuperview().inset(27) + make.height.equalTo(50) + } + durationItem.snp.makeConstraints { make in + make.left.equalToSuperview().inset(kScreenWid/2 + 18) + make.top.equalToSuperview().inset(27) + make.height.equalTo(distanceItem) + } + } + + @objc func groupAction(){ + App.shared.goToPage(SportSetVC()) + } + class LabelItem: BaseView { + let detailLabel = UILabel() + let titleLabel = UILabel() + + override func setupViews() { + addSubviews([titleLabel,detailLabel]) + titleLabel.do { v in + v.textColor = .color(.first) + v.font = .bold(24) + v.textAlignment = .left + } + detailLabel.do { v in + v.textColor = .color(.secondary) + v.font = .regular(12) + v.textAlignment = .left + } + } + + override func setupLayout() { + titleLabel.snp.makeConstraints { make in + make.top.equalToSuperview().inset(0) + make.left.right.equalToSuperview() + } + detailLabel.snp.makeConstraints { make in + make.bottom.equalToSuperview().inset(0) + make.left.right.equalToSuperview() + } + } + } + } +} diff --git a/ShenYin/ShenYin/UI/Sport/Sport/Sporting/SportingVC.swift b/ShenYin/ShenYin/UI/Sport/Sport/Sporting/SportingVC.swift new file mode 100644 index 0000000..e5e808a --- /dev/null +++ b/ShenYin/ShenYin/UI/Sport/Sport/Sporting/SportingVC.swift @@ -0,0 +1,164 @@ +// +// Untitled.swift +// ShenYin +// +// Created by liangshi on 2026/2/3. +// + +import MAMapKit + +class SportingVC: BaseVC { + var line: MAPolyline! + var mapView:MAMapView! + let simpleView = SimpleBoard() + let detailView = DetailBoard() + + var trackOverlay: MAPolyline? + + override func setupViews() { + mapView = MAMapView(frame: .zero) + mapView.delegate = self + mapView.showsUserLocation = true + mapView.zoomLevel = 16 + mapView.userTrackingMode = .followWithHeading +// mapView.desiredAccuracy = kCLLocationAccuracyBest + mapView.desiredAccuracy = kCLLocationAccuracyKilometer + mapView.distanceFilter = 0.001 + + + view.addSubviews([mapView,simpleView,detailView]) + mapView.do { v in + + } + simpleView.do { v in + v.yc.setCorners(withRoundedRect: CGRect(x: 0, y: 0, width: kScreenWid, height: 100), roundingCorners: [.topLeft,.topRight], cornerRadii: CGSize(width: 20, height: 20)) + v.yc.addTap(target: self, action: #selector(simpleTapAction)) + } + detailView.do { v in + v.yc.setCorners(withRoundedRect: CGRect(x: 0, y: 0, width: kScreenWid, height: 460), roundingCorners: [.topLeft,.topRight], cornerRadii: CGSize(width: 20, height: 20)) + v.isHidden = true + v.yc.addTap(target: self, action: #selector(detailTapAction)) + } + } + override func setupLayout() { + simpleView.snp.makeConstraints { make in + make.left.right.equalToSuperview() + make.bottom.equalToSuperview() + make.height.equalTo(100) + } + detailView.snp.makeConstraints { make in + make.left.right.equalToSuperview() + make.bottom.equalToSuperview() + make.height.equalTo(460) + } + mapView.snp.makeConstraints { make in + make.left.right.top.equalToSuperview() + make.bottom.equalTo(simpleView.snp.top).offset(20) + } + } + override func setupData() { + NotificationManager.add(self, selector: #selector(updateLocation(_:)), forName: .updateLocation) + SportManager.shared.start() + } + @objc func updateLocation(_ noti:Notification){ + if let userLocation = mapView.userLocation { + let latitude = userLocation.location.coordinate.latitude + let longitude = userLocation.location.coordinate.longitude + let altitude = userLocation.location.altitude + let point = TrackPoint(latitude: latitude, longitude: longitude, altitude: altitude) + + + SportManager.shared.pointList.append(point) + updateUI() + } + + simpleView.updateUI() + detailView.updateUI() + } + @objc func simpleTapAction(){ + simpleView.isHidden = true + detailView.isHidden = false + showDetail() + } + @objc func detailTapAction(){ + simpleView.isHidden = false + detailView.isHidden = true + showSimple() + } + func showSimple(){ + mapView.snp.remakeConstraints { make in + make.left.right.top.equalToSuperview() + make.bottom.equalTo(simpleView.snp.top).offset(20) + } + } + func showDetail(){ + mapView.snp.remakeConstraints { make in + make.left.right.top.equalToSuperview() + make.bottom.equalTo(detailView.snp.top).offset(20) + } + } + func updateUI(){ + // 移除旧轨迹 + if let oldTrackOverlay = trackOverlay { + mapView.remove(oldTrackOverlay) + } + + var trackCoordinates = [CLLocationCoordinate2D]() + for point in SportManager.shared.pointList { + let coordinate = CLLocationCoordinate2D(latitude: point.latitude, longitude: point.longitude) + trackCoordinates.append(coordinate) + } + + + + // 只有当有足够的点时才绘制轨迹线 + if trackCoordinates.count >= 2 { + let coordinatesPointer = UnsafeMutablePointer.allocate(capacity: trackCoordinates.count) + coordinatesPointer.initialize(from: trackCoordinates, count: trackCoordinates.count) + + // 创建新的轨迹线 + let newTrackOverlay = MAPolyline(coordinates: coordinatesPointer, count: UInt(trackCoordinates.count)) + + // 添加到地图 + mapView.add(newTrackOverlay) + + // 保存引用 + trackOverlay = newTrackOverlay + +// // 调整地图视野以适应轨迹 +// mapView.showOverlays([newTrackOverlay], edgePadding: UIEdgeInsets(top: 50, left: 50, bottom: 50, right: 50), animated: true) + } + } +} + +extension SportingVC:MAMapViewDelegate{ + func mapViewRequireLocationAuth(_ locationManager: CLLocationManager!) { + // locationManager.requestAlwaysAuthorization() + } + func mapView(_ mapView: MAMapView!, rendererFor overlay: MAOverlay!) -> MAOverlayRenderer! { + + if (overlay.isKind(of: MAPolyline.self)) + { + let renderer = MAPolylineRenderer(polyline: overlay as! MAPolyline) + renderer?.lineWidth = 8.0 + + + return renderer; + } + + return nil; + } + + func mapView(_ mapView: MAMapView!, didUpdate userLocation: MAUserLocation!, updatingLocation: Bool) { + Logger.debug(userLocation.location) + +// let latitude = userLocation.location.coordinate.latitude +// let longitude = userLocation.location.coordinate.longitude +// let altitude = userLocation.location.altitude +// let point = TrackPoint(latitude: latitude, longitude: longitude, altitude: altitude) +// +// +// SportManager.shared.pointList.append(point) +// updateUI() + } +} diff --git a/ShenYin/ShenYin/UI/Sport/Sport/Target/CalorieView.swift b/ShenYin/ShenYin/UI/Sport/Sport/Target/CalorieView.swift new file mode 100644 index 0000000..e3f5607 --- /dev/null +++ b/ShenYin/ShenYin/UI/Sport/Sport/Target/CalorieView.swift @@ -0,0 +1,45 @@ +// +// CaloriView.swift +// ShenYin +// +// Created by liangshi on 2026/2/3. +// + +import JXSegmentedView + +extension SportTargetVC{ + class CaloriView: BaseView ,JXSegmentedListContainerViewListDelegate{ + let label = UILabel() + + override func setupViews() { + addSubviews([label]) + label.do { v in + v.font = .bold(28) + v.textAlignment = .center + v.textColor = .color(.first) + v.text = "300 千卡" + v.yc.addTap(target: self, action: #selector(tapAction)) + } + } + override func setupLayout() { + label.snp.makeConstraints { make in + make.left.right.equalToSuperview() + make.top.equalToSuperview().inset(50) + } + } + + func listView() -> UIView { + return self + } + @objc func tapAction(){ + let list = Array(stride(from: 1, to: 1000, by: 1)).map { String($0) + " 千卡" } + let v = CustomPicker2(title: "卡路里".l10n, list:list ) + v.selectedIndex = 0 + v.show() + v.didSelected = {[weak self] (index,value) in + guard let self = self else { return } + label.text = value + } + } + } +} diff --git a/ShenYin/ShenYin/UI/Sport/Sport/Target/DistanceView.swift b/ShenYin/ShenYin/UI/Sport/Sport/Target/DistanceView.swift new file mode 100644 index 0000000..4aeadce --- /dev/null +++ b/ShenYin/ShenYin/UI/Sport/Sport/Target/DistanceView.swift @@ -0,0 +1,45 @@ +// +// DistanceView.swift +// ShenYin +// +// Created by liangshi on 2026/2/3. +// + +import JXSegmentedView + +extension SportTargetVC{ + class DistanceView: BaseView ,JXSegmentedListContainerViewListDelegate{ + let label = UILabel() + + override func setupViews() { + addSubviews([label]) + label.do { v in + v.font = .bold(28) + v.textAlignment = .center + v.textColor = .color(.first) + v.text = "3 km" + v.yc.addTap(target: self, action: #selector(tapAction)) + } + } + override func setupLayout() { + label.snp.makeConstraints { make in + make.left.right.equalToSuperview() + make.top.equalToSuperview().inset(50) + } + } + + func listView() -> UIView { + return self + } + @objc func tapAction(){ + let list = Array(stride(from: 1, to: 100, by: 1)).map { String($0) + " km" } + let v = CustomPicker2(title: "距离".l10n, list:list ) + v.selectedIndex = 0 + v.show() + v.didSelected = {[weak self] (index,value) in + guard let self = self else { return } + label.text = value + } + } + } +} diff --git a/ShenYin/ShenYin/UI/Sport/Sport/Target/DurationView.swift b/ShenYin/ShenYin/UI/Sport/Sport/Target/DurationView.swift new file mode 100644 index 0000000..aa5b92d --- /dev/null +++ b/ShenYin/ShenYin/UI/Sport/Sport/Target/DurationView.swift @@ -0,0 +1,44 @@ +// +// DurationView.swift +// ShenYin +// +// Created by liangshi on 2026/2/3. +// + +import JXSegmentedView + +extension SportTargetVC{ + class DurationView: BaseView ,JXSegmentedListContainerViewListDelegate{ + let label = UILabel() + + override func setupViews() { + addSubviews([label]) + label.do { v in + v.font = .bold(28) + v.textAlignment = .center + v.textColor = .color(.first) + v.text = "00:30" + v.yc.addTap(target: self, action: #selector(tapAction)) + } + } + override func setupLayout() { + label.snp.makeConstraints { make in + make.left.right.equalToSuperview() + make.top.equalToSuperview().inset(50) + } + } + + func listView() -> UIView { + return self + } + @objc func tapAction(){ + let v = CustomPicker3(title: "日期".l10n) + v.datePicker.datePickerMode = .time + v.show() + v.didSelected = {[weak self] (date) in + guard let self = self else { return } + label.text = date.format(.shortTime) + } + } + } +} diff --git a/ShenYin/ShenYin/UI/Sport/Sport/Target/SportTargetVC.swift b/ShenYin/ShenYin/UI/Sport/Sport/Target/SportTargetVC.swift new file mode 100644 index 0000000..79dcfa0 --- /dev/null +++ b/ShenYin/ShenYin/UI/Sport/Sport/Target/SportTargetVC.swift @@ -0,0 +1,127 @@ +// +// SportTargetVC.swift +// ShenYin +// +// Created by liangshi on 2026/2/3. +// + +import JXSegmentedView + + +enum SportTargetType{ + case distance + case duration + case calorie + + var name:String{ + switch self { + case .distance: + return "距离".l10n + case .duration: + return "时长".l10n + case .calorie: + return "卡路里".l10n + } + } + static var all:[SportTargetType] = [.distance,.duration,.calorie] +} + +class SportTargetVC: BaseVC { + let okBtn = CustomButton() + private let segmentedDataSource = JXSegmentedTitleDataSource() + private let segmentedView = JXSegmentedView() + private lazy var listContainerView: JXSegmentedListContainerView! = { + let view = JXSegmentedListContainerView(dataSource: self, type: .scrollView) + return view + }() + + var list:[SportTargetType] = SportTargetType.all + override func viewDidLoad() { + super.viewDidLoad() + + + } + + override func setupLayout(){ + + segmentedView.snp.makeConstraints { (m) in + m.left.right.equalToSuperview() + m.height.equalTo(44.fitR) + m.top.equalToSuperview().inset(kNavBtm) + } + listContainerView.snp.makeConstraints { (m) in + m.width.left.equalToSuperview() + m.top.equalTo(segmentedView.snp.bottom) + m.bottom.equalToSuperview() + } + okBtn.snp.makeConstraints { make in + make.bottom.equalToSuperview().inset(80) + make.left.right.equalToSuperview().inset(51) + make.height.equalTo(46) + } + } + override func setupViews(){ + navBar.title = "设置目标".l10n + view.addSubview(segmentedView) + view.addSubview(listContainerView) + view.addSubview(okBtn) + + view.backgroundColor = .color(.bgColor) + segmentedView.do { (v) in + segmentedDataSource.titleNormalColor = .color(.secondary) + segmentedDataSource.titleSelectedColor = .color(.first) + segmentedDataSource.titleNormalFont = .regular(14) + segmentedDataSource.titleSelectedFont = .bold(16) + segmentedDataSource.isTitleZoomEnabled = true + // segmentedDataSource.titleSelectedZoomScale = 1 + segmentedDataSource.isTitleStrokeWidthEnabled = true + segmentedDataSource.isItemSpacingAverageEnabled = true + segmentedDataSource.isTitleColorGradientEnabled = true + segmentedDataSource.titles = list.map{"\($0.name)"} + segmentedView.backgroundColor = .clear + + //配置指示器 + let indicator = JXSegmentedIndicatorLineView() + indicator.indicatorWidth = 20 + indicator.indicatorHeight = 4 + indicator.verticalOffset = 6 + indicator.indicatorColor = .color(.sport) + segmentedView.indicators = [indicator] + + v.dataSource = segmentedDataSource + v.listContainer = listContainerView + } + okBtn.set(title: "确定".l10n,style: .sport, target: self, action: #selector(okAction)) + } + @objc func okAction(){ + self.navigationController?.popViewController(animated: true) + } +} + +extension SportTargetVC: JXSegmentedListContainerViewListDelegate { + func listView() -> UIView { + return view + } +} + +extension SportTargetVC: JXSegmentedListContainerViewDataSource { + func numberOfLists(in listContainerView: JXSegmentedListContainerView) -> Int { + if let titleDataSource = segmentedView.dataSource as? JXSegmentedBaseDataSource { + return titleDataSource.dataSource.count + } + return 0 + } + + func listContainerView(_ listContainerView: JXSegmentedListContainerView, initListAt index: Int) -> JXSegmentedListContainerViewListDelegate { + let type = list[index] + switch type { + case .distance: + return DistanceView() + case .duration: + return DurationView() + case .calorie: + return CaloriView() + } + + } +} diff --git a/ShenYin/ShenYin/UI/Sport/Sport/Tools/IBSpeaker.swift b/ShenYin/ShenYin/UI/Sport/Sport/Tools/IBSpeaker.swift new file mode 100644 index 0000000..8175151 --- /dev/null +++ b/ShenYin/ShenYin/UI/Sport/Sport/Tools/IBSpeaker.swift @@ -0,0 +1,87 @@ +// +// Untitled.swift +// ShenYin +// +// Created by liangshi on 2026/2/26. +// + + +import AVFoundation + +class IBSpeaker: NSObject, AVSpeechSynthesizerDelegate { + static let shared = IBSpeaker() + // MARK: - Properties + private let synthesizer = AVSpeechSynthesizer() + private var textBuffer: String = "" // 用于存储待播放的文字 + private var isPlaying: Bool = false // 是否正在播放 + + + // MARK: - Initializer + override init() { + super.init() + synthesizer.delegate = self + } + + // MARK: - Public Methods + + /// 添加文字并开始播放 + func appendText(_ text: String) { + textBuffer += text + + // 如果没有正在播放,则开始播放 + if !isPlaying { + playNextChunk() + } + } + + /// 停止播放并清空缓冲区 + func stop() { + synthesizer.stopSpeaking(at: .immediate) + textBuffer = "" + isPlaying = false + } + + // MARK: - Private Methods + + /// 播放缓冲区中的下一段文字 + private func playNextChunk() { + guard !textBuffer.isEmpty else { + isPlaying = false + return + } + + // 每次播放一段文字(可以根据需要调整) + let chunkSize = textBuffer.count // 每次播放的字符数 + let endIndex = textBuffer.index(textBuffer.startIndex, offsetBy: min(chunkSize, textBuffer.count)) + let chunk = String(textBuffer[.. Double { + guard totalKm > 0 && totalTimeSec > 0 else { return 0.0 } + let totalHour = Double(totalTimeSec) / 3600.0 // 秒转小时 + let avgSpeed = totalKm / totalHour + return Double((df1.string(from: NSNumber(value: avgSpeed)) ?? "0.0")) ?? 0.0 + } + + // ====================== 1. 总里程(同之前,改用TrackPoint) ====================== + static func calculateTotalDistanceMeter(pointList: [TrackPoint]?) -> Double { + guard let pointList = pointList, pointList.count >= 2 else { return 0.0 } + var total: Double = 0 + for i in 0.. Int { + guard totalDistanceMeter > 0 && stride > 0 else { return 0 } + // 总里程 ÷ 步幅 = 步数(四舍五入取整) + return Int(round(totalDistanceMeter / Double(stride))) + } + + static func calculateTotalDistanceKm(pointList: [TrackPoint]?) -> Double { + let meter = calculateTotalDistanceMeter(pointList: pointList) + return Double((df.string(from: NSNumber(value: meter / 1000)) ?? "0.00")) ?? 0.0 + } + + // 两点球面距离(米) + private static func calculateDistanceBetweenTwoPoints(lat1: Double, lng1: Double, lat2: Double, lng2: Double) -> Double { + let radLat1 = lat1 * Double.pi / 180.0 + let radLng1 = lng1 * Double.pi / 180.0 + let radLat2 = lat2 * Double.pi / 180.0 + let radLng2 = lng2 * Double.pi / 180.0 + let dLat = radLat2 - radLat1 + let dLng = radLng2 - radLng1 + + let a = sin(dLat / 2) * sin(dLat / 2) + + cos(radLat1) * cos(radLat2) * + sin(dLng / 2) * sin(dLng / 2) + let c = 2 * atan2(sqrt(a), sqrt(1 - a)) + return EARTH_RADIUS * c + } + + // ====================== 2. 配速(分/公里) ====================== + static func calculatePace(totalTimeSec: Int, totalDistanceKm: Double) -> String { + guard totalDistanceKm > 0 && totalTimeSec > 0 else { return "--'--''" } + let secPerKm = Double(totalTimeSec) / totalDistanceKm + let min = Int(secPerKm / 60) + let sec = Int(secPerKm.truncatingRemainder(dividingBy: 60)) + return String(format: "%d'%02d''", min, sec) + } + + static func calculatePaceNum(totalTimeSec: Int, totalDistanceKm: Double) -> Double { + guard totalDistanceKm > 0 && totalTimeSec > 0 else { return 0 } + return Double(totalTimeSec) / totalDistanceKm + } + + // ====================== 3. 卡路里 ====================== + static func calculateCalorie(totalDistanceKm: Double, weightKg: Float, sportType: Int) -> Int { + guard totalDistanceKm > 0 && weightKg > 0 else { return 0 } + let coef: Double + switch sportType { + case 1: coef = 1.0; break // 跑 + case 2: coef = 0.8; break // 慢跑 + default: coef = 0.5; break // èµ° + } + return Int(round(Double(weightKg) * totalDistanceKm * coef)) + } + + // ====================== 4. 步频(步/分钟) ====================== + static func calculateCadence(stepCount: Int, totalTimeSec: Int) -> Double { + guard stepCount > 0 && totalTimeSec > 0 else { return 0 } + let min = Double(totalTimeSec) / 60.0 + let cadence = Double(stepCount) / min + return Double((df1.string(from: NSNumber(value: cadence)) ?? "0.0")) ?? 0.0 + } + + // ====================== 5. 步幅(米/步) ====================== + static func calculateStrideLength(totalDistanceMeter: Double, stepCount: Int) -> Double { + guard totalDistanceMeter > 0 && stepCount > 0 else { return 0 } + let stride = totalDistanceMeter / Double(stepCount) + return Double((df1.string(from: NSNumber(value: stride)) ?? "0.0")) ?? 0.0 + } + + // ====================== 6. 海拔变化(累计上升、累计下降、净变化) ====================== + static func calculateAltitudeChange(pointList: [TrackPoint]?) -> AltitudeResult { + let res = AltitudeResult() + guard let pointList = pointList,!pointList.isEmpty else { return res } + + let firstAlt = pointList[0].altitude + let lastAlt = pointList[pointList.count - 1].altitude + res.netChange = lastAlt - firstAlt + + var up: Double = 0, down: Double = 0 + for i in 1.. 0.5 { + up += delta + } else if delta < -0.5 { + down -= delta + } + } + res.totalUp = Double((df1.string(from: NSNumber(value: up)) ?? "0.0")) ?? 0.0 + res.totalDown = Double((df1.string(from: NSNumber(value: down)) ?? "0.0")) ?? 0.0 + res.netChange = Double((df1.string(from: NSNumber(value: res.netChange)) ?? "0.0")) ?? 0.0 + return res + } + + +} + + + +// 存储轨迹点,包含经纬度 + 海拔 +class TrackPoint { + var latitude: Double + var longitude: Double + var altitude: Double // 海拔(ç±³) + + init(latitude: Double, longitude: Double, altitude: Double) { + self.latitude = latitude + self.longitude = longitude + self.altitude = altitude + } + } +// 海拔结果实体 +class AltitudeResult { + var totalUp: Double = 0.0 // 累计上升(ç±³) + var totalDown: Double = 0.0 // 累计下降(ç±³) + var netChange: Double = 0.0 // 净变化(ç±³) + } diff --git a/ShenYin/ShenYin/UI/Sport/Sport/Tools/SportManager.swift b/ShenYin/ShenYin/UI/Sport/Sport/Tools/SportManager.swift new file mode 100644 index 0000000..ba6a749 --- /dev/null +++ b/ShenYin/ShenYin/UI/Sport/Sport/Tools/SportManager.swift @@ -0,0 +1,126 @@ +// +// SportManager.swift +// ShenYin +// +// Created by liangshi on 2026/2/10. +// + +class SportManager { + static let shared = SportManager() + var sportType = SportType.ride + + var pointList:[TrackPoint] = [TrackPoint]() + + var timer:Timer? + /// 运动时间 + var duration = 0 + /// 运动距离 + var distance = 0.0 + /// 当前配速 + var peisu = 0.0 + /// 步数 + var bushu = 0 + + private var hasPlayed100M = false + private var hasPlayed500M = false + private var hasPlayed1000M = false + + func start(){ + duration = 0 + if let t = timer,t.isValid { + t.invalidate() + } + timer = Timer.scheduledTimer(timeInterval: 1, target: self, selector: #selector(timerAction), userInfo: nil, repeats: true) + } + func stop(){ + if let t = timer,t.isValid { + t.invalidate() + } + SportManager.shared.play(text: "运动已结束") + } + func pause(){ + if let t = timer,t.isValid { + t.invalidate() + } + SportManager.shared.play(text: "运动已暂停") + } + func resume(){ + if let t = timer,t.isValid { + t.invalidate() + } + timer = Timer.scheduledTimer(timeInterval: 1, target: self, selector: #selector(timerAction), userInfo: nil, repeats: true) + } + + @objc func timerAction(){ + self.distance = SportCalculateUtil.calculateTotalDistanceMeter(pointList: pointList) + if self.distance == 0 { + self.peisu = 0 + self.bushu = 0 + } + else{ + self.peisu = (Double(duration)/60.0) / (Double(distance) / 1000.0) + self.bushu = Int(distance/0.6) + } + + duration += 1 + NotificationManager.post(type: .updateLocation, objtect: nil) + + if self.distance > 100.0 && hasPlayed100M == false{ + hasPlayed100M = true + SportManager.shared.play(text: "您已经跑了100ç±³,继续加油") + } + else if self.distance > 500.0 && hasPlayed500M == false{ + hasPlayed500M = true + SportManager.shared.play(text: "您已经跑了500ç±³,继续加油") + } + else if self.distance > 1000.0 && hasPlayed1000M == false { + hasPlayed1000M = true + SportManager.shared.play(text: "您已经跑了1000ç±³,继续加油") + } + } + + var durationStr:String{ + return YLToos.formatSeconds(duration) + } + var distanceStr:String { + return String(format: "%.2f", distance/1000.0) + } + var peisuStr:String { + let minutes = Int(peisu) + let seconds = Int(peisu.truncatingRemainder(dividingBy: 1)*60) + return String(format: "%d'%02d''", minutes, seconds) + } + var calorieStr:String { + let value = SportCalculateUtil.calculateCalorie(totalDistanceKm: distance, weightKg: 50, sportType: 1) + return "\(value)" + } + var bushuStr:String { + return "\(bushu)" + } + var speedStr:String { + let value = distance/Double(duration) + return String(format: "%.1f", value) + } + var avgBuFuStr:String { + let value = distance/Double(bushu) + return String(format: "%.1f", value) + } + var avgBuPinStr:String { + let bupin = Double(bushu)/(Double(duration)/60.0) + return "\(Int(bupin))" + } + var upStr:String { + let value = SportCalculateUtil.calculateAltitudeChange(pointList: pointList) + return String(format: "%.1f", value.totalUp) + } + var downStr:String { + let value = SportCalculateUtil.calculateAltitudeChange(pointList: pointList) + return String(format: "%.1f", value.totalDown) + } +} +extension SportManager{ + func play(text:String ){ + IBSpeaker.shared.appendText(text) + } +} + diff --git a/ShenYin/ShenYin/UI/Sport/SportHomeVC.swift b/ShenYin/ShenYin/UI/Sport/SportHomeVC.swift new file mode 100644 index 0000000..cedefa5 --- /dev/null +++ b/ShenYin/ShenYin/UI/Sport/SportHomeVC.swift @@ -0,0 +1,131 @@ +// +// SportHomeVC.swift +// ShenYin +// +// Created by liangshi on 2026/1/20. +// + +import JXSegmentedView + + +enum SportHomeType{ + case sport + case record + case translate + case music + + var name:String{ + switch self { + case .sport: + return "运动" + case .record: + return "录音" + case .translate: + return "翻译" + case .music: + return "音乐" + } + } + static var all:[SportHomeType] = [.sport,.record,.translate,.music] +} + +class SportHomeVC: BaseVC { + let icon = UIImageView() + private let segmentedDataSource = JXSegmentedTitleDataSource() + private let segmentedView = JXSegmentedView() + private lazy var listContainerView: JXSegmentedListContainerView! = { + let view = JXSegmentedListContainerView(dataSource: self, type: .scrollView) + return view + }() + + var list:[SportHomeType] = SportHomeType.all + override func viewDidLoad() { + super.viewDidLoad() + + // AudioPlayView.shared.show() + self.view.addSubview(AudioPlayView.shared) + MapManager.shared.setupSdk() + } + + override func setupLayout(){ + icon.snp.makeConstraints { make in + make.size.equalTo(32) + make.bottom.equalToSuperview().inset(10) + make.centerX.equalToSuperview() + } + segmentedView.snp.makeConstraints { (m) in + m.left.right.equalToSuperview() + m.height.equalTo(44.fitR) + m.top.equalToSuperview().inset(kNavBtm) + } + listContainerView.snp.makeConstraints { (m) in + m.width.left.equalToSuperview() + m.top.equalTo(segmentedView.snp.bottom) + m.bottom.equalToSuperview() + } + } + override func setupViews(){ + icon.do { v in + v.image = UIImage(named: "sport_ai") + } + navBar.addSubview(icon) + view.addSubview(segmentedView) + view.addSubview(listContainerView) + + view.backgroundColor = .color(.bgColor) + segmentedView.do { (v) in + segmentedDataSource.titleNormalColor = .color(.secondary) + segmentedDataSource.titleSelectedColor = .color(.first) + segmentedDataSource.titleNormalFont = .regular(14) + segmentedDataSource.titleSelectedFont = .bold(16) + segmentedDataSource.isTitleZoomEnabled = true + // segmentedDataSource.titleSelectedZoomScale = 1 + segmentedDataSource.isTitleStrokeWidthEnabled = true + segmentedDataSource.isItemSpacingAverageEnabled = true + segmentedDataSource.isTitleColorGradientEnabled = true + segmentedDataSource.titles = list.map{"\($0.name)"} + segmentedView.backgroundColor = .clear + + //配置指示器 + let indicator = JXSegmentedIndicatorLineView() + indicator.indicatorWidth = 20 + indicator.indicatorHeight = 4 + indicator.verticalOffset = 6 + indicator.indicatorColor = .color(.theme) + segmentedView.indicators = [indicator] + + v.dataSource = segmentedDataSource + v.listContainer = listContainerView + } + } +} + +extension SportHomeVC: JXSegmentedListContainerViewListDelegate { + func listView() -> UIView { + return view + } +} + +extension SportHomeVC: JXSegmentedListContainerViewDataSource { + func numberOfLists(in listContainerView: JXSegmentedListContainerView) -> Int { + if let titleDataSource = segmentedView.dataSource as? JXSegmentedBaseDataSource { + return titleDataSource.dataSource.count + } + return 0 + } + + func listContainerView(_ listContainerView: JXSegmentedListContainerView, initListAt index: Int) -> JXSegmentedListContainerViewListDelegate { + let type = list[index] + switch type { + case .sport: + return SportRootView() + case .record: + return RecordRootView() + case .translate: + return TranslateRootView() + case .music: + return MusicRootView() + } + + } +} diff --git a/ShenYin/ShenYin/UI/Sport/Translate/TranslateRootView.swift b/ShenYin/ShenYin/UI/Sport/Translate/TranslateRootView.swift new file mode 100644 index 0000000..f321243 --- /dev/null +++ b/ShenYin/ShenYin/UI/Sport/Translate/TranslateRootView.swift @@ -0,0 +1,49 @@ +// +// TranslateVC.swift +// ShenYin +// +// Created by liangshi on 2026/1/20. +// + +import UIKit +import JXSegmentedView + +class TranslateRootView: BaseView{ + let content = SprotRocrodView() + + override func setupViews() { + addSubviews([content]) + content.do { v in + v.header.titleLabel.text = "翻译".l10n + v.item1.titleLabel.text = "对话翻译".l10n + v.item1.icon.image = UIImage(named: "sport_record") + v.item2.titleLabel.text = "同传听译".l10n + v.item2.icon.image = UIImage(named: "sport_call") + v.header.yc.addTap(target: self, action: #selector(historyAction)) + v.item1.yc.addTap(target: self, action: #selector(dialogTranslateAction)) + v.item2.yc.addTap(target: self, action: #selector(tctyAction)) + } + } + + override func setupLayout() { + content.snp.makeConstraints { make in + make.left.right.top.equalToSuperview().inset(18) + make.height.equalTo(209) + } + } + @objc func historyAction(){ + App.shared.goToPage(HistoryRecordVC()) + } + @objc func dialogTranslateAction(){ + CCPage.gotoDialogTranslate() + } + @objc func tctyAction(){ + CCPage.gotoTcty() + } +} + +extension TranslateRootView: JXSegmentedListContainerViewListDelegate { + func listView() -> UIView { + return self + } +} diff --git a/ShenYin/ShenYin/UI/common/NotificationManager.swift b/ShenYin/ShenYin/UI/common/NotificationManager.swift index 21b1732..7596e8d 100644 --- a/ShenYin/ShenYin/UI/common/NotificationManager.swift +++ b/ShenYin/ShenYin/UI/common/NotificationManager.swift @@ -11,6 +11,7 @@ enum NotificationType:String{ case deviceValidChange case login case logout + case updateLocation var value:String{ switch self { @@ -20,6 +21,8 @@ enum NotificationType:String{ return "login" case .logout: return "logout" + case .updateLocation: + return "updateLocation" default: return "unknown" } @@ -36,9 +39,9 @@ class NotificationManager { NotificationCenter.default.post(name: Notification.Name(type.value), object: objtect, userInfo:nil) } - @objc func handleNotification(_ notification: Notification) { - if let data = notification.userInfo?["data"] as? String { - print("Received data: \(data)") - } - } +// @objc func handleNotification(_ notification: Notification) { +// if let data = notification.userInfo?["data"] as? String { +// print("Received data: \(data)") +// } +// } } diff --git a/ShenYin/podfile b/ShenYin/podfile index 72904a7..d068380 100644 --- a/ShenYin/podfile +++ b/ShenYin/podfile @@ -23,7 +23,7 @@ target 'ShenYin' do pod 'SwiftDate'#时间转换 pod 'SVProgressHUD'#蒙版 pod 'MBProgressHUD'#蒙版 - pod 'Kingfisher', '~> 6.0'#图片下载K + pod 'Kingfisher'#图片下载 pod 'FSPagerView' pod "TabPageViewController" #tabpage pod 'JXSegmentedView' @@ -51,9 +51,9 @@ target 'ShenYin' do pod 'HorizonCalendar'#日历 #高德地图 -# pod 'AMap3DMap-NO-IDFA', '10.0.700' -# pod 'AMapLocation-NO-IDFA' -# pod 'AMapSearch-NO-IDFA', '9.7.0' + pod 'AMap3DMap-NO-IDFA', '10.0.700' + pod 'AMapLocation-NO-IDFA' + pod 'AMapSearch-NO-IDFA', '9.7.0' end