添加群组cell

This commit is contained in:
Fin 2024-12-26 11:05:03 +08:00
parent 612cadafec
commit 3f8e7ef150
6 changed files with 198 additions and 173 deletions

View File

@ -103,7 +103,7 @@
0672CB06256903F700570C9D /* MessageListViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0672CB05256903F700570C9D /* MessageListViewModel.swift */; };
06787C392A710568008ABDD7 /* GesturePassTextView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 06787C382A710568008ABDD7 /* GesturePassTextView.swift */; };
06787C3B2AB82BDB008ABDD7 /* CrashReportViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 06787C3A2AB82BDB008ABDD7 /* CrashReportViewController.swift */; };
067B2EB525693E38008B6BE1 /* MessageTableViewCellViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 067B2EB425693E38008B6BE1 /* MessageTableViewCellViewModel.swift */; };
067B2EB525693E38008B6BE1 /* MessageSection.swift in Sources */ = {isa = PBXBuildFile; fileRef = 067B2EB425693E38008B6BE1 /* MessageSection.swift */; };
06802E5320ECC40C00767047 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 0661A549204FDA4100965E4E /* Assets.xcassets */; };
06840DBB272298FB001B3193 /* BKColor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 06840DBA272298FB001B3193 /* BKColor.swift */; };
0687F2A82CCB791A00B2A52F /* UIFont+Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0687F2A72CCB791A00B2A52F /* UIFont+Extension.swift */; };
@ -344,7 +344,7 @@
0672CB05256903F700570C9D /* MessageListViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MessageListViewModel.swift; sourceTree = "<group>"; };
06787C382A710568008ABDD7 /* GesturePassTextView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GesturePassTextView.swift; sourceTree = "<group>"; };
06787C3A2AB82BDB008ABDD7 /* CrashReportViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CrashReportViewController.swift; sourceTree = "<group>"; };
067B2EB425693E38008B6BE1 /* MessageTableViewCellViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MessageTableViewCellViewModel.swift; sourceTree = "<group>"; };
067B2EB425693E38008B6BE1 /* MessageSection.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MessageSection.swift; sourceTree = "<group>"; };
0683486A2050F1310024B6DA /* Bark.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = Bark.entitlements; sourceTree = "<group>"; };
0683487020510FB20024B6DA /* UserNotifications.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = UserNotifications.framework; path = System/Library/Frameworks/UserNotifications.framework; sourceTree = SDKROOT; };
0683487220510FB20024B6DA /* UserNotificationsUI.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = UserNotificationsUI.framework; path = System/Library/Frameworks/UserNotificationsUI.framework; sourceTree = SDKROOT; };
@ -535,6 +535,7 @@
isa = PBXGroup;
children = (
06B1158E247BB1FB006D91FB /* Message.swift */,
067B2EB425693E38008B6BE1 /* MessageSection.swift */,
064CABA5256BE9510018155C /* PreviewModel.swift */,
06BD4DA92901352E003364DB /* Object+Dictionary.swift */,
061894C629A75BEA00E001C2 /* Algorithm.swift */,
@ -679,7 +680,6 @@
children = (
066890072D1946D500E106F2 /* MessageItemView.swift */,
0667D191247D162C005DE2ED /* MessageTableViewCell.swift */,
067B2EB425693E38008B6BE1 /* MessageTableViewCellViewModel.swift */,
0668900A2D19525400E106F2 /* ShowLessAndClearView.swift */,
0668900C2D19582400E106F2 /* MessageGroupHeaderView.swift */,
061C17072D1BDA4B00891D66 /* MessageGroupMoreView.swift */,
@ -1266,7 +1266,7 @@
06AE311E266F54CC00B39FBB /* GroupCellViewModel.swift in Sources */,
0637FA8620E0AB6600E80174 /* UIColor+Extension.swift in Sources */,
0637FA8A20E0D58800E80174 /* NewServerViewController.swift in Sources */,
067B2EB525693E38008B6BE1 /* MessageTableViewCellViewModel.swift in Sources */,
067B2EB525693E38008B6BE1 /* MessageSection.swift in Sources */,
0637FA8220E09C4B00E80174 /* BarkNavigationController.swift in Sources */,
06AE3118266F4E2E00B39FBB /* GroupFilterViewController.swift in Sources */,
0637FA7A20E092B300E80174 /* Observable+Extension.swift in Sources */,

View File

@ -45,11 +45,12 @@ class MessageListViewController: BaseViewController<MessageListViewModel> {
return UIBarButtonItem(customView: btn)
}()
let tableView: UITableView = {
lazy var tableView: UITableView = {
let tableView = UITableView()
tableView.separatorStyle = .none
tableView.backgroundColor = BKColor.background.primary
tableView.register(MessageTableViewCell.self, forCellReuseIdentifier: "\(MessageTableViewCell.self)")
tableView.register(MessageGroupTableViewCell.self, forCellReuseIdentifier: "\(MessageGroupTableViewCell.self)")
// LargeTitle LargeTitle
//
// tableView.contentInset = UIEdgeInsets(top: 20, left: 0, bottom: 0, right: 0)
@ -57,6 +58,10 @@ class MessageListViewController: BaseViewController<MessageListViewModel> {
// contentInset header
tableView.tableHeaderView = UIView(frame: CGRect(x: 0, y: 0, width: 0, height: 20))
tableView.rx.setDelegate(self).disposed(by: rx.disposeBag)
tableView.mj_footer = MJRefreshAutoFooter()
tableView.refreshControl = UIRefreshControl()
return tableView
}()
@ -71,9 +76,6 @@ class MessageListViewController: BaseViewController<MessageListViewModel> {
tableView.snp.makeConstraints { make in
make.edges.equalToSuperview()
}
tableView.rx.setDelegate(self).disposed(by: rx.disposeBag)
tableView.mj_footer = MJRefreshAutoFooter()
tableView.refreshControl = UIRefreshControl()
// tab
Client.shared.currentTabBarController?
@ -99,12 +101,108 @@ class MessageListViewController: BaseViewController<MessageListViewModel> {
self?.tableView.refreshControl?.sendActions(for: .valueChanged)
}).disposed(by: rx.disposeBag)
}
// tableView
private lazy var dataSource = RxTableViewSectionedAnimatedDataSource<MessageSection>(
animationConfiguration: AnimationConfiguration(
insertAnimation: .none,
reloadAnimation: .none,
deleteAnimation: .left
),
configureCell: { _, tableView, _, item -> UITableViewCell in
switch item {
case .message(let message):
guard let cell = tableView.dequeueReusableCell(withIdentifier: "\(MessageTableViewCell.self)") as? MessageTableViewCell else {
return UITableViewCell()
}
cell.message = message
return cell
case .messageGroup(let title, let totalCount, let messages):
guard let cell = tableView.dequeueReusableCell(withIdentifier: "\(MessageGroupTableViewCell.self)") as? MessageGroupTableViewCell else {
return UITableViewCell()
}
cell.showLessAction = { [weak self, weak cell] in
guard let self else { return }
UIView.animate(withDuration: 0.6, delay: 0, usingSpringWithDamping: 0.8, initialSpringVelocity: 0.2) {
self.tableView.performBatchUpdates {
cell?.isExpanded = false
}
}
}
cell.messages = messages
cell.groupName = title
cell.moreCount = max(0, totalCount - messages.count)
return cell
}
}, canEditRowAtIndexPath: { _, _ in
true
}
)
override func bindViewModel() {
guard let deleteBtn = deleteButton.customView as? BKButton else {
guard let groupBtn = groupButton.customView as? BKButton else {
return
}
let batchDelete = deleteBtn.rx
let itemSelected = tableView.rx.itemSelected.asDriver().compactMap { [weak self] indexPath -> Int? in
guard let self else { return nil }
if let cell = self.tableView.cellForRow(at: indexPath) as? MessageGroupTableViewCell {
if !cell.isExpanded {
UIView.animate(withDuration: 0.6, delay: 0, usingSpringWithDamping: 0.8, initialSpringVelocity: 0.2) {
self.tableView.performBatchUpdates {
cell.isExpanded = true
}
}
return nil
}
}
return indexPath.row
}
let output = viewModel.transform(
input: MessageListViewModel.Input(
refresh: tableView.refreshControl!.rx.controlEvent(.valueChanged).asDriver(),
loadMore: tableView.mj_footer!.rx.refresh.asDriver(),
itemDelete: tableView.rx.itemDeleted.asDriver().map { $0.row },
itemSelected: itemSelected,
delete: getBatchDeleteDriver(),
groupTap: groupBtn.rx.tap.asDriver(),
searchText: navigationItem.searchController!.searchBar.rx.text.asObservable()
))
// tableView
output.refreshAction
.drive(tableView.rx.refreshAction)
.disposed(by: rx.disposeBag)
output.messages
.drive(tableView.rx.items(dataSource: dataSource))
.disposed(by: rx.disposeBag)
// messagealert
output.alertMessage.drive(onNext: { [weak self] message in
self?.alertMessage(message: message.0, indexPath: IndexPath(row: message.1, section: 0))
}).disposed(by: rx.disposeBag)
//
output.groupFilter
.drive(onNext: { [weak self] groupModel in
self?.navigationController?.present(BarkNavigationController(rootViewController: GroupFilterViewController(viewModel: groupModel)), animated: true, completion: nil)
}).disposed(by: rx.disposeBag)
//
output.title
.drive(self.navigationItem.rx.title).disposed(by: rx.disposeBag)
}
private func getBatchDeleteDriver() -> Driver<MessageDeleteType> {
guard let deleteBtn = deleteButton.customView as? BKButton else {
return Driver.never()
}
return deleteBtn.rx
.tap
.flatMapLatest { _ -> PublishRelay<MessageDeleteType> in
let relay = PublishRelay<MessageDeleteType>()
@ -144,66 +242,10 @@ class MessageListViewController: BaseViewController<MessageListViewModel> {
return relay
}
guard let groupBtn = groupButton.customView as? BKButton else {
return
}
let output = viewModel.transform(
input: MessageListViewModel.Input(
refresh: tableView.refreshControl!.rx.controlEvent(.valueChanged).asDriver(),
loadMore: tableView.mj_footer!.rx.refresh.asDriver(),
itemDelete: tableView.rx.itemDeleted.asDriver().map { $0.row },
itemSelected: tableView.rx.itemSelected.asDriver().map { $0.row },
delete: batchDelete.asDriver(onErrorDriveWith: .empty()),
groupTap: groupBtn.rx.tap.asDriver(),
searchText: navigationItem.searchController!.searchBar.rx.text.asObservable()
))
// tableView
output.refreshAction
.drive(tableView.rx.refreshAction)
.disposed(by: rx.disposeBag)
// tableView
let dataSource = RxTableViewSectionedAnimatedDataSource<MessageSection>(
animationConfiguration: AnimationConfiguration(
insertAnimation: .none,
reloadAnimation: .none,
deleteAnimation: .left
),
configureCell: { _, tableView, _, item -> UITableViewCell in
guard let cell = tableView.dequeueReusableCell(withIdentifier: "\(MessageTableViewCell.self)") as? MessageTableViewCell else {
return UITableViewCell()
}
cell.message = item.message
return cell
}, canEditRowAtIndexPath: { _, _ in
true
}
)
output.messages
.drive(tableView.rx.items(dataSource: dataSource))
.disposed(by: rx.disposeBag)
// messagealert
output.alertMessage.drive(onNext: { [weak self] message in
self?.alertMessage(message: message.0, indexPath: IndexPath(row: message.1, section: 0))
}).disposed(by: rx.disposeBag)
//
output.groupFilter
.drive(onNext: { [weak self] groupModel in
self?.navigationController?.present(BarkNavigationController(rootViewController: GroupFilterViewController(viewModel: groupModel)), animated: true, completion: nil)
}).disposed(by: rx.disposeBag)
//
output.title
.drive(self.navigationItem.rx.title).disposed(by: rx.disposeBag)
.asDriver(onErrorDriveWith: .empty())
}
func alertMessage(message: String, indexPath: IndexPath) {
private func alertMessage(message: String, indexPath: IndexPath) {
let alertController = UIAlertController(title: nil, message: nil, preferredStyle: .actionSheet)
let copyAction = UIAlertAction(title: NSLocalizedString("CopyAll"), style: .default, handler: { [weak self]
(_: UIAlertAction) in

View File

@ -12,13 +12,6 @@ import RxCocoa
import RxDataSources
import RxSwift
enum MessageListCellItem {
///
case message(model: Message)
///
case messageGroup(group: [Message])
}
class MessageListViewModel: ViewModel, ViewModelType {
struct Input {
var refresh: Driver<Void>
@ -67,7 +60,10 @@ class MessageListViewModel: ViewModel, ViewModelType {
}
var messages: [Message] = []
for i in startIndex..<endIndex {
messages.append(result[i])
// messages.append(result[i].freeze())
// freeze freeze copy
// copy message 访退
messages.append(result[i].copyMessage())
}
page += 1
return messages
@ -81,7 +77,6 @@ class MessageListViewModel: ViewModel, ViewModelType {
return ("", 0)
}
let message = results[index]
// let message = model.message
var copyContent: String = ""
if let title = message.title {
@ -113,10 +108,8 @@ class MessageListViewModel: ViewModel, ViewModelType {
// Message MessageSection
func messagesToMessageSection(messages: [Message]) -> [MessageSection] {
let cellViewModels = messages.map { message -> MessageTableViewCellViewModel in
MessageTableViewCellViewModel(message: message)
}
return [MessageSection(header: "model", messages: cellViewModels)]
let items = messages.map { MessageListCellItem.message(model: $0) }
return [MessageSection(header: "model", messages: items)]
}
//
filterGroups
@ -161,16 +154,14 @@ class MessageListViewModel: ViewModel, ViewModelType {
.subscribe(onNext: { [weak self] in
guard let strongSelf = self else { return }
let messages = strongSelf.getNextPage()
let cellViewModels = messages.map { message -> MessageTableViewCellViewModel in
MessageTableViewCellViewModel(message: message)
}
let items = messages.map { MessageListCellItem.message(model: $0) }
refreshAction.accept(.endLoadmore)
if var section = messagesRelay.value.first {
section.messages.append(contentsOf: cellViewModels)
section.messages.append(contentsOf: items)
messagesRelay.accept([section])
} else {
messagesRelay.accept([MessageSection(header: "model", messages: cellViewModels)])
messagesRelay.accept([MessageSection(header: "model", messages: items)])
}
}).disposed(by: rx.disposeBag)

View File

@ -6,9 +6,9 @@
// Copyright © 2020 Fin. All rights reserved.
//
//import IceCream
import RealmSwift
import UIKit
class Message: Object {
@objc dynamic var id = NSUUID().uuidString
@objc dynamic var title: String?
@ -25,7 +25,16 @@ class Message: Object {
override class func indexedProperties() -> [String] {
return ["group", "createDate"]
}
func copyMessage() -> Message {
let message = Message()
message.id = self.id
message.title = self.title
message.subtitle = self.subtitle
message.body = self.body
message.url = self.url
message.group = self.group
message.createDate = self.createDate
return message
}
}
//extension Message: CKRecordConvertible {}
//extension Message: CKRecordRecoverable {}

View File

@ -0,0 +1,66 @@
//
// MessageTableViewCellViewModel.swift
// Bark
//
// Created by huangfeng on 2020/11/21.
// Copyright © 2020 Fin. All rights reserved.
//
import Differentiator
import Foundation
import RxCocoa
import RxDataSources
enum MessageListCellItem: Equatable {
///
case message(model: Message)
///
case messageGroup(name: String, totalCount: Int, messages: [Message])
static func == (lhs: Self, rhs: Self) -> Bool {
switch (lhs, rhs) {
case (.message(let l), .message(let r)):
return l == r
case (.messageGroup(let l, _, _), .messageGroup(let r, _, _)):
return l == r
default:
return false
}
}
}
struct MessageSection {
var header: String
var messages: [MessageListCellItem]
}
extension MessageSection: AnimatableSectionModelType {
typealias Item = MessageListCellItem
typealias Identity = String
var items: [MessageListCellItem] {
return self.messages
}
init(original: MessageSection, items: [MessageListCellItem]) {
self = original
self.messages = items
}
var identity: String {
return header
}
}
extension MessageListCellItem: IdentifiableType {
typealias Identity = String
var identity: String {
switch self {
case .message(let model):
return model.id
case .messageGroup(let name, _, _):
return name
}
}
}

View File

@ -1,83 +0,0 @@
//
// MessageTableViewCellViewModel.swift
// Bark
//
// Created by huangfeng on 2020/11/21.
// Copyright © 2020 Fin. All rights reserved.
//
import Differentiator
import Foundation
import RxCocoa
import RxDataSources
class MessageTableViewCellViewModel: ViewModel {
// 使crash
let message: Message
var identity: String
let title: BehaviorRelay<String>
let subtitle: BehaviorRelay<String>
let body: BehaviorRelay<String>
let url: BehaviorRelay<String>
let date = BehaviorRelay<String>(value: "")
var dateStyle = BehaviorRelay<MessageListCellDateStyle>(value: .relative)
init(message: Message) {
self.message = message
self.identity = message.id
self.title = BehaviorRelay<String>(value: message.title ?? "")
self.subtitle = BehaviorRelay<String>(value: message.subtitle ?? "")
self.body = BehaviorRelay<String>(value: message.body ?? "")
self.url = BehaviorRelay<String>(value: message.url ?? "")
super.init()
dateStyle.map { style in
switch style {
case .relative:
return self.message.createDate?.agoFormatString() ?? ""
case .exact:
return self.message.createDate?.formatString(format: "yyyy-MM-dd HH:mm") ?? ""
}
}
.bind(to: date)
.disposed(by: rx.disposeBag)
}
}
struct MessageSection {
var header: String
var messages: [MessageTableViewCellViewModel]
}
extension MessageSection: AnimatableSectionModelType {
typealias Item = MessageTableViewCellViewModel
typealias Identity = String
var items: [MessageTableViewCellViewModel] {
return self.messages
}
init(original: MessageSection, items: [MessageTableViewCellViewModel]) {
self = original
self.messages = items
}
var identity: String {
return header
}
}
extension MessageTableViewCellViewModel: IdentifiableType {
typealias Identity = String
override func isEqual(_ object: Any?) -> Bool {
if let obj = object as? MessageTableViewCellViewModel {
// cell12 ...
return self.identity == obj.identity && self.date.value == obj.date.value
}
return super.isEqual(object)
}
}