- 改版(登录100%、首页50%)
|
|
@ -294,6 +294,8 @@
|
||||||
30EFF3E72FDAA93D00EB35D4 /* PrivacyPolicyView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 30EFF3E62FDAA93D00EB35D4 /* PrivacyPolicyView.swift */; };
|
30EFF3E72FDAA93D00EB35D4 /* PrivacyPolicyView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 30EFF3E62FDAA93D00EB35D4 /* PrivacyPolicyView.swift */; };
|
||||||
30EFF3E82FCA8C7000227D26 /* AuthenticationServices.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 30EFF3E92FCA8C7000227D26 /* AuthenticationServices.framework */; };
|
30EFF3E82FCA8C7000227D26 /* AuthenticationServices.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 30EFF3E92FCA8C7000227D26 /* AuthenticationServices.framework */; };
|
||||||
557E8D5330187A5C0032AB51 /* BaseNavigationView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 557E8D5230187A5C0032AB51 /* BaseNavigationView.swift */; };
|
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 */; };
|
55BF752D2FFE53F70055DA57 /* LocationPermissionPopView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 55BF752C2FFE53F70055DA57 /* LocationPermissionPopView.swift */; };
|
||||||
55BF75352FFF91690055DA57 /* InteractionEmojiCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 55BF75342FFF91690055DA57 /* InteractionEmojiCell.swift */; };
|
55BF75352FFF91690055DA57 /* InteractionEmojiCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 55BF75342FFF91690055DA57 /* InteractionEmojiCell.swift */; };
|
||||||
A1B2C3D42FDABC8C009215C1 /* PrivacyInfo.xcprivacy in Resources */ = {isa = PBXBuildFile; fileRef = A1B2C3D52FDABC8C009215C1 /* PrivacyInfo.xcprivacy */; };
|
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; };
|
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; };
|
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 = "<group>"; };
|
557E8D5230187A5C0032AB51 /* BaseNavigationView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BaseNavigationView.swift; sourceTree = "<group>"; };
|
||||||
|
55B2179030217D6600784774 /* HomeView2.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HomeView2.swift; sourceTree = "<group>"; };
|
||||||
|
55B2179230218CE100784774 /* GroupMemberView2.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GroupMemberView2.swift; sourceTree = "<group>"; };
|
||||||
55BF752C2FFE53F70055DA57 /* LocationPermissionPopView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LocationPermissionPopView.swift; sourceTree = "<group>"; };
|
55BF752C2FFE53F70055DA57 /* LocationPermissionPopView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LocationPermissionPopView.swift; sourceTree = "<group>"; };
|
||||||
55BF75342FFF91690055DA57 /* InteractionEmojiCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InteractionEmojiCell.swift; sourceTree = "<group>"; };
|
55BF75342FFF91690055DA57 /* InteractionEmojiCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InteractionEmojiCell.swift; sourceTree = "<group>"; };
|
||||||
A1B2C3D52FDABC8C009215C1 /* PrivacyInfo.xcprivacy */ = {isa = PBXFileReference; lastKnownFileType = text.xml; path = PrivacyInfo.xcprivacy; sourceTree = "<group>"; };
|
A1B2C3D52FDABC8C009215C1 /* PrivacyInfo.xcprivacy */ = {isa = PBXFileReference; lastKnownFileType = text.xml; path = PrivacyInfo.xcprivacy; sourceTree = "<group>"; };
|
||||||
|
|
@ -1003,9 +1007,11 @@
|
||||||
isa = PBXGroup;
|
isa = PBXGroup;
|
||||||
children = (
|
children = (
|
||||||
305A76272FCA8C7000227D26 /* GroupMemberView.swift */,
|
305A76272FCA8C7000227D26 /* GroupMemberView.swift */,
|
||||||
305A76282FCA8C7000227D26 /* HomeView.swift */,
|
|
||||||
305A76292FCA8C7000227D26 /* HomeViewController.swift */,
|
305A76292FCA8C7000227D26 /* HomeViewController.swift */,
|
||||||
305A798B2FCAB99300227D26 /* HomeViewModel.swift */,
|
305A798B2FCAB99300227D26 /* HomeViewModel.swift */,
|
||||||
|
305A76282FCA8C7000227D26 /* HomeView.swift */,
|
||||||
|
55B2179030217D6600784774 /* HomeView2.swift */,
|
||||||
|
55B2179230218CE100784774 /* GroupMemberView2.swift */,
|
||||||
30A7A9102FCAEE3D00105780 /* GroupListPopView.swift */,
|
30A7A9102FCAEE3D00105780 /* GroupListPopView.swift */,
|
||||||
30D87CDE2FDFF1A100E958FD /* QuickMessageView.swift */,
|
30D87CDE2FDFF1A100E958FD /* QuickMessageView.swift */,
|
||||||
30D87CDC2FDFF07500E958FD /* InteractionView.swift */,
|
30D87CDC2FDFF07500E958FD /* InteractionView.swift */,
|
||||||
|
|
@ -1915,6 +1921,7 @@
|
||||||
30EFF3E02FDA9CE300EB35D4 /* EmergencyContactModel.swift in Sources */,
|
30EFF3E02FDA9CE300EB35D4 /* EmergencyContactModel.swift in Sources */,
|
||||||
305A76B62FCA8C7000227D26 /* UITextField+Extensions.swift in Sources */,
|
305A76B62FCA8C7000227D26 /* UITextField+Extensions.swift in Sources */,
|
||||||
30EFF3A82FD7C6A400EB35D4 /* GroupSettingViewModel.swift in Sources */,
|
30EFF3A82FD7C6A400EB35D4 /* GroupSettingViewModel.swift in Sources */,
|
||||||
|
55B2179130217D6600784774 /* HomeView2.swift in Sources */,
|
||||||
30B819F22FF3CE3C00FAB693 /* ItineraryTraceView.swift in Sources */,
|
30B819F22FF3CE3C00FAB693 /* ItineraryTraceView.swift in Sources */,
|
||||||
305A76B72FCA8C7000227D26 /* UIView+Extension.swift in Sources */,
|
305A76B72FCA8C7000227D26 /* UIView+Extension.swift in Sources */,
|
||||||
305A76B82FCA8C7000227D26 /* UIViewController+Extension.swift in Sources */,
|
305A76B82FCA8C7000227D26 /* UIViewController+Extension.swift in Sources */,
|
||||||
|
|
@ -1934,6 +1941,7 @@
|
||||||
3062E8C02FCED7BB00CEF511 /* GroupIconListView.swift in Sources */,
|
3062E8C02FCED7BB00CEF511 /* GroupIconListView.swift in Sources */,
|
||||||
30BF300C2FED09BA00D9CB52 /* ScheduleDetailView.swift in Sources */,
|
30BF300C2FED09BA00D9CB52 /* ScheduleDetailView.swift in Sources */,
|
||||||
305A76C22FCA8C7000227D26 /* BaseViewModel.swift in Sources */,
|
305A76C22FCA8C7000227D26 /* BaseViewModel.swift in Sources */,
|
||||||
|
55B2179330218CE100784774 /* GroupMemberView2.swift in Sources */,
|
||||||
30A87A622FEE724D0095E7C6 /* CreateBubbleTiemView.swift in Sources */,
|
30A87A622FEE724D0095E7C6 /* CreateBubbleTiemView.swift in Sources */,
|
||||||
30DC185A2FD11E7A0041DCD1 /* WebOperations.swift in Sources */,
|
30DC185A2FD11E7A0041DCD1 /* WebOperations.swift in Sources */,
|
||||||
30A87A642FEE75520095E7C6 /* CreateBubbleTipsView.swift in Sources */,
|
30A87A642FEE75520095E7C6 /* CreateBubbleTipsView.swift in Sources */,
|
||||||
|
|
@ -2155,6 +2163,7 @@
|
||||||
INFOPLIST_KEY_NSPhotoLibraryAddUsageDescription = "为了保存图片到您的相册,请允许添加照片,谢谢。";
|
INFOPLIST_KEY_NSPhotoLibraryAddUsageDescription = "为了保存图片到您的相册,请允许添加照片,谢谢。";
|
||||||
INFOPLIST_KEY_NSPhotoLibraryUsageDescription = "我们需要访问您的相册,以便您可以发送照片。";
|
INFOPLIST_KEY_NSPhotoLibraryUsageDescription = "我们需要访问您的相册,以便您可以发送照片。";
|
||||||
INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES;
|
INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES;
|
||||||
|
INFOPLIST_KEY_UIBackgroundModes = location;
|
||||||
INFOPLIST_KEY_UILaunchStoryboardName = LaunchScreen;
|
INFOPLIST_KEY_UILaunchStoryboardName = LaunchScreen;
|
||||||
INFOPLIST_KEY_UIMainStoryboardFile = Main;
|
INFOPLIST_KEY_UIMainStoryboardFile = Main;
|
||||||
INFOPLIST_KEY_UISupportedInterfaceOrientations = UIInterfaceOrientationPortrait;
|
INFOPLIST_KEY_UISupportedInterfaceOrientations = UIInterfaceOrientationPortrait;
|
||||||
|
|
@ -2208,6 +2217,7 @@
|
||||||
INFOPLIST_KEY_NSPhotoLibraryAddUsageDescription = "为了保存图片到您的相册,请允许添加照片,谢谢。";
|
INFOPLIST_KEY_NSPhotoLibraryAddUsageDescription = "为了保存图片到您的相册,请允许添加照片,谢谢。";
|
||||||
INFOPLIST_KEY_NSPhotoLibraryUsageDescription = "我们需要访问您的相册,以便您可以发送照片。";
|
INFOPLIST_KEY_NSPhotoLibraryUsageDescription = "我们需要访问您的相册,以便您可以发送照片。";
|
||||||
INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES;
|
INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES;
|
||||||
|
INFOPLIST_KEY_UIBackgroundModes = location;
|
||||||
INFOPLIST_KEY_UILaunchStoryboardName = LaunchScreen;
|
INFOPLIST_KEY_UILaunchStoryboardName = LaunchScreen;
|
||||||
INFOPLIST_KEY_UIMainStoryboardFile = Main;
|
INFOPLIST_KEY_UIMainStoryboardFile = Main;
|
||||||
INFOPLIST_KEY_UISupportedInterfaceOrientations = UIInterfaceOrientationPortrait;
|
INFOPLIST_KEY_UISupportedInterfaceOrientations = UIInterfaceOrientationPortrait;
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
}
|
||||||
|
}
|
||||||
BIN
QuickLocation/Assets.xcassets/Common/arrow_right.imageset/arrow-right@2x.png
vendored
Normal file
|
After Width: | Height: | Size: 362 B |
BIN
QuickLocation/Assets.xcassets/Common/arrow_right.imageset/arrow-right@3x.png
vendored
Normal file
|
After Width: | Height: | Size: 620 B |
|
|
@ -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
|
||||||
|
}
|
||||||
|
}
|
||||||
BIN
QuickLocation/Assets.xcassets/Home/group_member_bg2.imageset/group_member_bg2@2x.png
vendored
Normal file
|
After Width: | Height: | Size: 111 KiB |
BIN
QuickLocation/Assets.xcassets/Home/group_member_bg2.imageset/group_member_bg2@3x.png
vendored
Normal file
|
After Width: | Height: | Size: 188 KiB |
|
|
@ -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
|
||||||
|
}
|
||||||
|
}
|
||||||
BIN
QuickLocation/Assets.xcassets/Home/group_name_icon.imageset/vuesax/bold/mouse@2x.png
vendored
Normal file
|
After Width: | Height: | Size: 1.2 KiB |
BIN
QuickLocation/Assets.xcassets/Home/group_name_icon.imageset/vuesax/bold/mouse@3x.png
vendored
Normal file
|
After Width: | Height: | Size: 2.2 KiB |
|
|
@ -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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
After Width: | Height: | Size: 45 KiB |
|
After Width: | Height: | Size: 94 KiB |
|
Before Width: | Height: | Size: 2.9 KiB After Width: | Height: | Size: 1.1 KiB |
|
Before Width: | Height: | Size: 4.4 KiB After Width: | Height: | Size: 2.2 KiB |
|
Before Width: | Height: | Size: 2.7 KiB After Width: | Height: | Size: 992 B |
|
Before Width: | Height: | Size: 3.9 KiB After Width: | Height: | Size: 1.9 KiB |
|
Before Width: | Height: | Size: 3.4 KiB After Width: | Height: | Size: 1.7 KiB |
|
Before Width: | Height: | Size: 5.0 KiB After Width: | Height: | Size: 4.7 KiB |
|
|
@ -59,16 +59,12 @@ public let kScreenWidth = UIScreen.main.bounds.size.width
|
||||||
public let kScreenHeight = UIScreen.main.bounds.size.height
|
public let kScreenHeight = UIScreen.main.bounds.size.height
|
||||||
/// 状态栏高度
|
/// 状态栏高度
|
||||||
public var kStatusBarHeight: CGFloat {
|
public var kStatusBarHeight: CGFloat {
|
||||||
if #available(iOS 13.0, *) {
|
|
||||||
let statusBarHeight = kKeyWindow?
|
let statusBarHeight = kKeyWindow?
|
||||||
.windowScene?
|
.windowScene?
|
||||||
.statusBarManager?
|
.statusBarManager?
|
||||||
.statusBarFrame
|
.statusBarFrame
|
||||||
.height ?? 0
|
.height ?? 0
|
||||||
return statusBarHeight
|
return statusBarHeight
|
||||||
} else {
|
|
||||||
return UIApplication.shared.statusBarFrame.height
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
/// 顶部导航+状态栏高度
|
/// 顶部导航+状态栏高度
|
||||||
public let kNaviHeight = 44 + kStatusBarHeight
|
public let kNaviHeight = 44 + kStatusBarHeight
|
||||||
|
|
|
||||||
|
|
@ -69,5 +69,9 @@
|
||||||
<string>douyu.otf</string>
|
<string>douyu.otf</string>
|
||||||
<string>zihunbiantaoti-r.ttf</string>
|
<string>zihunbiantaoti-r.ttf</string>
|
||||||
</array>
|
</array>
|
||||||
|
<key>UIBackgroundModes</key>
|
||||||
|
<array>
|
||||||
|
<string>location</string>
|
||||||
|
</array>
|
||||||
</dict>
|
</dict>
|
||||||
</plist>
|
</plist>
|
||||||
|
|
|
||||||
|
|
@ -58,9 +58,9 @@ final class MainTabBarController: UITabBarController {
|
||||||
|
|
||||||
view.addSubview(customTabBar)
|
view.addSubview(customTabBar)
|
||||||
customTabBar.delegate = self
|
customTabBar.delegate = self
|
||||||
// 点击"探索"(index=1)时游客跳登录、不选中
|
// 点击"探索"(index=1)或"圈子"(index=2)时游客跳登录、不选中
|
||||||
customTabBar.shouldSelectTab = { index in
|
customTabBar.shouldSelectTab = { index in
|
||||||
if index == 1 || index == 2, AppContextManager.shared.isGuest {
|
if (index == 1 || index == 2), AppContextManager.shared.isGuest {
|
||||||
AppRouter.push(Route.login)
|
AppRouter.push(Route.login)
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
@ -98,11 +98,6 @@ final class MainTabBarController: UITabBarController {
|
||||||
// MARK: - QuickLocationTabBarDelegate
|
// MARK: - QuickLocationTabBarDelegate
|
||||||
extension MainTabBarController: QuickLocationTabBarDelegate {
|
extension MainTabBarController: QuickLocationTabBarDelegate {
|
||||||
func tabBar(_ tabBar: QuickLocationTabBar, didSelectTabAt index: Int) {
|
func tabBar(_ tabBar: QuickLocationTabBar, didSelectTabAt index: Int) {
|
||||||
// 点击"探索"(index=1)时判断游客,是则跳登录、不选中
|
|
||||||
if index == 1 || index == 2, AppContextManager.shared.isGuest {
|
|
||||||
AppRouter.push(Route.login)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
selectedIndex = index
|
selectedIndex = index
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -24,9 +24,9 @@ final class QuickLocationTabBar: UIView {
|
||||||
}
|
}
|
||||||
|
|
||||||
// MARK: - Design Constants (in iOS points, from Lanhu @2x 容器 2)
|
// MARK: - Design Constants (in iOS points, from Lanhu @2x 容器 2)
|
||||||
private let barContentHeight: CGFloat = 56
|
private let barContentHeight: CGFloat = 49
|
||||||
private let containerCornerRadius: CGFloat = 28
|
private let containerCornerRadius: CGFloat = 0
|
||||||
private let horizontalInset: CGFloat = 18
|
private let horizontalInset: CGFloat = 0
|
||||||
private let iconSize: CGFloat = 32
|
private let iconSize: CGFloat = 32
|
||||||
private let iconLabelSpacing: CGFloat = 4
|
private let iconLabelSpacing: CGFloat = 4
|
||||||
private let labelHeight: CGFloat = 11
|
private let labelHeight: CGFloat = 11
|
||||||
|
|
@ -46,6 +46,7 @@ final class QuickLocationTabBar: UIView {
|
||||||
private let items: [TabItem]
|
private let items: [TabItem]
|
||||||
private var tabViews: [UIView] = []
|
private var tabViews: [UIView] = []
|
||||||
private let containerView = UIView()
|
private let containerView = UIView()
|
||||||
|
private let topSeparatorView = UIView()
|
||||||
|
|
||||||
private(set) var selectedIndex: Int = 0
|
private(set) var selectedIndex: Int = 0
|
||||||
|
|
||||||
|
|
@ -72,12 +73,11 @@ final class QuickLocationTabBar: UIView {
|
||||||
|
|
||||||
containerView.backgroundColor = .white
|
containerView.backgroundColor = .white
|
||||||
containerView.layer.cornerRadius = containerCornerRadius
|
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)
|
addSubview(containerView)
|
||||||
|
|
||||||
|
topSeparatorView.backgroundColor = UIColor(hexStr: "#E8E8E8")
|
||||||
|
addSubview(topSeparatorView)
|
||||||
|
|
||||||
for (index, item) in items.enumerated() {
|
for (index, item) in items.enumerated() {
|
||||||
let tabView = makeTabView(item: item, index: index)
|
let tabView = makeTabView(item: item, index: index)
|
||||||
containerView.addSubview(tabView)
|
containerView.addSubview(tabView)
|
||||||
|
|
@ -86,11 +86,18 @@ final class QuickLocationTabBar: UIView {
|
||||||
|
|
||||||
containerView.translatesAutoresizingMaskIntoConstraints = false
|
containerView.translatesAutoresizingMaskIntoConstraints = false
|
||||||
|
|
||||||
|
topSeparatorView.translatesAutoresizingMaskIntoConstraints = false
|
||||||
|
|
||||||
NSLayoutConstraint.activate([
|
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.leadingAnchor.constraint(equalTo: leadingAnchor, constant: horizontalInset),
|
||||||
containerView.trailingAnchor.constraint(equalTo: trailingAnchor, constant: -horizontalInset),
|
containerView.trailingAnchor.constraint(equalTo: trailingAnchor, constant: -horizontalInset),
|
||||||
containerView.bottomAnchor.constraint(equalTo: bottomAnchor, constant: -kSafeBottomMargin),
|
containerView.bottomAnchor.constraint(equalTo: bottomAnchor),
|
||||||
containerView.heightAnchor.constraint(equalToConstant: barContentHeight)
|
containerView.heightAnchor.constraint(equalToConstant: barContentHeight + kSafeBottomMargin)
|
||||||
])
|
])
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -157,7 +164,7 @@ final class QuickLocationTabBar: UIView {
|
||||||
guard containerWidth > 0, !tabViews.isEmpty else { return }
|
guard containerWidth > 0, !tabViews.isEmpty else { return }
|
||||||
|
|
||||||
let tabWidth = containerWidth / CGFloat(tabViews.count)
|
let tabWidth = containerWidth / CGFloat(tabViews.count)
|
||||||
let tabHeight = containerView.bounds.height
|
let tabHeight = containerView.bounds.height - kSafeBottomMargin
|
||||||
|
|
||||||
let totalHeight = iconSize + iconLabelSpacing + labelHeight
|
let totalHeight = iconSize + iconLabelSpacing + labelHeight
|
||||||
let startY = (tabHeight - totalHeight) / 2
|
let startY = (tabHeight - totalHeight) / 2
|
||||||
|
|
|
||||||
|
|
@ -657,13 +657,13 @@ class GroupMemberListCell: UICollectionViewCell {
|
||||||
nameLab.textColor = UIColor(hexStr: isCurrentUser ? "#16B3FF" : "#0F2846")
|
nameLab.textColor = UIColor(hexStr: isCurrentUser ? "#16B3FF" : "#0F2846")
|
||||||
selectedBgView.isHidden = !isSelected
|
selectedBgView.isHidden = !isSelected
|
||||||
// 会员权益
|
// 会员权益
|
||||||
if AppContextManager.shared.vip > 1, model.is_online {
|
// if AppContextManager.shared.vip > 1, model.is_online {
|
||||||
batteryInfoView.isHidden = model.battery.int == 0
|
// batteryInfoView.isHidden = model.battery.int == 0
|
||||||
// 电量 16是电池图标宽度,右边有电池造型需要减去
|
// // 电量 16是电池图标宽度,右边有电池造型需要减去
|
||||||
let batteryPercent = min(CGFloat(model.battery.int), 100)
|
// let batteryPercent = min(CGFloat(model.battery.int), 100)
|
||||||
batteryView.layoutChain.width(CGFloat(16 - 1) * batteryPercent / 100.0)
|
// batteryView.layoutChain.width(CGFloat(16 - 1) * batteryPercent / 100.0)
|
||||||
batteryLab.text = "\(model.battery)%"
|
// batteryLab.text = "\(model.battery)%"
|
||||||
}
|
// }
|
||||||
}
|
}
|
||||||
|
|
||||||
private func setupSubviews() {
|
private func setupSubviews() {
|
||||||
|
|
@ -684,8 +684,8 @@ class GroupMemberListCell: UICollectionViewCell {
|
||||||
selectedBgView.layoutChain.edges()
|
selectedBgView.layoutChain.edges()
|
||||||
|
|
||||||
avaterImgView.layoutChain
|
avaterImgView.layoutChain
|
||||||
.top(11)
|
.top(9)
|
||||||
.edgesHorzontal(10)
|
.edgesHorzontal(5.5)
|
||||||
.heightToWidth(1)
|
.heightToWidth(1)
|
||||||
|
|
||||||
batteryInfoView.layoutChain
|
batteryInfoView.layoutChain
|
||||||
|
|
|
||||||
|
|
@ -161,7 +161,11 @@ class GroupView: UIView {
|
||||||
return iv
|
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 = {
|
lazy var scanBtn: UIButton = {
|
||||||
let btn = UIButton(type: .custom)
|
let btn = UIButton(type: .custom)
|
||||||
|
|
|
||||||
|
|
@ -5,7 +5,6 @@
|
||||||
// Created by 八条 on 2026/5/29.
|
// Created by 八条 on 2026/5/29.
|
||||||
//
|
//
|
||||||
|
|
||||||
import UIKit
|
|
||||||
import UIKit
|
import UIKit
|
||||||
import RxSwift
|
import RxSwift
|
||||||
import RxCocoa
|
import RxCocoa
|
||||||
|
|
|
||||||
|
|
@ -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")
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -26,13 +26,15 @@ class HomeViewController: BaseViewController {
|
||||||
override var preferredStatusBarStyle: UIStatusBarStyle { .default }
|
override var preferredStatusBarStyle: UIStatusBarStyle { .default }
|
||||||
|
|
||||||
// MARK: - Properties
|
// MARK: - Properties
|
||||||
fileprivate var rootView: HomeView!
|
fileprivate var rootView: HomeView2!
|
||||||
|
|
||||||
private var tableView: UITableView {
|
private var memberCV: UICollectionView {
|
||||||
rootView.groupMemberView.tableView
|
rootView.groupMemberView.memberCV
|
||||||
}
|
}
|
||||||
|
|
||||||
private var viewModel = HomeViewModel()
|
private var viewModel = HomeViewModel()
|
||||||
|
/// 当前选中的成员(用于 GroupMemberListCell 选中态)
|
||||||
|
private var selectedMemberId: String = ""
|
||||||
|
|
||||||
private let locationManager = CLLocationManager()
|
private let locationManager = CLLocationManager()
|
||||||
private var currentHeading: Double = 0
|
private var currentHeading: Double = 0
|
||||||
|
|
@ -63,7 +65,7 @@ class HomeViewController: BaseViewController {
|
||||||
MAMapView.updatePrivacyShow(.didShow, privacyInfo: .didContain)
|
MAMapView.updatePrivacyShow(.didShow, privacyInfo: .didContain)
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
rootView = HomeView(frame: UIScreen.main.bounds)
|
rootView = HomeView2(frame: UIScreen.main.bounds)
|
||||||
view = rootView
|
view = rootView
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -74,7 +76,7 @@ class HomeViewController: BaseViewController {
|
||||||
setupHeading()
|
setupHeading()
|
||||||
reactiveAction()
|
reactiveAction()
|
||||||
|
|
||||||
rootView.quickMessageView.tagListView.delegate = self
|
// rootView.quickMessageView.tagListView.delegate = self
|
||||||
// MQTT 位置上报
|
// MQTT 位置上报
|
||||||
UIDevice.current.isBatteryMonitoringEnabled = true
|
UIDevice.current.isBatteryMonitoringEnabled = true
|
||||||
startLocationTimer()
|
startLocationTimer()
|
||||||
|
|
@ -84,7 +86,7 @@ class HomeViewController: BaseViewController {
|
||||||
super.viewDidAppear(animated)
|
super.viewDidAppear(animated)
|
||||||
|
|
||||||
let manager = AuthorizeManager.manager(type: .locationAlways)
|
let manager = AuthorizeManager.manager(type: .locationAlways)
|
||||||
rootView.warningIconView.isHidden = manager?.authorizeStatus() == .authorized
|
rootView.warningView.isHidden = manager?.authorizeStatus() == .authorized
|
||||||
}
|
}
|
||||||
|
|
||||||
override func viewDidDisappear(_ animated: Bool) {
|
override func viewDidDisappear(_ animated: Bool) {
|
||||||
|
|
@ -140,49 +142,49 @@ class HomeViewController: BaseViewController {
|
||||||
// MARK: - Actions
|
// MARK: - Actions
|
||||||
private func reactiveAction() {
|
private func reactiveAction() {
|
||||||
// 气泡
|
// 气泡
|
||||||
rootView.bubbleView.rx.tapGesture.subscribe { _ in
|
// rootView.bubbleView.rx.tapGesture.subscribe { _ in
|
||||||
AppRouter.push(Route.createBubble)
|
// AppRouter.push(Route.createBubble)
|
||||||
}.disposed(by: disposeBag)
|
// }.disposed(by: disposeBag)
|
||||||
|
|
||||||
// 签到
|
// 签到
|
||||||
rootView.signInView.rx.tapGesture.subscribe { _ in
|
// rootView.signInView.rx.tapGesture.subscribe { _ in
|
||||||
let vc = SignInVC(lastLocation: self.lastLocation)
|
// let vc = SignInVC(lastLocation: self.lastLocation)
|
||||||
vc.isNeedLogin = true
|
// vc.isNeedLogin = true
|
||||||
AppRouter.push(vc)
|
// AppRouter.push(vc)
|
||||||
}.disposed(by: disposeBag)
|
// }.disposed(by: disposeBag)
|
||||||
|
|
||||||
// SOS
|
// SOS
|
||||||
rootView.sosView.rx.tapGesture.subscribe { _ in
|
// rootView.sosView.rx.tapGesture.subscribe { _ in
|
||||||
let vc = SOSViewController()
|
// let vc = SOSViewController()
|
||||||
vc.isNeedLogin = true
|
// vc.isNeedLogin = true
|
||||||
AppRouter.push(vc)
|
// AppRouter.push(vc)
|
||||||
}.disposed(by: disposeBag)
|
// }.disposed(by: disposeBag)
|
||||||
|
|
||||||
// 行程
|
// 行程
|
||||||
rootView.scheduleView.rx.tapGesture.subscribe { _ in
|
// rootView.scheduleView.rx.tapGesture.subscribe { _ in
|
||||||
guard let model = self.viewModel.groupModel else { return }
|
// guard let model = self.viewModel.groupModel else { return }
|
||||||
AppRouter.push(Route.groupSchedule, userInfo: ["groupKey": model.default_group_key])
|
// AppRouter.push(Route.groupSchedule, userInfo: ["groupKey": model.default_group_key])
|
||||||
}.disposed(by: disposeBag)
|
// }.disposed(by: disposeBag)
|
||||||
|
|
||||||
// 顶部圈子
|
// 顶部圈子
|
||||||
rootView.groupView.rx.tapGesture.subscribe { _ in
|
// rootView.groupView.rx.tapGesture.subscribe { _ in
|
||||||
guard let groupModel = self.viewModel.groupModel else { return }
|
// guard let groupModel = self.viewModel.groupModel else { return }
|
||||||
let groupViewFrame = self.view.convert(self.rootView.groupView.frame, from: self.rootView)
|
// let groupViewFrame = self.view.convert(self.rootView.groupView.frame, from: self.rootView)
|
||||||
let startPointY = groupViewFrame.origin.y + groupViewFrame.height
|
// let startPointY = groupViewFrame.origin.y + groupViewFrame.height
|
||||||
GroupListPopView.show(start: CGPoint(x: 0, y: startPointY + 20),
|
// GroupListPopView.show(start: CGPoint(x: 0, y: startPointY + 20),
|
||||||
groupModel: groupModel) { groupKey in
|
// groupModel: groupModel) { groupKey in
|
||||||
guard let key = groupKey else { return }
|
// guard let key = groupKey else { return }
|
||||||
self.requestOperateGroup(groupKey: key)
|
// self.requestOperateGroup(groupKey: key)
|
||||||
}
|
// }
|
||||||
}.disposed(by: disposeBag)
|
// }.disposed(by: disposeBag)
|
||||||
|
|
||||||
// 圈子成员列表 刷新列表
|
// 圈子成员列表 刷新列表
|
||||||
rootView.groupMemberView.refreshBtn.rx.tap.subscribe(onNext: { _ in
|
// rootView.groupMemberView.refreshBtn.rx.tap.subscribe(onNext: { _ in
|
||||||
self.requestGroupInfo()
|
// self.requestGroupInfo()
|
||||||
}).disposed(by: disposeBag)
|
// }).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])
|
AppRouter.push(Route.inviteJoin, userInfo: ["groupInfo": self.viewModel.groupInfo])
|
||||||
}).disposed(by: disposeBag)
|
}).disposed(by: disposeBag)
|
||||||
|
|
||||||
|
|
@ -206,7 +208,7 @@ class HomeViewController: BaseViewController {
|
||||||
guard let self = self, let model = response.model else { return }
|
guard let self = self, let model = response.model else { return }
|
||||||
Defaults[\.loginToken] = model.token
|
Defaults[\.loginToken] = model.token
|
||||||
AppContextManager.shared.systemConfig = model.config
|
AppContextManager.shared.systemConfig = model.config
|
||||||
rootView.searchLottieView.isHidden = model.config?.isIntercept == true
|
// rootView.searchLottieView.isHidden = model.config?.isIntercept == true
|
||||||
self.getUserIMToken()
|
self.getUserIMToken()
|
||||||
self.requestUserInfo { [weak self] in
|
self.requestUserInfo { [weak self] in
|
||||||
self?.requestGroupInfo()
|
self?.requestGroupInfo()
|
||||||
|
|
@ -249,66 +251,85 @@ class HomeViewController: BaseViewController {
|
||||||
}.disposed(by: disposeBag)
|
}.disposed(by: disposeBag)
|
||||||
|
|
||||||
// 面板关闭回调
|
// 面板关闭回调
|
||||||
rootView.onDismissPanel = { [weak self] in
|
// rootView.onDismissPanel = { [weak self] in
|
||||||
self?.isMemberPanelShown = false
|
// self?.isMemberPanelShown = false
|
||||||
(self?.tabBarController as? MainTabBarController)?.setTabBarHidden(false)
|
// (self?.tabBarController as? MainTabBarController)?.setTabBarHidden(false)
|
||||||
}
|
// }
|
||||||
|
|
||||||
// 导航按钮回调
|
// 导航按钮回调
|
||||||
rootView.interactionView.onNavigate = { [weak self] in
|
// rootView.interactionView.onNavigate = { [weak self] in
|
||||||
guard let self = self, let member = self.rootView.interactionView.currentMember else { return }
|
// guard let self = self, let member = self.rootView.interactionView.currentMember else { return }
|
||||||
self.rootView.dismissMemberPanel()
|
// self.rootView.dismissMemberPanel()
|
||||||
let userCoord = self.lastLocation?.coordinate
|
// let userCoord = self.lastLocation?.coordinate
|
||||||
let groupName = self.viewModel.groupName
|
// let groupName = self.viewModel.groupName
|
||||||
let model = self.viewModel.groupModel
|
// let model = self.viewModel.groupModel
|
||||||
let iconIndex = model?.groups.first(where: { $0.group_key == model?.default_group_key })?.icon_index ?? 1
|
// 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)")
|
// let vc = NavigationVC(member: member, currentUserCoord: userCoord, groupName: groupName, groupIcon: "\(iconIndex)")
|
||||||
self.navigationController?.pushViewController(vc, animated: true)
|
// self.navigationController?.pushViewController(vc, animated: true)
|
||||||
}
|
// }
|
||||||
|
|
||||||
// 发送表情
|
// 发送表情
|
||||||
rootView.interactionView.onSendEmote = { [weak self] emoteIdx in
|
// rootView.interactionView.onSendEmote = { [weak self] emoteIdx in
|
||||||
guard let self = self else { return }
|
// guard let self = self else { return }
|
||||||
self.requestSendEmote(emoteIdx: emoteIdx)
|
// self.requestSendEmote(emoteIdx: emoteIdx)
|
||||||
}
|
// }
|
||||||
|
|
||||||
// 分享
|
// 分享
|
||||||
rootView.interactionView.onShare = {
|
// rootView.interactionView.onShare = {
|
||||||
SharePopView.show()
|
// SharePopView.show()
|
||||||
}
|
// }
|
||||||
}
|
}
|
||||||
|
|
||||||
private func bindViewModel() {
|
private func bindViewModel() {
|
||||||
viewModel.output.sectionedItems
|
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)
|
.disposed(by: disposeBag)
|
||||||
|
|
||||||
let selectedMember = tableView.rx.modelSelected(GroupMemberModel.self)
|
let selectedMember = memberCV.rx.modelSelected(GroupMemberModel.self)
|
||||||
.share()
|
.share()
|
||||||
|
|
||||||
selectedMember
|
selectedMember
|
||||||
.subscribe(viewModel.cellAction.inputs)
|
.subscribe(viewModel.cellAction.inputs)
|
||||||
.disposed(by: disposeBag)
|
.disposed(by: disposeBag)
|
||||||
|
|
||||||
// 选中成员 → 地图定位
|
// 选中成员 → 高亮 + 地图定位
|
||||||
selectedMember
|
selectedMember
|
||||||
.subscribe(onNext: { [weak self] model in
|
.subscribe(onNext: { [weak self] model in
|
||||||
guard let self = self else { return }
|
self?.selectMember(userId: model.user_id, centerMap: true, dismissPanel: true)
|
||||||
self.locateMember(userId: model.user_id)
|
|
||||||
})
|
})
|
||||||
.disposed(by: disposeBag)
|
.disposed(by: disposeBag)
|
||||||
}
|
}
|
||||||
|
|
||||||
// MARK: - UITableViewDataSource
|
// MARK: - UICollectionViewDataSource
|
||||||
lazy private var dataSource: RxTableViewSectionedReloadDataSource<GroupMemberListSectionModel> = {
|
lazy private var dataSource: RxCollectionViewSectionedReloadDataSource<GroupMemberListSectionModel> = {
|
||||||
return RxTableViewSectionedReloadDataSource<GroupMemberListSectionModel>(
|
RxCollectionViewSectionedReloadDataSource<GroupMemberListSectionModel> { [weak self] _, collectionView, indexPath, model in
|
||||||
configureCell: { (_, tableView, indexPath, model) in
|
let cell: GroupMemberListCell = collectionView.dequeueReusableCell(for: indexPath)
|
||||||
let cell: GroupMemberCell = tableView.dequeueReusableCell(for: indexPath)
|
|
||||||
cell.configure(model: model,
|
cell.configure(model: model,
|
||||||
isCurrentUser: self.viewModel.isCurrentUser(id: model.user_id),
|
isCurrentUser: self?.viewModel.isCurrentUser(id: model.user_id) ?? false,
|
||||||
isOwn: self.viewModel.isGroupOwn(id: model.user_id))
|
isSelected: model.user_id == self?.selectedMemberId)
|
||||||
return cell
|
return cell
|
||||||
})
|
}
|
||||||
}()
|
}()
|
||||||
|
|
||||||
// MARK: - API
|
// MARK: - API
|
||||||
|
|
@ -329,7 +350,7 @@ class HomeViewController: BaseViewController {
|
||||||
UserService.userInfo().subscribe { response in
|
UserService.userInfo().subscribe { response in
|
||||||
guard let model = response.model else { return }
|
guard let model = response.model else { return }
|
||||||
AppContextManager.shared.saveAccount(model)
|
AppContextManager.shared.saveAccount(model)
|
||||||
self.rootView.avatarImgView.image = model.userIcon
|
// self.rootView.avatarImgView.image = model.userIcon
|
||||||
completion?()
|
completion?()
|
||||||
}.disposed(by: disposeBag)
|
}.disposed(by: disposeBag)
|
||||||
}
|
}
|
||||||
|
|
@ -341,8 +362,8 @@ class HomeViewController: BaseViewController {
|
||||||
NotificationCenter.default.post(name: .RefreshIMGroupListNotification, object: nil)
|
NotificationCenter.default.post(name: .RefreshIMGroupListNotification, object: nil)
|
||||||
|
|
||||||
guard isDefaultGroup else { return }
|
guard isDefaultGroup else { return }
|
||||||
self.rootView.groupMemberView.setupCountData(self.viewModel.memberCount, 1)
|
// self.rootView.groupMemberView.setupCountData(self.viewModel.memberCount, 1)
|
||||||
self.rootView.groupNameLab.text = self.viewModel.groupName
|
self.rootView.groupMemberView.groupNameLab.text = self.viewModel.groupName
|
||||||
self.syncMemberAnnotations(model.select_group_employee)
|
self.syncMemberAnnotations(model.select_group_employee)
|
||||||
self.refreshMQTTSubscriptions(model.select_group_employee)
|
self.refreshMQTTSubscriptions(model.select_group_employee)
|
||||||
}.disposed(by: disposeBag)
|
}.disposed(by: disposeBag)
|
||||||
|
|
@ -355,19 +376,19 @@ class HomeViewController: BaseViewController {
|
||||||
}
|
}
|
||||||
|
|
||||||
private func requestNotice() {
|
private func requestNotice() {
|
||||||
UserService.notice().subscribe { response in
|
// UserService.notice().subscribe { response in
|
||||||
self.rootView.noticeLab.text = ""
|
// self.rootView.noticeLab.text = ""
|
||||||
guard let data = response.data, let noticeList = data["notice"] as? [String] else { return }
|
// guard let data = response.data, let noticeList = data["notice"] as? [String] else { return }
|
||||||
self.rootView.noticeView.isHidden = noticeList.count == 0
|
// self.rootView.noticeView.isHidden = noticeList.count == 0
|
||||||
self.rootView.noticeLab.text = noticeList.joined(separator: " ") + " "
|
// self.rootView.noticeLab.text = noticeList.joined(separator: " ") + " "
|
||||||
if let loop = data["loop"] as? Bool {
|
// if let loop = data["loop"] as? Bool {
|
||||||
self.rootView.noticeLab.type = loop ? .continuous : .leftRight
|
// self.rootView.noticeLab.type = loop ? .continuous : .leftRight
|
||||||
self.rootView.noticeLab.onScrollLoopComplete = {
|
// self.rootView.noticeLab.onScrollLoopComplete = {
|
||||||
guard loop == false else { return }
|
// guard loop == false else { return }
|
||||||
self.rootView.noticeView.isHidden = true
|
// self.rootView.noticeView.isHidden = true
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
}.disposed(by: disposeBag)
|
// }.disposed(by: disposeBag)
|
||||||
}
|
}
|
||||||
|
|
||||||
// MARK: - Map Setup
|
// MARK: - Map Setup
|
||||||
|
|
@ -404,7 +425,7 @@ class HomeViewController: BaseViewController {
|
||||||
if hitRect.contains(point) {
|
if hitRect.contains(point) {
|
||||||
self.isMemberPanelShown = true
|
self.isMemberPanelShown = true
|
||||||
(self.tabBarController as? MainTabBarController)?.setTabBarHidden(true)
|
(self.tabBarController as? MainTabBarController)?.setTabBarHidden(true)
|
||||||
rootView.showMemberPanel(member: ann.member)
|
// rootView.showMemberPanel(member: ann.member)
|
||||||
viewModel.targetUid = ann.member.id
|
viewModel.targetUid = ann.member.id
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
|
|
@ -511,14 +532,58 @@ class HomeViewController: BaseViewController {
|
||||||
#endif
|
#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 !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
|
/// 选中成员后地图定位到该成员,收起 GroupMemberView
|
||||||
private func locateMember(userId: String) {
|
private func locateMember(userId: String) {
|
||||||
rootView.dismissGroupMemberView()
|
selectMember(userId: userId, centerMap: true, dismissPanel: true)
|
||||||
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)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|
@ -623,28 +688,28 @@ extension HomeViewController {
|
||||||
let emoteUserId = parts[1]
|
let emoteUserId = parts[1]
|
||||||
|
|
||||||
if firstChar == "1" || firstChar == "2" { // 表情
|
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 nickName = self.viewModel.getUserNickName(id: emoteUserId)
|
||||||
let fullText = nickName + "对你发送了表情"
|
let fullText = nickName + "对你发送了表情"
|
||||||
let attr = NSMutableAttributedString(string: fullText)
|
let attr = NSMutableAttributedString(string: fullText)
|
||||||
attr.addAttribute(.font, value: UIFont.systemFont(ofSize: 12, weight: .medium), range: NSRange(location: 0, length: fullText.count))
|
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: "#16B3FF"), range: NSRange(location: 0, length: nickName.count))
|
||||||
DispatchQueue.main.async { [weak self] in
|
// DispatchQueue.main.async { [weak self] in
|
||||||
guard let self = self else { return }
|
// guard let self = self else { return }
|
||||||
self.rootView.messageLab.attributedText = attr
|
// self.rootView.messageLab.attributedText = attr
|
||||||
self.rootView.messageBubbleView.isHidden = false
|
// self.rootView.messageBubbleView.isHidden = false
|
||||||
if let path = Bundle.main.path(forResource: "\(emojiFileName)\(lastChar)", ofType: "json") {
|
// if let path = Bundle.main.path(forResource: "\(emojiFileName)\(lastChar)", ofType: "json") {
|
||||||
self.rootView.emojiPopView.animation = LottieAnimation.filepath(path)
|
// self.rootView.emojiPopView.animation = LottieAnimation.filepath(path)
|
||||||
self.rootView.bringSubviewToFront(self.rootView.emojiPopView)
|
// self.rootView.bringSubviewToFront(self.rootView.emojiPopView)
|
||||||
self.rootView.emojiPopView.isHidden = false
|
// self.rootView.emojiPopView.isHidden = false
|
||||||
self.rootView.emojiPopView.play { completed in
|
// self.rootView.emojiPopView.play { completed in
|
||||||
self.rootView.emojiPopView.isHidden = true
|
// self.rootView.emojiPopView.isHidden = true
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
DispatchQueue.main.asyncAfter(deadline: .now() + 5) {
|
// DispatchQueue.main.asyncAfter(deadline: .now() + 5) {
|
||||||
self.rootView.messageBubbleView.isHidden = true
|
// self.rootView.messageBubbleView.isHidden = true
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
}
|
}
|
||||||
else if firstChar == "3" { // 文字
|
else if firstChar == "3" { // 文字
|
||||||
let textIdx = lastChar.integer
|
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(.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: "#16B3FF"), range: NSRange(location: 0, length: nickName.count))
|
||||||
attr.addAttribute(.foregroundColor, value: UIColor(hexStr: "#333333"), range: NSRange(location: nickName.count, length: texts[textIdx].count))
|
attr.addAttribute(.foregroundColor, value: UIColor(hexStr: "#333333"), range: NSRange(location: nickName.count, length: texts[textIdx].count))
|
||||||
DispatchQueue.main.async { [weak self] in
|
// DispatchQueue.main.async { [weak self] in
|
||||||
guard let self = self else { return }
|
// guard let self = self else { return }
|
||||||
self.rootView.messageLab.attributedText = attr
|
// self.rootView.messageLab.attributedText = attr
|
||||||
self.rootView.messageBubbleView.isHidden = false
|
// self.rootView.messageBubbleView.isHidden = false
|
||||||
DispatchQueue.main.asyncAfter(deadline: .now() + 5) {
|
// DispatchQueue.main.asyncAfter(deadline: .now() + 5) {
|
||||||
self.rootView.messageBubbleView.isHidden = true
|
// self.rootView.messageBubbleView.isHidden = true
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
}
|
}
|
||||||
case "bubble": // 气泡
|
case "bubble": // 气泡
|
||||||
guard let userId = msg.data?.user_id else { return }
|
guard let userId = msg.data?.user_id else { return }
|
||||||
|
|
@ -683,18 +748,18 @@ extension HomeViewController {
|
||||||
guard !self.isSOSPlaying else { return }
|
guard !self.isSOSPlaying else { return }
|
||||||
self.isSOSPlaying = true
|
self.isSOSPlaying = true
|
||||||
self.playSOSAlarm()
|
self.playSOSAlarm()
|
||||||
self.rootView.sosPopView.isHidden = false
|
// self.rootView.sosPopView.isHidden = false
|
||||||
self.rootView.sosPopView.play { completed in
|
// self.rootView.sosPopView.play { completed in
|
||||||
self.rootView.sosPopView.stop()
|
// self.rootView.sosPopView.stop()
|
||||||
self.rootView.sosPopView.isHidden = true
|
// self.rootView.sosPopView.isHidden = true
|
||||||
}
|
// }
|
||||||
DispatchQueue.main.asyncAfter(deadline: .now() + 15) { [weak self] in
|
// DispatchQueue.main.asyncAfter(deadline: .now() + 15) { [weak self] in
|
||||||
self?.sosPlayer?.stop()
|
// self?.sosPlayer?.stop()
|
||||||
self?.sosPlayer = nil
|
// self?.sosPlayer = nil
|
||||||
self?.rootView.sosPopView.stop()
|
// self?.rootView.sosPopView.stop()
|
||||||
self?.rootView.sosPopView.isHidden = true
|
// self?.rootView.sosPopView.isHidden = true
|
||||||
self?.isSOSPlaying = false
|
// self?.isSOSPlaying = false
|
||||||
}
|
// }
|
||||||
}
|
}
|
||||||
case "join": // 圈子有成员加入
|
case "join": // 圈子有成员加入
|
||||||
guard let gk = msg.data?.group_key,
|
guard let gk = msg.data?.group_key,
|
||||||
|
|
@ -726,7 +791,7 @@ extension HomeViewController {
|
||||||
guard self.viewModel.memberIsExist(userId: userId) else { return }
|
guard self.viewModel.memberIsExist(userId: userId) else { return }
|
||||||
self.viewModel.removeMember(userId: userId)
|
self.viewModel.removeMember(userId: userId)
|
||||||
self.removeAnnotation(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)")
|
MQTTService.shared.unsubscribe(topic: "smartdrive/\(userId)")
|
||||||
}
|
}
|
||||||
case "dismiss": // 圈子解散
|
case "dismiss": // 圈子解散
|
||||||
|
|
@ -754,7 +819,7 @@ extension HomeViewController {
|
||||||
private func updateOnlineCount() {
|
private func updateOnlineCount() {
|
||||||
let total = viewModel.memberCount
|
let total = viewModel.memberCount
|
||||||
let online = viewModel.memberList.filter { $0.is_online }.count
|
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?.configure(with: memberAnnotation.member)
|
||||||
|
annotationView?.onTap = { [weak self] ann in
|
||||||
|
self?.selectMember(userId: ann.member.id, centerMap: true, dismissPanel: false)
|
||||||
|
}
|
||||||
|
|
||||||
if memberAnnotation.member.isCurrentUser {
|
if memberAnnotation.member.isCurrentUser {
|
||||||
currentUserAnnotation = memberAnnotation
|
currentUserAnnotation = memberAnnotation
|
||||||
|
|
|
||||||
|
|
@ -41,7 +41,7 @@ class LoginView: UIView {
|
||||||
}
|
}
|
||||||
|
|
||||||
func setupAgreementTextWithCarrier(carrier: String) {
|
func setupAgreementTextWithCarrier(carrier: String) {
|
||||||
let text = carrier.isEmpty ? "登录即同意《隐私政策》和《用户协议》" : "登录即同意\(carrier)、《隐私政策》和《用户协议》"
|
let text = carrier.isEmpty ? "登录即同意《用户协议》和《隐私政策》" : "登录即同意\(carrier)、《隐私政策》和《用户协议》"
|
||||||
agreementLabel.text = text
|
agreementLabel.text = text
|
||||||
// 创建段落样式
|
// 创建段落样式
|
||||||
let paragraphStyle = NSMutableParagraphStyle()
|
let paragraphStyle = NSMutableParagraphStyle()
|
||||||
|
|
@ -65,27 +65,31 @@ class LoginView: UIView {
|
||||||
}
|
}
|
||||||
|
|
||||||
private func setupUI() {
|
private func setupUI() {
|
||||||
addSubview(bgMaskImage)
|
// addSubview(bgMaskImage)
|
||||||
addSubview(backBtn)
|
addSubview(backBtn)
|
||||||
addSubview(guestLoginButton)
|
addSubview(guestLoginButton)
|
||||||
|
addSubview(welcomeView)
|
||||||
|
welcomeView.addSubview(welcomeLineView)
|
||||||
|
welcomeView.addSubview(welcomeLabel)
|
||||||
|
welcomeView.addSubview(welcomeTitleLab)
|
||||||
|
|
||||||
addSubview(inputContainerView)
|
addSubview(inputContainerView)
|
||||||
inputContainerView.addSubview(welcomeLabel)
|
inputContainerView.addSubview(phoneInputView)
|
||||||
inputContainerView.addSubview(phoneNumberLabel)
|
inputContainerView.addSubview(smsCodeInputView)
|
||||||
inputContainerView.addSubview(carrierLabel)
|
addSubview(loginButton)
|
||||||
inputContainerView.addSubview(otherPhoneView)
|
|
||||||
otherPhoneView.addSubview(phoneInputView)
|
|
||||||
otherPhoneView.addSubview(smsCodeInputView)
|
|
||||||
inputContainerView.addSubview(loginButton)
|
|
||||||
addSubview(appleLoginBtn)
|
addSubview(appleLoginBtn)
|
||||||
addSubview(wechatLoginBtn)
|
addSubview(wechatLoginBtn)
|
||||||
addSubview(phoneLoginBtn)
|
addSubview(phoneLoginBtn)
|
||||||
|
addSubview(otherLoginTitleLab)
|
||||||
|
addSubview(leftSeparator)
|
||||||
|
addSubview(rightSeparator)
|
||||||
|
|
||||||
addSubview(agreementView)
|
addSubview(agreementView)
|
||||||
agreementView.addSubview(checkBox)
|
agreementView.addSubview(checkBox)
|
||||||
agreementView.addSubview(agreementTV)
|
agreementView.addSubview(agreementTV)
|
||||||
agreementView.addSubview(agreementLabel)
|
agreementView.addSubview(agreementLabel)
|
||||||
|
|
||||||
bgMaskImage.layoutChain.edges()
|
// bgMaskImage.layoutChain.edges()
|
||||||
|
|
||||||
backBtn.layoutChain
|
backBtn.layoutChain
|
||||||
.top(54)
|
.top(54)
|
||||||
|
|
@ -100,31 +104,30 @@ class LoginView: UIView {
|
||||||
.height(29)
|
.height(29)
|
||||||
|
|
||||||
inputContainerView.layoutChain
|
inputContainerView.layoutChain
|
||||||
.centerX()
|
.centerY()
|
||||||
.centerY(self, offset: -17)
|
|
||||||
.edgesHorzontal()
|
.edgesHorzontal()
|
||||||
.height(306)
|
|
||||||
|
welcomeView.layoutChain
|
||||||
|
.bottomToTopOfView(inputContainerView, offset: -100)
|
||||||
|
.left(38)
|
||||||
|
|
||||||
|
welcomeLineView.layoutChain
|
||||||
|
.edges(excludingEdge: .right)
|
||||||
|
.width(4)
|
||||||
|
|
||||||
welcomeLabel.layoutChain
|
welcomeLabel.layoutChain
|
||||||
.top(0)
|
.top()
|
||||||
.centerX()
|
.leftToRightOfView(welcomeLineView, offset: 12)
|
||||||
.edgesHorzontal()
|
|
||||||
|
|
||||||
phoneNumberLabel.layoutChain
|
welcomeTitleLab.layoutChain
|
||||||
.topToBottomOfView(welcomeLabel, offset: 78)
|
.topToBottomOfView(welcomeLabel)
|
||||||
.centerX()
|
.leftToView(welcomeLabel)
|
||||||
|
.right()
|
||||||
carrierLabel.layoutChain
|
.bottom()
|
||||||
.topToBottomOfView(phoneNumberLabel, offset: 4)
|
|
||||||
.centerX()
|
|
||||||
|
|
||||||
otherPhoneView.layoutChain
|
|
||||||
.topToBottomOfView(welcomeLabel, offset: 43)
|
|
||||||
.centerX()
|
|
||||||
|
|
||||||
phoneInputView.layoutChain
|
phoneInputView.layoutChain
|
||||||
.edges(excludingEdge: .bottom)
|
.edges(excludingEdge: .bottom)
|
||||||
.width(259)
|
.edgesHorzontal(38)
|
||||||
.height(50)
|
.height(50)
|
||||||
|
|
||||||
phoneLineView.layoutChain
|
phoneLineView.layoutChain
|
||||||
|
|
@ -145,13 +148,14 @@ class LoginView: UIView {
|
||||||
|
|
||||||
smsCodeInputView.layoutChain
|
smsCodeInputView.layoutChain
|
||||||
.topToBottomOfView(phoneInputView, offset: 20)
|
.topToBottomOfView(phoneInputView, offset: 20)
|
||||||
.edges(excludingEdge: .top)
|
.leftToView(phoneInputView)
|
||||||
.widthToView(phoneInputView)
|
.rightToView(phoneInputView)
|
||||||
.heightToView(phoneInputView)
|
.heightToView(phoneInputView)
|
||||||
|
.bottom()
|
||||||
|
|
||||||
smsCodeBtn.layoutChain
|
smsCodeBtn.layoutChain
|
||||||
.edgesVertical()
|
.edgesVertical(5)
|
||||||
.right()
|
.right(5)
|
||||||
.width(100)
|
.width(100)
|
||||||
|
|
||||||
smsCodeTF.layoutChain
|
smsCodeTF.layoutChain
|
||||||
|
|
@ -160,15 +164,13 @@ class LoginView: UIView {
|
||||||
.rightToLeftOfView(smsCodeBtn)
|
.rightToLeftOfView(smsCodeBtn)
|
||||||
|
|
||||||
loginButton.layoutChain
|
loginButton.layoutChain
|
||||||
.topToBottomOfView(otherPhoneView, offset: 63)
|
.topToBottomOfView(inputContainerView, offset: 46)
|
||||||
.centerX()
|
.edgesHorzontal(30)
|
||||||
.width(247)
|
.height(56)
|
||||||
.height(50)
|
|
||||||
|
|
||||||
agreementView.layoutChain
|
agreementView.layoutChain
|
||||||
.leftToView(loginButton)
|
.topToBottomOfView(loginButton, offset: 14)
|
||||||
.rightToView(loginButton)
|
.centerX()
|
||||||
.bottom(kSafeBottomMargin + 20)
|
|
||||||
.height(30)
|
.height(30)
|
||||||
|
|
||||||
agreementLabel.layoutChain.edges()
|
agreementLabel.layoutChain.edges()
|
||||||
|
|
@ -185,9 +187,9 @@ class LoginView: UIView {
|
||||||
.top()
|
.top()
|
||||||
|
|
||||||
phoneLoginBtn.layoutChain
|
phoneLoginBtn.layoutChain
|
||||||
.bottomToTopOfView(agreementView, offset: -30)
|
.bottom(kSafeBottomMargin + 30)
|
||||||
.centerX()
|
.centerX()
|
||||||
.width(38)
|
.width(40)
|
||||||
.heightToWidth(1)
|
.heightToWidth(1)
|
||||||
|
|
||||||
appleLoginBtn.layoutChain
|
appleLoginBtn.layoutChain
|
||||||
|
|
@ -201,6 +203,22 @@ class LoginView: UIView {
|
||||||
.centerY(phoneLoginBtn)
|
.centerY(phoneLoginBtn)
|
||||||
.widthToView(phoneLoginBtn)
|
.widthToView(phoneLoginBtn)
|
||||||
.heightToView(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) {
|
override init(frame: CGRect) {
|
||||||
|
|
@ -215,6 +233,24 @@ class LoginView: UIView {
|
||||||
fatalError("init(coder:) has not been implemented")
|
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
|
// MARK: - UI Components
|
||||||
lazy var bgMaskImage: UIImageView = {
|
lazy var bgMaskImage: UIImageView = {
|
||||||
let iv = UIImageView()
|
let iv = UIImageView()
|
||||||
|
|
@ -240,12 +276,32 @@ class LoginView: UIView {
|
||||||
return btn
|
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 = {
|
lazy var welcomeLabel: UILabel = {
|
||||||
let label = UILabel()
|
let label = UILabel()
|
||||||
label.text = "欢迎登录"
|
label.text = "欢迎登录"
|
||||||
label.font = UIFont(name: "DOUYU Font", size: 24) ?? .systemFont(ofSize: 24, weight: .medium)
|
label.font = .systemFont(ofSize: 18, weight: .medium)
|
||||||
label.textColor = UIColor(hexStr: "#030303")
|
label.textColor = .black
|
||||||
label.textAlignment = .center
|
return label
|
||||||
|
}()
|
||||||
|
|
||||||
|
lazy var welcomeTitleLab: UILabel = {
|
||||||
|
let label = UILabel()
|
||||||
|
label.text = "极速定位"
|
||||||
|
label.font = .systemFont(ofSize: 32, weight: .bold)
|
||||||
|
label.textColor = .black
|
||||||
return label
|
return label
|
||||||
}()
|
}()
|
||||||
|
|
||||||
|
|
@ -272,21 +328,11 @@ class LoginView: UIView {
|
||||||
return view
|
return view
|
||||||
}()
|
}()
|
||||||
|
|
||||||
// 其他手机号登录
|
|
||||||
lazy var otherPhoneView: UIView = {
|
|
||||||
let view = UIView()
|
|
||||||
view.backgroundColor = .clear
|
|
||||||
// view.isHidden = true
|
|
||||||
return view
|
|
||||||
}()
|
|
||||||
|
|
||||||
// 手机号
|
// 手机号
|
||||||
lazy var phoneInputView: UIView = {
|
lazy var phoneInputView: UIView = {
|
||||||
let view = UIView()
|
let view = UIView()
|
||||||
view.backgroundColor = .clear
|
view.backgroundColor = UIColor(hexStr: "#F8F8F8")
|
||||||
view.borderWidth = 1
|
view.cornerRadius = 12
|
||||||
view.borderColor = .white
|
|
||||||
view.cornerRadius = 8
|
|
||||||
view.addSubview(areaCodeLab)
|
view.addSubview(areaCodeLab)
|
||||||
view.addSubview(phoneLineView)
|
view.addSubview(phoneLineView)
|
||||||
view.addSubview(phoneInputTF)
|
view.addSubview(phoneInputTF)
|
||||||
|
|
@ -297,14 +343,14 @@ class LoginView: UIView {
|
||||||
let label = UILabel()
|
let label = UILabel()
|
||||||
label.text = "+86"
|
label.text = "+86"
|
||||||
label.font = .systemFont(ofSize: 16, weight: .medium)
|
label.font = .systemFont(ofSize: 16, weight: .medium)
|
||||||
label.textColor = .white
|
label.textColor = UIColor(hexStr: "#3D3D3D")
|
||||||
label.textAlignment = .center
|
label.textAlignment = .center
|
||||||
return label
|
return label
|
||||||
}()
|
}()
|
||||||
|
|
||||||
lazy var phoneLineView: UIView = {
|
lazy var phoneLineView: UIView = {
|
||||||
let view = UIView()
|
let view = UIView()
|
||||||
view.backgroundColor = .white
|
view.backgroundColor = UIColor(hexStr: "#D8D8D8")
|
||||||
return view
|
return view
|
||||||
}()
|
}()
|
||||||
|
|
||||||
|
|
@ -312,7 +358,7 @@ class LoginView: UIView {
|
||||||
let textField = UITextField()
|
let textField = UITextField()
|
||||||
textField.font = .systemFont(ofSize: 16, weight: .medium)
|
textField.font = .systemFont(ofSize: 16, weight: .medium)
|
||||||
textField.placeholderColor(placeholder: "请输入手机号码",
|
textField.placeholderColor(placeholder: "请输入手机号码",
|
||||||
color: .white)
|
color: UIColor(hexStr: "#767676"))
|
||||||
// textField.tintColor = ThemeManager.shared.color.mainColor
|
// textField.tintColor = ThemeManager.shared.color.mainColor
|
||||||
textField.clearButtonMode = .whileEditing
|
textField.clearButtonMode = .whileEditing
|
||||||
textField.keyboardType = .numberPad
|
textField.keyboardType = .numberPad
|
||||||
|
|
@ -322,10 +368,8 @@ class LoginView: UIView {
|
||||||
// 验证码
|
// 验证码
|
||||||
lazy var smsCodeInputView: UIView = {
|
lazy var smsCodeInputView: UIView = {
|
||||||
let view = UIView()
|
let view = UIView()
|
||||||
view.backgroundColor = .clear
|
view.backgroundColor = UIColor(hexStr: "#F8F8F8")
|
||||||
view.borderWidth = 1
|
view.cornerRadius = 12
|
||||||
view.borderColor = .white
|
|
||||||
view.cornerRadius = 8
|
|
||||||
view.addSubview(smsCodeTF)
|
view.addSubview(smsCodeTF)
|
||||||
view.addSubview(smsCodeBtn)
|
view.addSubview(smsCodeBtn)
|
||||||
return view
|
return view
|
||||||
|
|
@ -335,29 +379,53 @@ class LoginView: UIView {
|
||||||
let textField = UITextField()
|
let textField = UITextField()
|
||||||
textField.font = .systemFont(ofSize: 16, weight: .medium)
|
textField.font = .systemFont(ofSize: 16, weight: .medium)
|
||||||
textField.placeholderColor(placeholder: "请输入验证码",
|
textField.placeholderColor(placeholder: "请输入验证码",
|
||||||
color: .white)
|
color: UIColor(hexStr: "#767676"))
|
||||||
textField.keyboardType = .numberPad
|
textField.keyboardType = .numberPad
|
||||||
return textField
|
return textField
|
||||||
}()
|
}()
|
||||||
|
|
||||||
lazy var smsCodeBtn: UIButton = {
|
lazy var smsCodeBtn: UIButton = {
|
||||||
let button = UIButton()
|
let button = UIButton()
|
||||||
|
button.backgroundColor = .white
|
||||||
|
button.cornerRadius = 8
|
||||||
button.setTitle("获取验证码", for: .normal)
|
button.setTitle("获取验证码", for: .normal)
|
||||||
button.setTitleColor(UIColor(hexStr: "#030303"), for: .normal)
|
button.setTitleColor(UIColor(hexStr: "#1A1A1A"), for: .normal)
|
||||||
button.setTitleColor(UIColor(hexStr: "#999999", alpha: 1.0), for: .disabled)
|
button.setTitleColor(UIColor(hexStr: "#767676"), for: .disabled)
|
||||||
button.titleLabel?.font = .systemFont(ofSize: 16, weight: .medium)
|
button.titleLabel?.font = .systemFont(ofSize: 12, weight: .medium)
|
||||||
return button
|
return button
|
||||||
}()
|
}()
|
||||||
|
|
||||||
lazy var loginButton: UIButton = {
|
lazy var loginButton: UIButton = {
|
||||||
let btn = UIButton(type: .system)
|
let btn = UIButton(type: .system)
|
||||||
btn.setTitle("立即登录", for: .normal)
|
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.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
|
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 = {
|
lazy var wechatLoginBtn: UIButton = {
|
||||||
let btn = UIButton(type: .custom)
|
let btn = UIButton(type: .custom)
|
||||||
btn.setBackgroundImage(UIImage(named: "Login/wechat"), for: .normal)
|
btn.setBackgroundImage(UIImage(named: "Login/wechat"), for: .normal)
|
||||||
|
|
|
||||||
|
|
@ -57,10 +57,10 @@ class OneTapLoginView: UIView {
|
||||||
}
|
}
|
||||||
|
|
||||||
private func setupUI() {
|
private func setupUI() {
|
||||||
addSubview(bgMaskImage)
|
// addSubview(bgMaskImage)
|
||||||
addSubview(backBtn)
|
addSubview(backBtn)
|
||||||
addSubview(inputContainerView)
|
addSubview(inputContainerView)
|
||||||
inputContainerView.addSubview(welcomeLabel)
|
// inputContainerView.addSubview(welcomeLabel)
|
||||||
inputContainerView.addSubview(phoneNumberLabel)
|
inputContainerView.addSubview(phoneNumberLabel)
|
||||||
inputContainerView.addSubview(carrierLabel)
|
inputContainerView.addSubview(carrierLabel)
|
||||||
inputContainerView.addSubview(loginButton)
|
inputContainerView.addSubview(loginButton)
|
||||||
|
|
@ -70,7 +70,7 @@ class OneTapLoginView: UIView {
|
||||||
agreementView.addSubview(agreementLabel)
|
agreementView.addSubview(agreementLabel)
|
||||||
agreementView.addSubview(agreementTV)
|
agreementView.addSubview(agreementTV)
|
||||||
|
|
||||||
bgMaskImage.layoutChain.edges()
|
// bgMaskImage.layoutChain.edges()
|
||||||
|
|
||||||
backBtn.layoutChain
|
backBtn.layoutChain
|
||||||
.top(54)
|
.top(54)
|
||||||
|
|
@ -84,28 +84,27 @@ class OneTapLoginView: UIView {
|
||||||
.edgesHorzontal()
|
.edgesHorzontal()
|
||||||
.height(301)
|
.height(301)
|
||||||
|
|
||||||
welcomeLabel.layoutChain
|
// welcomeLabel.layoutChain
|
||||||
.top(0)
|
// .top(0)
|
||||||
.centerX()
|
// .centerX()
|
||||||
.edgesHorzontal()
|
// .edgesHorzontal()
|
||||||
|
|
||||||
phoneNumberLabel.layoutChain
|
phoneNumberLabel.layoutChain
|
||||||
.topToBottomOfView(welcomeLabel, offset: 78)
|
.top()
|
||||||
.centerX()
|
.centerX()
|
||||||
|
|
||||||
carrierLabel.layoutChain
|
carrierLabel.layoutChain
|
||||||
.topToBottomOfView(phoneNumberLabel, offset: 4)
|
.topToBottomOfView(phoneNumberLabel, offset: 14)
|
||||||
.centerX()
|
.centerX()
|
||||||
|
|
||||||
loginButton.layoutChain
|
loginButton.layoutChain
|
||||||
.topToBottomOfView(carrierLabel, offset: 99)
|
.topToBottomOfView(carrierLabel, offset: 99)
|
||||||
.centerX()
|
.edgesHorzontal(30)
|
||||||
.width(247)
|
.height(56)
|
||||||
.height(50)
|
|
||||||
|
|
||||||
agreementView.layoutChain
|
agreementView.layoutChain
|
||||||
|
.topToBottomOfView(inputContainerView, offset: 14)
|
||||||
.edgesHorzontal(30)
|
.edgesHorzontal(30)
|
||||||
.bottom(kSafeBottomMargin + 20)
|
|
||||||
|
|
||||||
checkBox.layoutChain
|
checkBox.layoutChain
|
||||||
.left()
|
.left()
|
||||||
|
|
@ -166,7 +165,7 @@ class OneTapLoginView: UIView {
|
||||||
|
|
||||||
lazy var welcomeLabel: UILabel = {
|
lazy var welcomeLabel: UILabel = {
|
||||||
let label = UILabel()
|
let label = UILabel()
|
||||||
label.text = "欢迎登录"
|
// label.text = "欢迎登录"
|
||||||
label.font = UIFont(name: "DOUYU Font", size: 24) ?? .systemFont(ofSize: 24, weight: .medium)
|
label.font = UIFont(name: "DOUYU Font", size: 24) ?? .systemFont(ofSize: 24, weight: .medium)
|
||||||
label.textColor = UIColor(hexStr: "#030303")
|
label.textColor = UIColor(hexStr: "#030303")
|
||||||
label.textAlignment = .center
|
label.textAlignment = .center
|
||||||
|
|
@ -175,16 +174,16 @@ class OneTapLoginView: UIView {
|
||||||
|
|
||||||
lazy var phoneNumberLabel: UILabel = {
|
lazy var phoneNumberLabel: UILabel = {
|
||||||
let label = UILabel()
|
let label = UILabel()
|
||||||
label.font = .systemFont(ofSize: 20, weight: .medium)
|
label.font = .systemFont(ofSize: 32, weight: .heavy)
|
||||||
label.textColor = ThemeManager.shared.color.titleAuxColor
|
label.textColor = UIColor(hexStr: "#3D3D3D")
|
||||||
label.textAlignment = .center
|
label.textAlignment = .center
|
||||||
return label
|
return label
|
||||||
}()
|
}()
|
||||||
|
|
||||||
lazy var carrierLabel: UILabel = {
|
lazy var carrierLabel: UILabel = {
|
||||||
let label = UILabel()
|
let label = UILabel()
|
||||||
label.font = .systemFont(ofSize: 12)
|
label.font = .systemFont(ofSize: 14, weight: .medium)
|
||||||
label.textColor = ThemeManager.shared.color.contentColor
|
label.textColor = UIColor(hexStr: "#767676")
|
||||||
label.textAlignment = .center
|
label.textAlignment = .center
|
||||||
return label
|
return label
|
||||||
}()
|
}()
|
||||||
|
|
@ -192,9 +191,10 @@ class OneTapLoginView: UIView {
|
||||||
lazy var loginButton: UIButton = {
|
lazy var loginButton: UIButton = {
|
||||||
let btn = UIButton(type: .system)
|
let btn = UIButton(type: .system)
|
||||||
btn.setTitle("本机号码一键登录", for: .normal)
|
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.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
|
return btn
|
||||||
}()
|
}()
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -13,53 +13,51 @@ final class MemberAnnotationView: MAAnnotationView {
|
||||||
var onTap: ((MemberAnnotation) -> Void)?
|
var onTap: ((MemberAnnotation) -> Void)?
|
||||||
|
|
||||||
// MARK: - Design Constants
|
// MARK: - Design Constants
|
||||||
static let avatarOuterSize: CGFloat = 50
|
private static let bubbleSize: CGFloat = 56
|
||||||
static let avatarInnerSize: CGFloat = 48
|
private static let bubbleCorner: CGFloat = 14
|
||||||
static let nameTagWidth: CGFloat = 60
|
private static let borderWidth: CGFloat = 4
|
||||||
static let nameTagHeight: CGFloat = 20
|
private static let pointerWidth: CGFloat = 12
|
||||||
static let totalHeight: CGFloat = avatarOuterSize + 4 + nameTagHeight
|
private static let pointerHeight: CGFloat = 7
|
||||||
static let radarSize: CGFloat = 180
|
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 = {
|
private static let currentUserDotColor = UIColor(hexStr: "#5ED4FF")
|
||||||
let v = UIView()
|
private static let memberDotPalette: [UIColor] = [
|
||||||
v.backgroundColor = .clear
|
UIColor(hexStr: "#FF6B6B"),
|
||||||
return v
|
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()
|
let v = UIView()
|
||||||
v.backgroundColor = .white
|
v.backgroundColor = .white
|
||||||
v.layer.cornerRadius = avatarOuterSize / 2
|
v.layer.cornerRadius = 8
|
||||||
v.layer.shadowColor = UIColor.black.withAlphaComponent(0.15).cgColor
|
v.layer.shadowColor = UIColor.black.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.shadowOffset = CGSize(width: 0, height: 1)
|
v.layer.shadowOffset = CGSize(width: 0, height: 1)
|
||||||
v.layer.shadowRadius = 2
|
v.layer.shadowRadius = 2
|
||||||
v.layer.shadowOpacity = 1
|
v.layer.shadowOpacity = 0.12
|
||||||
|
v.isHidden = true
|
||||||
return v
|
return v
|
||||||
}()
|
}()
|
||||||
|
|
||||||
private lazy var nameLabel: UILabel = {
|
private let nameLabel: UILabel = {
|
||||||
let l = UILabel()
|
let l = UILabel()
|
||||||
l.font = UIFont(name: "PingFangSC-Medium", size: 11)
|
l.font = UIFont(name: "PingFangSC-Medium", size: 11)
|
||||||
?? UIFont.systemFont(ofSize: 11, weight: .medium)
|
?? UIFont.systemFont(ofSize: 11, weight: .medium)
|
||||||
|
|
@ -69,10 +67,55 @@ final class MemberAnnotationView: MAAnnotationView {
|
||||||
return l
|
return l
|
||||||
}()
|
}()
|
||||||
|
|
||||||
// MARK: - Layers
|
private let bubbleView: UIView = {
|
||||||
private var headingLayer: CAShapeLayer?
|
let v = UIView()
|
||||||
private var pulseLayer: CAShapeLayer?
|
v.backgroundColor = .white
|
||||||
private var isCurrentUser: Bool = false
|
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
|
// MARK: - Init
|
||||||
override init!(annotation: MAAnnotation!, reuseIdentifier: String!) {
|
override init!(annotation: MAAnnotation!, reuseIdentifier: String!) {
|
||||||
|
|
@ -89,34 +132,41 @@ final class MemberAnnotationView: MAAnnotationView {
|
||||||
isEnabled = true
|
isEnabled = true
|
||||||
|
|
||||||
addSubview(containerView)
|
addSubview(containerView)
|
||||||
|
|
||||||
containerView.addSubview(avatarOuterCircle)
|
|
||||||
avatarOuterCircle.addSubview(avatarImageView)
|
|
||||||
|
|
||||||
containerView.addSubview(nameTagView)
|
containerView.addSubview(nameTagView)
|
||||||
nameTagView.addSubview(nameLabel)
|
nameTagView.addSubview(nameLabel)
|
||||||
|
containerView.layer.addSublayer(pointerLayer)
|
||||||
|
containerView.addSubview(bubbleView)
|
||||||
|
bubbleView.addSubview(avatarImageView)
|
||||||
|
containerView.addSubview(dotOuterView)
|
||||||
|
dotOuterView.addSubview(dotInnerView)
|
||||||
|
dotInnerView.addSubview(dotCenterView)
|
||||||
}
|
}
|
||||||
|
|
||||||
// MARK: - Configure
|
// MARK: - Configure
|
||||||
func configure(with member: CircleMember) {
|
func configure(with member: CircleMember) {
|
||||||
self.isEnabled = true
|
isEnabled = true
|
||||||
avatarImageView.image = UIImage(named: "UserIcon/\(member.avatar)")
|
|
||||||
avatarImageView.backgroundColor = member.avatar.isEmpty ? .lightGray : .clear
|
|
||||||
nameLabel.text = member.name
|
|
||||||
isCurrentUser = member.isCurrentUser
|
|
||||||
canShowCallout = false
|
canShowCallout = false
|
||||||
|
|
||||||
// 非当前用户标注可点击弹出面板
|
if let img = UIImage(named: "UserIcon/\(member.avatar)") {
|
||||||
if !member.isCurrentUser && gestureRecognizers?.isEmpty ?? true {
|
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))
|
let tap = UITapGestureRecognizer(target: self, action: #selector(handleTap))
|
||||||
addGestureRecognizer(tap)
|
addGestureRecognizer(tap)
|
||||||
}
|
}
|
||||||
|
|
||||||
if member.isCurrentUser {
|
layoutMarker()
|
||||||
configureAsCurrentUser(heading: member.heading)
|
|
||||||
} else {
|
|
||||||
configureAsMember()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@objc private func handleTap() {
|
@objc private func handleTap() {
|
||||||
|
|
@ -124,144 +174,94 @@ final class MemberAnnotationView: MAAnnotationView {
|
||||||
onTap?(ann)
|
onTap?(ann)
|
||||||
}
|
}
|
||||||
|
|
||||||
private func configureAsCurrentUser(heading: Double) {
|
/// 朝向扇形已从图示样式中移除,保留 API 兼容 HomeViewController
|
||||||
nameTagView.isHidden = true
|
func updateHeading(_ heading: Double) {}
|
||||||
stopPulseAnimation()
|
|
||||||
|
|
||||||
let outer = Self.avatarOuterSize
|
// MARK: - Layout
|
||||||
let padding: CGFloat = 30
|
private func layoutMarker() {
|
||||||
let size = outer + padding * 2
|
let bubble = Self.bubbleSize
|
||||||
bounds = CGRect(x: 0, y: 0, width: size, height: size)
|
let pointerH = Self.pointerHeight
|
||||||
centerOffset = .zero
|
let gap = Self.gapBubbleToDot
|
||||||
containerView.frame = bounds
|
let dot = Self.dotOuterSize
|
||||||
|
|
||||||
let avatarX = (size - outer) / 2
|
let nameH: CGFloat = showsNameTag ? Self.nameTagHeight : 0
|
||||||
let avatarY = (size - outer) / 2
|
let nameGap: CGFloat = showsNameTag ? Self.gapNameToBubble : 0
|
||||||
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
|
|
||||||
)
|
|
||||||
|
|
||||||
addHeadingIndicator(heading: heading)
|
var nameTagWidth: CGFloat = 0
|
||||||
}
|
if showsNameTag {
|
||||||
|
|
||||||
private func configureAsMember() {
|
|
||||||
nameTagView.isHidden = false
|
|
||||||
headingLayer?.removeFromSuperlayer()
|
|
||||||
headingLayer = nil
|
|
||||||
stopPulseAnimation()
|
|
||||||
|
|
||||||
// 动态宽度
|
|
||||||
let text = nameLabel.text ?? ""
|
let text = nameLabel.text ?? ""
|
||||||
let textWidth = (text as NSString).size(withAttributes: [.font: nameLabel.font as Any]).width
|
let textWidth = (text as NSString).size(withAttributes: [.font: nameLabel.font as Any]).width
|
||||||
let tagWidth = min(max(textWidth + 16, 44), 120)
|
nameTagWidth = min(max(textWidth + Self.nameTagHPadding * 2, 44), 120)
|
||||||
let totalWidth = max(tagWidth, Self.avatarOuterSize)
|
}
|
||||||
let tagTop: CGFloat = 0
|
|
||||||
let avatarTop = Self.nameTagHeight + 4
|
let totalWidth = max(bubble, nameTagWidth)
|
||||||
let totalHeight = avatarTop + Self.avatarOuterSize
|
let totalHeight = nameH + nameGap + bubble + pointerH + gap + dot
|
||||||
|
|
||||||
bounds = CGRect(x: 0, y: 0, width: totalWidth, height: totalHeight)
|
bounds = CGRect(x: 0, y: 0, width: totalWidth, height: totalHeight)
|
||||||
containerView.frame = bounds
|
containerView.frame = bounds
|
||||||
// avatar 中心对准 annotation 坐标
|
|
||||||
let avatarCenterY = avatarTop + Self.avatarOuterSize / 2
|
|
||||||
centerOffset = CGPoint(x: 0, y: avatarCenterY - totalHeight / 2)
|
|
||||||
|
|
||||||
// nameTag 在头像上面
|
// 色点中心对准 annotation 坐标(负 y 将视图上移)
|
||||||
|
centerOffset = CGPoint(x: 0, y: -(totalHeight / 2 - Self.dotOuterSize / 2))
|
||||||
|
|
||||||
|
if showsNameTag {
|
||||||
nameTagView.frame = CGRect(
|
nameTagView.frame = CGRect(
|
||||||
x: (totalWidth - tagWidth) / 2, y: tagTop,
|
x: (totalWidth - nameTagWidth) / 2,
|
||||||
width: tagWidth, height: Self.nameTagHeight
|
y: 0,
|
||||||
|
width: nameTagWidth,
|
||||||
|
height: Self.nameTagHeight
|
||||||
)
|
)
|
||||||
nameLabel.frame = nameTagView.bounds.insetBy(dx: 6, dy: 0)
|
nameLabel.frame = nameTagView.bounds.insetBy(dx: Self.nameTagHPadding - 2, dy: 0)
|
||||||
|
}
|
||||||
|
|
||||||
// 头像在 nameTag 下面
|
let bubbleY = nameH + nameGap
|
||||||
avatarOuterCircle.frame = CGRect(
|
bubbleView.frame = CGRect(
|
||||||
x: (totalWidth - Self.avatarOuterSize) / 2, y: avatarTop,
|
x: (totalWidth - bubble) / 2,
|
||||||
width: Self.avatarOuterSize, height: Self.avatarOuterSize
|
y: bubbleY,
|
||||||
|
width: bubble,
|
||||||
|
height: bubble
|
||||||
)
|
)
|
||||||
|
let inner = bubble - Self.borderWidth * 2
|
||||||
avatarImageView.frame = CGRect(
|
avatarImageView.frame = CGRect(
|
||||||
x: (Self.avatarOuterSize - Self.avatarInnerSize) / 2,
|
x: Self.borderWidth, y: Self.borderWidth,
|
||||||
y: (Self.avatarOuterSize - Self.avatarInnerSize) / 2,
|
width: inner, height: inner
|
||||||
width: Self.avatarInnerSize, height: Self.avatarInnerSize
|
)
|
||||||
|
|
||||||
|
// 白色三角:尖朝下,顶边贴气泡底边
|
||||||
|
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
|
// MARK: - Dot Color
|
||||||
func updateHeading(_ heading: Double) {
|
private static func dotColor(for member: CircleMember) -> UIColor {
|
||||||
headingLayer?.removeFromSuperlayer()
|
if member.isCurrentUser {
|
||||||
headingLayer = nil
|
return currentUserDotColor
|
||||||
addHeadingIndicator(heading: heading)
|
|
||||||
}
|
}
|
||||||
|
let hash = member.id.utf8.reduce(0) { ($0 &* 31) &+ Int($1) }
|
||||||
private func addHeadingIndicator(heading: Double) {
|
let index = abs(hash) % memberDotPalette.count
|
||||||
headingLayer?.removeFromSuperlayer()
|
return memberDotPalette[index]
|
||||||
|
|
||||||
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
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
|
||||||
|
|
@ -81,7 +81,13 @@ class AccountVC: BaseViewController {
|
||||||
private func deleteAccount() {
|
private func deleteAccount() {
|
||||||
DLToast.showLoading()
|
DLToast.showLoading()
|
||||||
UserService.deleteAccount().subscribe(onNext: { response in
|
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)
|
}, onError: { _ in }).disposed(by: disposeBag)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||