diff --git a/Bark/Assets.xcassets/group_collapse.imageset/Contents.json b/Bark/Assets.xcassets/group_collapse.imageset/Contents.json new file mode 100644 index 0000000..428c10f --- /dev/null +++ b/Bark/Assets.xcassets/group_collapse.imageset/Contents.json @@ -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 + } +} diff --git a/Bark/Assets.xcassets/group_collapse.imageset/folder_code_24dp_5F6368_FILL0_wght400_GRAD0_opsz242 (2).png b/Bark/Assets.xcassets/group_collapse.imageset/folder_code_24dp_5F6368_FILL0_wght400_GRAD0_opsz242 (2).png new file mode 100644 index 0000000..e266f8f Binary files /dev/null and b/Bark/Assets.xcassets/group_collapse.imageset/folder_code_24dp_5F6368_FILL0_wght400_GRAD0_opsz242 (2).png differ diff --git a/Bark/Assets.xcassets/group_collapse.imageset/folder_code_24dp_5F6368_FILL0_wght400_GRAD0_opsz242 (3).png b/Bark/Assets.xcassets/group_collapse.imageset/folder_code_24dp_5F6368_FILL0_wght400_GRAD0_opsz242 (3).png new file mode 100644 index 0000000..d8d768e Binary files /dev/null and b/Bark/Assets.xcassets/group_collapse.imageset/folder_code_24dp_5F6368_FILL0_wght400_GRAD0_opsz242 (3).png differ diff --git a/Bark/Assets.xcassets/group_expand.imageset/Contents.json b/Bark/Assets.xcassets/group_expand.imageset/Contents.json new file mode 100644 index 0000000..9b7b7f8 --- /dev/null +++ b/Bark/Assets.xcassets/group_expand.imageset/Contents.json @@ -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 + } +} diff --git a/Bark/Assets.xcassets/group_expand.imageset/folder_code_24dp_5F6368_FILL0_wght400_GRAD0_opsz24 1.png b/Bark/Assets.xcassets/group_expand.imageset/folder_code_24dp_5F6368_FILL0_wght400_GRAD0_opsz24 1.png new file mode 100644 index 0000000..2b4e58d Binary files /dev/null and b/Bark/Assets.xcassets/group_expand.imageset/folder_code_24dp_5F6368_FILL0_wght400_GRAD0_opsz24 1.png differ diff --git a/Bark/Assets.xcassets/group_expand.imageset/folder_code_24dp_5F6368_FILL0_wght400_GRAD0_opsz24.png b/Bark/Assets.xcassets/group_expand.imageset/folder_code_24dp_5F6368_FILL0_wght400_GRAD0_opsz24.png new file mode 100644 index 0000000..07aced2 Binary files /dev/null and b/Bark/Assets.xcassets/group_expand.imageset/folder_code_24dp_5F6368_FILL0_wght400_GRAD0_opsz24.png differ diff --git a/Controller/MessageListViewController.swift b/Controller/MessageListViewController.swift index e0e6311..f30a30b 100644 --- a/Controller/MessageListViewController.swift +++ b/Controller/MessageListViewController.swift @@ -40,11 +40,15 @@ class MessageListViewController: BaseViewController { 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 = [] + lazy var tableView: UITableView = { let tableView = UITableView() tableView.separatorStyle = .none @@ -128,11 +132,13 @@ class MessageListViewController: BaseViewController { 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 { cell.isExpanded = true } } + if let groupName = cell.cellData?.groupName { + self.expandedGroup.insert(groupName) + } return nil } } @@ -169,7 +178,7 @@ class MessageListViewController: BaseViewController { 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 { }).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) // 标题 diff --git a/Controller/MessageListViewModel.swift b/Controller/MessageListViewModel.swift index af953da..ee0cff6 100644 --- a/Controller/MessageListViewModel.swift +++ b/Controller/MessageListViewModel.swift @@ -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 + /// 加载更多 var loadMore: Driver + /// 删除 var itemDelete: Driver + /// 点击 var itemSelected: Driver + /// 批量删除 var delete: Driver - var groupTap: Driver + /// 切换群组和列表显示样式 + var groupToggleTap: Driver + /// 搜索 var searchText: Observable } struct Output { + /// 数据源 var messages: Driver<[MessageSection]> + /// 刷新控件状态 var refreshAction: Driver + /// 点击后,弹出提示 var alertMessage: Driver<(String, Int)> - var groupFilter: Driver + /// 群组过滤 + var type: Driver + /// 标题 var title: Driver } + + /// 当前显示类型 + private var type: MessageListType = .list + /// 当前页数 + private var page = 0 + /// 每页数量 + private let pageCount = 20 + + /// 全部群组 + private var groups: Results? + /// 全部数据(懒加载) private var results: Results? - // 根据群组获取消息 + /// 获取筛选后的全部数据源 (懒加载) private func getResults(filterGroups: [String?], searchText: String?) -> Results? { 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.. Results? { + 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.. [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.. + if let group { + messageResult = results.filter("group == %@", group) + } else { + messageResult = results.filter("group == nil") + } + + var messages: [Message] = [] + for i in 0.. 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() ) } diff --git a/Model/MessageSection.swift b/Model/MessageSection.swift index c30395a..c00519f 100644 --- a/Model/MessageSection.swift +++ b/Model/MessageSection.swift @@ -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 ?? "" } } } diff --git a/View/MessageList/MessageItemView.swift b/View/MessageList/MessageItemView.swift index c4f0c35..37b8290 100644 --- a/View/MessageList/MessageItemView.swift +++ b/View/MessageList/MessageItemView.swift @@ -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() } diff --git a/View/MessageList/MessageTableViewCell.swift b/View/MessageList/MessageTableViewCell.swift index 49c8bbd..3451f58 100644 --- a/View/MessageList/MessageTableViewCell.swift +++ b/View/MessageList/MessageTableViewCell.swift @@ -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)