Search for suggestion modules

dev
wenlei 11 months ago
parent b62de39551
commit 33577405d6

@ -8,12 +8,38 @@
import Foundation
import RxDataSources
struct MyComment: Codable {
let id: String?
let commenterId: String?
let nickName: String?
let createTime: String?
let commentContent: String?
let journalId: String?
let commenterAvatar: String?
let userId: String?
let content: String?
let journalImage: String?
private enum CodingKeys: String, CodingKey {
case id = "_id"
case commenterId
case nickName
case createTime
case commentContent
case journalId
case commenterAvatar
case userId
case content
case journalImage
}
}
struct MyCommentListSection {
var items: [Comment]
var items: [MyComment]
}
extension MyCommentListSection: SectionModelType {
typealias Item = Comment
typealias Item = MyComment
init(original: MyCommentListSection, items: [Item]) {
self = original
@ -45,16 +71,16 @@ struct Comment: Codable, IdentifiableType, Equatable {
let avatar: String?
let state: Int?
let userId: String?
let content: String?
var content: String?
let commentCount: Int?
let nickName: String?
let publishTime: String?
let location: String?
let parentId: String?
let id: String
let thumbupCountString: String?
var thumbupCountString: String?
let journalImage: String?
let haveThumbup: Bool?
var haveThumbup: Bool?
//
var parentCommentCount: Int?
@ -85,22 +111,42 @@ struct Comment: Codable, IdentifiableType, Equatable {
}
static func == (lhs: Comment, rhs: Comment) -> Bool {
return lhs.id == rhs.id
return lhs.id == rhs.id && lhs.haveThumbup == rhs.haveThumbup && lhs.content == rhs.content
}
}
enum CommentType {
enum CommentType: IdentifiableType, Equatable {
case comment(Comment)
case quote(Comment)
var identity: String {
switch self {
case .comment(let comment), .quote(let comment):
return comment.id
}
}
struct CommentSection {
var items: [CommentType]
static func == (lhs: CommentType, rhs: CommentType) -> Bool {
return lhs.identity == rhs.identity
}
}
extension CommentSection: SectionModelType {
typealias Item = CommentType
struct CommentSection: IdentifiableType, Equatable {
var id: String
var items: [Comment]
var identity: String {
return id
}
static func == (lhs: CommentSection, rhs: CommentSection) -> Bool {
return lhs.id == rhs.id && lhs.items == rhs.items
}
}
extension CommentSection: AnimatableSectionModelType {
typealias Item = Comment
init(original: CommentSection, items: [Item]) {
self = original
@ -111,5 +157,22 @@ extension CommentSection: SectionModelType {
struct CommentThumbState: Codable {
let thumbState: Bool
enum CodingKeys: String, CodingKey {
case thumbState
}
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
do {
thumbState = try container.decode(Bool.self, forKey: .thumbState)
} catch DecodingError.typeMismatch {
let intValue = try container.decode(Int.self, forKey: .thumbState)
thumbState = intValue != 0
}
}
}

@ -90,3 +90,20 @@ extension SearchCategorySection: SectionModelType {
self.items = items
}
}
struct SearchSuggestionSection: Codable {
var items: [String]
}
extension SearchSuggestionSection: SectionModelType {
typealias Item = String
init(original: SearchSuggestionSection, items: [Item]) {
self = original
self.items = items
}
}

@ -157,7 +157,6 @@ class HomeViewCell: UITableViewCell {
lazy var rollingNoticeView: GYRollingNoticeView = {
let rollingNoticeView = GYRollingNoticeView.init()
rollingNoticeView.backgroundColor = .red
rollingNoticeView.stayInterval = 5
rollingNoticeView.dataSource = self
// rollingNoticeView.delegate = self

@ -87,11 +87,14 @@ class AudioMoreActionViewModel: ViewModel, ViewModelType {
toShare.accept(())
case 1://
guard let id = audioTrack?.id else { return }
if isLike.value {
guard let id = audioTrack?.id,
let haveCollect = audioTrack.value?.haveCollect else { return }
if haveCollect {
self.requestCancelLike(journalNo: id)
.subscribe { _ in
isLike.accept(false)
var new = self.audioTrack.value
new?.haveCollect = false
self.audioTrack.accept(new)
dismiss.accept(())
} onError: { error in
@ -100,7 +103,9 @@ class AudioMoreActionViewModel: ViewModel, ViewModelType {
} else {
self.requestLike(journalNo: id)
.subscribe { _ in
isLike.accept(true)
var new = self.audioTrack.value
new?.haveCollect = true
self.audioTrack.accept(new)
dismiss.accept(())
} onError: { error in

@ -9,6 +9,7 @@ import UIKit
import RxSwift
import RxCocoa
import RxDataSources
import FSPopoverView
class CommentDetailViewController: TableViewController {
let commentDetailHeaderView: CommentDetailHeaderView = {
@ -23,23 +24,27 @@ class CommentDetailViewController: TableViewController {
return commentToolView
}()
private let menuView: FSPopoverListView = {
let menuView = FSPopoverListView.init(scrollDirection: .horizontal)
menuView.shadowOpacity = 0
menuView.shadowRadius = 0
menuView.shadowColor = .clear
menuView.cornerRadius = 3
menuView.transitioningDelegate = nil
menuView.arrowSize = CGSize.init(width: 18, height: 5)
menuView.backgroundColor = .primaryText()
return menuView
}()
override func viewDidLoad() {
super.viewDidLoad()
}
//
// override func viewWillAppear(_ animated: Bool) {
// super.viewWillAppear(animated)
//
// self.navigationController?.setNavigationBarHidden(true, animated: false)
// }
//
//
// override func viewWillDisappear(_ animated: Bool) {
// super.viewWillDisappear(animated)
// self.navigationController?.setNavigationBarHidden(false, animated: false)
// }
override func makeUI() {
super.makeUI()
@ -47,7 +52,6 @@ class CommentDetailViewController: TableViewController {
view.backgroundColor = .clear
view.addSubview(tableView)
self.navigationController?.view.backgroundColor = UIColor.clear
// UIColor.black.withAlphaComponent(0.5)
tableView.register(CommentViewCell.self, forCellReuseIdentifier: "CommentViewCell")
tableView.tableHeaderView = commentDetailHeaderView
@ -67,14 +71,55 @@ class CommentDetailViewController: TableViewController {
guard let viewModel = viewModel as? CommentDetailViewModel else { return }
let input = CommentDetailViewModel.Input.init(viewWillAppear: rx.viewWillAppear,
modelSelected: tableView.rx.modelSelected(CommentType.self).asDriver(),
modelSelected: tableView.rx.modelSelected(Comment.self).asDriver(),
footerRefresh: footerRefreshTrigger,
commentText: commentToolView.textField.rx.text.asDriver(),
sendCommentTrigger: commentToolView.textField.rx.controlEvent(.editingDidEndOnExit).asDriver())
let output = viewModel.transform(input: input)
let dataSource = CommentDetailViewController.dataSource()
let dataSource = RxTableViewSectionedAnimatedDataSource<CommentSection>(
animationConfiguration: AnimationConfiguration.init(insertAnimation: .top, reloadAnimation: .none, deleteAnimation: .automatic),
configureCell: { dataSource, tableView, indexPath, item in
let cell: CommentViewCell = tableView.dequeueReusableCell(withIdentifier: "CommentViewCell", for: indexPath) as! CommentViewCell
cell.comment = item
cell.likeButton.rx.tap.subscribe { _ in
viewModel.likeSelected.accept(item)
}.disposed(by: cell.rx.disposeBag)
// cell.avatarView.rx.tapGesture().when(.recognized)
// .subscribe { [weak self] tap in
// guard let userID = item.userId else { return }
//
// let personalViewModel = PersonalViewModel.init(userID: userID, provider: viewModel.provider)
// self?.navigator.show(segue: .personal(viewModel: personalViewModel), sender: self?.presentingViewController)
//
// }.disposed(by: cell.rx.disposeBag)
cell.rx.longPressGesture().when(.recognized)
.subscribe { [weak self] tap in
guard let self = self else { return }
if item.userId == UserDefaults.AccountInfo.string(forKey: .userID) {
self.menuView.items = self.setupMenuItems(cell: cell, features: [.copy, .report, .delete])
} else {
self.menuView.items = self.setupMenuItems(cell: cell, features: [.copy, .delete])
}
if let location = tap.element?.location(in: cell) {
self.menuView.present(fromPoint: location, in: cell, displayIn: self.view)
}
}.disposed(by: cell.rx.disposeBag)
return cell
}
)
output.items.bind(to: tableView.rx.items(dataSource: dataSource)).disposed(by: rx.disposeBag)
@ -92,6 +137,11 @@ class CommentDetailViewController: TableViewController {
self.updateHeaderViewFrame()
}.disposed(by: rx.disposeBag)
output.clearTextSubject.subscribe { [weak self] _ in
self?.commentToolView.textField.text = ""
}.disposed(by: rx.disposeBag)
//
NotificationCenter.default.rx.notification(UIResponder.keyboardWillShowNotification)
.subscribe(onNext: { notification in
@ -140,9 +190,6 @@ class CommentDetailViewController: TableViewController {
tapGesture.rx.event
.bind(onNext: { [weak self] _ in
self?.view.endEditing(true)
self?.navigator.dismiss(sender: self)
})
.disposed(by: rx.disposeBag)
@ -190,6 +237,42 @@ class CommentDetailViewController: TableViewController {
}
func setupMenuItems(cell: CommentViewCell, features: [PopoverMenu] = [.copy, .delete]) -> [FSPopoverListItem] {
guard let viewModel = viewModel as? CommentViewModel,
let comment = cell.comment else { return []}
let items: [FSPopoverListItem] = features.map { feature in
let item = FSPopoverListTextItem(scrollDirection: .horizontal)
item.title = feature.description
item.titleFont = UIFont.systemFont(ofSize: 12)
item.isSeparatorHidden = false
item.titleColor = .white
item.contentInset = .init(top: 9, left: 15, bottom: 9, right: 15)
item.selectedHandler = { item in
guard let item = item as? FSPopoverListTextItem else {
return
}
switch item.title {
case PopoverMenu.copy.description:
UIPasteboard.general.string = cell.comment?.content
case PopoverMenu.report.description:
viewModel.itemReport.accept(comment)
case PopoverMenu.delete.description:
viewModel.itemDelete.accept(comment)
default: break
}
}
item.updateLayout()
return item
}
items.last?.isSeparatorHidden = true
return items
}
}
extension CommentDetailViewController {
@ -208,29 +291,6 @@ extension CommentDetailViewController {
}
extension CommentDetailViewController {
static func dataSource() -> RxTableViewSectionedReloadDataSource<CommentSection> {
return RxTableViewSectionedReloadDataSource<CommentSection>(
configureCell: { dataSource, tableView, indexPath, item in
switch item {
case .comment(let comment):
let cell: CommentViewCell = tableView.dequeueReusableCell(withIdentifier: "CommentViewCell", for: indexPath) as! CommentViewCell
cell.comment = comment
return cell
case .quote(let commentQuote):
let cell: CommentQuoteViewCell = tableView.dequeueReusableCell(withIdentifier: "CommentQuoteViewCell", for: indexPath) as! CommentQuoteViewCell
cell.comment = commentQuote
return cell
}
})
}
}
class CommentDetailHeaderView: UIView {
@ -300,9 +360,7 @@ class CommentDetailHeaderView: UIView {
avatarView.snp.makeConstraints { make in
make.left.equalTo(self).offset(18)
make.size.equalTo(CGSize.init(width: 40, height: 40))
// make.centerY.equalTo(self)
make.top.equalTo(self).offset(24)
// make.bottom.equalTo(self).offset(-21)
}
nameLabel.snp.makeConstraints { make in

@ -14,7 +14,7 @@ class CommentDetailViewModel: ViewModel, ViewModelType {
struct Input {
let viewWillAppear: ControlEvent<Bool>
let modelSelected: Driver<CommentType>
let modelSelected: Driver<Comment>
let footerRefresh: Observable<Void>
let commentText: Driver<String?>
let sendCommentTrigger: Driver<Void>
@ -25,12 +25,17 @@ class CommentDetailViewModel: ViewModel, ViewModelType {
let comment: Driver<Comment>
let items: BehaviorRelay<[CommentSection]>
let modelSelected: Driver<CommentType>
let modelSelected: Driver<Comment>
let clearTextSubject: PublishRelay<Void>
}
let items = BehaviorRelay<[CommentSection]>.init(value: [])
let itemSelected = PublishSubject<Comment>()
let clearTextSubject = PublishRelay<Void>()
let likeSelected = PublishRelay<Comment>()
let itemReport = PublishRelay<Comment>()
let comment: Comment
@ -44,11 +49,8 @@ class CommentDetailViewModel: ViewModel, ViewModelType {
input.viewWillAppear.subscribe { _ in
self.requestSubCommentData(parentId: self.comment.id, page: self.page, size: 10)
.subscribe { commentArray in
let newArray = commentArray.map { comment in
return CommentType.comment(comment)
}
self.items.accept([CommentSection.init(items: newArray)])
self.items.accept([CommentSection.init(id: "", items: commentArray)])
} onError: { error in
@ -63,14 +65,10 @@ class CommentDetailViewModel: ViewModel, ViewModelType {
.trackActivity(self.footerLoading)
})
.subscribe(onNext: { (commentArray) in
let newArray = commentArray.map { comment in
return CommentType.comment(comment)
}
guard let new = self.items.value.first else { return }
self.items.accept([CommentSection.init(items: new.items + newArray)])
self.items.accept([CommentSection.init(id: "", items: new.items + commentArray)])
}).disposed(by: rx.disposeBag)
@ -93,8 +91,9 @@ class CommentDetailViewModel: ViewModel, ViewModelType {
self.sendCommentData(content: comment, journalId: self.comment.journalId, parentId: self.comment.id, journalImage: self.comment.journalImage)
.subscribe { comment in
//TODO cell
SVProgressHUD.showText(withStatus: "发送成功")
self.insertComment(newComment: comment)
self.clearTextSubject.accept(())
} onError: { error in
}.disposed(by: self.rx.disposeBag)
@ -104,12 +103,40 @@ class CommentDetailViewModel: ViewModel, ViewModelType {
})
likeSelected.subscribe { [weak self] comment in
guard let self = self,
let comment = comment.element else { return }
self.requestCommentLike(commentID: comment.id)
.observe(on: MainScheduler.instance)
.subscribe(onNext: { commentThumbState in
var new = comment
if let thumbupCount = Int(new.thumbupCountString ?? "0") {
let newThumbupCount = commentThumbState.thumbState == true ? (thumbupCount + 1) : (thumbupCount - 1)
new.thumbupCountString = "\(newThumbupCount)"
}
new.haveThumbup = commentThumbState.thumbState
self.updateComment(withUpdatedComment: new)
}, onError: { error in
}).disposed(by: self.rx.disposeBag)
}.disposed(by: rx.disposeBag)
return Output.init(comment: Driver.just(comment),
items: items,
modelSelected: input.modelSelected)
modelSelected: input.modelSelected,
clearTextSubject: clearTextSubject)
}
@ -127,19 +154,35 @@ class CommentDetailViewModel: ViewModel, ViewModelType {
.trackError(error)
}
func insertComment(in index: Int, with newCommentType: CommentType) {
func sendCommentData(content: String, journalId: String?, parentId: String?, journalImage: String?) -> Observable<Comment> {
return self.provider.sendComment(content: content, journalId: journalId, parentId: parentId, journalImage: journalImage)
.trackActivity(loading)
.trackError(error)
}
func insertComment(newComment: Comment) {
var firstSection = items.value.first
firstSection?.items.insert(newCommentType, at: index + 1)
firstSection?.items.insert(newComment, at: 0)
if let updatedSection = firstSection {
items.accept([updatedSection])
}
}
func sendCommentData(content: String, journalId: String?, parentId: String?, journalImage: String?) -> Observable<Comment> {
return self.provider.sendComment(content: content, journalId: journalId, parentId: parentId, journalImage: journalImage)
.trackActivity(loading)
.trackError(error)
func updateComment(withUpdatedComment updatedComment: Comment) {
let updatedSections = items.value.map { section -> CommentSection in
let updatedComments = section.items.map { comment -> Comment in
guard comment.id == updatedComment.id else { return comment }
return updatedComment
}
return CommentSection(id: section.id, items: updatedComments)
}
items.accept(updatedSections)
}
}

@ -7,6 +7,7 @@
import UIKit
import AttributedString
import RxSwift
class CommentHeaderView: UIView {
var titleLabel: UILabel = {
@ -193,6 +194,11 @@ class CommentViewCell: UITableViewCell {
}
//
// override func prepareForReuse() {
// super.prepareForReuse()
// disposeBag = DisposeBag()
// }
override func awakeFromNib() {
super.awakeFromNib()

@ -23,7 +23,6 @@ class CommentViewController: ViewController {
let tableView = UITableView.init()
tableView.separatorColor = .clear
tableView.register(CommentViewCell.self, forCellReuseIdentifier: "CommentViewCell")
tableView.register(CommentQuoteViewCell.self, forCellReuseIdentifier: "CommentQuoteViewCell")
tableView.keyboardDismissMode = .onDrag
return tableView
@ -92,6 +91,7 @@ class CommentViewController: ViewController {
guard let viewModel = viewModel as? CommentViewModel else { return }
let input = CommentViewModel.Input.init(viewWillAppear: rx.viewWillAppear,
selection: tableView.rx.itemSelected.asDriver(),
currentCommentListType: currentCommentListType,
@ -104,37 +104,34 @@ class CommentViewController: ViewController {
)
let output = viewModel.transform(input: input)
let dataSource = RxTableViewSectionedReloadDataSource<CommentSection>(
let dataSource = RxTableViewSectionedAnimatedDataSource<CommentSection>(
animationConfiguration: AnimationConfiguration.init(insertAnimation: .top, reloadAnimation: .none, deleteAnimation: .automatic),
configureCell: { dataSource, tableView, indexPath, item in
switch item {
case .comment(let comment):
let cell: CommentViewCell = tableView.dequeueReusableCell(withIdentifier: "CommentViewCell", for: indexPath) as! CommentViewCell
cell.comment = comment
cell.comment = item
cell.likeButton.rx.tap.subscribe { _ in
viewModel.likeSelected.accept(item)
}.disposed(by: cell.rx.disposeBag)
cell.avatarView.rx.tapGesture().when(.recognized)
.subscribe { [weak self] tap in
guard let userID = comment.userId else { return }
guard let userID = item.userId else { return }
let personalViewModel = PersonalViewModel.init(userID: userID, provider: viewModel.provider)
self?.navigator.show(segue: .personal(viewModel: personalViewModel), sender: self)
}.disposed(by: self.rx.disposeBag)
}.disposed(by: cell.rx.disposeBag)
cell.likeButton.rx.tap.subscribe { _ in
viewModel.likeSelected.onNext(comment.id)
}.disposed(by: self.rx.disposeBag)
cell.moreButton.rx.tap.subscribe { _ in
let commentDetailViewModel = CommentDetailViewModel.init(comment: comment, provider: viewModel.provider)
let commentDetailViewModel = CommentDetailViewModel.init(comment: item, provider: viewModel.provider)
self.navigator.show(segue: .commentDetail(viewModel:commentDetailViewModel), sender: self, transition: .modal)
}.disposed(by: self.rx.disposeBag)
}.disposed(by: cell.rx.disposeBag)
@ -142,53 +139,30 @@ class CommentViewController: ViewController {
.subscribe { [weak self] tap in
guard let self = self else { return }
if comment.userId == UserDefaults.AccountInfo.string(forKey: .userID) {
self.menuView.items = self.setupMenuItems(features: [.copy, .report, .delete])
if item.userId == UserDefaults.AccountInfo.string(forKey: .userID) {
self.menuView.items = self.setupMenuItems(cell: cell, features: [.copy, .report, .delete])
} else {
self.menuView.items = self.setupMenuItems(features: [.copy, .delete])
self.menuView.items = self.setupMenuItems(cell: cell, features: [.copy, .delete])
}
if let location = tap.element?.location(in: cell) {
self.menuView.present(fromPoint: location, in: cell, displayIn: self.view)
}
}.disposed(by: self.rx.disposeBag)
return cell
case .quote(let commentQuote):
let cell: CommentQuoteViewCell = tableView.dequeueReusableCell(withIdentifier: "CommentQuoteViewCell", for: indexPath) as! CommentQuoteViewCell
cell.comment = commentQuote
cell.nameClousres = {[weak self] userID in
let personalViewModel = PersonalViewModel.init(userID: userID, provider: viewModel.provider)
self?.navigator.show(segue: .personal(viewModel: personalViewModel), sender: self)
}
cell.moreClousres = {[weak self] commentID in
// let commentDetailViewModel = CommentDetailViewModel.init(commentID: commentID, provider: viewModel.provider)
// self?.navigator.show(segue: .commentDetail(viewModel: commentDetailViewModel), sender: self)
}
}.disposed(by: cell.rx.disposeBag)
return cell
}
}
)
output.items.bind(to: tableView.rx.items(dataSource: dataSource)).disposed(by: rx.disposeBag)
output.items
.bind(to: tableView.rx.items(dataSource: dataSource)).disposed(by: rx.disposeBag)
output.itemSelected.subscribe { [weak self] sectionItem in
output.clearTextSubject.subscribe { [weak self] _ in
self?.commentToolView.textField.text = ""
}.disposed(by: rx.disposeBag)
//
NotificationCenter.default.rx.notification(UIResponder.keyboardWillShowNotification)
.subscribe(onNext: { notification in
@ -267,7 +241,10 @@ class CommentViewController: ViewController {
func setupMenuItems(features: [PopoverMenu] = [.copy, .delete]) -> [FSPopoverListItem] {
func setupMenuItems(cell: CommentViewCell, features: [PopoverMenu] = [.copy, .delete]) -> [FSPopoverListItem] {
guard let viewModel = viewModel as? CommentViewModel,
let comment = cell.comment else { return []}
let items: [FSPopoverListItem] = features.map { feature in
let item = FSPopoverListTextItem(scrollDirection: .horizontal)
item.title = feature.description
@ -279,7 +256,16 @@ class CommentViewController: ViewController {
guard let item = item as? FSPopoverListTextItem else {
return
}
print(item.title ?? "")
switch item.title {
case PopoverMenu.copy.description:
UIPasteboard.general.string = cell.comment?.content
case PopoverMenu.report.description:
viewModel.itemReport.accept(comment)
case PopoverMenu.delete.description:
viewModel.itemDelete.accept(comment)
default: break
}
}
item.updateLayout()
return item

@ -31,21 +31,25 @@ class CommentViewModel: ViewModel, ViewModelType {
let insertCommment: PublishSubject<Comment>
let toCommentDetail: PublishRelay<Comment>
let clearTextSubject: PublishRelay<Void>
}
let items = BehaviorRelay<[CommentSection]>.init(value: [])
let commentItems = BehaviorRelay<[CommentType]>.init(value: [])
let quoteItems = BehaviorRelay<[CommentType]>.init(value: [])
// let commentItems = BehaviorRelay<[CommentType]>.init(value: [])
// let quoteItems = BehaviorRelay<[CommentType]>.init(value: [])
let itemSelected = PublishSubject<Comment>()
let likeSelected = PublishSubject<String>()
let insertCommment: PublishSubject<Comment> = .init()
let toCommentDetail: PublishRelay<Comment> = .init()
let clearTextSubject = PublishRelay<Void>()
let likeSelected = PublishRelay<Comment>()
let itemReport = PublishRelay<Comment>()
let itemDelete = PublishRelay<Comment>()
var journal: Journal
@ -99,9 +103,6 @@ class CommentViewModel: ViewModel, ViewModelType {
} onError: { error in
} .disposed(by: self.rx.disposeBag)
}
@ -110,27 +111,24 @@ class CommentViewModel: ViewModel, ViewModelType {
}.disposed(by: rx.disposeBag)
likeSelected.subscribe { [weak self] comment in
guard let self = self,
let comment = comment.element else { return }
items.subscribe { commentSectionArray in
guard let firstSection = self.items.value.first else { return }
}.disposed(by: rx.disposeBag)
input.selection.drive { indexPath in
guard let sectionItem = self.items.value.first?.items[indexPath.row] else { return }
// self.itemSelected.onNext(sectionItem)
}.disposed(by: rx.disposeBag)
self.requestCommentLike(commentID: comment.id)
.observe(on: MainScheduler.instance)
.subscribe(onNext: { commentThumbState in
var new = comment
likeSelected.subscribe { [weak self] commentID in
guard let self = self else { return }
if let thumbupCount = Int(new.thumbupCountString ?? "0") {
let newThumbupCount = commentThumbState.thumbState == true ? (thumbupCount + 1) : (thumbupCount - 1)
new.thumbupCountString = "\(newThumbupCount)"
}
new.haveThumbup = commentThumbState.thumbState
self.requestCommentLike(commentID: commentID)
.subscribe(onNext: { commentThumbState in
self.updateComment(withUpdatedComment: new)
}, onError: { error in
@ -151,10 +149,8 @@ class CommentViewModel: ViewModel, ViewModelType {
self.sendCommentData(content: comment, journalId: self.journal.id, parentId: nil, journalImage: self.journal.image)
.subscribe { comment in
//TODO cell
self.insertComment(newComment: comment)
self.clearTextSubject.accept(())
} onError: { error in
}.disposed(by: self.rx.disposeBag)
@ -163,50 +159,30 @@ class CommentViewModel: ViewModel, ViewModelType {
}
})
itemReport.subscribe { comment in
}.disposed(by: rx.disposeBag)
itemDelete.subscribe { comment in
}.disposed(by: rx.disposeBag)
return Output.init(items: items,
itemSelected: itemSelected,
insertCommment: insertCommment,
toCommentDetail: toCommentDetail)
toCommentDetail: toCommentDetail,
clearTextSubject: clearTextSubject)
}
private func handleReceivedComments(comments: [Comment]) {
let commentTypes = comments.map { CommentType.comment($0) }
var commentSections = [CommentSection(items: commentTypes)]
let commentSections = [CommentSection.init(id: "", items: comments)]
self.items.accept(commentSections)
// for (index, commentType) in commentTypes.enumerated() {
// if case let .comment(comment) = commentType, comment.commentCount != 0 {
// self.requestSubCommentData(parentId: comment.id, page: 1, size: 1)
// .subscribe { [weak self] subCommentArray in
// guard let self = self else { return }
//
// self.handleReceivedSubComments(parentCommentCount: comment.commentCount ?? 0, subComments: subCommentArray, mainCommentIndex: index)
// } onError: { error in
// print(error)
// }.disposed(by: self.rx.disposeBag)
// }
// }
}
private func handleReceivedSubComments(parentCommentCount: Int, subComments: [Comment], mainCommentIndex: Int) {
guard var firstSection = self.items.value.first else { return }
let quoteTypes = subComments.map { comment in
var newComment = comment
newComment.parentCommentCount = parentCommentCount
return CommentType.quote(newComment)
}
firstSection.items.insert(contentsOf: quoteTypes, at: mainCommentIndex + 1)
self.items.accept([firstSection])
}
func requestHotCommentListData(journalID: String, page: Int, size: Int) -> Observable<[Comment]> {
return self.provider.hotCommentList(journalId: journalID, page: page, size: size)
@ -222,10 +198,6 @@ class CommentViewModel: ViewModel, ViewModelType {
}
func requestSubCommentData(parentId: String, page: Int, size: Int) -> Observable<[Comment]> {
return self.provider.subCommentList(parentId: parentId, page: page, size: size)
.trackActivity(loading)
@ -240,20 +212,35 @@ class CommentViewModel: ViewModel, ViewModelType {
.trackError(error)
}
func insertComment(in index: Int, with newCommentType: CommentType) {
func sendCommentData(content: String, journalId: String?, parentId: String?, journalImage: String?) -> Observable<Comment> {
return self.provider.sendComment(content: content, journalId: journalId, parentId: parentId, journalImage: journalImage)
.trackActivity(loading)
.trackError(error)
}
func insertComment(newComment: Comment) {
var firstSection = items.value.first
firstSection?.items.insert(newCommentType, at: index + 1)
firstSection?.items.insert(newComment, at: 0)
if let updatedSection = firstSection {
items.accept([updatedSection])
}
}
func sendCommentData(content: String, journalId: String?, parentId: String?, journalImage: String?) -> Observable<Comment> {
return self.provider.sendComment(content: content, journalId: journalId, parentId: parentId, journalImage: journalImage)
.trackActivity(loading)
.trackError(error)
func updateComment(withUpdatedComment updatedComment: Comment) {
let updatedSections = items.value.map { section -> CommentSection in
let updatedComments = section.items.map { comment -> Comment in
guard comment.id == updatedComment.id else { return comment }
return updatedComment
}
return CommentSection(id: section.id, items: updatedComments)
}
items.accept(updatedSections)
}

@ -177,7 +177,7 @@ class JournalDetailController: TableViewController {
output.journal.subscribe { [weak self] journal in
guard let self = self else { return }
self.commentToolView.commentCountButton.commentCountLabel.text = "\(journal)"
self.commentToolView.commentCountButton.commentCountLabel.text = "\(journal.totalCommentReply ?? "0")"
self.journalCollapsibleHeaderView.journal = journal
self.updateHeader()

@ -227,6 +227,7 @@ class JournalCollapsibleHeaderView: UIView {
saveButton.rx.tap.subscribe { [weak self] _ in
guard let self = self else { return }
self.popoverView.dismiss()
}.disposed(by: rx.disposeBag)

@ -172,18 +172,11 @@ class JournalDetailViewModel: ViewModel, ViewModelType {
item.subscribe { [weak self] audioTrack in
guard let audioTrack = audioTrack.element else { return }
guard let audioTrack = audioTrack.element,
let newTrack = audioTrack else { return }
//
// var new = items.value
// if var header = new.first?.header {
// header.haveCollect = false
//
// if let items = new.first?.items {
// new[0] = .audio(header: header, items: items)
// items.accept(new)
// }
// }
self?.updateAudioTrack(with: newTrack, identifiedBy: newTrack.id)
}.disposed(by: rx.disposeBag)
@ -198,13 +191,30 @@ class JournalDetailViewModel: ViewModel, ViewModelType {
)
}
func sectionType(for sectionIndex: Int) -> JournalItem? {
guard sectionIndex < self.items.value.count else { return nil }
let section = items.value[sectionIndex]
// item
//
return section.items.first
func updateAudioTrack(with newTrack: AudioTrack, identifiedBy trackId: String) {
let currentSections = items.value
let updatedSections = currentSections.map { section -> JournalSection in
switch section {
case .audioSection(let header, let items):
let updatedItems = items.map { item -> JournalItem in
switch item {
case .audioItem(let model) where model.id == trackId:
return .audioItem(model: newTrack)
default:
return item
}
}
return .audioSection(header: header, items: updatedItems)
case .journalSection(let header, let items):
return .journalSection(header: header, items: items)
}
}
items.accept(updatedSections)
}
func requestMusic(journalNo: String) -> Observable<[AudioTrack]> {

@ -85,7 +85,7 @@ extension MyCommentListController {
let cell: CommentListViewCell = tableView.dequeueReusableCell(withIdentifier: "CommentListViewCell", for: indexPath) as! CommentListViewCell
cell.comment = item
cell.myComment = item
return cell
}
@ -180,17 +180,17 @@ class CommentListViewCell: UITableViewCell {
var buttonTapCallback: ((User) -> ())?
var comment: Comment? {
var myComment: MyComment? {
didSet {
guard let comment = comment else { return }
avatarView.kf.setImage(with: URL.init(string: comment.avatar ?? ""))
audioTrackView.kf.setImage(with: URL.init(string: comment.journalImage ?? ""))
nameLabel.text = comment.nickName
dateLabel.text = "\(comment.publishTime)"
commentLabel.text = comment.content
// myCommentLabel.text = "\(comment.comm)"
guard let myComment = myComment else { return }
avatarView.kf.setImage(with: URL.init(string: myComment.commenterAvatar ?? ""))
audioTrackView.kf.setImage(with: URL.init(string: myComment.journalImage ?? ""))
nameLabel.text = myComment.nickName
let date = Date.init(dateString: myComment.createTime ?? "", format: "yyyy-MM-dd HH:mm:ss")
dateLabel.text = date.timeAgoSinceNow
commentLabel.text = myComment.content
myCommentLabel.text = "我的评论:\(myComment.commentContent ?? "")"
}
}

@ -30,20 +30,9 @@ class MyCommentListViewModel: ViewModel, ViewModelType {
input.viewWillAppear.subscribe { [weak self] _ in
guard let self = self else { return }
self.requestCommentList(page: self.page, size: 10)
.subscribe { [weak self] commentArray in
guard let self = self else { return }
} onError: { error in
}.disposed(by: self.rx.disposeBag)
}.disposed(by: rx.disposeBag)
input.headerRefresh.flatMapLatest({ [weak self] () -> Observable<[Comment]> in
input.headerRefresh.flatMapLatest({ [weak self] () -> Observable<[MyComment]> in
guard let self = self else { return Observable.just([]) }
self.page = 1
@ -54,7 +43,7 @@ class MyCommentListViewModel: ViewModel, ViewModelType {
self.items.accept([MyCommentListSection.init(items: items)])
}).disposed(by: rx.disposeBag)
input.footerRefresh.flatMapLatest({ [weak self] () -> Observable<[Comment]> in
input.footerRefresh.flatMapLatest({ [weak self] () -> Observable<[MyComment]> in
guard let self = self else { return Observable.just([]) }
self.page += 1
return self.requestCommentList(page: self.page, size: 10)
@ -68,7 +57,7 @@ class MyCommentListViewModel: ViewModel, ViewModelType {
}
func requestCommentList(page: Int, size: Int) -> Observable<[Comment]> {
func requestCommentList(page: Int, size: Int) -> Observable<[MyComment]> {
self.provider.myCommentList(page: page, size: size)
.trackActivity(loading)
.trackError(error)

@ -104,7 +104,7 @@ class PersonalViewController: ViewController {
}.disposed(by: rx.disposeBag)
collectionView.mj_header = nil
}
@ -137,7 +137,9 @@ class PersonalViewController: ViewController {
cell.audioTrack = audioTrack
cell.buttonTapCallback = { [weak self] audioTrack in
let audioMoreActionViewModel = AudioMoreActionViewModel.init(audioTrack: viewModel.needRefresh, provider: viewModel.provider)
viewModel.item.accept(audioTrack)
let audioMoreActionViewModel = AudioMoreActionViewModel.init(audioTrack: viewModel.item, provider: viewModel.provider)
self?.navigator.show(segue: .audioMore(viewModel: audioMoreActionViewModel), sender: self, transition: .navigationPresent(type: .audioMore))

@ -42,7 +42,7 @@ class PersonalViewModel: ViewModel, ViewModelType {
let itemSelected = PublishSubject<PersonInfoItem>()
let user = BehaviorRelay<User?>.init(value: nil)
let personInfoLikeType = BehaviorRelay<PersonInfoLikeType>.init(value: .audio)
let needRefresh: BehaviorRelay<AudioTrack?> = .init(value: nil)
let item: BehaviorRelay<AudioTrack?> = .init(value: nil)
let followingButtonTrigger = PublishRelay<Void>.init()
@ -84,10 +84,10 @@ class PersonalViewModel: ViewModel, ViewModelType {
self.page = 1
if personInfoLikeType == .audio {
return self.requestCollectSongList(userId: UserDefaults.AccountInfo.string(forKey: .userID) ?? "", page: self.page, size: 10)
return self.requestCollectSongList(userId: self.userID, page: self.page, size: 10)
.trackActivity(self.headerLoading)
} else {
return self.requestJournalCollectList(userId: UserDefaults.AccountInfo.string(forKey: .userID) ?? "", page: self.page, size: 10)
return self.requestJournalCollectList(userId: self.userID, page: self.page, size: 10)
.trackActivity(self.headerLoading)
}

@ -15,9 +15,45 @@ class SearchResultsController: ViewController {
let searchTopBar: SearchTopBar = {
let searchTopBar = SearchTopBar.init()
searchTopBar.setContentHuggingPriority(.required, for: .horizontal)
searchTopBar.setContentCompressionResistancePriority(.required, for: .horizontal)
return searchTopBar
}()
lazy var segmentControl: ScrollSegmentView = {
var style = SegmentStyle()
style.showLine = true
style.normalTitleColor = UIColor.tertiaryText()
style.selectedTitleColor = UIColor.white
style.backgroundColor = UIColor.white
style.titleSelectFont = UIFont.systemFont(ofSize: 14, weight: .medium)
style.titleFont = UIFont.systemFont(ofSize: 14)
style.scrollLineHeight = 0
style.scrollLineColor = .clear
style.coverBackgroundColor = .init(hex: 0x0d0d0d)
style.normalborderColor = UIColor.tertiaryText()
style.scrollTitle = false
style.showCover = true
// style.scrollTitle = true
// style.lineSpace = 12
let segmentControl = ScrollSegmentView.init(frame: CGRect.init(x: 0, y: 0, width: 200, height: 32), segmentStyle: style, titles: ["单曲", "期刊"])
segmentControl.isHidden = true
// segmentControl.scrollView.backgroundColor = .red
segmentControl.setContentHuggingPriority(.required, for: .horizontal)
segmentControl.setContentCompressionResistancePriority(.required, for: .horizontal)
return segmentControl
}()
let searchNoDataView: SearchNoDataView = {
@ -55,6 +91,15 @@ class SearchResultsController: ViewController {
return collectionView
}()
let searchSuggestionsView: SearchSuggestionsView = {
let searchSuggestionsView = SearchSuggestionsView.init()
searchSuggestionsView.isHidden = true
return searchSuggestionsView
}()
let searchType = BehaviorRelay<SearchType>.init(value: .audio)
override func viewDidLoad() {
@ -66,7 +111,7 @@ class SearchResultsController: ViewController {
override func makeUI() {
super.makeUI()
searchTopBar.segmentControl.titleBtnOnClick = { [weak self] (label, index) in
segmentControl.titleBtnOnClick = { [weak self] (label, index) in
if index == 0 {
self?.searchType.accept(.audio)
} else {
@ -80,8 +125,10 @@ class SearchResultsController: ViewController {
view.backgroundColor = .white
view.addSubview(searchTopBar)
view.addSubview(segmentControl)
view.addSubview(searchNoDataView)
view.addSubview(collectionView)
view.addSubview(searchSuggestionsView)
}
@ -108,21 +155,34 @@ class SearchResultsController: ViewController {
let input = SearchResultsViewModel.Input.init(viewWillAppear: rx.viewWillAppear,
closeButtonTrigger: self.searchTopBar.cancelButton.rx.tap.asDriver(),
searchText: searchTopBar.searchControl.textField.rx.text.asDriver(),
searchTrigger: searchTopBar.searchControl.textField.rx.controlEvent(.editingDidEndOnExit).asDriver(),
modelSelected: collectionView.rx.modelSelected(SearchResultsItem.self).asDriver(),
suggestionSelected: searchSuggestionsView.tableView.rx.modelSelected(String.self).asDriver(),
searchType: searchType)
let output = viewModel.transform(input: input)
let dataSource = SearchResultsController.dataSource { [weak self] cell, audioTrack in
// let audioMoreActionViewModel = AudioMoreActionViewModel.init(audioTrack: audioTrack, provider: viewModel.provider)
//
// self?.navigator.show(segue: .audioMore(viewModel: audioMoreActionViewModel), sender: self, transition: .navigationPresent(type: .audioMore))
let item: BehaviorRelay<AudioTrack?> = .init(value: audioTrack)
let audioMoreActionViewModel = AudioMoreActionViewModel.init(audioTrack: item, provider: viewModel.provider)
self?.navigator.show(segue: .audioMore(viewModel: audioMoreActionViewModel), sender: self, transition: .navigationPresent(type: .audioMore))
}
output.items.bind(to: collectionView.rx.items(dataSource: dataSource)).disposed(by: rx.disposeBag)
output.items.subscribe { items in
self.segmentControl.isHidden = items.element?.first?.items.isEmpty ?? true
self.searchSuggestionsView.isHidden = true
}.disposed(by: rx.disposeBag)
searchTopBar.cancelButton.rx.tap.subscribe { [weak self] _ in
self?.navigator.pop(sender: self)
@ -156,6 +216,19 @@ class SearchResultsController: ViewController {
}.disposed(by: rx.disposeBag)
let searchSuggestionsDataSource = SearchSuggestionsView.dataSource()
output.suggestionsItems.bind(to: searchSuggestionsView.tableView.rx.items(dataSource: searchSuggestionsDataSource)).disposed(by: rx.disposeBag)
output.suggestionsItems.subscribe { suggestionsItems in
self.searchSuggestionsView.isHidden = suggestionsItems.element?.first?.items.isEmpty ?? true
}.disposed(by: rx.disposeBag)
searchSuggestionsView.tableView.rx.itemSelected
.subscribe { indexPath in
self.searchSuggestionsView.tableView.deselectRow(at: indexPath, animated: true)
}.disposed(by: rx.disposeBag)
}
@ -167,7 +240,13 @@ class SearchResultsController: ViewController {
make.left.equalTo(view)
make.right.equalTo(view)
make.top.equalTo(view).offset(BaseDimensions.statusBarHeight + 15)
make.height.equalTo(135)
}
segmentControl.snp.makeConstraints { make in
make.left.equalTo(view).offset(18)
make.top.equalTo(searchTopBar.snp.bottom).offset(9)
make.width.equalTo(200)
make.height.equalTo(40)
}
searchNoDataView.snp.makeConstraints { make in
@ -178,7 +257,14 @@ class SearchResultsController: ViewController {
collectionView.snp.makeConstraints { make in
make.left.equalTo(view)
make.right.equalTo(view)
make.top.equalTo(searchTopBar.snp.bottom).offset(0)
make.top.equalTo(segmentControl.snp.bottom).offset(0)
make.bottom.equalTo(view)
}
searchSuggestionsView.snp.makeConstraints { make in
make.left.equalTo(view)
make.right.equalTo(view)
make.top.equalTo(searchTopBar.snp.bottom)
make.bottom.equalTo(view)
}
@ -190,7 +276,7 @@ class SearchResultsController: ViewController {
extension SearchResultsController {
//TODO
static func dataSource(_ buttonTapHandler: @escaping (UITableViewCell, AudioTrack) -> Void) -> RxCollectionViewSectionedReloadDataSource<SearchResultsSection> {
static func dataSource(_ buttonTapHandler: @escaping (UICollectionViewCell, AudioTrack) -> Void) -> RxCollectionViewSectionedReloadDataSource<SearchResultsSection> {
return RxCollectionViewSectionedReloadDataSource<SearchResultsSection>(
configureCell: { dataSource, collectionView, indexPath, item in
@ -200,6 +286,10 @@ extension SearchResultsController {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "JournalAudioCollectionViewCell", for: indexPath) as! JournalAudioCollectionViewCell
cell.audioTrack = audioTrack
cell.buttonTapCallback = { audioTrack in
buttonTapHandler(cell, audioTrack)
}
return cell
case .journal(let journal):
@ -252,7 +342,6 @@ class SearchTopBar: UIView {
let searchControl: SearchControl = {
let searchControl = SearchControl.init()
searchControl.layer.cornerRadius = 20
return searchControl
}()
@ -267,33 +356,7 @@ class SearchTopBar: UIView {
lazy var segmentControl: ScrollSegmentView = {
var style = SegmentStyle()
style.showLine = true
style.normalTitleColor = UIColor.tertiaryText()
style.selectedTitleColor = UIColor.white
style.backgroundColor = UIColor.white
style.titleSelectFont = UIFont.systemFont(ofSize: 14, weight: .medium)
style.titleFont = UIFont.systemFont(ofSize: 14)
style.scrollLineHeight = 0
style.scrollLineColor = .clear
style.coverBackgroundColor = .init(hex: 0x0d0d0d)
style.normalborderColor = UIColor.tertiaryText()
style.scrollTitle = false
style.showCover = true
// style.scrollTitle = true
// style.lineSpace = 12
let segmentControl = ScrollSegmentView.init(frame: CGRect.init(x: 0, y: 0, width: 200, height: 32), segmentStyle: style, titles: ["单曲", "期刊"])
// segmentControl.scrollView.backgroundColor = .red
return segmentControl
}()
@ -312,7 +375,6 @@ class SearchTopBar: UIView {
addSubview(searchControl)
addSubview(cancelButton)
addSubview(segmentControl)
}
@ -329,17 +391,10 @@ class SearchTopBar: UIView {
searchControl.snp.makeConstraints { make in
make.left.equalTo(self).offset(18)
make.top.equalTo(self).offset(15)
make.bottom.equalTo(self).offset(-15)
make.height.equalTo(40)
make.right.equalTo(cancelButton.snp.left).offset(-12)
}
segmentControl.snp.makeConstraints { make in
make.left.equalTo(self).offset(18)
make.top.equalTo(searchControl.snp.bottom).offset(24)
make.bottom.equalTo(self).offset(-24)
make.width.equalTo(200)
}
}
}
@ -376,3 +431,86 @@ class SearchNoDataView: UIView {
}
}
}
class SearchSuggestionsView: UIView {
let tableView: UITableView = {
let tableView = UITableView.init()
tableView.register(SearchSuggestionsViewCell.self, forCellReuseIdentifier: "SearchSuggestionsViewCell")
return tableView
}()
override init(frame: CGRect) {
super.init(frame: frame)
makeUI()
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
func makeUI() {
addSubview(tableView)
}
override func layoutSubviews() {
super.layoutSubviews()
tableView.snp.makeConstraints { make in
make.edges.equalTo(self)
}
}
}
extension SearchSuggestionsView {
static func dataSource() -> RxTableViewSectionedReloadDataSource<SearchSuggestionSection> {
return RxTableViewSectionedReloadDataSource<SearchSuggestionSection>(
configureCell: { dataSource, tableView, indexPath, item in
let cell: SearchSuggestionsViewCell = tableView.dequeueReusableCell(withIdentifier: "SearchSuggestionsViewCell", for: indexPath) as! SearchSuggestionsViewCell
cell.titleLabel.text = item
return cell
}
)
}
}
class SearchSuggestionsViewCell: UITableViewCell {
let titleLabel: UILabel = {
let titleLabel = UILabel.init()
titleLabel.textColor = .tertiaryText()
return titleLabel
}()
override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
super.init(style: style, reuseIdentifier: reuseIdentifier)
makeUI()
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
func makeUI() {
contentView.addSubview(titleLabel)
titleLabel.snp.makeConstraints { make in
make.left.equalTo(contentView).offset(18)
make.centerY.equalTo(contentView)
make.right.equalTo(contentView).offset(-18)
}
}
}

@ -16,18 +16,21 @@ class SearchResultsViewModel: ViewModel, ViewModelType {
let viewWillAppear: ControlEvent<Bool>
let closeButtonTrigger: Driver<Void>
let searchText: Driver<String?>
let searchTrigger: Driver<Void>
let modelSelected: Driver<SearchResultsItem>
let suggestionSelected: Driver<String>
let searchType: BehaviorRelay<SearchType>
}
struct Output {
let items: BehaviorRelay<[SearchResultsSection]>
let suggestionsItems: BehaviorRelay<[SearchSuggestionSection]>
let modelSelected: Driver<SearchResultsItem>
}
let items = BehaviorRelay<[SearchResultsSection]>.init(value: [])
let suggestionsItems = BehaviorRelay<[SearchSuggestionSection]>.init(value: [])
let audioTrackItems = BehaviorRelay<[SearchResultsItem]>.init(value: [])
let journalItems = BehaviorRelay<[SearchResultsItem]>.init(value: [])
@ -43,7 +46,24 @@ class SearchResultsViewModel: ViewModel, ViewModelType {
}.disposed(by: rx.disposeBag)
input.searchText.debounce(.milliseconds(500))
input.searchText.distinctUntilChanged().debounce(.milliseconds(500))
.drive { searchText in
self.fetchSuggestionsData(keyword: searchText ?? "")
.subscribe { suggestions in
self.suggestionsItems.accept([SearchSuggestionSection.init(items: suggestions)])
} onError: { error in
self.items.accept([SearchResultsSection.journal(title: "", items: [])])
}.disposed(by: self.rx.disposeBag)
}.disposed(by: rx.disposeBag)
input.searchTrigger
.withLatestFrom(input.searchText)
.drive { searchText in
self.fetchSearchData(keyword: searchText ?? "")
.subscribe { [weak self] searchResults in
@ -60,12 +80,42 @@ class SearchResultsViewModel: ViewModel, ViewModelType {
self?.journalItems.accept(journalArray ?? [])
} onError: { error in
self.items.accept([SearchResultsSection.journal(title: "", items: [])])
}.disposed(by: self.rx.disposeBag)
}.disposed(by: rx.disposeBag)
input.suggestionSelected.drive { searchText in
self.fetchSearchData(keyword: searchText)
.subscribe { [weak self] searchResults in
let audioTrackArray = searchResults.songs?.map({ audioTrack in
return SearchResultsItem.single(item: audioTrack)
})
let journalArray = searchResults.journals?.map({ journal in
return SearchResultsItem.journal(item: journal)
})
self?.audioTrackItems.accept(audioTrackArray ?? [])
self?.journalItems.accept(journalArray ?? [])
} onError: { error in
self.items.accept([SearchResultsSection.journal(title: "", items: [])])
}.disposed(by: self.rx.disposeBag)
}.disposed(by: rx.disposeBag)
input.searchType.subscribe { [weak self] searchType in
guard let self = self else { return }
@ -104,6 +154,7 @@ class SearchResultsViewModel: ViewModel, ViewModelType {
return Output.init(items: items,
suggestionsItems: suggestionsItems,
modelSelected: input.modelSelected)
}
@ -114,4 +165,11 @@ class SearchResultsViewModel: ViewModel, ViewModelType {
.trackError(error)
}
func fetchSuggestionsData(keyword: String, limit: Int = 10) -> Observable<[String]> {
return self.provider.suggestions(query: keyword, limit: limit)
.trackActivity(loading)
.trackError(error)
}
}

@ -338,6 +338,7 @@ class SearchControl: UIControl {
let textField = UITextField.init()
textField.font = UIFont.systemFont(ofSize: 15)
textField.placeholder = "输入期刊/歌曲名"
textField.returnKeyType = .send
return textField
}()

@ -93,9 +93,12 @@ protocol IndieMusicAPI {
///
func myThumbupList(page: Int, size: Int) -> Single<[MineThumbup]>
///
func myCommentList(page: Int, size: Int) -> Single<[Comment]>
func myCommentList(page: Int, size: Int) -> Single<[MyComment]>
///
func feedback(type: Int, content: String, images: [UIImage], contact: String) -> Single<Void>
///
func suggestions(query: String, limit: Int) -> Single<[String]>
}

@ -59,7 +59,7 @@ enum APIConfig {
case myThumbupList(Int, Int)
case myCommentReplyList(Int, Int)
case feedback([Data], [String: Any])
case suggestions([String: Any])
}
extension APIConfig: TargetType {
@ -147,12 +147,14 @@ extension APIConfig: TargetType {
case .feedback:
return "luoo-user/my/feedback"
case .suggestions:
return "luoo-music/search/autoComplete"
}
}
var method: Moya.Method {
switch self {
case .wechatAccessToken, .journalList, .journalMusic, .countryCode, .imageCheckCode, .getUserInfo, .carousel, .otherUserInfo, .single, .journal, .messageList, .collectSongList, .journalCollectList, .followingList, .followerList, .blackList, .hotCommentList, .latestCommentList, .subCommentList, .filterMenu, .journalRecommend, .searchCategory, .serach, .randomAudioTrack, .myThumbupList, .myCommentReplyList:
case .wechatAccessToken, .journalList, .journalMusic, .countryCode, .imageCheckCode, .getUserInfo, .carousel, .otherUserInfo, .single, .journal, .messageList, .collectSongList, .journalCollectList, .followingList, .followerList, .blackList, .hotCommentList, .latestCommentList, .subCommentList, .filterMenu, .journalRecommend, .searchCategory, .serach, .randomAudioTrack, .myThumbupList, .myCommentReplyList, .suggestions:
return .get
case .sendsms, .login, .autoLogin, .editAvatar, .like, .checkVersion, .logout, .sendComment, .feedback:
return .post
@ -166,7 +168,7 @@ extension APIConfig: TargetType {
var parameterEncoding: ParameterEncoding {
switch self {
case .wechatAccessToken, .journalList, .journalMusic, .countryCode, .sendsms, .imageCheckCode, .login, .getUserInfo, .carousel, .otherUserInfo, .single, .journal, .messageList, .like, .cancelLike, .collectSongList, .journalCollectList, .followingList, .followerList, .blackList, .hotCommentList, .latestCommentList, .subCommentList, .filterMenu, .journalRecommend, .searchCategory, .serach, .randomAudioTrack, .myThumbupList, .myCommentReplyList:
case .wechatAccessToken, .journalList, .journalMusic, .countryCode, .sendsms, .imageCheckCode, .login, .getUserInfo, .carousel, .otherUserInfo, .single, .journal, .messageList, .like, .cancelLike, .collectSongList, .journalCollectList, .followingList, .followerList, .blackList, .hotCommentList, .latestCommentList, .subCommentList, .filterMenu, .journalRecommend, .searchCategory, .serach, .randomAudioTrack, .myThumbupList, .myCommentReplyList, .suggestions:
return URLEncoding.default
case .autoLogin, .editUserInfo, .editAvatar, .checkVersion, .logout, .commentLike, .sendComment, .feedback:
@ -181,7 +183,7 @@ extension APIConfig: TargetType {
case .wechatAccessToken, .countryCode, .journalMusic, .imageCheckCode, .getUserInfo, .carousel, .otherUserInfo, .single, .journal, .messageList, .followingList, .followerList, .blackList, .hotCommentList, .latestCommentList, .subCommentList, .filterMenu, .journalRecommend, .searchCategory, .serach, .randomAudioTrack, .myThumbupList, .myCommentReplyList:
return .requestPlain
case .login(let dic), .journalList(let dic), .sendsms(let dic), .autoLogin(let dic), .editUserInfo(let dic), .like(let dic), .cancelLike(let dic), .logout(let dic), .checkVersion(let dic), .commentLike(_, let dic), .sendComment(let dic), .collectSongList(let dic), .journalCollectList(let dic):
case .login(let dic), .journalList(let dic), .sendsms(let dic), .autoLogin(let dic), .editUserInfo(let dic), .like(let dic), .cancelLike(let dic), .logout(let dic), .checkVersion(let dic), .commentLike(_, let dic), .sendComment(let dic), .collectSongList(let dic), .journalCollectList(let dic), .suggestions(let dic):
parameters = dic
return .requestParameters(parameters: parameters, encoding: parameterEncoding)
@ -224,7 +226,7 @@ extension APIConfig: TargetType {
var headers : [String : String]? {
switch self {
case .autoLogin, .getUserInfo, .journalList, .journalMusic, .otherUserInfo, .like, .cancelLike, .single, .journal, .collectSongList, .journalCollectList, .followingList, .messageList, .followerList, .blackList, .editUserInfo, .logout, .editAvatar, .hotCommentList, .latestCommentList, .subCommentList, .commentLike, .filterMenu, .journalRecommend, .serach, .randomAudioTrack, .sendComment, .myThumbupList, .myCommentReplyList, .feedback:
case .autoLogin, .getUserInfo, .journalList, .journalMusic, .otherUserInfo, .like, .cancelLike, .single, .journal, .collectSongList, .journalCollectList, .followingList, .messageList, .followerList, .blackList, .editUserInfo, .logout, .editAvatar, .hotCommentList, .latestCommentList, .subCommentList, .commentLike, .filterMenu, .journalRecommend, .serach, .randomAudioTrack, .sendComment, .myThumbupList, .myCommentReplyList, .feedback, .suggestions:
return ["Authorization": AuthManager.shared.token?.basicToken ?? ""]
default:
return nil

@ -311,8 +311,8 @@ extension RestApi {
return requestObject(.myThumbupList(page, size), with: "data.rows", type: [MineThumbup].self)
}
func myCommentList(page: Int, size: Int) -> Single<[Comment]> {
return requestObject(.myCommentReplyList(page, size), with: "data.rows", type: [Comment].self)
func myCommentList(page: Int, size: Int) -> Single<[MyComment]> {
return requestObject(.myCommentReplyList(page, size), with: "data.rows", type: [MyComment].self)
}
@ -329,4 +329,13 @@ extension RestApi {
return requestWithoutMapping(.feedback(nonNilDataArray, dic)).map { _ in }
}
func suggestions(query: String, limit: Int) -> Single<[String]> {
let dic = ["query": query,
"limit": limit
] as [String : Any]
return requestObject(.suggestions(dic), with: "data", type: [String].self)
}
}

Loading…
Cancel
Save