jsdw_ios/QuickLocation/Model/GroupModel.swift

615 lines
18 KiB
Swift
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

//
// GroupModel.swift
// QuickLocation
//
// Created by on 2026/5/30.
//
import ObjectMapper
import RxDataSources
import Kingfisher
private let kGroupTransformInt64 = TransformOf<Int64, Any>(fromJSON: { value in
switch value {
case let value as Int64:
return value
case let value as Int:
return Int64(value)
case let value as Double:
return Int64(value)
case let value as String:
return Int64(value)
default:
return nil
}
}, toJSON: { value in
value
})
///
struct UserGroupResponse: BaseModelProtocol {
//
var code: String?
//
var message: String?
//
var model: GroupModel?
init?(map: Map) {}
mutating func mapping(map: Map) {
code <- map["code"]
message <- map["msg"]
model <- map["data"]
}
}
///
struct RecommandGroupResponse: BaseModelProtocol {
//
var code: String?
//
var message: String?
//
var list: [GroupInfoModel] = []
init?(map: Map) {}
mutating func mapping(map: Map) {
code <- map["code"]
message <- map["msg"]
list <- map["data"]
}
}
///
struct GroupInfoResponse: BaseModelProtocol {
//
var code: String?
//
var message: String?
//
var model: GroupInfoModel?
//
var list: [GroupMemberModel] = []
//
var reviewCount: Int = 0
init?(map: Map) {}
mutating func mapping(map: Map) {
code <- map["code"]
message <- map["msg"]
model <- map["data.attr"]
list <- map["data.employee"]
reviewCount <- map["data.reviewCount"]
}
}
///
struct GroupUsersResponse: BaseModelProtocol {
var code: String?
var message: String?
var model: GroupInfoModel?
var list: [GroupMemberModel] = []
init?(map: Map) {}
mutating func mapping(map: Map) {
code <- (map["code"], kIntTransformStr)
message <- map["message"]
model <- map["data.group"]
list <- map["data.mates"]
}
func isValid(for groupKey: String) -> Bool {
code == "0" && model?.group_key == groupKey
}
}
///
struct GroupTemplateResponse: BaseModelProtocol {
var code: String?
var message: String?
var list: [GroupTemplateModel] = []
init?(map: Map) {}
mutating func mapping(map: Map) {
code <- (map["code"], kIntTransformStr)
message <- map["message"]
list <- map["data"]
}
}
struct GroupTemplateModel: Mappable, Equatable {
var name: String = ""
var type: Int = 0
var limit: Int = -1
var icon: String = ""
init?(map: Map) {}
mutating func mapping(map: Map) {
name <- map["name"]
type <- (map["type"], kStrTransformInt)
limit <- (map["limit"], kStrTransformInt)
icon <- map["icon"]
}
}
struct ReviewMemberListResponse: BaseModelProtocol {
//
var code: String?
//
var message: String?
///
var list: [GroupMemberModel] = []
init?(map: Map) {}
mutating func mapping(map: Map) {
code <- map["code"]
message <- map["msg"]
list <- map["data"]
}
}
struct GroupModel: Mappable {
///
var default_group_key: String = ""
///
var has_unread_email: Bool = false
/// SOS
var is_anyone_sos: Bool = false
///
var groups: [GroupInfoModel] = []
///
var select_group_employee: [GroupMemberModel] = []
var score: Int = 0
init?(map: Map) {
}
mutating func mapping(map: Map) {
default_group_key <- map["default_group_key"]
has_unread_email <- map["has_unread_email"]
is_anyone_sos <- map["is_anyone_sos"]
score <- map["score"]
groups <- map["groups"]
select_group_employee <- map["select_group_employee"]
}
}
struct GroupInfoModel: Mappable, Equatable {
///
var group_key: String = ""
///
var is_owner: Bool = false
///
var name: String = ""
/// icon
var icon_index: Int = 1
var groupIcon: UIImage {
UIImage(named: "GroupIcon/\(icon_index)") ?? UIImage()
}
/// icon_index
var group_type: Int = 0
var group_template: GroupTemplateModel?
var resolvedGroupType: Int {
if group_type > 0 { return group_type }
if let type = group_template?.type, type > 0 { return type }
return icon_index
}
var groupTemplateName: String {
let name = group_template?.name.trimmingCharacters(in: .whitespacesAndNewlines) ?? ""
return name.isEmpty ? "未知类型" : name
}
var isCoupleGroup: Bool {
if groupTemplateName.contains("情侣") { return true }
if let limit = group_template?.limit, limit == 2 { return true }
return false
}
var groupIconURL: String {
group_template?.icon.trimmingCharacters(in: .whitespacesAndNewlines) ?? ""
}
///
var labels: [String] = []
///
var review: Bool = false
///
var people_no: Int = 0
///
var description: String = ""
///
var level: String = ""
///
var share_code: String = ""
///
var privilege = GroupPrivilege()
init?(map: Map) {
}
mutating func mapping(map: Map) {
group_key <- map["group_key"]
is_owner <- map["is_owner"]
name <- map["name"]
icon_index <- map["icon_index"]
group_type <- (map["group_type"], kStrTransformInt)
group_template <- map["group_template"]
labels <- map["labels"]
level <- (map["level"], kIntTransformStr)
review <- map["review"]
description <- map["description"]
share_code <- map["share_code"]
people_no <- (map["people_no"], kStrTransformInt)
privilege <- map["privilege"]
if map["app_usage_record"].isKeyPresent {
privilege.app_usage_record <- (map["app_usage_record"], kFlexibleBool)
}
if map["phone_report"].isKeyPresent {
privilege.phone_report <- (map["phone_report"], kFlexibleBool)
}
if map["app_lock"].isKeyPresent {
privilege.app_lock <- (map["app_lock"], kFlexibleBool)
}
if map["pigeon_mail"].isKeyPresent {
privilege.pigeon_mail <- (map["pigeon_mail"], kFlexibleBool)
}
}
}
struct GroupPrivilege: Mappable, Equatable {
var app_usage_record = false
var phone_report = false
var app_lock = false
var pigeon_mail = false
init() {}
init?(map: Map) {}
mutating func mapping(map: Map) {
app_usage_record <- (map["app_usage_record"], kFlexibleBool)
phone_report <- (map["phone_report"], kFlexibleBool)
app_lock <- (map["app_lock"], kFlexibleBool)
pigeon_mail <- (map["pigeon_mail"], kFlexibleBool)
}
}
private let kFlexibleBool = TransformOf<Bool, Any>(fromJSON: { value in
switch value {
case let value as Bool:
return value
case let value as NSNumber:
return value.boolValue
case let value as Int:
return value != 0
case let value as String:
let text = value.trimmingCharacters(in: .whitespacesAndNewlines).lowercased()
return text == "1" || text == "true" || text == "yes"
default:
return false
}
}, toJSON: { value in
value
})
extension UIImageView {
@MainActor
func setGroupIcon(_ model: GroupInfoModel) {
setGroupIcon(url: model.groupIconURL, fallback: model.groupIcon)
}
@MainActor
func setGroupIcon(url: String, fallback: UIImage?) {
kf.cancelDownloadTask()
image = fallback
guard let imageURL = URL(string: url), !url.isEmpty else { return }
kf.setImage(with: imageURL, placeholder: fallback) { [weak self] result in
if case .failure = result {
self?.image = fallback
}
}
}
}
extension GroupInfoModel: IdentifiableType {
public typealias Identity = String
public var identity: String {
return group_key
}
}
struct CommonGroupModel: Mappable, Equatable {
var group_key: String = ""
var group_name: String = ""
var icon: String = ""
var icon_index: Int = 1
var group_template: GroupTemplateModel?
var groupIconURL: String {
let templateIcon = group_template?.icon.trimmingCharacters(in: .whitespacesAndNewlines) ?? ""
if !templateIcon.isEmpty { return templateIcon }
return icon.trimmingCharacters(in: .whitespacesAndNewlines)
}
var groupIcon: UIImage {
UIImage(named: "GroupIcon/\(icon_index)")
?? UIImage(named: "GroupIcon/1")
?? UIImage()
}
init(groupKey: String, groupName: String) {
group_key = groupKey
group_name = groupName
}
init(info: GroupInfoModel) {
group_key = info.group_key
group_name = info.name
icon = info.groupIconURL
icon_index = info.icon_index
group_template = info.group_template
}
init?(map: Map) {}
mutating func mapping(map: Map) {
group_key <- map["group_key"]
group_name <- map["group_name"]
if group_name.isEmpty {
group_name <- map["name"]
}
icon <- map["icon"]
icon_index <- (map["icon_index"], kStrTransformInt)
group_template <- map["group_template"]
if icon.isEmpty {
icon = group_template?.icon ?? ""
}
}
mutating func apply(_ info: GroupInfoModel) {
if group_name.isEmpty || group_name == "未知圈子" {
group_name = info.name
}
if icon.isEmpty { icon = info.groupIconURL }
if icon_index <= 1 { icon_index = info.icon_index }
if group_template == nil { group_template = info.group_template }
}
static func deduplicated(_ groups: [CommonGroupModel]) -> [CommonGroupModel] {
var seenKeys = Set<String>()
var result: [CommonGroupModel] = []
for group in groups {
let key = group.group_key.trimmingCharacters(in: .whitespacesAndNewlines)
guard !key.isEmpty, seenKeys.insert(key).inserted else { continue }
result.append(group)
}
return result
}
}
struct GroupMemberModel: Mappable, Equatable {
///
var user_id: String = ""
///
var head_pic: String = "1"
var avater: String = ""
var displayHeadPic: String {
HeadPic.resolved(head_pic, avater)
}
///
var userIcon: UIImage {
HeadPic.image(for: displayHeadPic)
}
///
var nick_name: String = ""
///
var remark: String = ""
var showName: String {
remark.isEmpty ? nick_name : remark
}
/// 1 2 3 4
var level: Int = 1
var vipIcon: UIImage {
switch level {
case 2: //
return UIImage(named: "VipIcon/2") ?? UIImage()
case 3: //
return UIImage(named: "VipIcon/1") ?? UIImage()
default:
return UIImage()
}
}
/// 0 1SOS 2
var status: Int = 0
///
var join_days: Int = 0
/// 使使
var join_time: Int64 = 0
/// 线
var is_online: Bool = false
///
var battery: String = ""
/// 0 1-36 MoodStatusCatalog
var mood: Int = 0
/// 线
var last_active_time: Int64 = 0
/// 线MQTT
var lastUpdateText: String {
let nowMs = Date().timeIntervalSince1970
let msgTimeMs = Double(last_active_time)
let diffSec = nowMs - msgTimeMs
let isOnline = diffSec < 60
let lastUpdateText: String
if isOnline {
lastUpdateText = ""
} else if diffSec < 3600 {
lastUpdateText = "\(Int(diffSec / 60))分钟前"
} else if diffSec < 86400 {
lastUpdateText = "\(Int(diffSec / 3600))小时前"
} else {
lastUpdateText = "\(Int(diffSec / 86400))天前"
}
return lastUpdateText
}
///
var last_position: String = ""
///
var common_groups: [CommonGroupModel] = []
/// extra.relation_idx
var extra = MemberExtra()
var lastLocation: String {
guard last_position.contains(":") else { return "" }
let arr = last_position.components(separatedBy: ":")
return arr.last ?? ""
}
init?(map: Map) {
}
mutating func mapping(map: Map) {
user_id <- map["user_id"]
head_pic <- (map["head_pic"], kFlexString)
avater <- (map["avater"], kFlexString)
if avater.isEmpty {
avater <- (map["avatar"], kFlexString)
}
level <- (map["level"], kStrTransformInt)
nick_name <- map["nick_name"]
remark <- map["remark"]
battery <- map["battery"]
last_position <- map["last_position"]
is_online <- map["is_online"]
last_active_time <- map["last_active_time"]
status <- map["status"]
mood <- (map["mood"], kStrTransformInt)
join_days <- (map["join_days"], kStrTransformInt)
join_time <- (map["join_time"], kGroupTransformInt64)
common_groups <- map["common_groups"]
common_groups = CommonGroupModel.deduplicated(common_groups)
extra <- (map["extra"], kMemberExtraTransform)
if extra.relation_idx.isEmpty {
extra.relation_idx <- (map["relation_idx"], kMemberRelationIndexTransform)
}
}
}
struct MemberExtra: Mappable, Equatable {
var relation_idx: String = ""
var privilege = GroupPrivilege()
init() {}
init?(map: Map) {}
mutating func mapping(map: Map) {
relation_idx <- (map["relation_idx"], kMemberRelationIndexTransform)
privilege <- map["privilege"]
}
static func decode(_ value: Any?) -> MemberExtra {
if let extra = value as? MemberExtra {
return extra
}
if let json = value as? [String: Any] {
return MemberExtra(JSON: json) ?? MemberExtra()
}
if let json = value as? NSDictionary,
let dictionary = json as? [String: Any] {
return MemberExtra(JSON: dictionary) ?? MemberExtra()
}
let data: Data?
if let value = value as? String {
data = value.data(using: .utf8)
} else {
data = value as? Data
}
guard let data,
let json = try? JSONSerialization.jsonObject(with: data) as? [String: Any] else {
return MemberExtra()
}
return MemberExtra(JSON: json) ?? MemberExtra()
}
static func relationIndex(from value: Any?) -> String {
let raw = transformStr(value) ?? ""
return raw.trimmingCharacters(in: .whitespacesAndNewlines)
}
}
let kMemberRelationIndexTransform = TransformOf<String, Any>(fromJSON: { value in
MemberExtra.relationIndex(from: value)
}, toJSON: { value in
value
})
let kMemberExtraTransform = TransformOf<MemberExtra, Any>(fromJSON: { value in
MemberExtra.decode(value)
}, toJSON: { value in
value?.toJSON()
})
extension GroupMemberModel {
/// New responses use seconds; older payloads may still contain milliseconds.
var joinDate: Date? {
guard join_time > 0 else { return nil }
let seconds = join_time >= 100_000_000_000
? TimeInterval(join_time) / 1000
: TimeInterval(join_time)
return Date(timeIntervalSince1970: seconds)
}
}
enum GroupOwnerMatcher {
static func matches(userId: String, groupKey: String) -> Bool {
!userId.isEmpty && !groupKey.isEmpty && groupKey.contains(userId)
}
}
enum GroupMemberSorter {
/// -> -> 线 ->
static func sorted(_ members: [GroupMemberModel],
groupKey: String,
currentUserId: String) -> [GroupMemberModel] {
members.enumerated()
.sorted { lhs, rhs in
let lhsPriority = priority(lhs.element,
groupKey: groupKey,
currentUserId: currentUserId)
let rhsPriority = priority(rhs.element,
groupKey: groupKey,
currentUserId: currentUserId)
if lhsPriority != rhsPriority { return lhsPriority < rhsPriority }
return lhs.offset < rhs.offset
}
.map(\.element)
}
private static func priority(_ member: GroupMemberModel,
groupKey: String,
currentUserId: String) -> Int {
if GroupOwnerMatcher.matches(userId: member.user_id, groupKey: groupKey) { return 0 }
if !currentUserId.isEmpty, member.user_id == currentUserId { return 1 }
if member.is_online { return 2 }
return 3
}
}
extension GroupMemberModel: IdentifiableType {
public typealias Identity = String
public var identity: String {
return user_id
}
}