mirror of
https://github.com/Finb/Bark.git
synced 2025-12-08 21:36:01 +00:00
消息列表页面可切换按群组展示。 close: #141
This commit is contained in:
parent
3f8e7ef150
commit
9dbd9aec06
22
Bark/Assets.xcassets/group_collapse.imageset/Contents.json
vendored
Normal file
22
Bark/Assets.xcassets/group_collapse.imageset/Contents.json
vendored
Normal 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 |
22
Bark/Assets.xcassets/group_expand.imageset/Contents.json
vendored
Normal file
22
Bark/Assets.xcassets/group_expand.imageset/Contents.json
vendored
Normal 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
|
||||
}
|
||||
}
|
||||
BIN
Bark/Assets.xcassets/group_expand.imageset/folder_code_24dp_5F6368_FILL0_wght400_GRAD0_opsz24 1.png
vendored
Normal file
BIN
Bark/Assets.xcassets/group_expand.imageset/folder_code_24dp_5F6368_FILL0_wght400_GRAD0_opsz24 1.png
vendored
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 3.5 KiB |
BIN
Bark/Assets.xcassets/group_expand.imageset/folder_code_24dp_5F6368_FILL0_wght400_GRAD0_opsz24.png
vendored
Normal file
BIN
Bark/Assets.xcassets/group_expand.imageset/folder_code_24dp_5F6368_FILL0_wght400_GRAD0_opsz24.png
vendored
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 3.9 KiB |
@ -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)
|
||||
|
||||
// 标题
|
||||
|
||||
@ -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()
|
||||
)
|
||||
}
|
||||
|
||||
@ -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 ?? ""
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -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()
|
||||
}
|
||||
|
||||
@ -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)
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user