diff --git a/QuickLocation.xcodeproj/project.pbxproj b/QuickLocation.xcodeproj/project.pbxproj index 0b31e9aa..c833cf6b 100644 --- a/QuickLocation.xcodeproj/project.pbxproj +++ b/QuickLocation.xcodeproj/project.pbxproj @@ -294,6 +294,8 @@ 30EFF3E72FDAA93D00EB35D4 /* PrivacyPolicyView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 30EFF3E62FDAA93D00EB35D4 /* PrivacyPolicyView.swift */; }; 30EFF3E82FCA8C7000227D26 /* AuthenticationServices.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 30EFF3E92FCA8C7000227D26 /* AuthenticationServices.framework */; }; 557E8D5330187A5C0032AB51 /* BaseNavigationView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 557E8D5230187A5C0032AB51 /* BaseNavigationView.swift */; }; + 55B2179130217D6600784774 /* HomeView2.swift in Sources */ = {isa = PBXBuildFile; fileRef = 55B2179030217D6600784774 /* HomeView2.swift */; }; + 55B2179330218CE100784774 /* GroupMemberView2.swift in Sources */ = {isa = PBXBuildFile; fileRef = 55B2179230218CE100784774 /* GroupMemberView2.swift */; }; 55BF752D2FFE53F70055DA57 /* LocationPermissionPopView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 55BF752C2FFE53F70055DA57 /* LocationPermissionPopView.swift */; }; 55BF75352FFF91690055DA57 /* InteractionEmojiCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 55BF75342FFF91690055DA57 /* InteractionEmojiCell.swift */; }; A1B2C3D42FDABC8C009215C1 /* PrivacyInfo.xcprivacy in Resources */ = {isa = PBXBuildFile; fileRef = A1B2C3D52FDABC8C009215C1 /* PrivacyInfo.xcprivacy */; }; @@ -601,6 +603,8 @@ 3E4359082FC48D26003470A5 /* QuickLocation.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = QuickLocation.app; sourceTree = BUILT_PRODUCTS_DIR; }; 475D33CAFA1E1911EB1F8D9F /* Pods_QuickLocation.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_QuickLocation.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 557E8D5230187A5C0032AB51 /* BaseNavigationView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BaseNavigationView.swift; sourceTree = ""; }; + 55B2179030217D6600784774 /* HomeView2.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HomeView2.swift; sourceTree = ""; }; + 55B2179230218CE100784774 /* GroupMemberView2.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GroupMemberView2.swift; sourceTree = ""; }; 55BF752C2FFE53F70055DA57 /* LocationPermissionPopView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LocationPermissionPopView.swift; sourceTree = ""; }; 55BF75342FFF91690055DA57 /* InteractionEmojiCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InteractionEmojiCell.swift; sourceTree = ""; }; A1B2C3D52FDABC8C009215C1 /* PrivacyInfo.xcprivacy */ = {isa = PBXFileReference; lastKnownFileType = text.xml; path = PrivacyInfo.xcprivacy; sourceTree = ""; }; @@ -1003,9 +1007,11 @@ isa = PBXGroup; children = ( 305A76272FCA8C7000227D26 /* GroupMemberView.swift */, - 305A76282FCA8C7000227D26 /* HomeView.swift */, 305A76292FCA8C7000227D26 /* HomeViewController.swift */, 305A798B2FCAB99300227D26 /* HomeViewModel.swift */, + 305A76282FCA8C7000227D26 /* HomeView.swift */, + 55B2179030217D6600784774 /* HomeView2.swift */, + 55B2179230218CE100784774 /* GroupMemberView2.swift */, 30A7A9102FCAEE3D00105780 /* GroupListPopView.swift */, 30D87CDE2FDFF1A100E958FD /* QuickMessageView.swift */, 30D87CDC2FDFF07500E958FD /* InteractionView.swift */, @@ -1915,6 +1921,7 @@ 30EFF3E02FDA9CE300EB35D4 /* EmergencyContactModel.swift in Sources */, 305A76B62FCA8C7000227D26 /* UITextField+Extensions.swift in Sources */, 30EFF3A82FD7C6A400EB35D4 /* GroupSettingViewModel.swift in Sources */, + 55B2179130217D6600784774 /* HomeView2.swift in Sources */, 30B819F22FF3CE3C00FAB693 /* ItineraryTraceView.swift in Sources */, 305A76B72FCA8C7000227D26 /* UIView+Extension.swift in Sources */, 305A76B82FCA8C7000227D26 /* UIViewController+Extension.swift in Sources */, @@ -1934,6 +1941,7 @@ 3062E8C02FCED7BB00CEF511 /* GroupIconListView.swift in Sources */, 30BF300C2FED09BA00D9CB52 /* ScheduleDetailView.swift in Sources */, 305A76C22FCA8C7000227D26 /* BaseViewModel.swift in Sources */, + 55B2179330218CE100784774 /* GroupMemberView2.swift in Sources */, 30A87A622FEE724D0095E7C6 /* CreateBubbleTiemView.swift in Sources */, 30DC185A2FD11E7A0041DCD1 /* WebOperations.swift in Sources */, 30A87A642FEE75520095E7C6 /* CreateBubbleTipsView.swift in Sources */, @@ -2155,6 +2163,7 @@ INFOPLIST_KEY_NSPhotoLibraryAddUsageDescription = "为了保存图片到您的相册,请允许添加照片,谢谢。"; INFOPLIST_KEY_NSPhotoLibraryUsageDescription = "我们需要访问您的相册,以便您可以发送照片。"; INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES; + INFOPLIST_KEY_UIBackgroundModes = location; INFOPLIST_KEY_UILaunchStoryboardName = LaunchScreen; INFOPLIST_KEY_UIMainStoryboardFile = Main; INFOPLIST_KEY_UISupportedInterfaceOrientations = UIInterfaceOrientationPortrait; @@ -2208,6 +2217,7 @@ INFOPLIST_KEY_NSPhotoLibraryAddUsageDescription = "为了保存图片到您的相册,请允许添加照片,谢谢。"; INFOPLIST_KEY_NSPhotoLibraryUsageDescription = "我们需要访问您的相册,以便您可以发送照片。"; INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES; + INFOPLIST_KEY_UIBackgroundModes = location; INFOPLIST_KEY_UILaunchStoryboardName = LaunchScreen; INFOPLIST_KEY_UIMainStoryboardFile = Main; INFOPLIST_KEY_UISupportedInterfaceOrientations = UIInterfaceOrientationPortrait; diff --git a/QuickLocation/Assets.xcassets/Common/arrow_right.imageset/Contents.json b/QuickLocation/Assets.xcassets/Common/arrow_right.imageset/Contents.json new file mode 100644 index 00000000..f5c718fd --- /dev/null +++ b/QuickLocation/Assets.xcassets/Common/arrow_right.imageset/Contents.json @@ -0,0 +1,22 @@ +{ + "images" : [ + { + "idiom" : "universal", + "scale" : "1x" + }, + { + "filename" : "arrow-right@2x.png", + "idiom" : "universal", + "scale" : "2x" + }, + { + "filename" : "arrow-right@3x.png", + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/QuickLocation/Assets.xcassets/Common/arrow_right.imageset/arrow-right@2x.png b/QuickLocation/Assets.xcassets/Common/arrow_right.imageset/arrow-right@2x.png new file mode 100644 index 00000000..e9054969 Binary files /dev/null and b/QuickLocation/Assets.xcassets/Common/arrow_right.imageset/arrow-right@2x.png differ diff --git a/QuickLocation/Assets.xcassets/Common/arrow_right.imageset/arrow-right@3x.png b/QuickLocation/Assets.xcassets/Common/arrow_right.imageset/arrow-right@3x.png new file mode 100644 index 00000000..ffde0ed0 Binary files /dev/null and b/QuickLocation/Assets.xcassets/Common/arrow_right.imageset/arrow-right@3x.png differ diff --git a/QuickLocation/Assets.xcassets/Home/group_member_bg2.imageset/Contents.json b/QuickLocation/Assets.xcassets/Home/group_member_bg2.imageset/Contents.json new file mode 100644 index 00000000..c825467f --- /dev/null +++ b/QuickLocation/Assets.xcassets/Home/group_member_bg2.imageset/Contents.json @@ -0,0 +1,22 @@ +{ + "images" : [ + { + "idiom" : "universal", + "scale" : "1x" + }, + { + "filename" : "group_member_bg2@2x.png", + "idiom" : "universal", + "scale" : "2x" + }, + { + "filename" : "group_member_bg2@3x.png", + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/QuickLocation/Assets.xcassets/Home/group_member_bg2.imageset/group_member_bg2@2x.png b/QuickLocation/Assets.xcassets/Home/group_member_bg2.imageset/group_member_bg2@2x.png new file mode 100644 index 00000000..4f86e7b7 Binary files /dev/null and b/QuickLocation/Assets.xcassets/Home/group_member_bg2.imageset/group_member_bg2@2x.png differ diff --git a/QuickLocation/Assets.xcassets/Home/group_member_bg2.imageset/group_member_bg2@3x.png b/QuickLocation/Assets.xcassets/Home/group_member_bg2.imageset/group_member_bg2@3x.png new file mode 100644 index 00000000..b1bb0932 Binary files /dev/null and b/QuickLocation/Assets.xcassets/Home/group_member_bg2.imageset/group_member_bg2@3x.png differ diff --git a/QuickLocation/Assets.xcassets/Home/group_name_icon.imageset/Contents.json b/QuickLocation/Assets.xcassets/Home/group_name_icon.imageset/Contents.json new file mode 100644 index 00000000..19293478 --- /dev/null +++ b/QuickLocation/Assets.xcassets/Home/group_name_icon.imageset/Contents.json @@ -0,0 +1,22 @@ +{ + "images" : [ + { + "idiom" : "universal", + "scale" : "1x" + }, + { + "filename" : "vuesax/bold/mouse@2x.png", + "idiom" : "universal", + "scale" : "2x" + }, + { + "filename" : "vuesax/bold/mouse@3x.png", + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/QuickLocation/Assets.xcassets/Home/group_name_icon.imageset/vuesax/bold/mouse@2x.png b/QuickLocation/Assets.xcassets/Home/group_name_icon.imageset/vuesax/bold/mouse@2x.png new file mode 100644 index 00000000..a1b35263 Binary files /dev/null and b/QuickLocation/Assets.xcassets/Home/group_name_icon.imageset/vuesax/bold/mouse@2x.png differ diff --git a/QuickLocation/Assets.xcassets/Home/group_name_icon.imageset/vuesax/bold/mouse@3x.png b/QuickLocation/Assets.xcassets/Home/group_name_icon.imageset/vuesax/bold/mouse@3x.png new file mode 100644 index 00000000..dbbfeccb Binary files /dev/null and b/QuickLocation/Assets.xcassets/Home/group_name_icon.imageset/vuesax/bold/mouse@3x.png differ diff --git a/QuickLocation/Assets.xcassets/Home/invite.imageset/Contents.json b/QuickLocation/Assets.xcassets/Home/invite.imageset/Contents.json new file mode 100644 index 00000000..72d71d65 --- /dev/null +++ b/QuickLocation/Assets.xcassets/Home/invite.imageset/Contents.json @@ -0,0 +1,22 @@ +{ + "images" : [ + { + "idiom" : "universal", + "scale" : "1x" + }, + { + "filename" : "invite@2x.png", + "idiom" : "universal", + "scale" : "2x" + }, + { + "filename" : "invite@3x.png", + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/QuickLocation/Assets.xcassets/Home/invite.imageset/invite@2x.png b/QuickLocation/Assets.xcassets/Home/invite.imageset/invite@2x.png new file mode 100644 index 00000000..cf974a0a Binary files /dev/null and b/QuickLocation/Assets.xcassets/Home/invite.imageset/invite@2x.png differ diff --git a/QuickLocation/Assets.xcassets/Home/invite.imageset/invite@3x.png b/QuickLocation/Assets.xcassets/Home/invite.imageset/invite@3x.png new file mode 100644 index 00000000..2cc82b4d Binary files /dev/null and b/QuickLocation/Assets.xcassets/Home/invite.imageset/invite@3x.png differ diff --git a/QuickLocation/Assets.xcassets/Login/apple.imageset/Group 1444@2x.png b/QuickLocation/Assets.xcassets/Login/apple.imageset/Group 1444@2x.png index a40c785a..e9dd2327 100644 Binary files a/QuickLocation/Assets.xcassets/Login/apple.imageset/Group 1444@2x.png and b/QuickLocation/Assets.xcassets/Login/apple.imageset/Group 1444@2x.png differ diff --git a/QuickLocation/Assets.xcassets/Login/apple.imageset/Group 1444@3x.png b/QuickLocation/Assets.xcassets/Login/apple.imageset/Group 1444@3x.png index 397fde7b..8bad5e5a 100644 Binary files a/QuickLocation/Assets.xcassets/Login/apple.imageset/Group 1444@3x.png and b/QuickLocation/Assets.xcassets/Login/apple.imageset/Group 1444@3x.png differ diff --git a/QuickLocation/Assets.xcassets/Login/phone.imageset/Group 1445@2x.png b/QuickLocation/Assets.xcassets/Login/phone.imageset/Group 1445@2x.png index 11f5fafd..3ed56d0a 100644 Binary files a/QuickLocation/Assets.xcassets/Login/phone.imageset/Group 1445@2x.png and b/QuickLocation/Assets.xcassets/Login/phone.imageset/Group 1445@2x.png differ diff --git a/QuickLocation/Assets.xcassets/Login/phone.imageset/Group 1445@3x.png b/QuickLocation/Assets.xcassets/Login/phone.imageset/Group 1445@3x.png index 1bd3aee7..3818ccd0 100644 Binary files a/QuickLocation/Assets.xcassets/Login/phone.imageset/Group 1445@3x.png and b/QuickLocation/Assets.xcassets/Login/phone.imageset/Group 1445@3x.png differ diff --git a/QuickLocation/Assets.xcassets/Login/wechat.imageset/Group 1446@2x.png b/QuickLocation/Assets.xcassets/Login/wechat.imageset/Group 1446@2x.png index 4ab6bb91..aad5efe3 100644 Binary files a/QuickLocation/Assets.xcassets/Login/wechat.imageset/Group 1446@2x.png and b/QuickLocation/Assets.xcassets/Login/wechat.imageset/Group 1446@2x.png differ diff --git a/QuickLocation/Assets.xcassets/Login/wechat.imageset/Group 1446@3x.png b/QuickLocation/Assets.xcassets/Login/wechat.imageset/Group 1446@3x.png index 3e05cc1a..eba79202 100644 Binary files a/QuickLocation/Assets.xcassets/Login/wechat.imageset/Group 1446@3x.png and b/QuickLocation/Assets.xcassets/Login/wechat.imageset/Group 1446@3x.png differ diff --git a/QuickLocation/Common/Constant.swift b/QuickLocation/Common/Constant.swift index b7d18bf5..67aca406 100644 --- a/QuickLocation/Common/Constant.swift +++ b/QuickLocation/Common/Constant.swift @@ -59,16 +59,12 @@ public let kScreenWidth = UIScreen.main.bounds.size.width public let kScreenHeight = UIScreen.main.bounds.size.height /// 状态栏高度 public var kStatusBarHeight: CGFloat { - if #available(iOS 13.0, *) { - let statusBarHeight = kKeyWindow? - .windowScene? - .statusBarManager? - .statusBarFrame - .height ?? 0 - return statusBarHeight - } else { - return UIApplication.shared.statusBarFrame.height - } + let statusBarHeight = kKeyWindow? + .windowScene? + .statusBarManager? + .statusBarFrame + .height ?? 0 + return statusBarHeight } /// 顶部导航+状态栏高度 public let kNaviHeight = 44 + kStatusBarHeight diff --git a/QuickLocation/Info.plist b/QuickLocation/Info.plist index 48ac7de6..7e4c3f0d 100644 --- a/QuickLocation/Info.plist +++ b/QuickLocation/Info.plist @@ -69,5 +69,9 @@ douyu.otf zihunbiantaoti-r.ttf + UIBackgroundModes + + location + diff --git a/QuickLocation/Main/Tabbar/MainTabBarController.swift b/QuickLocation/Main/Tabbar/MainTabBarController.swift index 03ed6888..40beb5fe 100644 --- a/QuickLocation/Main/Tabbar/MainTabBarController.swift +++ b/QuickLocation/Main/Tabbar/MainTabBarController.swift @@ -58,9 +58,9 @@ final class MainTabBarController: UITabBarController { view.addSubview(customTabBar) customTabBar.delegate = self - // 点击"探索"(index=1)时游客跳登录、不选中 + // 点击"探索"(index=1)或"圈子"(index=2)时游客跳登录、不选中 customTabBar.shouldSelectTab = { index in - if index == 1 || index == 2, AppContextManager.shared.isGuest { + if (index == 1 || index == 2), AppContextManager.shared.isGuest { AppRouter.push(Route.login) return false } @@ -98,11 +98,6 @@ final class MainTabBarController: UITabBarController { // MARK: - QuickLocationTabBarDelegate extension MainTabBarController: QuickLocationTabBarDelegate { func tabBar(_ tabBar: QuickLocationTabBar, didSelectTabAt index: Int) { - // 点击"探索"(index=1)时判断游客,是则跳登录、不选中 - if index == 1 || index == 2, AppContextManager.shared.isGuest { - AppRouter.push(Route.login) - return - } selectedIndex = index } } diff --git a/QuickLocation/Main/Tabbar/QuickLocationTabBar.swift b/QuickLocation/Main/Tabbar/QuickLocationTabBar.swift index 0d12fa68..3d0fdd6f 100644 --- a/QuickLocation/Main/Tabbar/QuickLocationTabBar.swift +++ b/QuickLocation/Main/Tabbar/QuickLocationTabBar.swift @@ -24,9 +24,9 @@ final class QuickLocationTabBar: UIView { } // MARK: - Design Constants (in iOS points, from Lanhu @2x 容器 2) - private let barContentHeight: CGFloat = 56 - private let containerCornerRadius: CGFloat = 28 - private let horizontalInset: CGFloat = 18 + private let barContentHeight: CGFloat = 49 + private let containerCornerRadius: CGFloat = 0 + private let horizontalInset: CGFloat = 0 private let iconSize: CGFloat = 32 private let iconLabelSpacing: CGFloat = 4 private let labelHeight: CGFloat = 11 @@ -46,6 +46,7 @@ final class QuickLocationTabBar: UIView { private let items: [TabItem] private var tabViews: [UIView] = [] private let containerView = UIView() + private let topSeparatorView = UIView() private(set) var selectedIndex: Int = 0 @@ -72,12 +73,11 @@ final class QuickLocationTabBar: UIView { containerView.backgroundColor = .white containerView.layer.cornerRadius = containerCornerRadius - containerView.layer.shadowColor = UIColor(red: 0.059, green: 0.157, blue: 0.275, alpha: 0.1).cgColor - containerView.layer.shadowOffset = .zero - containerView.layer.shadowRadius = 8 - containerView.layer.shadowOpacity = 1 addSubview(containerView) + topSeparatorView.backgroundColor = UIColor(hexStr: "#E8E8E8") + addSubview(topSeparatorView) + for (index, item) in items.enumerated() { let tabView = makeTabView(item: item, index: index) containerView.addSubview(tabView) @@ -86,11 +86,18 @@ final class QuickLocationTabBar: UIView { containerView.translatesAutoresizingMaskIntoConstraints = false + topSeparatorView.translatesAutoresizingMaskIntoConstraints = false + NSLayoutConstraint.activate([ + topSeparatorView.leadingAnchor.constraint(equalTo: leadingAnchor), + topSeparatorView.trailingAnchor.constraint(equalTo: trailingAnchor), + topSeparatorView.topAnchor.constraint(equalTo: topAnchor), + topSeparatorView.heightAnchor.constraint(equalToConstant: 1.0 / UIScreen.main.scale), + containerView.leadingAnchor.constraint(equalTo: leadingAnchor, constant: horizontalInset), containerView.trailingAnchor.constraint(equalTo: trailingAnchor, constant: -horizontalInset), - containerView.bottomAnchor.constraint(equalTo: bottomAnchor, constant: -kSafeBottomMargin), - containerView.heightAnchor.constraint(equalToConstant: barContentHeight) + containerView.bottomAnchor.constraint(equalTo: bottomAnchor), + containerView.heightAnchor.constraint(equalToConstant: barContentHeight + kSafeBottomMargin) ]) } @@ -157,7 +164,7 @@ final class QuickLocationTabBar: UIView { guard containerWidth > 0, !tabViews.isEmpty else { return } let tabWidth = containerWidth / CGFloat(tabViews.count) - let tabHeight = containerView.bounds.height + let tabHeight = containerView.bounds.height - kSafeBottomMargin let totalHeight = iconSize + iconLabelSpacing + labelHeight let startY = (tabHeight - totalHeight) / 2 diff --git a/QuickLocation/Section/Group/GroupMemberList/GroupMemberListView.swift b/QuickLocation/Section/Group/GroupMemberList/GroupMemberListView.swift index 74f0db87..ac680b87 100644 --- a/QuickLocation/Section/Group/GroupMemberList/GroupMemberListView.swift +++ b/QuickLocation/Section/Group/GroupMemberList/GroupMemberListView.swift @@ -657,13 +657,13 @@ class GroupMemberListCell: UICollectionViewCell { nameLab.textColor = UIColor(hexStr: isCurrentUser ? "#16B3FF" : "#0F2846") selectedBgView.isHidden = !isSelected // 会员权益 - if AppContextManager.shared.vip > 1, model.is_online { - batteryInfoView.isHidden = model.battery.int == 0 - // 电量 16是电池图标宽度,右边有电池造型需要减去 - let batteryPercent = min(CGFloat(model.battery.int), 100) - batteryView.layoutChain.width(CGFloat(16 - 1) * batteryPercent / 100.0) - batteryLab.text = "\(model.battery)%" - } +// if AppContextManager.shared.vip > 1, model.is_online { +// batteryInfoView.isHidden = model.battery.int == 0 +// // 电量 16是电池图标宽度,右边有电池造型需要减去 +// let batteryPercent = min(CGFloat(model.battery.int), 100) +// batteryView.layoutChain.width(CGFloat(16 - 1) * batteryPercent / 100.0) +// batteryLab.text = "\(model.battery)%" +// } } private func setupSubviews() { @@ -684,8 +684,8 @@ class GroupMemberListCell: UICollectionViewCell { selectedBgView.layoutChain.edges() avaterImgView.layoutChain - .top(11) - .edgesHorzontal(10) + .top(9) + .edgesHorzontal(5.5) .heightToWidth(1) batteryInfoView.layoutChain diff --git a/QuickLocation/Section/Group/GroupView.swift b/QuickLocation/Section/Group/GroupView.swift index 932313e5..816fce69 100644 --- a/QuickLocation/Section/Group/GroupView.swift +++ b/QuickLocation/Section/Group/GroupView.swift @@ -161,8 +161,12 @@ class GroupView: UIView { return iv }() - lazy var navView = BaseNavigationView(title: "圈子") - + lazy var navView: BaseNavigationView = { + let nav = BaseNavigationView(title: "圈子") + nav.backBtn.isHidden = true + return nav + }() + lazy var scanBtn: UIButton = { let btn = UIButton(type: .custom) btn.setImage(UIImage(named: "Group/scan"), for: .normal) diff --git a/QuickLocation/Section/Home/GroupMemberView.swift b/QuickLocation/Section/Home/GroupMemberView.swift index e8ec5822..0e82fd0c 100644 --- a/QuickLocation/Section/Home/GroupMemberView.swift +++ b/QuickLocation/Section/Home/GroupMemberView.swift @@ -5,7 +5,6 @@ // Created by 八条 on 2026/5/29. // -import UIKit import UIKit import RxSwift import RxCocoa diff --git a/QuickLocation/Section/Home/GroupMemberView2.swift b/QuickLocation/Section/Home/GroupMemberView2.swift new file mode 100644 index 00000000..ddfec15d --- /dev/null +++ b/QuickLocation/Section/Home/GroupMemberView2.swift @@ -0,0 +1,352 @@ +// +// GroupMemberView2.swift +// QuickLocation +// +// Created by 八条 on 2026/8/4. +// + +import UIKit +import RxSwift +import RxCocoa + +class GroupMemberView2: UIView { + + private var navBarHeightConstraint: NSLayoutConstraint? + + /// 最大化时展开导航占位为 kNaviHeight,收起/dismiss 时为 0 + func setNavBarExpanded(_ expanded: Bool) { + navBarHeightConstraint?.constant = expanded ? kNaviHeight : 0 + } + + func setupMemberInfo(_ model: GroupMemberModel, isOwner: Bool) { + memberNameLab.text = model.nick_name + ownView.isHidden = !isOwner + statusDotView.backgroundColor = UIColor(hexStr: model.is_online ? "#67EA76" : "#D8D8D8") + statusLab.text = model.is_online ? "在线" : "离线" + locationLab.text = model.lastLocation + } + + private func setupRx() { + + } + + private func setupUI() { + addSubview(headerBgView) + addSubview(navBarView) + addSubview(scrollView) + + headerBgView.layoutChain + .edges(excludingEdge: .bottom) + .heightToWidth(160/375) + + navBarView.layoutChain + .edges(excludingEdge: .bottom) + .height(0) + navBarHeightConstraint = navBarView.jh_constraint( + .height, toAttribute: .notAnAttribute, otherView: nil, relation: .equal + ) + + scrollView.layoutChain + .topToBottomOfView(navBarView) + .edges(excludingEdge: .top) + scrollView.isScrollEnabled = false + + let lineView = UIView() + lineView.backgroundColor = .black.withAlphaComponent(0.2) + lineView.cornerRadius = 2 + addSubview(lineView) + lineView.layoutChain + .top(9) + .centerX() + .width(40) + .height(4) + } + + lazy var headerBgView: UIImageView = { + let view = UIImageView() + view.image = UIImage(named: "Home/group_member_bg2") + view.backgroundColor = .clear + view.contentMode = .scaleAspectFill + return view + }() + + lazy var navBarView: UIView = { + let view = UIView() + view.backgroundColor = .clear + view.clipsToBounds = true + return view + }() + + lazy var scrollView: UIScrollView = { + let view = UIScrollView() + view.backgroundColor = .clear + view.showsHorizontalScrollIndicator = false + view.bounces = false + + view.addSubview(scrollContentView) + scrollContentView.layoutChain.edges().widthToView(view) + + scrollContentView.addSubview(memberView) + memberView.layoutChain + .edges() + + return view + }() + + lazy var scrollContentView: UIView = { + let view = UIView() + view.backgroundColor = .clear + return view + }() + + // MARK: - 成员 + lazy var memberView: UIView = { + let view = UIView() + view.backgroundColor = .clear + + let icon = UIImageView() + icon.image = UIImage(named: "Home/group_name_icon") + view.addSubview(icon) + icon.layoutChain + .top(23) + .left(15) + .width(18) + .heightToWidth(1) + + view.addSubview(groupNameLab) + groupNameLab.layoutChain + .leftToRightOfView(icon, offset: 6) + .centerY(icon) + + view.addSubview(changeGroupBtn) + changeGroupBtn.layoutChain + .centerY(icon) + .right(15) + .height(20) + + let memberView = UIView() + memberView.backgroundColor = .white + memberView.cornerRadius = 20 + memberView.addSubview(memberCV) + view.addSubview(memberView) + + memberView.layoutChain + .topToBottomOfView(icon, offset: 12) + .edgesHorzontal(15) + .height(100) + + memberCV.layoutChain.edges(all: 5) + + view.addSubview(memberInfoView) + memberInfoView.layoutChain + .topToBottomOfView(memberView, offset: 12) + .edgesHorzontal(15) + .height(150) + .bottom() + + return view + }() + + lazy var groupNameLab: UILabel = { + let label = UILabel() + label.text = " " + label.font = .systemFont(ofSize: 16, weight: .bold) + label.textColor = UIColor(hexStr: "#353B4F") + return label + }() + + lazy var changeGroupBtn: UIButton = { + let btn = UIButton(type: .custom) + btn.setTitle("切换圈子 ", for: .normal) + btn.setTitleColor(UIColor(hexStr: "#353B4F"), for: .normal) + btn.titleLabel?.font = .systemFont(ofSize: 12, weight: .medium) + btn.setImage(UIImage(named: "Common/arrow_right"), for: .normal) + btn.semanticContentAttribute = .forceRightToLeft + return btn + }() + + /// 成员列表 + lazy var memberCV: UICollectionView = { + let layout = UICollectionViewFlowLayout() + layout.itemSize = CGSize(width: 61, height: 90) + layout.minimumLineSpacing = 15 + layout.sectionInset = UIEdgeInsets(top: 0, left: 8, bottom: 0, right: 8) + layout.scrollDirection = .horizontal + + let cv = UICollectionView(frame: .zero, collectionViewLayout: layout) + cv.backgroundColor = .clear + cv.showsHorizontalScrollIndicator = false + cv.register(GroupMemberListCell.self) + return cv + }() + + /// 成员信息 + lazy var memberInfoView: UIView = { + let view = UIView() + view.backgroundColor = .white + view.cornerRadius = 20 + + view.addSubview(memberNameLab) + memberNameLab.layoutChain + .top(20) + .left(18) + + view.addSubview(tagView) + tagView.layoutChain + .leftToRightOfView(memberNameLab, offset: 6) + .centerY(memberNameLab) + .height(20) + ownView.layoutChain.height(16) + + let statusView = UIView() + statusView.backgroundColor = .clear + statusView.addSubview(statusDotView) + statusView.addSubview(statusLab) + view.addSubview(statusView) + statusView.layoutChain + .right(18) + .centerY(memberNameLab) + + statusDotView.layoutChain + .left() + .width(8) + .heightToWidth(1) + .centerY() + + statusLab.layoutChain + .leftToRightOfView(statusDotView, offset: 4) + .edgesVertical() + .right() + + let locationIcon = UIImageView() + locationIcon.image = UIImage(named: "Home/member_location") + view.addSubview(locationIcon) + locationIcon.layoutChain + .topToBottomOfView(memberNameLab, offset: 17) + .left(20) + .width(10) + .height(10) + + view.addSubview(locationLab) + locationLab.layoutChain + .centerY(locationIcon) + .leftToRightOfView(locationIcon, offset: 7) + + view.addSubview(distanceView) + distanceView.layoutChain + .right(18) + .centerY(locationLab) + + locationLab.layoutChain.rightToLeftOfView(distanceView, offset: -30, relation: .greaterThanOrEqual) + + return view + }() + + lazy var memberNameLab: UILabel = { + let label = UILabel() + label.text = " " + label.font = .systemFont(ofSize: 12, weight: .bold) + label.textColor = UIColor(hexStr: "#353B4F") + return label + }() + + lazy var tagView: UIStackView = { + let view = UIStackView(arrangedSubviews: [ownView]) + view.axis = .horizontal + view.alignment = .center + view.spacing = 5 + view.backgroundColor = .clear + return view + }() + + // 圈主 + lazy var ownView: UIView = { + let view = UIView() + view.backgroundColor = UIColor(hexStr: "#E3F6FF") + view.cornerRadius = 6 + view.isHidden = true + + let label = UILabel() + label.textColor = UIColor(hexStr: "#128CC6") + label.font = .systemFont(ofSize: 10, weight: .bold) + label.text = "圈主" + + view.addSubview(label) + label.layoutChain + .edgesVertical(2) + .edgesHorzontal(8) + + return view + }() + + /// 在线状态 + lazy var statusDotView: UIView = { + let view = UIView() + view.backgroundColor = UIColor(hexStr: "#D8D8D8") + view.cornerRadius = 4 + return view + }() + + lazy var statusLab: UILabel = { + let label = UILabel() + label.text = " " + label.font = .systemFont(ofSize: 10, weight: .medium) + label.textColor = UIColor(hexStr: "#353B4F") + return label + }() + + // 位置 + lazy var locationLab: UILabel = { + let label = UILabel() + label.textColor = UIColor(hexStr: "#767676") + label.font = .systemFont(ofSize: 12, weight: .medium) + return label + }() + + // 距离 + lazy var distanceView: UIView = { + let view = UIView() + view.backgroundColor = UIColor(hexStr: "#353B4F") + view.cornerRadius = 6 + view.isHidden = true + + let label = UILabel() + label.textColor = UIColor(hexStr: "#58EDFF") + label.font = .systemFont(ofSize: 10, weight: .bold) + label.text = "距离" + + view.addSubview(label) + label.layoutChain + .left(5) + .edgesVertical(3) + + view.addSubview(distanceLab) + distanceLab.layoutChain + .leftToRightOfView(label, offset: 2) + .right(5) + .centerY() + + return view + }() + + lazy var distanceLab: UILabel = { + let label = UILabel() + label.textColor = .white + label.font = .systemFont(ofSize: 10, weight: .bold) + return label + }() + + override init(frame: CGRect) { + super.init(frame: frame) + backgroundColor = UIColor(hexStr: "#FAFAFA") + cornerRadius = 30 + layer.maskedCorners = [.layerMinXMinYCorner, .layerMaxXMinYCorner] + setupUI() + setupRx() + } + + required init?(coder aDecoder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + +} diff --git a/QuickLocation/Section/Home/HomeView2.swift b/QuickLocation/Section/Home/HomeView2.swift new file mode 100644 index 00000000..e95351bc --- /dev/null +++ b/QuickLocation/Section/Home/HomeView2.swift @@ -0,0 +1,382 @@ +// +// HomeView2.swift +// QuickLocation +// +// Created by 八条 on 2026/8/4. +// + +import UIKit +import RxSwift +import RxCocoa +import AMapNaviKit +import MarqueeLabel + +class HomeView2: UIView { + + var disposeBag = DisposeBag() + + let groupMemberView = GroupMemberView2(frame: .zero) + + /// 三段高度(可见区域) + private let stage1Height: CGFloat = 160 + 49 + kSafeBottomMargin + private let stage2Height: CGFloat = 327 + 49 + kSafeBottomMargin + + // MARK: - groupMemberView 拖拽(三段) + private var groupMemberTopConstraint: NSLayoutConstraint? + /// 第三段:贴顶 + private var stage3Top: CGFloat = 0 + /// 第二段:初始 + private var stage2Top: CGFloat = 0 + /// 第一段:最小 + private var stage1Top: CGFloat = 0 + private var isGroupMemberLimitsSet = false + private var panStartTop: CGFloat = 0 + /// 内嵌 scrollView 是否可滑动(仅第三段) + private var isSubCanScroll = false + + private var groupMemberScrollTopOffset: CGFloat { + -groupMemberView.scrollView.adjustedContentInset.top + } + + private var stageTops: [CGFloat] { + [stage3Top, stage2Top, stage1Top] + } + + var gpsSignalViewList: [UIView] = [] + /// 根据信号格数更新信号条颜色 + func updateGPSSignal(bars: Int) { + let filled = max(0, min(bars, gpsSignalViewList.count)) + for (i, view) in gpsSignalViewList.enumerated() { + view.backgroundColor = i < filled ? UIColor(hexStr: "#4ED178") : UIColor(hexStr: "#E0E0E0") + } + } + + private func setupRx() { + // 感叹号 + warningView.rx.tapGesture.subscribe { _ in + LocationPermissionPopView.show() + }.disposed(by: disposeBag) + } + + private func setupUI() { + addSubview(mapView) + addSubview(gpsSignalView) + addSubview(warningView) + addSubview(inviteBtn) + addSubview(locationView) + addSubview(groupMemberView) + + mapView.layoutChain + .edges(excludingEdge: .bottom) + .bottom(49 + kSafeBottomMargin) + + gpsSignalView.layoutChain + .top(kStatusBarHeight + 11) + .left(15) + + warningView.layoutChain + .topToBottomOfView(gpsSignalView, offset: 11) + .leftToView(gpsSignalView) + + groupMemberView.layoutChain + .top(kScreenHeight - stage2Height) + .edgesHorzontal() + .bottom() + + inviteBtn.layoutChain + .bottomToTopOfView(groupMemberView, offset: -9) + .edgesHorzontal(15) + .heightToWidth(50/345) + + locationView.layoutChain + .bottomToTopOfView(inviteBtn, offset: -8) + .left(15) + .width(40) + .height(40) + + groupMemberTopConstraint = groupMemberView.jh_constraint( + .top, toAttribute: .top, otherView: self, relation: .equal + ) + + let pan = UIPanGestureRecognizer(target: self, action: #selector(handleGroupMemberPan(_:))) + pan.delegate = self + groupMemberView.addGestureRecognizer(pan) + groupMemberView.scrollView.delegate = self + groupMemberView.scrollView.isScrollEnabled = false + } + + // MARK: - Map + lazy var mapView: MAMapView! = { + let mv = MAMapView() + mv.zoomLevel = 16 + mv.showsUserLocation = true + mv.userTrackingMode = .none + mv.showsCompass = false + mv.showsScale = false + mv.isRotateEnabled = false + mv.isRotateCameraEnabled = false + DispatchQueue.main.async { mv.logoCenter = CGPoint(x: kScreenWidth - 50, y: kStatusBarHeight) } + return mv + }() + + // MARK: - 卫星信号 + lazy var gpsSignalView: UIView = { + let view = UIView() + view.cornerRadius = 6 + view.backgroundColor = .white + + let signalView1 = UIView() + signalView1.backgroundColor = UIColor(hexStr: "#E6E6E6") + view.addSubview(signalView1) + signalView1.layoutChain + .left(7) + .bottom(5) + .width(3) + .height(4) + + let signalView2 = UIView() + signalView2.backgroundColor = UIColor(hexStr: "#E6E6E6") + view.addSubview(signalView2) + signalView2.layoutChain + .leftToRightOfView(signalView1, offset: 2) + .bottomToView(signalView1) + .width(3) + .height(6) + + let signalView3 = UIView() + signalView3.backgroundColor = UIColor(hexStr: "#E6E6E6") + view.addSubview(signalView3) + signalView3.layoutChain + .leftToRightOfView(signalView2, offset: 2) + .bottomToView(signalView1) + .width(3) + .height(8) + + let signalView4 = UIView() + signalView4.backgroundColor = UIColor(hexStr: "#E6E6E6") + view.addSubview(signalView4) + signalView4.layoutChain + .leftToRightOfView(signalView3, offset: 2) + .bottomToView(signalView1) + .width(3) + .height(10) + + let label = UILabel() + label.text = "卫星信号" + label.font = .systemFont(ofSize: 10, weight: .medium) + label.textColor = UIColor(hexStr: "#353B4F") + view.addSubview(label) + label.layoutChain + .leftToRightOfView(signalView4, offset: 2) + .edgesVertical(4) + .right(7) + + gpsSignalViewList = [signalView1, signalView2, signalView3, signalView4] + + return view + }() + + // MARK: - warning + lazy var warningView: UIView = { + let view = UIView() + view.backgroundColor = .white + view.cornerRadius = 8 + view.isHidden = true + + let icon = UIImageView() + + let label = UILabel() + label.text = "有权限未开启,数据可能会异常" + label.textColor = UIColor(hexStr: "#353B4F") + label.font = .systemFont(ofSize: 12, weight: .medium) + view.addSubview(label) + label.layoutChain + .edgesHorzontal(8) + .edgesVertical(10) + + return view + }() + + // MARK: - 定位按钮 + lazy var locationView: UIView = { + let view = UIView() + view.backgroundColor = .black.withAlphaComponent(0.4) + view.cornerRadius = 20 + + view.addSubview(locationIconView) + locationIconView.layoutChain.centerX().centerY() + + return view + }() + + lazy var locationIconView: UIImageView = { + let view = UIImageView() + view.backgroundColor = .clear + view.image = UIImage(named: "Home/location") + return view + }() + + // MARK: - 邀请加入 + lazy var inviteBtn: UIButton = { + let btn = UIButton(type: .custom) + btn.setBackgroundImage(UIImage(named: "Home/invite"), for: .normal) + return btn + }() + + override init(frame: CGRect) { + super.init(frame: .zero) + backgroundColor = .white + setupUI() + setupRx() + } + + required init?(coder aDecoder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + // MARK: - Layout + override func layoutSubviews() { + super.layoutSubviews() + if !isGroupMemberLimitsSet, bounds.height > 0 { + isGroupMemberLimitsSet = true + stage3Top = 0 + stage2Top = bounds.height - stage2Height + stage1Top = bounds.height - stage1Height + groupMemberTopConstraint?.constant = stage2Top + } + } + + // MARK: - Pan Gesture + @objc private func handleGroupMemberPan(_ pan: UIPanGestureRecognizer) { + guard isGroupMemberLimitsSet, let topConstraint = groupMemberTopConstraint else { return } + + switch pan.state { + case .began: + layoutIfNeeded() + panStartTop = groupMemberView.frame.minY + + case .changed: + let newTop = panStartTop + pan.translation(in: self).y + let scrollOffset = groupMemberView.scrollView.contentOffset.y + + if isSubCanScroll { + if scrollOffset > groupMemberScrollTopOffset + 0.5 { + return + } + + if pan.velocity(in: self).y > 0 || groupMemberView.frame.minY > stage3Top + 1 { + isSubCanScroll = false + panStartTop = groupMemberView.frame.minY + } + } else { + if groupMemberView.frame.minY <= stage3Top && newTop <= stage3Top { + isSubCanScroll = true + panStartTop = groupMemberView.frame.minY + topConstraint.constant = stage3Top + groupMemberView.setNavBarExpanded(true) + return + } + } + + let clamped = max(stage3Top, min(stage1Top, newTop)) + topConstraint.constant = clamped + + case .ended, .cancelled: + let velocity = pan.velocity(in: self) + let currentTop = groupMemberView.frame.minY + let target = snapTarget(currentTop: currentTop, velocityY: velocity.y) + applyStage(top: target, animated: true) + + default: + break + } + } + + /// 按位置与速度吸附到最近一档 + private func snapTarget(currentTop: CGFloat, velocityY: CGFloat) -> CGFloat { + if currentTop <= stage3Top + 5 { + // 顶部附近优先按位置,避免 scroll 松手速度误回收 + return nearestStage(to: currentTop) + } + if abs(velocityY) > 200 { + if velocityY < 0 { + // 上滑:往上一档(更小的 top 中取最大,即最近一档) + return stageTops.filter { $0 < currentTop - 1 }.max() ?? stage3Top + } else { + // 下滑:往下一档(更大的 top 中取最小,即最近一档) + return stageTops.filter { $0 > currentTop + 1 }.min() ?? stage1Top + } + } + return nearestStage(to: currentTop) + } + + private func nearestStage(to top: CGFloat) -> CGFloat { + stageTops.min(by: { abs($0 - top) < abs($1 - top) }) ?? stage2Top + } + + private func applyStage(top: CGFloat, animated: Bool) { + guard let topConstraint = groupMemberTopConstraint else { return } + let atTop = abs(top - stage3Top) < 0.5 + topConstraint.constant = top + + if !atTop { + isSubCanScroll = false + groupMemberView.scrollView.isScrollEnabled = false + groupMemberView.scrollView.setContentOffset(CGPoint(x: 0, y: groupMemberScrollTopOffset), animated: false) + } + isSubCanScroll = atTop + groupMemberView.setNavBarExpanded(atTop) + + let animations = { + self.layoutIfNeeded() + } + let completion: (Bool) -> Void = { _ in + self.groupMemberView.scrollView.isScrollEnabled = atTop + } + + if animated { + UIView.animate(withDuration: 0.25, delay: 0, + options: [.curveEaseOut, .allowUserInteraction], + animations: animations, completion: completion) + } else { + animations() + completion(true) + } + } + + /// 收起 GroupMemberView2 到第二段(初始段) + func dismissGroupMemberView() { + guard isGroupMemberLimitsSet else { return } + applyStage(top: stage2Top, animated: true) + } +} + +// MARK: - UIScrollViewDelegate +extension HomeView2: UIScrollViewDelegate { + func scrollViewWillBeginDragging(_ scrollView: UIScrollView) { + guard scrollView == groupMemberView.scrollView else { return } + if scrollView.contentOffset.y > groupMemberScrollTopOffset { + isSubCanScroll = true + } + } + + func scrollViewDidScroll(_ scrollView: UIScrollView) { + guard scrollView == groupMemberView.scrollView else { return } + if isSubCanScroll { + if scrollView.contentOffset.y <= groupMemberScrollTopOffset { + isSubCanScroll = false + scrollView.contentOffset = CGPoint(x: 0, y: groupMemberScrollTopOffset) + } + } else { + scrollView.contentOffset = CGPoint(x: 0, y: groupMemberScrollTopOffset) + } + } +} + +// MARK: - UIGestureRecognizerDelegate +extension HomeView2: UIGestureRecognizerDelegate { + func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, + shouldRecognizeSimultaneouslyWith other: UIGestureRecognizer) -> Bool { + return true + } +} diff --git a/QuickLocation/Section/Home/HomeViewController.swift b/QuickLocation/Section/Home/HomeViewController.swift index 751f6aec..f24781df 100644 --- a/QuickLocation/Section/Home/HomeViewController.swift +++ b/QuickLocation/Section/Home/HomeViewController.swift @@ -26,13 +26,15 @@ class HomeViewController: BaseViewController { override var preferredStatusBarStyle: UIStatusBarStyle { .default } // MARK: - Properties - fileprivate var rootView: HomeView! + fileprivate var rootView: HomeView2! - private var tableView: UITableView { - rootView.groupMemberView.tableView + private var memberCV: UICollectionView { + rootView.groupMemberView.memberCV } private var viewModel = HomeViewModel() + /// 当前选中的成员(用于 GroupMemberListCell 选中态) + private var selectedMemberId: String = "" private let locationManager = CLLocationManager() private var currentHeading: Double = 0 @@ -63,7 +65,7 @@ class HomeViewController: BaseViewController { MAMapView.updatePrivacyShow(.didShow, privacyInfo: .didContain) #endif - rootView = HomeView(frame: UIScreen.main.bounds) + rootView = HomeView2(frame: UIScreen.main.bounds) view = rootView } @@ -74,7 +76,7 @@ class HomeViewController: BaseViewController { setupHeading() reactiveAction() - rootView.quickMessageView.tagListView.delegate = self +// rootView.quickMessageView.tagListView.delegate = self // MQTT 位置上报 UIDevice.current.isBatteryMonitoringEnabled = true startLocationTimer() @@ -84,7 +86,7 @@ class HomeViewController: BaseViewController { super.viewDidAppear(animated) let manager = AuthorizeManager.manager(type: .locationAlways) - rootView.warningIconView.isHidden = manager?.authorizeStatus() == .authorized + rootView.warningView.isHidden = manager?.authorizeStatus() == .authorized } override func viewDidDisappear(_ animated: Bool) { @@ -140,49 +142,49 @@ class HomeViewController: BaseViewController { // MARK: - Actions private func reactiveAction() { // 气泡 - rootView.bubbleView.rx.tapGesture.subscribe { _ in - AppRouter.push(Route.createBubble) - }.disposed(by: disposeBag) +// rootView.bubbleView.rx.tapGesture.subscribe { _ in +// AppRouter.push(Route.createBubble) +// }.disposed(by: disposeBag) // 签到 - rootView.signInView.rx.tapGesture.subscribe { _ in - let vc = SignInVC(lastLocation: self.lastLocation) - vc.isNeedLogin = true - AppRouter.push(vc) - }.disposed(by: disposeBag) +// rootView.signInView.rx.tapGesture.subscribe { _ in +// let vc = SignInVC(lastLocation: self.lastLocation) +// vc.isNeedLogin = true +// AppRouter.push(vc) +// }.disposed(by: disposeBag) // SOS - rootView.sosView.rx.tapGesture.subscribe { _ in - let vc = SOSViewController() - vc.isNeedLogin = true - AppRouter.push(vc) - }.disposed(by: disposeBag) +// rootView.sosView.rx.tapGesture.subscribe { _ in +// let vc = SOSViewController() +// vc.isNeedLogin = true +// AppRouter.push(vc) +// }.disposed(by: disposeBag) // 行程 - rootView.scheduleView.rx.tapGesture.subscribe { _ in - guard let model = self.viewModel.groupModel else { return } - AppRouter.push(Route.groupSchedule, userInfo: ["groupKey": model.default_group_key]) - }.disposed(by: disposeBag) +// rootView.scheduleView.rx.tapGesture.subscribe { _ in +// guard let model = self.viewModel.groupModel else { return } +// AppRouter.push(Route.groupSchedule, userInfo: ["groupKey": model.default_group_key]) +// }.disposed(by: disposeBag) // 顶部圈子 - rootView.groupView.rx.tapGesture.subscribe { _ in - guard let groupModel = self.viewModel.groupModel else { return } - let groupViewFrame = self.view.convert(self.rootView.groupView.frame, from: self.rootView) - let startPointY = groupViewFrame.origin.y + groupViewFrame.height - GroupListPopView.show(start: CGPoint(x: 0, y: startPointY + 20), - groupModel: groupModel) { groupKey in - guard let key = groupKey else { return } - self.requestOperateGroup(groupKey: key) - } - }.disposed(by: disposeBag) +// rootView.groupView.rx.tapGesture.subscribe { _ in +// guard let groupModel = self.viewModel.groupModel else { return } +// let groupViewFrame = self.view.convert(self.rootView.groupView.frame, from: self.rootView) +// let startPointY = groupViewFrame.origin.y + groupViewFrame.height +// GroupListPopView.show(start: CGPoint(x: 0, y: startPointY + 20), +// groupModel: groupModel) { groupKey in +// guard let key = groupKey else { return } +// self.requestOperateGroup(groupKey: key) +// } +// }.disposed(by: disposeBag) // 圈子成员列表 刷新列表 - rootView.groupMemberView.refreshBtn.rx.tap.subscribe(onNext: { _ in - self.requestGroupInfo() - }).disposed(by: disposeBag) +// rootView.groupMemberView.refreshBtn.rx.tap.subscribe(onNext: { _ in +// self.requestGroupInfo() +// }).disposed(by: disposeBag) - // 圈子成员列表 邀请加入 - rootView.groupMemberView.inviteJoinBtn.rx.tap.subscribe(onNext: { _ in + // 邀请加入圈子 + rootView.inviteBtn.rx.tap.subscribe(onNext: { _ in AppRouter.push(Route.inviteJoin, userInfo: ["groupInfo": self.viewModel.groupInfo]) }).disposed(by: disposeBag) @@ -206,7 +208,7 @@ class HomeViewController: BaseViewController { guard let self = self, let model = response.model else { return } Defaults[\.loginToken] = model.token AppContextManager.shared.systemConfig = model.config - rootView.searchLottieView.isHidden = model.config?.isIntercept == true +// rootView.searchLottieView.isHidden = model.config?.isIntercept == true self.getUserIMToken() self.requestUserInfo { [weak self] in self?.requestGroupInfo() @@ -249,66 +251,85 @@ class HomeViewController: BaseViewController { }.disposed(by: disposeBag) // 面板关闭回调 - rootView.onDismissPanel = { [weak self] in - self?.isMemberPanelShown = false - (self?.tabBarController as? MainTabBarController)?.setTabBarHidden(false) - } +// rootView.onDismissPanel = { [weak self] in +// self?.isMemberPanelShown = false +// (self?.tabBarController as? MainTabBarController)?.setTabBarHidden(false) +// } // 导航按钮回调 - rootView.interactionView.onNavigate = { [weak self] in - guard let self = self, let member = self.rootView.interactionView.currentMember else { return } - self.rootView.dismissMemberPanel() - let userCoord = self.lastLocation?.coordinate - let groupName = self.viewModel.groupName - let model = self.viewModel.groupModel - let iconIndex = model?.groups.first(where: { $0.group_key == model?.default_group_key })?.icon_index ?? 1 - let vc = NavigationVC(member: member, currentUserCoord: userCoord, groupName: groupName, groupIcon: "\(iconIndex)") - self.navigationController?.pushViewController(vc, animated: true) - } +// rootView.interactionView.onNavigate = { [weak self] in +// guard let self = self, let member = self.rootView.interactionView.currentMember else { return } +// self.rootView.dismissMemberPanel() +// let userCoord = self.lastLocation?.coordinate +// let groupName = self.viewModel.groupName +// let model = self.viewModel.groupModel +// let iconIndex = model?.groups.first(where: { $0.group_key == model?.default_group_key })?.icon_index ?? 1 +// let vc = NavigationVC(member: member, currentUserCoord: userCoord, groupName: groupName, groupIcon: "\(iconIndex)") +// self.navigationController?.pushViewController(vc, animated: true) +// } // 发送表情 - rootView.interactionView.onSendEmote = { [weak self] emoteIdx in - guard let self = self else { return } - self.requestSendEmote(emoteIdx: emoteIdx) - } +// rootView.interactionView.onSendEmote = { [weak self] emoteIdx in +// guard let self = self else { return } +// self.requestSendEmote(emoteIdx: emoteIdx) +// } // 分享 - rootView.interactionView.onShare = { - SharePopView.show() - } +// rootView.interactionView.onShare = { +// SharePopView.show() +// } } private func bindViewModel() { viewModel.output.sectionedItems - .bind(to: tableView.rx.items(dataSource: dataSource)) + .do(onNext: { [weak self] sections in + guard let self = self else { return } + let items = sections.flatMap(\.items) + guard !items.isEmpty else { return } + let stillValid = items.contains { $0.user_id == self.selectedMemberId } + if self.selectedMemberId.isEmpty || !stillValid { + if let me = items.first(where: { self.viewModel.isCurrentUser(id: $0.user_id) }) { + self.selectedMemberId = me.user_id + self.rootView.groupMemberView.setupMemberInfo( + me, + isOwner: self.viewModel.isGroupOwn(id: me.user_id) + ) + } else { + self.selectedMemberId = items[0].user_id + self.rootView.groupMemberView.setupMemberInfo( + items[0], + isOwner: self.viewModel.isGroupOwn(id: items[0].user_id) + ) + } + } + }) + .bind(to: memberCV.rx.items(dataSource: dataSource)) .disposed(by: disposeBag) - let selectedMember = tableView.rx.modelSelected(GroupMemberModel.self) + let selectedMember = memberCV.rx.modelSelected(GroupMemberModel.self) .share() selectedMember .subscribe(viewModel.cellAction.inputs) .disposed(by: disposeBag) - // 选中成员 → 地图定位 + // 选中成员 → 高亮 + 地图定位 selectedMember .subscribe(onNext: { [weak self] model in - guard let self = self else { return } - self.locateMember(userId: model.user_id) + self?.selectMember(userId: model.user_id, centerMap: true, dismissPanel: true) }) .disposed(by: disposeBag) } - // MARK: - UITableViewDataSource - lazy private var dataSource: RxTableViewSectionedReloadDataSource = { - return RxTableViewSectionedReloadDataSource( - configureCell: { (_, tableView, indexPath, model) in - let cell: GroupMemberCell = tableView.dequeueReusableCell(for: indexPath) - cell.configure(model: model, - isCurrentUser: self.viewModel.isCurrentUser(id: model.user_id), - isOwn: self.viewModel.isGroupOwn(id: model.user_id)) - return cell - }) + // MARK: - UICollectionViewDataSource + lazy private var dataSource: RxCollectionViewSectionedReloadDataSource = { + RxCollectionViewSectionedReloadDataSource { [weak self] _, collectionView, indexPath, model in + let cell: GroupMemberListCell = collectionView.dequeueReusableCell(for: indexPath) + cell.configure(model: model, + isCurrentUser: self?.viewModel.isCurrentUser(id: model.user_id) ?? false, + isSelected: model.user_id == self?.selectedMemberId) + return cell + } }() // MARK: - API @@ -329,7 +350,7 @@ class HomeViewController: BaseViewController { UserService.userInfo().subscribe { response in guard let model = response.model else { return } AppContextManager.shared.saveAccount(model) - self.rootView.avatarImgView.image = model.userIcon +// self.rootView.avatarImgView.image = model.userIcon completion?() }.disposed(by: disposeBag) } @@ -341,8 +362,8 @@ class HomeViewController: BaseViewController { NotificationCenter.default.post(name: .RefreshIMGroupListNotification, object: nil) guard isDefaultGroup else { return } - self.rootView.groupMemberView.setupCountData(self.viewModel.memberCount, 1) - self.rootView.groupNameLab.text = self.viewModel.groupName +// self.rootView.groupMemberView.setupCountData(self.viewModel.memberCount, 1) + self.rootView.groupMemberView.groupNameLab.text = self.viewModel.groupName self.syncMemberAnnotations(model.select_group_employee) self.refreshMQTTSubscriptions(model.select_group_employee) }.disposed(by: disposeBag) @@ -355,19 +376,19 @@ class HomeViewController: BaseViewController { } private func requestNotice() { - UserService.notice().subscribe { response in - self.rootView.noticeLab.text = "" - guard let data = response.data, let noticeList = data["notice"] as? [String] else { return } - self.rootView.noticeView.isHidden = noticeList.count == 0 - self.rootView.noticeLab.text = noticeList.joined(separator: " ") + " " - if let loop = data["loop"] as? Bool { - self.rootView.noticeLab.type = loop ? .continuous : .leftRight - self.rootView.noticeLab.onScrollLoopComplete = { - guard loop == false else { return } - self.rootView.noticeView.isHidden = true - } - } - }.disposed(by: disposeBag) +// UserService.notice().subscribe { response in +// self.rootView.noticeLab.text = "" +// guard let data = response.data, let noticeList = data["notice"] as? [String] else { return } +// self.rootView.noticeView.isHidden = noticeList.count == 0 +// self.rootView.noticeLab.text = noticeList.joined(separator: " ") + " " +// if let loop = data["loop"] as? Bool { +// self.rootView.noticeLab.type = loop ? .continuous : .leftRight +// self.rootView.noticeLab.onScrollLoopComplete = { +// guard loop == false else { return } +// self.rootView.noticeView.isHidden = true +// } +// } +// }.disposed(by: disposeBag) } // MARK: - Map Setup @@ -404,7 +425,7 @@ class HomeViewController: BaseViewController { if hitRect.contains(point) { self.isMemberPanelShown = true (self.tabBarController as? MainTabBarController)?.setTabBarHidden(true) - rootView.showMemberPanel(member: ann.member) +// rootView.showMemberPanel(member: ann.member) viewModel.targetUid = ann.member.id break } @@ -511,14 +532,58 @@ class HomeViewController: BaseViewController { #endif } + /// 选中成员:同步列表高亮 / 信息卡,并可选居中地图、收起面板 + private func selectMember(userId: String, centerMap: Bool = true, dismissPanel: Bool = false) { + let listUserId = (userId == "current") ? AppContextManager.shared.userId : userId + guard !listUserId.isEmpty else { return } + + selectedMemberId = listUserId + if let model = viewModel.memberList.first(where: { $0.user_id == listUserId }) { + rootView.groupMemberView.setupMemberInfo( + model, + isOwner: viewModel.isGroupOwn(id: model.user_id) + ) + } + memberCV.reloadData() + + if let idx = viewModel.memberList.firstIndex(where: { $0.user_id == listUserId }) { + let indexPath = IndexPath(item: idx, section: 0) + DispatchQueue.main.async { [weak self] in + guard let self = self, + self.memberCV.numberOfSections > 0, + self.memberCV.numberOfItems(inSection: 0) > idx else { return } + self.memberCV.scrollToItem(at: indexPath, at: .centeredHorizontally, animated: true) + } + } + + #if !targetEnvironment(simulator) + if dismissPanel { + rootView.dismissGroupMemberView() + } + if centerMap { + centerMapOnMember(userId: userId) + } + #endif + } + #if !targetEnvironment(simulator) + /// 地图居中到成员(当前用户 id 为 "current" 或真实 userId) + private func centerMapOnMember(userId: String) { + let coordinate: CLLocationCoordinate2D? + if userId == "current" || viewModel.isCurrentUser(id: userId) { + coordinate = currentUserAnnotation?.coordinate + ?? lastLocation?.coordinate + } else { + coordinate = members.first(where: { $0.id == userId })?.coordinate + } + guard let coord = coordinate, CLLocationCoordinate2DIsValid(coord) else { return } + rootView.mapView.setCenter(coord, animated: true) + rootView.mapView.setZoomLevel(16, animated: true) + } + /// 选中成员后地图定位到该成员,收起 GroupMemberView private func locateMember(userId: String) { - rootView.dismissGroupMemberView() - guard let member = members.first(where: { $0.id == userId }), - CLLocationCoordinate2DIsValid(member.coordinate) else { return } - rootView.mapView.setCenter(member.coordinate, animated: true) - rootView.mapView.setZoomLevel(16, animated: true) + selectMember(userId: userId, centerMap: true, dismissPanel: true) } #endif @@ -623,28 +688,28 @@ extension HomeViewController { let emoteUserId = parts[1] if firstChar == "1" || firstChar == "2" { // 表情 - let emojiFileName = firstChar == "1" ? "normal_" : "fun_" +// let emojiFileName = firstChar == "1" ? "normal_" : "fun_" let nickName = self.viewModel.getUserNickName(id: emoteUserId) let fullText = nickName + "对你发送了表情" let attr = NSMutableAttributedString(string: fullText) attr.addAttribute(.font, value: UIFont.systemFont(ofSize: 12, weight: .medium), range: NSRange(location: 0, length: fullText.count)) attr.addAttribute(.foregroundColor, value: UIColor(hexStr: "#16B3FF"), range: NSRange(location: 0, length: nickName.count)) - DispatchQueue.main.async { [weak self] in - guard let self = self else { return } - self.rootView.messageLab.attributedText = attr - self.rootView.messageBubbleView.isHidden = false - if let path = Bundle.main.path(forResource: "\(emojiFileName)\(lastChar)", ofType: "json") { - self.rootView.emojiPopView.animation = LottieAnimation.filepath(path) - self.rootView.bringSubviewToFront(self.rootView.emojiPopView) - self.rootView.emojiPopView.isHidden = false - self.rootView.emojiPopView.play { completed in - self.rootView.emojiPopView.isHidden = true - } - } - DispatchQueue.main.asyncAfter(deadline: .now() + 5) { - self.rootView.messageBubbleView.isHidden = true - } - } +// DispatchQueue.main.async { [weak self] in +// guard let self = self else { return } +// self.rootView.messageLab.attributedText = attr +// self.rootView.messageBubbleView.isHidden = false +// if let path = Bundle.main.path(forResource: "\(emojiFileName)\(lastChar)", ofType: "json") { +// self.rootView.emojiPopView.animation = LottieAnimation.filepath(path) +// self.rootView.bringSubviewToFront(self.rootView.emojiPopView) +// self.rootView.emojiPopView.isHidden = false +// self.rootView.emojiPopView.play { completed in +// self.rootView.emojiPopView.isHidden = true +// } +// } +// DispatchQueue.main.asyncAfter(deadline: .now() + 5) { +// self.rootView.messageBubbleView.isHidden = true +// } +// } } else if firstChar == "3" { // 文字 let textIdx = lastChar.integer @@ -656,14 +721,14 @@ extension HomeViewController { attr.addAttribute(.font, value: UIFont.systemFont(ofSize: 12, weight: .medium), range: NSRange(location: 0, length: fullText.count)) attr.addAttribute(.foregroundColor, value: UIColor(hexStr: "#16B3FF"), range: NSRange(location: 0, length: nickName.count)) attr.addAttribute(.foregroundColor, value: UIColor(hexStr: "#333333"), range: NSRange(location: nickName.count, length: texts[textIdx].count)) - DispatchQueue.main.async { [weak self] in - guard let self = self else { return } - self.rootView.messageLab.attributedText = attr - self.rootView.messageBubbleView.isHidden = false - DispatchQueue.main.asyncAfter(deadline: .now() + 5) { - self.rootView.messageBubbleView.isHidden = true - } - } +// DispatchQueue.main.async { [weak self] in +// guard let self = self else { return } +// self.rootView.messageLab.attributedText = attr +// self.rootView.messageBubbleView.isHidden = false +// DispatchQueue.main.asyncAfter(deadline: .now() + 5) { +// self.rootView.messageBubbleView.isHidden = true +// } +// } } case "bubble": // 气泡 guard let userId = msg.data?.user_id else { return } @@ -683,18 +748,18 @@ extension HomeViewController { guard !self.isSOSPlaying else { return } self.isSOSPlaying = true self.playSOSAlarm() - self.rootView.sosPopView.isHidden = false - self.rootView.sosPopView.play { completed in - self.rootView.sosPopView.stop() - self.rootView.sosPopView.isHidden = true - } - DispatchQueue.main.asyncAfter(deadline: .now() + 15) { [weak self] in - self?.sosPlayer?.stop() - self?.sosPlayer = nil - self?.rootView.sosPopView.stop() - self?.rootView.sosPopView.isHidden = true - self?.isSOSPlaying = false - } +// self.rootView.sosPopView.isHidden = false +// self.rootView.sosPopView.play { completed in +// self.rootView.sosPopView.stop() +// self.rootView.sosPopView.isHidden = true +// } +// DispatchQueue.main.asyncAfter(deadline: .now() + 15) { [weak self] in +// self?.sosPlayer?.stop() +// self?.sosPlayer = nil +// self?.rootView.sosPopView.stop() +// self?.rootView.sosPopView.isHidden = true +// self?.isSOSPlaying = false +// } } case "join": // 圈子有成员加入 guard let gk = msg.data?.group_key, @@ -726,7 +791,7 @@ extension HomeViewController { guard self.viewModel.memberIsExist(userId: userId) else { return } self.viewModel.removeMember(userId: userId) self.removeAnnotation(userId: userId) - self.rootView.groupMemberView.setupCountData(self.viewModel.memberList.count, 1) +// self.rootView.groupMemberView.setupCountData(self.viewModel.memberList.count, 1) MQTTService.shared.unsubscribe(topic: "smartdrive/\(userId)") } case "dismiss": // 圈子解散 @@ -754,7 +819,7 @@ extension HomeViewController { private func updateOnlineCount() { let total = viewModel.memberCount let online = viewModel.memberList.filter { $0.is_online }.count - rootView.groupMemberView.setupCountData(total, online) +// rootView.groupMemberView.setupCountData(total, online) } /// 移除指定成员的标注 @@ -818,8 +883,9 @@ extension HomeViewController: MAMapViewDelegate { } annotationView?.configure(with: memberAnnotation.member) - - + annotationView?.onTap = { [weak self] ann in + self?.selectMember(userId: ann.member.id, centerMap: true, dismissPanel: false) + } if memberAnnotation.member.isCurrentUser { currentUserAnnotation = memberAnnotation diff --git a/QuickLocation/Section/Login/LoginView.swift b/QuickLocation/Section/Login/LoginView.swift index 320af4db..87cb6a38 100644 --- a/QuickLocation/Section/Login/LoginView.swift +++ b/QuickLocation/Section/Login/LoginView.swift @@ -41,7 +41,7 @@ class LoginView: UIView { } func setupAgreementTextWithCarrier(carrier: String) { - let text = carrier.isEmpty ? "登录即同意《隐私政策》和《用户协议》" : "登录即同意\(carrier)、《隐私政策》和《用户协议》" + let text = carrier.isEmpty ? "登录即同意《用户协议》和《隐私政策》" : "登录即同意\(carrier)、《隐私政策》和《用户协议》" agreementLabel.text = text // 创建段落样式 let paragraphStyle = NSMutableParagraphStyle() @@ -65,27 +65,31 @@ class LoginView: UIView { } private func setupUI() { - addSubview(bgMaskImage) +// addSubview(bgMaskImage) addSubview(backBtn) addSubview(guestLoginButton) + addSubview(welcomeView) + welcomeView.addSubview(welcomeLineView) + welcomeView.addSubview(welcomeLabel) + welcomeView.addSubview(welcomeTitleLab) + addSubview(inputContainerView) - inputContainerView.addSubview(welcomeLabel) - inputContainerView.addSubview(phoneNumberLabel) - inputContainerView.addSubview(carrierLabel) - inputContainerView.addSubview(otherPhoneView) - otherPhoneView.addSubview(phoneInputView) - otherPhoneView.addSubview(smsCodeInputView) - inputContainerView.addSubview(loginButton) + inputContainerView.addSubview(phoneInputView) + inputContainerView.addSubview(smsCodeInputView) + addSubview(loginButton) addSubview(appleLoginBtn) addSubview(wechatLoginBtn) addSubview(phoneLoginBtn) + addSubview(otherLoginTitleLab) + addSubview(leftSeparator) + addSubview(rightSeparator) addSubview(agreementView) agreementView.addSubview(checkBox) agreementView.addSubview(agreementTV) agreementView.addSubview(agreementLabel) - bgMaskImage.layoutChain.edges() +// bgMaskImage.layoutChain.edges() backBtn.layoutChain .top(54) @@ -100,31 +104,30 @@ class LoginView: UIView { .height(29) inputContainerView.layoutChain - .centerX() - .centerY(self, offset: -17) + .centerY() .edgesHorzontal() - .height(306) - + + welcomeView.layoutChain + .bottomToTopOfView(inputContainerView, offset: -100) + .left(38) + + welcomeLineView.layoutChain + .edges(excludingEdge: .right) + .width(4) + welcomeLabel.layoutChain - .top(0) - .centerX() - .edgesHorzontal() + .top() + .leftToRightOfView(welcomeLineView, offset: 12) - phoneNumberLabel.layoutChain - .topToBottomOfView(welcomeLabel, offset: 78) - .centerX() - - carrierLabel.layoutChain - .topToBottomOfView(phoneNumberLabel, offset: 4) - .centerX() - - otherPhoneView.layoutChain - .topToBottomOfView(welcomeLabel, offset: 43) - .centerX() + welcomeTitleLab.layoutChain + .topToBottomOfView(welcomeLabel) + .leftToView(welcomeLabel) + .right() + .bottom() phoneInputView.layoutChain .edges(excludingEdge: .bottom) - .width(259) + .edgesHorzontal(38) .height(50) phoneLineView.layoutChain @@ -145,13 +148,14 @@ class LoginView: UIView { smsCodeInputView.layoutChain .topToBottomOfView(phoneInputView, offset: 20) - .edges(excludingEdge: .top) - .widthToView(phoneInputView) + .leftToView(phoneInputView) + .rightToView(phoneInputView) .heightToView(phoneInputView) + .bottom() smsCodeBtn.layoutChain - .edgesVertical() - .right() + .edgesVertical(5) + .right(5) .width(100) smsCodeTF.layoutChain @@ -160,15 +164,13 @@ class LoginView: UIView { .rightToLeftOfView(smsCodeBtn) loginButton.layoutChain - .topToBottomOfView(otherPhoneView, offset: 63) - .centerX() - .width(247) - .height(50) + .topToBottomOfView(inputContainerView, offset: 46) + .edgesHorzontal(30) + .height(56) agreementView.layoutChain - .leftToView(loginButton) - .rightToView(loginButton) - .bottom(kSafeBottomMargin + 20) + .topToBottomOfView(loginButton, offset: 14) + .centerX() .height(30) agreementLabel.layoutChain.edges() @@ -185,9 +187,9 @@ class LoginView: UIView { .top() phoneLoginBtn.layoutChain - .bottomToTopOfView(agreementView, offset: -30) + .bottom(kSafeBottomMargin + 30) .centerX() - .width(38) + .width(40) .heightToWidth(1) appleLoginBtn.layoutChain @@ -201,6 +203,22 @@ class LoginView: UIView { .centerY(phoneLoginBtn) .widthToView(phoneLoginBtn) .heightToView(phoneLoginBtn) + + otherLoginTitleLab.layoutChain + .bottomToTopOfView(phoneLoginBtn, offset: -18) + .centerX() + + leftSeparator.layoutChain + .centerY(otherLoginTitleLab) + .rightToLeftOfView(otherLoginTitleLab, offset: -14) + .width(60) + .height(2) + + rightSeparator.layoutChain + .centerY(otherLoginTitleLab) + .leftToRightOfView(otherLoginTitleLab, offset: 14) + .widthToView(leftSeparator) + .heightToView(leftSeparator) } override init(frame: CGRect) { @@ -215,6 +233,24 @@ class LoginView: UIView { fatalError("init(coder:) has not been implemented") } + override func layoutSubviews() { + super.layoutSubviews() + + leftSeparator.layoutIfNeeded() + leftSeparator.setGradientLayer(frame: leftSeparator.bounds, + startPoint: CGPoint(x: 0, y: 0.5), + endPoint: CGPoint(x: 1, y: 0.5), + colors: [UIColor(hexStr: "#D8D8D8", alpha: 0), UIColor(hexStr: "#E5E5E5")], + locations: [0, 1]) + + rightSeparator.layoutIfNeeded() + rightSeparator.setGradientLayer(frame: rightSeparator.bounds, + startPoint: CGPoint(x: 0, y: 0.5), + endPoint: CGPoint(x: 1, y: 0.5), + colors: [UIColor(hexStr: "#E5E5E5"), UIColor(hexStr: "#D8D8D8", alpha: 0)], + locations: [0, 1]) + } + // MARK: - UI Components lazy var bgMaskImage: UIImageView = { let iv = UIImageView() @@ -240,12 +276,32 @@ class LoginView: UIView { return btn }() + lazy var welcomeView: UIView = { + let view = UIView() + view.backgroundColor = .clear + return view + }() + + lazy var welcomeLineView: UIView = { + let view = UIView() + view.backgroundColor = UIColor(hexStr: "#99F2FC") + view.cornerRadius = 2 + return view + }() + lazy var welcomeLabel: UILabel = { let label = UILabel() label.text = "欢迎登录" - label.font = UIFont(name: "DOUYU Font", size: 24) ?? .systemFont(ofSize: 24, weight: .medium) - label.textColor = UIColor(hexStr: "#030303") - label.textAlignment = .center + label.font = .systemFont(ofSize: 18, weight: .medium) + label.textColor = .black + return label + }() + + lazy var welcomeTitleLab: UILabel = { + let label = UILabel() + label.text = "极速定位" + label.font = .systemFont(ofSize: 32, weight: .bold) + label.textColor = .black return label }() @@ -271,22 +327,12 @@ class LoginView: UIView { view.backgroundColor = .clear return view }() - - // 其他手机号登录 - lazy var otherPhoneView: UIView = { - let view = UIView() - view.backgroundColor = .clear -// view.isHidden = true - return view - }() // 手机号 lazy var phoneInputView: UIView = { let view = UIView() - view.backgroundColor = .clear - view.borderWidth = 1 - view.borderColor = .white - view.cornerRadius = 8 + view.backgroundColor = UIColor(hexStr: "#F8F8F8") + view.cornerRadius = 12 view.addSubview(areaCodeLab) view.addSubview(phoneLineView) view.addSubview(phoneInputTF) @@ -297,14 +343,14 @@ class LoginView: UIView { let label = UILabel() label.text = "+86" label.font = .systemFont(ofSize: 16, weight: .medium) - label.textColor = .white + label.textColor = UIColor(hexStr: "#3D3D3D") label.textAlignment = .center return label }() lazy var phoneLineView: UIView = { let view = UIView() - view.backgroundColor = .white + view.backgroundColor = UIColor(hexStr: "#D8D8D8") return view }() @@ -312,7 +358,7 @@ class LoginView: UIView { let textField = UITextField() textField.font = .systemFont(ofSize: 16, weight: .medium) textField.placeholderColor(placeholder: "请输入手机号码", - color: .white) + color: UIColor(hexStr: "#767676")) // textField.tintColor = ThemeManager.shared.color.mainColor textField.clearButtonMode = .whileEditing textField.keyboardType = .numberPad @@ -322,10 +368,8 @@ class LoginView: UIView { // 验证码 lazy var smsCodeInputView: UIView = { let view = UIView() - view.backgroundColor = .clear - view.borderWidth = 1 - view.borderColor = .white - view.cornerRadius = 8 + view.backgroundColor = UIColor(hexStr: "#F8F8F8") + view.cornerRadius = 12 view.addSubview(smsCodeTF) view.addSubview(smsCodeBtn) return view @@ -335,29 +379,53 @@ class LoginView: UIView { let textField = UITextField() textField.font = .systemFont(ofSize: 16, weight: .medium) textField.placeholderColor(placeholder: "请输入验证码", - color: .white) + color: UIColor(hexStr: "#767676")) textField.keyboardType = .numberPad return textField }() lazy var smsCodeBtn: UIButton = { let button = UIButton() + button.backgroundColor = .white + button.cornerRadius = 8 button.setTitle("获取验证码", for: .normal) - button.setTitleColor(UIColor(hexStr: "#030303"), for: .normal) - button.setTitleColor(UIColor(hexStr: "#999999", alpha: 1.0), for: .disabled) - button.titleLabel?.font = .systemFont(ofSize: 16, weight: .medium) + button.setTitleColor(UIColor(hexStr: "#1A1A1A"), for: .normal) + button.setTitleColor(UIColor(hexStr: "#767676"), for: .disabled) + button.titleLabel?.font = .systemFont(ofSize: 12, weight: .medium) return button }() lazy var loginButton: UIButton = { let btn = UIButton(type: .system) btn.setTitle("立即登录", for: .normal) - btn.setTitleColor(UIColor(hexStr: "#0F2846"), for: .normal) + btn.setTitleColor(.white, for: .normal) btn.titleLabel?.font = .systemFont(ofSize: 16, weight: .medium) - btn.setBackgroundImage(UIImage(named: "Login/login_bg"), for: .normal) + btn.setBackgroundImage(UIImage(named: "Common/button_bg_2"), for: .normal) + btn.cornerRadius = 20 return btn }() + lazy var otherLoginTitleLab: UILabel = { + let label = UILabel() + label.text = "其他登录方式" + label.font = .systemFont(ofSize: 14, weight: .regular) + label.textColor = UIColor(hexStr: "#767676") + + return label + }() + + lazy var leftSeparator: UIView = { + let view = UIView() + view.backgroundColor = .clear + return view + }() + + lazy var rightSeparator: UIView = { + let view = UIView() + view.backgroundColor = .clear + return view + }() + lazy var wechatLoginBtn: UIButton = { let btn = UIButton(type: .custom) btn.setBackgroundImage(UIImage(named: "Login/wechat"), for: .normal) diff --git a/QuickLocation/Section/Login/OneTapLoginView.swift b/QuickLocation/Section/Login/OneTapLoginView.swift index 4cf27e35..2b9f4923 100644 --- a/QuickLocation/Section/Login/OneTapLoginView.swift +++ b/QuickLocation/Section/Login/OneTapLoginView.swift @@ -57,10 +57,10 @@ class OneTapLoginView: UIView { } private func setupUI() { - addSubview(bgMaskImage) +// addSubview(bgMaskImage) addSubview(backBtn) addSubview(inputContainerView) - inputContainerView.addSubview(welcomeLabel) +// inputContainerView.addSubview(welcomeLabel) inputContainerView.addSubview(phoneNumberLabel) inputContainerView.addSubview(carrierLabel) inputContainerView.addSubview(loginButton) @@ -70,7 +70,7 @@ class OneTapLoginView: UIView { agreementView.addSubview(agreementLabel) agreementView.addSubview(agreementTV) - bgMaskImage.layoutChain.edges() +// bgMaskImage.layoutChain.edges() backBtn.layoutChain .top(54) @@ -84,28 +84,27 @@ class OneTapLoginView: UIView { .edgesHorzontal() .height(301) - welcomeLabel.layoutChain - .top(0) - .centerX() - .edgesHorzontal() +// welcomeLabel.layoutChain +// .top(0) +// .centerX() +// .edgesHorzontal() phoneNumberLabel.layoutChain - .topToBottomOfView(welcomeLabel, offset: 78) + .top() .centerX() carrierLabel.layoutChain - .topToBottomOfView(phoneNumberLabel, offset: 4) + .topToBottomOfView(phoneNumberLabel, offset: 14) .centerX() loginButton.layoutChain .topToBottomOfView(carrierLabel, offset: 99) - .centerX() - .width(247) - .height(50) + .edgesHorzontal(30) + .height(56) agreementView.layoutChain + .topToBottomOfView(inputContainerView, offset: 14) .edgesHorzontal(30) - .bottom(kSafeBottomMargin + 20) checkBox.layoutChain .left() @@ -166,7 +165,7 @@ class OneTapLoginView: UIView { lazy var welcomeLabel: UILabel = { let label = UILabel() - label.text = "欢迎登录" +// label.text = "欢迎登录" label.font = UIFont(name: "DOUYU Font", size: 24) ?? .systemFont(ofSize: 24, weight: .medium) label.textColor = UIColor(hexStr: "#030303") label.textAlignment = .center @@ -175,16 +174,16 @@ class OneTapLoginView: UIView { lazy var phoneNumberLabel: UILabel = { let label = UILabel() - label.font = .systemFont(ofSize: 20, weight: .medium) - label.textColor = ThemeManager.shared.color.titleAuxColor + label.font = .systemFont(ofSize: 32, weight: .heavy) + label.textColor = UIColor(hexStr: "#3D3D3D") label.textAlignment = .center return label }() lazy var carrierLabel: UILabel = { let label = UILabel() - label.font = .systemFont(ofSize: 12) - label.textColor = ThemeManager.shared.color.contentColor + label.font = .systemFont(ofSize: 14, weight: .medium) + label.textColor = UIColor(hexStr: "#767676") label.textAlignment = .center return label }() @@ -192,9 +191,10 @@ class OneTapLoginView: UIView { lazy var loginButton: UIButton = { let btn = UIButton(type: .system) btn.setTitle("本机号码一键登录", for: .normal) - btn.setTitleColor(UIColor(hexStr: "#0F2846"), for: .normal) + btn.setTitleColor(.white, for: .normal) btn.titleLabel?.font = .systemFont(ofSize: 16, weight: .medium) - btn.setBackgroundImage(UIImage(named: "Login/login_bg"), for: .normal) + btn.setBackgroundImage(UIImage(named: "Common/button_bg_2"), for: .normal) + btn.cornerRadius = 20 return btn }() diff --git a/QuickLocation/Section/Map/MemberAnnotationView.swift b/QuickLocation/Section/Map/MemberAnnotationView.swift index 56e2140f..c86716be 100644 --- a/QuickLocation/Section/Map/MemberAnnotationView.swift +++ b/QuickLocation/Section/Map/MemberAnnotationView.swift @@ -13,53 +13,51 @@ final class MemberAnnotationView: MAAnnotationView { var onTap: ((MemberAnnotation) -> Void)? // MARK: - Design Constants - static let avatarOuterSize: CGFloat = 50 - static let avatarInnerSize: CGFloat = 48 - static let nameTagWidth: CGFloat = 60 - static let nameTagHeight: CGFloat = 20 - static let totalHeight: CGFloat = avatarOuterSize + 4 + nameTagHeight - static let radarSize: CGFloat = 180 + private static let bubbleSize: CGFloat = 56 + private static let bubbleCorner: CGFloat = 14 + private static let borderWidth: CGFloat = 4 + private static let pointerWidth: CGFloat = 12 + private static let pointerHeight: CGFloat = 7 + private static let gapBubbleToDot: CGFloat = 4 + private static let gapNameToBubble: CGFloat = 4 + private static let nameTagHeight: CGFloat = 20 + private static let nameTagHPadding: CGFloat = 8 + private static let dotOuterSize: CGFloat = 12 + private static let dotInnerSize: CGFloat = 10 - private let containerView: UIView = { - let v = UIView() - v.backgroundColor = .clear - return v - }() + private static let currentUserDotColor = UIColor(hexStr: "#5ED4FF") + private static let memberDotPalette: [UIColor] = [ + UIColor(hexStr: "#FF6B6B"), + UIColor(hexStr: "#FF9F43"), + UIColor(hexStr: "#FECA57"), + UIColor(hexStr: "#1DD1A1"), + UIColor(hexStr: "#54A0FF"), + UIColor(hexStr: "#5F27CD"), + UIColor(hexStr: "#FF9FF3"), + UIColor(hexStr: "#00D2D3"), + UIColor(hexStr: "#EE5A24"), + UIColor(hexStr: "#10AC84"), + UIColor(hexStr: "#2E86DE"), + UIColor(hexStr: "#341F97"), + ] - private let avatarOuterCircle: UIView = { + private var showsNameTag = false + + private let containerView = UIView() + + private let nameTagView: UIView = { let v = UIView() v.backgroundColor = .white - v.layer.cornerRadius = avatarOuterSize / 2 - v.layer.shadowColor = UIColor.black.withAlphaComponent(0.15).cgColor - v.layer.shadowOffset = CGSize(width: 0, height: 2) - v.layer.shadowRadius = 4 - v.layer.shadowOpacity = 1 - return v - }() - - private let avatarImageView: UIImageView = { - let iv = UIImageView() - iv.contentMode = .scaleAspectFill - iv.layer.cornerRadius = avatarInnerSize / 2 - iv.layer.borderWidth = 1 - iv.layer.borderColor = UIColor.white.cgColor - iv.clipsToBounds = true - iv.backgroundColor = UIColor(hexStr: "#E0E0E0") - return iv - }() - - private lazy var nameTagView: UIView = { - let v = UIView() - v.backgroundColor = .white - v.layer.cornerRadius = 4 - v.layer.shadowColor = UIColor.black.withAlphaComponent(0.1).cgColor + v.layer.cornerRadius = 8 + v.layer.shadowColor = UIColor.black.cgColor v.layer.shadowOffset = CGSize(width: 0, height: 1) v.layer.shadowRadius = 2 - v.layer.shadowOpacity = 1 + v.layer.shadowOpacity = 0.12 + v.isHidden = true return v }() - private lazy var nameLabel: UILabel = { + private let nameLabel: UILabel = { let l = UILabel() l.font = UIFont(name: "PingFangSC-Medium", size: 11) ?? UIFont.systemFont(ofSize: 11, weight: .medium) @@ -69,10 +67,55 @@ final class MemberAnnotationView: MAAnnotationView { return l }() - // MARK: - Layers - private var headingLayer: CAShapeLayer? - private var pulseLayer: CAShapeLayer? - private var isCurrentUser: Bool = false + private let bubbleView: UIView = { + let v = UIView() + v.backgroundColor = .white + v.layer.cornerRadius = bubbleCorner + v.layer.shadowColor = UIColor.black.cgColor + v.layer.shadowOffset = CGSize(width: 0, height: 2) + v.layer.shadowRadius = 4 + v.layer.shadowOpacity = 0.18 + return v + }() + + private let avatarImageView: UIImageView = { + let iv = UIImageView() + iv.contentMode = .scaleAspectFill + iv.clipsToBounds = true + iv.backgroundColor = UIColor(hexStr: "#E0E0E0") + iv.layer.cornerRadius = bubbleCorner - borderWidth * 0.6 + return iv + }() + + private let pointerLayer: CAShapeLayer = { + let layer = CAShapeLayer() + layer.fillColor = UIColor.white.cgColor + return layer + }() + + private let dotOuterView: UIView = { + let v = UIView() + v.backgroundColor = .white + v.layer.cornerRadius = dotOuterSize / 2 + v.layer.shadowColor = UIColor.black.cgColor + v.layer.shadowOffset = CGSize(width: 0, height: 1) + v.layer.shadowRadius = 2 + v.layer.shadowOpacity = 0.2 + return v + }() + + private let dotInnerView: UIView = { + let v = UIView() + v.layer.cornerRadius = dotInnerSize / 2 + return v + }() + + private let dotCenterView: UIView = { + let v = UIView() + v.backgroundColor = .white + v.layer.cornerRadius = 2 + return v + }() // MARK: - Init override init!(annotation: MAAnnotation!, reuseIdentifier: String!) { @@ -89,34 +132,41 @@ final class MemberAnnotationView: MAAnnotationView { isEnabled = true addSubview(containerView) - - containerView.addSubview(avatarOuterCircle) - avatarOuterCircle.addSubview(avatarImageView) - containerView.addSubview(nameTagView) nameTagView.addSubview(nameLabel) + containerView.layer.addSublayer(pointerLayer) + containerView.addSubview(bubbleView) + bubbleView.addSubview(avatarImageView) + containerView.addSubview(dotOuterView) + dotOuterView.addSubview(dotInnerView) + dotInnerView.addSubview(dotCenterView) } // MARK: - Configure func configure(with member: CircleMember) { - self.isEnabled = true - avatarImageView.image = UIImage(named: "UserIcon/\(member.avatar)") - avatarImageView.backgroundColor = member.avatar.isEmpty ? .lightGray : .clear - nameLabel.text = member.name - isCurrentUser = member.isCurrentUser + isEnabled = true canShowCallout = false - // 非当前用户标注可点击弹出面板 - if !member.isCurrentUser && gestureRecognizers?.isEmpty ?? true { + if let img = UIImage(named: "UserIcon/\(member.avatar)") { + avatarImageView.image = img + } else { + avatarImageView.image = UIImage(named: "Common/default_avatar") + } + avatarImageView.backgroundColor = member.avatar.isEmpty ? .lightGray : .clear + + dotInnerView.backgroundColor = Self.dotColor(for: member) + + showsNameTag = !member.isCurrentUser + nameLabel.text = member.name + nameTagView.isHidden = !showsNameTag + + // 所有标注(含当前用户)可点击,同步列表选中 + if gestureRecognizers?.isEmpty ?? true { let tap = UITapGestureRecognizer(target: self, action: #selector(handleTap)) addGestureRecognizer(tap) } - if member.isCurrentUser { - configureAsCurrentUser(heading: member.heading) - } else { - configureAsMember() - } + layoutMarker() } @objc private func handleTap() { @@ -124,144 +174,94 @@ final class MemberAnnotationView: MAAnnotationView { onTap?(ann) } - private func configureAsCurrentUser(heading: Double) { - nameTagView.isHidden = true - stopPulseAnimation() + /// 朝向扇形已从图示样式中移除,保留 API 兼容 HomeViewController + func updateHeading(_ heading: Double) {} - let outer = Self.avatarOuterSize - let padding: CGFloat = 30 - let size = outer + padding * 2 - bounds = CGRect(x: 0, y: 0, width: size, height: size) - centerOffset = .zero - containerView.frame = bounds + // MARK: - Layout + private func layoutMarker() { + let bubble = Self.bubbleSize + let pointerH = Self.pointerHeight + let gap = Self.gapBubbleToDot + let dot = Self.dotOuterSize - let avatarX = (size - outer) / 2 - let avatarY = (size - outer) / 2 - avatarOuterCircle.frame = CGRect(x: avatarX, y: avatarY, - width: outer, height: outer) - avatarImageView.frame = CGRect( - x: (outer - Self.avatarInnerSize) / 2, - y: (outer - Self.avatarInnerSize) / 2, - width: Self.avatarInnerSize, height: Self.avatarInnerSize - ) + let nameH: CGFloat = showsNameTag ? Self.nameTagHeight : 0 + let nameGap: CGFloat = showsNameTag ? Self.gapNameToBubble : 0 - addHeadingIndicator(heading: heading) - } + var nameTagWidth: CGFloat = 0 + if showsNameTag { + let text = nameLabel.text ?? "" + let textWidth = (text as NSString).size(withAttributes: [.font: nameLabel.font as Any]).width + nameTagWidth = min(max(textWidth + Self.nameTagHPadding * 2, 44), 120) + } - private func configureAsMember() { - nameTagView.isHidden = false - headingLayer?.removeFromSuperlayer() - headingLayer = nil - stopPulseAnimation() - - // 动态宽度 - let text = nameLabel.text ?? "" - let textWidth = (text as NSString).size(withAttributes: [.font: nameLabel.font as Any]).width - let tagWidth = min(max(textWidth + 16, 44), 120) - let totalWidth = max(tagWidth, Self.avatarOuterSize) - let tagTop: CGFloat = 0 - let avatarTop = Self.nameTagHeight + 4 - let totalHeight = avatarTop + Self.avatarOuterSize + let totalWidth = max(bubble, nameTagWidth) + let totalHeight = nameH + nameGap + bubble + pointerH + gap + dot bounds = CGRect(x: 0, y: 0, width: totalWidth, height: totalHeight) containerView.frame = bounds - // avatar 中心对准 annotation 坐标 - let avatarCenterY = avatarTop + Self.avatarOuterSize / 2 - centerOffset = CGPoint(x: 0, y: avatarCenterY - totalHeight / 2) - // nameTag 在头像上面 - nameTagView.frame = CGRect( - x: (totalWidth - tagWidth) / 2, y: tagTop, - width: tagWidth, height: Self.nameTagHeight - ) - nameLabel.frame = nameTagView.bounds.insetBy(dx: 6, dy: 0) + // 色点中心对准 annotation 坐标(负 y 将视图上移) + centerOffset = CGPoint(x: 0, y: -(totalHeight / 2 - Self.dotOuterSize / 2)) - // 头像在 nameTag 下面 - avatarOuterCircle.frame = CGRect( - x: (totalWidth - Self.avatarOuterSize) / 2, y: avatarTop, - width: Self.avatarOuterSize, height: Self.avatarOuterSize + if showsNameTag { + nameTagView.frame = CGRect( + x: (totalWidth - nameTagWidth) / 2, + y: 0, + width: nameTagWidth, + height: Self.nameTagHeight + ) + nameLabel.frame = nameTagView.bounds.insetBy(dx: Self.nameTagHPadding - 2, dy: 0) + } + + let bubbleY = nameH + nameGap + bubbleView.frame = CGRect( + x: (totalWidth - bubble) / 2, + y: bubbleY, + width: bubble, + height: bubble ) + let inner = bubble - Self.borderWidth * 2 avatarImageView.frame = CGRect( - x: (Self.avatarOuterSize - Self.avatarInnerSize) / 2, - y: (Self.avatarOuterSize - Self.avatarInnerSize) / 2, - width: Self.avatarInnerSize, height: Self.avatarInnerSize + x: Self.borderWidth, y: Self.borderWidth, + width: inner, height: inner + ) + + // 白色三角:尖朝下,顶边贴气泡底边 + let pointerTop = bubbleY + bubble - 1 + let pointerPath = UIBezierPath() + let leftX = (totalWidth - Self.pointerWidth) / 2 + pointerPath.move(to: CGPoint(x: leftX, y: pointerTop)) + pointerPath.addLine(to: CGPoint(x: leftX + Self.pointerWidth, y: pointerTop)) + pointerPath.addLine(to: CGPoint(x: totalWidth / 2, y: pointerTop + pointerH)) + pointerPath.close() + pointerLayer.path = pointerPath.cgPath + + let dotY = bubbleY + bubble + pointerH + gap + dotOuterView.frame = CGRect( + x: (totalWidth - dot) / 2, y: dotY, + width: dot, height: dot + ) + let inset = (Self.dotOuterSize - Self.dotInnerSize) / 2 + dotInnerView.frame = CGRect( + x: inset, y: inset, + width: Self.dotInnerSize, height: Self.dotInnerSize + ) + let centerSize: CGFloat = 4 + let centerInset = (Self.dotInnerSize - centerSize) / 2 + dotCenterView.frame = CGRect( + x: centerInset, y: centerInset, + width: centerSize, height: centerSize ) } - // MARK: - Heading - func updateHeading(_ heading: Double) { - headingLayer?.removeFromSuperlayer() - headingLayer = nil - addHeadingIndicator(heading: heading) - } - - private func addHeadingIndicator(heading: Double) { - headingLayer?.removeFromSuperlayer() - - let center = CGPoint(x: bounds.width / 2, y: bounds.height / 2) - let radius: CGFloat = 80 - - let halfFan = 30.0 * .pi / 180 - let centerAngle = CGFloat(heading) * .pi / 180 - .pi / 2 - let startAngle = centerAngle - halfFan - let endAngle = centerAngle + halfFan - - let path = UIBezierPath() - path.move(to: center) - path.addArc(withCenter: center, radius: radius, - startAngle: startAngle, endAngle: endAngle, - clockwise: true) - path.close() - - let shapeLayer = CAShapeLayer() - shapeLayer.frame = containerView.bounds - shapeLayer.path = path.cgPath - shapeLayer.fillColor = UIColor(hexStr: "#16B3FF").withAlphaComponent(0.25).cgColor - shapeLayer.strokeColor = UIColor(hexStr: "#16B3FF").withAlphaComponent(0.4).cgColor - shapeLayer.lineWidth = 1 - containerView.layer.insertSublayer(shapeLayer, at: 0) - headingLayer = shapeLayer - } - - // MARK: - Pulse Animation - private func startPulseAnimation() { - guard pulseLayer == nil else { return } - - let layer = CAShapeLayer() - layer.fillColor = UIColor.clear.cgColor - layer.strokeColor = UIColor(hexStr: "#16B3FF").withAlphaComponent(0.5).cgColor - layer.lineWidth = 2 - layer.frame = bounds - containerView.layer.insertSublayer(layer, at: 0) - pulseLayer = layer - - let center = CGPoint(x: bounds.width / 2, y: bounds.height / 2) - let radius = Self.avatarOuterSize / 2 + 8 - let ringRect = CGRect(x: center.x - radius, y: center.y - radius, - width: radius * 2, height: radius * 2) - layer.path = UIBezierPath(ovalIn: ringRect).cgPath - - let scale = CABasicAnimation(keyPath: "transform.scale") - scale.fromValue = 0.9 - scale.toValue = 1.5 - - let opacity = CABasicAnimation(keyPath: "opacity") - opacity.fromValue = 0.8 - opacity.toValue = 0.0 - - let group = CAAnimationGroup() - group.animations = [scale, opacity] - group.duration = 1.6 - group.repeatCount = .infinity - group.timingFunction = CAMediaTimingFunction(name: .easeOut) - - layer.add(group, forKey: "pulse") - } - - private func stopPulseAnimation() { - pulseLayer?.removeAllAnimations() - pulseLayer?.removeFromSuperlayer() - pulseLayer = nil + // MARK: - Dot Color + private static func dotColor(for member: CircleMember) -> UIColor { + if member.isCurrentUser { + return currentUserDotColor + } + let hash = member.id.utf8.reduce(0) { ($0 &* 31) &+ Int($1) } + let index = abs(hash) % memberDotPalette.count + return memberDotPalette[index] } } #endif diff --git a/QuickLocation/Section/Mine/Account/AccountVC.swift b/QuickLocation/Section/Mine/Account/AccountVC.swift index 2013311d..f3e92503 100644 --- a/QuickLocation/Section/Mine/Account/AccountVC.swift +++ b/QuickLocation/Section/Mine/Account/AccountVC.swift @@ -81,7 +81,13 @@ class AccountVC: BaseViewController { private func deleteAccount() { DLToast.showLoading() UserService.deleteAccount().subscribe(onNext: { response in - DLToast.show(text: "申请成功") + DLToast.show(text: "申请成功") { + NotificationCenter.default.post(name: .invalidatePopupQueue, object: nil) + GeYanSdk.deletePreResultCache() + AppContextManager.shared.deleteAccount() + GroupIMService.shared.logout() + AppDelegate.shared.showMainViewController() + } }, onError: { _ in }).disposed(by: disposeBag) }