消息列表页面可切换按群组展示。 close: #141

This commit is contained in:
Fin 2024-12-26 17:23:19 +08:00
parent 3f8e7ef150
commit 9dbd9aec06
11 changed files with 212 additions and 98 deletions

View File

@ -0,0 +1,22 @@
{
"images" : [
{
"idiom" : "universal",
"scale" : "1x"
},
{
"filename" : "folder_code_24dp_5F6368_FILL0_wght400_GRAD0_opsz242 (3).png",
"idiom" : "universal",
"scale" : "2x"
},
{
"filename" : "folder_code_24dp_5F6368_FILL0_wght400_GRAD0_opsz242 (2).png",
"idiom" : "universal",
"scale" : "3x"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.6 KiB

View File

@ -0,0 +1,22 @@
{
"images" : [
{
"idiom" : "universal",
"scale" : "1x"
},
{
"filename" : "folder_code_24dp_5F6368_FILL0_wght400_GRAD0_opsz24 1.png",
"idiom" : "universal",
"scale" : "2x"
},
{
"filename" : "folder_code_24dp_5F6368_FILL0_wght400_GRAD0_opsz24.png",
"idiom" : "universal",
"scale" : "3x"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.9 KiB

View File

@ -40,11 +40,15 @@ class MessageListViewController: BaseViewController<MessageListViewModel> {
let groupButton: UIBarButtonItem = {
let btn = BKButton()
btn.setImage(UIImage(named: "baseline_folder_open_black_24pt"), for: .normal)
btn.setImage(UIImage(named: "group_expand")?.withRenderingMode(.alwaysTemplate), for: .normal)
btn.setImage(UIImage(named: "group_collapse")?.withRenderingMode(.alwaysTemplate), for: .selected)
btn.imageView?.tintColor = UIColor.black
btn.frame = CGRect(x: 0, y: 0, width: 40, height: 40)
return UIBarButtonItem(customView: btn)
}()
private var expandedGroup: Set<String> = []
lazy var tableView: UITableView = {
let tableView = UITableView()
tableView.separatorStyle = .none
@ -128,11 +132,13 @@ class MessageListViewController: BaseViewController<MessageListViewModel> {
self.tableView.performBatchUpdates {
cell?.isExpanded = false
}
if let groupName = cell?.cellData?.groupName {
self.expandedGroup.remove(groupName)
}
}
}
cell.messages = messages
cell.groupName = title
cell.moreCount = max(0, totalCount - messages.count)
cell.cellData = (title, max(0, totalCount - messages.count), messages)
cell.isExpanded = self.expandedGroup.contains(title)
return cell
}
@ -155,6 +161,9 @@ class MessageListViewController: BaseViewController<MessageListViewModel> {
cell.isExpanded = true
}
}
if let groupName = cell.cellData?.groupName {
self.expandedGroup.insert(groupName)
}
return nil
}
}
@ -169,7 +178,7 @@ class MessageListViewController: BaseViewController<MessageListViewModel> {
itemDelete: tableView.rx.itemDeleted.asDriver().map { $0.row },
itemSelected: itemSelected,
delete: getBatchDeleteDriver(),
groupTap: groupBtn.rx.tap.asDriver(),
groupToggleTap: groupBtn.rx.tap.asDriver(),
searchText: navigationItem.searchController!.searchBar.rx.text.asObservable()
))
@ -188,9 +197,9 @@ class MessageListViewController: BaseViewController<MessageListViewModel> {
}).disposed(by: rx.disposeBag)
//
output.groupFilter
.drive(onNext: { [weak self] groupModel in
self?.navigationController?.present(BarkNavigationController(rootViewController: GroupFilterViewController(viewModel: groupModel)), animated: true, completion: nil)
output.type
.drive(onNext: { [weak self] type in
(self?.groupButton.customView as? UIButton)?.isSelected = type == .group
}).disposed(by: rx.disposeBag)
//

View File

@ -12,28 +12,58 @@ import RxCocoa
import RxDataSources
import RxSwift
enum MessageListType {
//
case list
//
case group
}
class MessageListViewModel: ViewModel, ViewModelType {
struct Input {
///
var refresh: Driver<Void>
///
var loadMore: Driver<Void>
///
var itemDelete: Driver<Int>
///
var itemSelected: Driver<Int>
///
var delete: Driver<MessageDeleteType>
var groupTap: Driver<Void>
///
var groupToggleTap: Driver<Void>
///
var searchText: Observable<String?>
}
struct Output {
///
var messages: Driver<[MessageSection]>
///
var refreshAction: Driver<MJRefreshAction>
///
var alertMessage: Driver<(String, Int)>
var groupFilter: Driver<GroupFilterViewModel>
///
var type: Driver<MessageListType>
///
var title: Driver<String>
}
///
private var type: MessageListType = .list
///
private var page = 0
///
private let pageCount = 20
///
private var groups: Results<Message>?
///
private var results: Results<Message>?
//
///
private func getResults(filterGroups: [String?], searchText: String?) -> Results<Message>? {
if let realm = try? Realm() {
var results = realm.objects(Message.self)
@ -49,26 +79,78 @@ class MessageListViewModel: ViewModel, ViewModelType {
return nil
}
private var page = 0
private let pageCount = 20
private func getNextPage() -> [Message] {
if let result = results {
let startIndex = page * pageCount
let endIndex = min(startIndex + pageCount, result.count)
guard endIndex > startIndex else {
return []
}
var messages: [Message] = []
for i in startIndex..<endIndex {
// messages.append(result[i].freeze())
// freeze freeze copy
// copy message 访退
messages.append(result[i].copyMessage())
}
page += 1
return messages
///
private func getGroups() -> Results<Message>? {
if let realm = try? Realm() {
return realm.objects(Message.self)
.sorted(byKeyPath: "createDate", ascending: false)
.distinct(by: ["group"])
// .value(forKeyPath: "group") as? [String?] ?? []
}
return []
return nil
}
/// message
private func getListNextPage() -> [MessageListCellItem] {
guard let result = results else {
return []
}
let startIndex = page * pageCount
let endIndex = min(startIndex + pageCount, result.count)
guard endIndex > startIndex else {
return []
}
var messages: [MessageListCellItem] = []
for i in startIndex..<endIndex {
// messages.append(result[i].freeze())
// freeze freeze copy
// copy message 访退
messages.append(.message(model: result[i].copyMessage()))
}
page += 1
return messages
}
/// group
private func getGroupNextPage() -> [MessageListCellItem] {
guard let groups, let results else {
return []
}
let startIndex = page * pageCount
let endIndex = min(startIndex + pageCount, groups.count)
guard endIndex > startIndex else {
return []
}
var items: [MessageListCellItem] = []
for i in startIndex..<endIndex {
let group = groups[i].group
let messageResult: Results<Message>
if let group {
messageResult = results.filter("group == %@", group)
} else {
messageResult = results.filter("group == nil")
}
var messages: [Message] = []
for i in 0..<min(messageResult.count, 5) {
messages.append(messageResult[i].copyMessage())
}
if messages.count > 0 {
items.append(.messageGroup(name: group ?? NSLocalizedString("default"), totalCount: messageResult.count, messages: messages))
}
}
page += 1
return items
}
private func getNextPage() -> [MessageListCellItem] {
if type == .list {
return getListNextPage()
}
return getGroupNextPage()
}
func transform(input: Input) -> Output {
@ -126,22 +208,29 @@ class MessageListViewModel: ViewModel, ViewModelType {
.combineLatest(filterGroups, input.searchText)
.subscribe(onNext: { [weak self] groups, searchText in
self?.results = self?.getResults(filterGroups: groups, searchText: searchText)
self?.groups = self?.getGroups()
}).disposed(by: rx.disposeBag)
//
let messageTypeChanged = input.groupToggleTap.compactMap { () -> MessageListType? in
self.type = self.type == .group ? .list : .group
return self.type
}
//
Observable
.merge(
input.refresh.asObservable().map { () },
filterGroups.map { _ in () },
input.searchText.asObservable().map { _ in () }
input.searchText.asObservable().map { _ in () },
messageTypeChanged.asObservable().map { _ in () }
)
.subscribe(onNext: { [weak self] in
guard let strongSelf = self else { return }
strongSelf.page = 0
let messages = strongSelf.getNextPage()
messagesRelay.accept(
messagesToMessageSection(
messages: strongSelf.getNextPage()
)
[MessageSection(header: "model", messages: messages)]
)
refreshAction.accept(.endRefresh)
}).disposed(by: rx.disposeBag)
@ -153,8 +242,7 @@ class MessageListViewModel: ViewModel, ViewModelType {
.delay(.milliseconds(10), scheduler: MainScheduler.instance)
.subscribe(onNext: { [weak self] in
guard let strongSelf = self else { return }
let messages = strongSelf.getNextPage()
let items = messages.map { MessageListCellItem.message(model: $0) }
let items = strongSelf.getNextPage()
refreshAction.accept(.endLoadmore)
if var section = messagesRelay.value.first {
@ -205,48 +293,15 @@ class MessageListViewModel: ViewModel, ViewModelType {
}
strongSelf.page = 0
messagesRelay.accept(messagesToMessageSection(messages: strongSelf.getNextPage()))
messagesRelay.accept([MessageSection(header: "model", messages: strongSelf.getNextPage())])
}).disposed(by: rx.disposeBag)
//
let groupFilter = input.groupTap.compactMap { () -> GroupFilterViewModel? in
if let realm = try? Realm() {
let groups = realm.objects(Message.self)
.distinct(by: ["group"])
.value(forKeyPath: "group") as? [String?]
let groupModels = groups?.compactMap { groupName -> GroupFilterModel in
var check = true
if filterGroups.value.count > 0 {
check = filterGroups.value.contains(groupName)
}
return GroupFilterModel(name: groupName, checked: check)
}
if let models = groupModels {
let viewModel = GroupFilterViewModel(groups: models)
// group
viewModel.done.subscribe(onNext: { filterGroups in
Settings["me.fin.filterGroups"] = filterGroups
}).disposed(by: viewModel.rx.disposeBag)
//
viewModel.done
.bind(to: filterGroups)
.disposed(by: viewModel.rx.disposeBag)
return viewModel
}
}
return nil
}
return Output(
messages: messagesRelay.asDriver(onErrorJustReturn: []),
refreshAction: refreshAction.asDriver(),
alertMessage: alertMessage,
groupFilter: groupFilter.asDriver(),
type: Driver.merge(messageTypeChanged.asDriver(), Driver.just(self.type)),
title: titleRelay.asDriver()
)
}

View File

@ -59,8 +59,8 @@ extension MessageListCellItem: IdentifiableType {
switch self {
case .message(let model):
return model.id
case .messageGroup(let name, _, _):
return name
case .messageGroup(_, _, let messages):
return messages.first?.id ?? ""
}
}
}

View File

@ -85,13 +85,12 @@ class MessageItemView: UIView {
blackMaskView.alpha = maskAlpha
}
}
var isShowShadow: Bool = false {
var isShowSubviews: Bool = true {
didSet {
panel.layer.shadowColor = UIColor.black.withAlphaComponent(0.1).cgColor
panel.layer.shadowOffset = CGSize(width: 0, height: 5)
panel.layer.shadowRadius = 5
panel.layer.shadowOpacity = isShowShadow ? 0.05 : 0
for view in [bodyLabel, dateLabel] {
view.alpha = isShowSubviews ? 1 : 0
}
}
}
@ -116,13 +115,6 @@ class MessageItemView: UIView {
}).disposed(by: rx.disposeBag)
}
convenience init(isShowShadow: Bool) {
self.init()
defer {
self.isShowShadow = isShowShadow
}
}
@available(*, unavailable)
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
@ -142,7 +134,7 @@ class MessageItemView: UIView {
panel.snp.makeConstraints { make in
make.left.equalToSuperview().offset(16)
make.right.equalToSuperview().offset(-16).priority(.medium)
make.right.equalToSuperview().offset(-16)
make.top.equalToSuperview()
make.bottom.equalToSuperview()
}

View File

@ -51,11 +51,11 @@ class MessageGroupTableViewCell: UITableViewCell {
/// 5 5
private let messageViews = [
MessageItemView(isShowShadow: true),
MessageItemView(isShowShadow: true),
MessageItemView(isShowShadow: true),
MessageItemView(isShowShadow: true),
MessageItemView(isShowShadow: true)
MessageItemView(),
MessageItemView(),
MessageItemView(),
MessageItemView(),
MessageItemView()
]
/// header
@ -75,7 +75,7 @@ class MessageGroupTableViewCell: UITableViewCell {
}
///
var messages: [Message] = [] {
private var messages: [Message] = [] {
didSet {
for (index, item) in messageViews.enumerated() {
if index < messages.count {
@ -85,19 +85,18 @@ class MessageGroupTableViewCell: UITableViewCell {
item.isHidden = true
}
}
refreshLayout()
}
}
///
var moreCount: Int = 0 {
private var moreCount: Int = 0 {
didSet {
moreView.count = moreCount
}
}
///
var groupName: String? {
private var groupName: String? {
set {
if let newValue, !newValue.isEmpty {
groupHeader.groupName = newValue
@ -110,6 +109,14 @@ class MessageGroupTableViewCell: UITableViewCell {
}
}
var cellData: (groupName: String?, moreCount: Int, messages: [Message])? {
didSet {
groupName = cellData?.groupName ?? ""
moreCount = cellData?.moreCount ?? 0
messages = cellData?.messages ?? []
}
}
///
var showLessAction: (() -> Void)? {
get {
@ -199,6 +206,8 @@ class MessageGroupTableViewCell: UITableViewCell {
item.snp.remakeConstraints { make in
make.left.right.equalToSuperview()
if isExpanded {
item.isShowSubviews = true
if index == 0 {
make.top.equalTo(groupHeader.snp.bottom).offset(8)
} else {
@ -215,11 +224,16 @@ class MessageGroupTableViewCell: UITableViewCell {
} else {
if index == 0 {
make.top.equalToSuperview()
item.isShowSubviews = true
item.transform = .identity
} else {
// 1
make.top.equalToSuperview().offset(min(index * 8, 1 * 8))
make.height.equalTo(messageViews[0])
make.bottom.equalTo(messageViews[0].snp.bottom).offset(min(index * 8, 1 * 8))
make.height.equalTo(40)
item.isShowSubviews = false
// index
let scale = 1 - CGFloat(index) * 0.04
item.transform = CGAffineTransform(scaleX: scale, y: 1)