Comment on details of interactive optimization

dev
wenlei 9 months ago
parent 0aaddb5136
commit 106f56637e

@ -185,6 +185,7 @@
77C9B9ED2B4BEA610006C83F /* MyCommentListViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 77C9B9EC2B4BEA610006C83F /* MyCommentListViewModel.swift */; };
77C9B9EF2B4C2A910006C83F /* AudioTrackListViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 77C9B9EE2B4C2A910006C83F /* AudioTrackListViewController.swift */; };
77C9B9F12B4C2B3A0006C83F /* AudioTrackListViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 77C9B9F02B4C2B3A0006C83F /* AudioTrackListViewModel.swift */; };
77CEFEFC2B81EC600071B671 /* PresentAndDismissTransition.swift in Sources */ = {isa = PBXBuildFile; fileRef = 77CEFEFB2B81EC600071B671 /* PresentAndDismissTransition.swift */; };
77DFA9C52B4E8388005B8B13 /* MineDownloadViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 77DFA9C42B4E8388005B8B13 /* MineDownloadViewController.swift */; };
77DFA9C72B4E83B0005B8B13 /* MineDownloadViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 77DFA9C62B4E83B0005B8B13 /* MineDownloadViewModel.swift */; };
77FA0B282B0B3E1E00404C5E /* Journal.swift in Sources */ = {isa = PBXBuildFile; fileRef = 77FA0B272B0B3E1E00404C5E /* Journal.swift */; };
@ -434,6 +435,7 @@
77C9B9EC2B4BEA610006C83F /* MyCommentListViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MyCommentListViewModel.swift; sourceTree = "<group>"; };
77C9B9EE2B4C2A910006C83F /* AudioTrackListViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AudioTrackListViewController.swift; sourceTree = "<group>"; };
77C9B9F02B4C2B3A0006C83F /* AudioTrackListViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AudioTrackListViewModel.swift; sourceTree = "<group>"; };
77CEFEFB2B81EC600071B671 /* PresentAndDismissTransition.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PresentAndDismissTransition.swift; sourceTree = "<group>"; };
77DFA9C42B4E8388005B8B13 /* MineDownloadViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MineDownloadViewController.swift; sourceTree = "<group>"; };
77DFA9C62B4E83B0005B8B13 /* MineDownloadViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MineDownloadViewModel.swift; sourceTree = "<group>"; };
77FA0B272B0B3E1E00404C5E /* Journal.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Journal.swift; sourceTree = "<group>"; };
@ -727,6 +729,7 @@
77C9B9D22B4B9B180006C83F /* BorderLabel.swift */,
77A659CE2B4FDC4100B408C3 /* PullToDismissable.swift */,
77A659D02B4FDC5200B408C3 /* PullToDismissTransition.swift */,
77CEFEFB2B81EC600071B671 /* PresentAndDismissTransition.swift */,
770228F02B57AD2C00E07F7A /* RefreshLoadingView.swift */,
775D075D2B5E5BCA009270D3 /* GradientLayerLabel.swift */,
);
@ -1314,6 +1317,7 @@
7751D3702B43A4FC00F1F2BD /* CacheViewController.swift in Sources */,
7751D3642B42BC2E00F1F2BD /* AccountViewModel.swift in Sources */,
778B8A5D2AF8EC610034AFD4 /* Application.swift in Sources */,
77CEFEFC2B81EC600071B671 /* PresentAndDismissTransition.swift in Sources */,
7751D3622B42BC0C00F1F2BD /* AccountViewController.swift in Sources */,
77A659CF2B4FDC4100B408C3 /* PullToDismissable.swift in Sources */,
77620D9A2B69DA1A00798861 /* EditSignatureViewController.swift in Sources */,

@ -86,19 +86,11 @@ class Navigator {
func get(segue: Scene) -> UIViewController? {
switch segue {
case .tabs(let viewModel):
let rootVC = HomeTabBarController(viewModel: viewModel, navigator: self)
// let rootVC = IndieMusicViewController(viewModel: viewModel, navigator: self)
let tabVC = HomeTabBarController(viewModel: viewModel, navigator: self)
let nav = tabVC
let home = HomeViewController(viewModel: viewModel, navigator: self)
let search = SearchViewController(viewModel: viewModel, navigator: self)
let mine = MineViewController(viewModel: viewModel, navigator: self)
// rootVC.setViewController([home, search, mine])
rootVC.viewControllers = [home, search, mine]
return rootVC
return nav
case .search(let viewModel): return SearchViewController(viewModel: viewModel, navigator: self)
case .journalDetail(viewModel: let viewModel):
return JournalDetailController.init(viewModel: viewModel, navigator: self)
@ -276,8 +268,8 @@ class Navigator {
case .modal:
DispatchQueue.main.async {
let nav = NavigationController(rootViewController: target)
nav.setNavigationBarHidden(true, animated: true)
// nav.isPullToDismissEnabled = true
nav.setNavigationBarHidden(true, animated: false)
nav.isPullToDismissEnabled = true
nav.modalPresentationStyle = .custom
nav.modalPresentationCapturesStatusBarAppearance = true

@ -24,8 +24,8 @@ class SceneDelegate: UIResponder, UIWindowSceneDelegate, WXApiDelegate {
let libsManager = LibsManager.shared
libsManager.setupLibs(with: window)
IQKeyboardManager.shared.enable = true
IQKeyboardManager.shared.enableAutoToolbar = true
// IQKeyboardManager.shared.enable = true
// IQKeyboardManager.shared.enableAutoToolbar = true
Application.shared.presentInitialScreen(in: window!)

@ -101,11 +101,17 @@ class NavigationController: UINavigationController {
extension NavigationController {
override func pushViewController(_ viewController: UIViewController, animated: Bool)
{
if !children.isEmpty {
// viewController.hidesBottomBarWhenPushed = true
// if children.count > 1 {
// viewController.hidesBottomBarWhenPushed = false
// }
if let tabbar = self.tabBarController as? HomeTabBarController {
tabbar.showAllBar(false, animated: true)
}
// viewController.hidesBottomBarWhenPushed = true
// let leftButton = UIButton.init()
@ -146,6 +152,14 @@ extension NavigationController {
return super.popViewController(animated: animated)
}
override func popToRootViewController(animated: Bool) -> [UIViewController]? {
if let tabbar = self.tabBarController as? HomeTabBarController {
tabbar.refreshTabBarItems()
}
return super.popToRootViewController(animated: animated)
}
@objc func backAction() {
self.popViewController(animated: true)
}
@ -188,11 +202,21 @@ extension NavigationController: PullToDismissable, UIViewControllerTransitioning
setupPullToDismiss()
}
func animationController(forPresented presented: UIViewController, presenting: UIViewController, source: UIViewController) -> UIViewControllerAnimatedTransitioning? {
guard isPullToDismissEnabled else { return nil }
if self.children.first is CommentDetailViewController {
return PresentAndDismissTransition(false)
} else {
return nil
}
}
func animationController(forDismissed dismissed: UIViewController)
-> UIViewControllerAnimatedTransitioning? {
guard isPullToDismissEnabled else { return nil }
return pullToDismissTransition
return PresentAndDismissTransition(true)
}
func interactionControllerForDismissal(using animator: UIViewControllerAnimatedTransitioning)

@ -0,0 +1,111 @@
//
// PresentAndDismissTransition.swift
// IndieMusic
//
// Created by WenLei on 2024/2/18.
//
import Foundation
class DimmingView:UIView {
override init(frame: CGRect) {
super.init(frame: frame)
self.backgroundColor = UIColor.black
self.alpha = 0
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
class PresentAndDismissTransition: NSObject, UIViewControllerAnimatedTransitioning {
private var isDismiss:Bool!
convenience init(_ isDismiss:Bool) {
self.init()
self.isDismiss = isDismiss
}
func transitionDuration(using transitionContext: UIViewControllerContextTransitioning?) -> TimeInterval {
return 0.4
}
func animateTransition(using transitionContext: UIViewControllerContextTransitioning) {
guard let toViewController = transitionContext.viewController(forKey: .to),let fromViewController = transitionContext.viewController(forKey: .from) else {
return
}
if !self.isDismiss {
//Present
toViewController.view.frame.size.height -= BaseDimensions.topHeight + 22
toViewController.view.frame.origin.y = UIScreen.main.bounds.size.height
transitionContext.containerView.addSubview(toViewController.view)
let toViewpath = UIBezierPath(roundedRect: toViewController.view.bounds, byRoundingCorners: [.topLeft, .topRight], cornerRadii: CGSize(width: 6, height: 6))
let toViewmask = CAShapeLayer()
toViewmask.path = toViewpath.cgPath
toViewController.view.layer.mask = toViewmask
let fromViewpath = UIBezierPath(roundedRect: fromViewController.view.bounds, byRoundingCorners: [.topLeft, .topRight], cornerRadii: CGSize(width: 6, height: 6))
let fromViewmask = CAShapeLayer()
fromViewmask.path = fromViewpath.cgPath
fromViewController.view.layer.mask = fromViewmask
let dimmingView = DimmingView(frame: fromViewController.view.frame)
transitionContext.containerView.insertSubview(dimmingView, belowSubview: toViewController.view)
fromViewController.view.transform = CGAffineTransform(scaleX: 1.0, y: 1.0)
UIView.animate(withDuration: transitionDuration(using: transitionContext), delay: 0, options: [.curveEaseOut], animations: {
dimmingView.alpha = 0.7
// fromViewController.view.transform = CGAffineTransform(scaleX: 0.9, y: 0.9)
toViewController.view.frame.origin.y = BaseDimensions.topHeight + 22
}) { (_) in
// fromViewController.view.transform = CGAffineTransform(scaleX: 0.9, y: 0.9)
transitionContext.completeTransition(!transitionContext.transitionWasCancelled)
}
} else {
//Dismiss
let dimmingView = transitionContext.containerView.subviews.first(where: { (view) -> Bool in
return view is DimmingView
})
// fromViewController.view.frame.origin.y = BaseDimensions.topHeight + 22 //or use finalFrame
let fromViewSnpaShot = fromViewController.view.snapshotView(afterScreenUpdates: false)
if let fromViewSnpaShot = fromViewSnpaShot {
fromViewController.view.isHidden = true
fromViewSnpaShot.frame = fromViewController.view.frame
transitionContext.containerView.addSubview(fromViewSnpaShot)
}
dimmingView?.alpha = 0.7
// toViewController.view.transform = CGAffineTransform(scaleX: 0.9, y: 0.9)
UIView.animate(withDuration: transitionDuration(using: transitionContext), delay: 0, options: [.curveLinear], animations: {
dimmingView?.alpha = 0
fromViewSnpaShot?.frame.origin.y = UIScreen.main.bounds.size.height
// toViewController.view.transform = CGAffineTransform(scaleX: 1.0, y: 1.0)
}) { (_) in
if (!transitionContext.transitionWasCancelled) {
toViewController.view.transform = .identity
dimmingView?.removeFromSuperview()
toViewController.view.layer.mask = nil
}
fromViewSnpaShot?.removeFromSuperview()
fromViewController.view.isHidden = false
transitionContext.completeTransition(!transitionContext.transitionWasCancelled)
}
}
}
}

@ -407,8 +407,8 @@ extension PullToDismissTransition: UIViewControllerAnimatedTransitioning {
let completeBlock: (Bool) -> Void = { [weak self] finished -> Void in
if finished {
self?.dimmingView?.removeFromSuperview()
self?.dimmingView = nil
// self?.dimmingView?.removeFromSuperview()
// self?.dimmingView = nil
}
completionHandler?()

@ -202,9 +202,9 @@ class ViewController: UIViewController, Navigatable {
self.navigationItem.leftBarButtonItem = nil
}
if let tabbar = self.tabBarController as? HomeTabBarController, self.navigationController?.viewControllers.count == 1 {
tabbar.showAllBar(true, animated: true)
}
// if let tabbar = self.tabBarController as? HomeTabBarController, self.navigationController?.viewControllers.count == 1 {
// tabbar.showAllBar(true, animated: true)
// }
}
@objc func closeAction(sender: AnyObject) {

@ -8,6 +8,11 @@
import Foundation
import AVFoundation
import MediaPlayer
import Kingfisher
import RxSwift
import RxCocoa
import RxViewController
class AudioManager {
@ -31,15 +36,9 @@ class AudioManager {
}
commandCenter.playCommand.addTarget { [weak self] event in
//
return .success
}
setupRemoteTransportControls()
commandCenter.pauseCommand.addTarget { [weak self] event in
//
return .success
}
}
@objc func playerDidFinishPlaying(note: NSNotification) {
@ -64,6 +63,8 @@ class AudioManager {
var isSeekInProgress = false
var chasingTime = CMTime.zero
var isPlayingState = BehaviorSubject<Bool>(value: false)
var disposeBag = DisposeBag()
func setPlaylist(list: [AudioTrack]) {
@ -90,17 +91,22 @@ class AudioManager {
self.currentTrack = track
NotificationCenter.default.post(name: .notiPlayAudioTrack, object: track)
setLockScreenDisplay(track: track)
// updateLockScreenDisplay(track: track)
player?.rx.observe(AVPlayer.TimeControlStatus.self, "timeControlStatus")
.map { $0 == .playing }
.bind(to: isPlayingState)
.disposed(by: disposeBag)
playerItem.rx.observe(AVPlayerItem.Status.self, "status")
.filter { $0 == .readyToPlay }
.subscribe(onNext: { [weak self] _ in
self?.updateLockScreenDisplay(track: track)
})
.disposed(by: disposeBag)
// var nowPlayingInfo = [String: Any]()
// nowPlayingInfo[MPMediaItemPropertyTitle] = ""
// nowPlayingInfo[MPMediaItemPropertyArtist] = ""
// nowPlayingInfo[MPNowPlayingInfoPropertyElapsedPlaybackTime] = 100
// nowPlayingInfo[MPMediaItemPropertyPlaybackDuration] = 200
// nowPlayingInfo[MPNowPlayingInfoPropertyPlaybackRate] = 1
//
// MPNowPlayingInfoCenter.default().nowPlayingInfo = nowPlayingInfo
//
} catch {
@ -158,6 +164,9 @@ class AudioManager {
public func pause() {
if let player = player {
player.pause()
// if let currentTrack = currentTrack {
// updateLockScreenDisplay(track: currentTrack)
// }
NotificationCenter.default.post(name: .notiPlayPause, object: nil)
}
}
@ -165,6 +174,9 @@ class AudioManager {
public func resume() {
if let player = player {
player.play()
// if let currentTrack = currentTrack {
// updateLockScreenDisplay(track: currentTrack)
// }
NotificationCenter.default.post(name: .notiPlayResume, object: nil)
}
}
@ -185,6 +197,9 @@ class AudioManager {
} else {
self.trySeekToChaseTime()
}
if let currentTrack = self.currentTrack {
self.updateLockScreenDisplay(track: currentTrack)
}
}
}
@ -203,16 +218,101 @@ class AudioManager {
}
func setLockScreenDisplay(track: AudioTrack) {
var info = Dictionary<String, Any>()
info[MPMediaItemPropertyTitle] = track.title//
info[MPMediaItemPropertyArtist] = track.artist//
// [info setObject:self.model.filename forKey:MPMediaItemPropertyAlbumTitle];//
info[MPMediaItemPropertyAlbumArtist] = track.artist//
// info[MPMediaItemPropertyArtwork] = MPMediaItemArtwork(image: UIImage.init(named: track.pic!))//
info[MPMediaItemPropertyPlaybackDuration] = 200
info[MPNowPlayingInfoPropertyPlaybackRate] = 1.0//
func updateLockScreenDisplay(track: AudioTrack) {
guard let player = player, let currentItem = player.currentItem, currentItem.status == .readyToPlay else { return }
var info = [String: Any]()
//
info[MPMediaItemPropertyTitle] = track.title
info[MPMediaItemPropertyArtist] = track.artist
// ...
//
let duration = CMTimeGetSeconds(currentItem.duration)
let currentTime = CMTimeGetSeconds(player.currentTime())
info[MPMediaItemPropertyPlaybackDuration] = duration
info[MPNowPlayingInfoPropertyElapsedPlaybackTime] = currentTime
info[MPNowPlayingInfoPropertyPlaybackRate] = player.rate
//
updateAlbumArtwork(urlString: track.pic) { artwork in
info[MPMediaItemPropertyArtwork] = artwork
DispatchQueue.main.async {
MPNowPlayingInfoCenter.default().nowPlayingInfo = info
}
}
}
func updateAlbumArtwork(urlString: String?, completion: @escaping (MPMediaItemArtwork) -> Void) {
guard let urlString = urlString, let url = URL(string: urlString) else { return }
KingfisherManager.shared.retrieveImage(with: url) { result in
switch result {
case .success(let value):
let image = value.image
let artwork = MPMediaItemArtwork(boundsSize: image.size) { _ in return image }
DispatchQueue.main.async {
completion(artwork)
}
case .failure(let error):
print("Error fetching image: \(error)")
//
}
}
}
func setupRemoteTransportControls() {
commandCenter.playCommand.isEnabled = true
commandCenter.playCommand.addTarget { [unowned self] event in
if let currentTrack = currentTrack, !self.isPlaying() {
self.player?.play()
// updateLockScreenDisplay(track: currentTrack)
return .success
}
return .commandFailed
}
commandCenter.pauseCommand.isEnabled = true
commandCenter.pauseCommand.addTarget { [unowned self] event in
if let currentTrack = currentTrack, self.isPlaying() {
self.player?.pause()
// updateLockScreenDisplay(track: currentTrack)
return .success
}
return .commandFailed
}
commandCenter.changePlaybackPositionCommand.addTarget { event in
guard let event = event as? MPChangePlaybackPositionCommandEvent else {
return .commandFailed
}
let time = CMTime.init(seconds: event.positionTime, preferredTimescale: 1)
self.seekActually(time: time)
//
return .success
}
commandCenter.nextTrackCommand.addTarget { event in
self.nextTrack()
return .success
}
commandCenter.previousTrackCommand.addTarget { event in
self.previousTrack()
return .success
}
}
}

@ -16,7 +16,7 @@ import RxDataSources
enum FeedbackPhotoType {
case add
case photo(String)
case photo(UIImage)
}
struct FeedbackSection {

@ -21,7 +21,8 @@ enum HomeTabBarItem: Int {
return NavigationController(rootViewController: vc)
case .mine:
let vc = MineViewController(viewModel: viewModel, navigator: navigator)
return NavigationController(rootViewController: vc)
let nav = NavigationController(rootViewController: vc)
return nav
}
}
@ -109,34 +110,22 @@ class HomeTabBarController: UITabBarController, Navigatable {
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
makeUI()
bindViewModel()
guard let viewModel = viewModel else { return }
playerTabBar.addTarget(self, action: #selector(playerTabBarClick), for: .touchUpInside)
// let playerViewModel = PlayerViewModel.init(provider: viewModel.provider)
// self.playerVC = PlayerViewController.init(viewModel: playerViewModel, navigator: self.navigator)
// playerVC.modalPresentationStyle = .custom
// playerVC.transitioningDelegate = self
//
// playerPresentInteractor = PlayerInteractor(self, self.playerTabBar, playerVC)
// playerPresentInteractor.wantsInteractiveStart = false
// playerDismissInteractor = PlayerInteractor(playerVC, playerVC.view, nil)
// playerDismissInteractor.wantsInteractiveStart = false
// customeTabBar.delegate = self
}
func makeUI() {
tabBar.isHidden = true
tabBar.backgroundColor = .blue
view.addSubview(playerTabBar)
view.addSubview(customeTabBar)
customeTabBar.snp.makeConstraints { make in
make.left.equalTo(view)
make.right.equalTo(view)
@ -149,12 +138,15 @@ class HomeTabBarController: UITabBarController, Navigatable {
make.right.equalTo(view)
make.bottom.equalTo(customeTabBar.snp.top).offset(BaseDimensions.tabBarHeight)
}
}
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
// self.tabBar.isHidden = true
self.delegate = self
}
func bindViewModel() {
@ -169,8 +161,9 @@ class HomeTabBarController: UITabBarController, Navigatable {
output.tabBarItems.delay(.milliseconds(50)).drive(onNext: { [weak self] (tabBarItems) in
if let strongSelf = self {
let controllers = tabBarItems.map { $0.getController(with: viewModel.viewModel(for: $0), navigator: strongSelf.navigator) }
// strongSelf.showAllBar(true, animated: false)
strongSelf.setViewControllers(controllers, animated: false)
self?.selectedIndex = 0
}
}).disposed(by: rx.disposeBag)
@ -235,24 +228,66 @@ class HomeTabBarController: UITabBarController, Navigatable {
var tabItems = [UITabBarItem]()
for (index, viewController) in viewControllers.enumerated() {
var tabItem: UITabBarItem = viewController.tabBarItem
if tabItem.title == nil && tabItem.image == nil && tabItem.selectedImage == nil {
tabItem = UITabBarItem(title: "", image: nil, tag: index)
tabItem = UITabBarItem(title: nil, image: nil, tag: index)
}
tabItems.append(tabItem)
}
customeTabBar.setItems(tabItems, animated: false)
customeTabBar.selectedItem = customeTabBar.items?.first
customeTabBar.selectedItem = customeTabBar.items?[self.selectedIndex]
// tabBar.frame = CGRect.zero
// tabBar.isHidden = true
}
func refreshTabBarItems() {
guard let viewModel = self.viewModel, let navigator = self.navigator else { return }
let tabBarItems: [HomeTabBarItem] = [.home, .search, .mine]
let viewControllers = tabBarItems.map { item -> UIViewController in
return item.getController(with: viewModel.viewModel(for: item), navigator: navigator)
}
self.setViewControllers(viewControllers, animated: false)
tabBar.isHidden = true
tabBar.frame = CGRect.zero
}
override func tabBar(_ tabBar: UITabBar, didSelect item: UITabBarItem) {
super.tabBar(tabBar, didSelect: item)
}
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
}
override func viewWillDisappear(_ animated: Bool) {
super.viewWillDisappear(animated)
}
func showAllBar(_ isShowing: Bool, animated: Bool) {
if isAnimating { return }
isAnimating = true
if isShowing {
customeTabBar.isHidden = false
@ -321,17 +356,6 @@ class HomeTabBarController: UITabBarController, Navigatable {
self.isAnimating = false
}
// UIView.animate(withDuration: animationDuration,
// delay: 0,
// usingSpringWithDamping: 0.7,
// initialSpringVelocity: 0,
// options: .curveEaseInOut,
// animations: {
//
// }) { (completed) in
// }
}
open func showTabBar(_ isShowing: Bool, animated: Bool) {
@ -435,59 +459,14 @@ class HomeTabBarController: UITabBarController, Navigatable {
}
@objc func playerTabBarClick() {
// let player = PlayerViewController.init(viewModel: playerViewModel, navigator: self.navigator)
// player.modalPresentationStyle = .custom
// player.isPullToDismissEnabled = true
// player.modalPresentationCapturesStatusBarAppearance = true
//
// self.present(player, animated: true)
// let player = TestViewController.init()
// player.modalPresentationStyle = .custom
// player.isPullToDismissEnabled = true
// player.modalPresentationCapturesStatusBarAppearance = true
////
// let nav = NavigationController.init(rootViewController: player)
//
// nav.modalPresentationStyle = .custom
// nav.isPullToDismissEnabled = true
// nav.modalPresentationCapturesStatusBarAppearance = true
//
// self.present(nav, animated: true)
}
}
extension HomeTabBarController: UITabBarControllerDelegate {
func tabBarController(_ tabBarController: UITabBarController, shouldSelect viewController: UIViewController) -> Bool {
return viewController != tabBarController.selectedViewController
}
//extension HomeTabBarController: UIViewControllerTransitioningDelegate {
//
// func animationController(forPresented presented: UIViewController, presenting: UIViewController, source: UIViewController) -> UIViewControllerAnimatedTransitioning? {
// return PlayerPresentAnimator()
// }
//
// func interactionControllerForPresentation(using animator: UIViewControllerAnimatedTransitioning) -> UIViewControllerInteractiveTransitioning? {
// return playerPresentInteractor
// }
//
// func animationController(forDismissed dismissed: UIViewController) -> UIViewControllerAnimatedTransitioning? {
// return PlayerDismissAnimator()
// }
//
// func interactionControllerForDismissal(using animator: UIViewControllerAnimatedTransitioning) -> UIViewControllerInteractiveTransitioning? {
// return playerDismissInteractor
// }
//
//}
//
//
}

@ -58,11 +58,16 @@ class HomeTabBarViewModel: ViewModel, ViewModelType {
checkVersion()
input.notiPlayAudioTrack.subscribe { [weak self] noti in
guard let track = noti.element?.object as? AudioTrack else { return }
AudioManager.sharedInstance.player?.addProgressObserver { currentTime, duration, progress in
self?.progress.accept(Float(progress))
self?.duration.accept(Float(duration))
self?.time.accept(Float(currentTime))
print("viewmodel addProgressObserver : \(progress)")
// AudioManager.sharedInstance.updateNowPlayingInfo(track: track, progress: progress, duration: duration)
}
}.disposed(by: rx.disposeBag)

@ -45,7 +45,7 @@ class HomeViewController: TableViewController {
self.navigationItem.leftBarButtonItem = nil
if let tabbar = self.tabBarController as? HomeTabBarController {
if let tabbar = self.tabBarController as? HomeTabBarController, tabbar.customeTabBar.isHidden == true {
tabbar.showAllBar(true, animated: true)
}
@ -53,6 +53,13 @@ class HomeViewController: TableViewController {
navigationBar.passthroughView = homePagerView
}
if AudioManager.sharedInstance.playlist?.count ?? 0 > 0 {
tableView.contentInset = .init(top: 0, left: 0, bottom: BaseDimensions.tabBarHeight + 66, right: 0)
} else {
tableView.contentInset = .init(top: 0, left: 0, bottom: BaseDimensions.tabBarHeight, right: 0)
}
}
override func viewWillDisappear(_ animated: Bool) {
@ -61,6 +68,7 @@ class HomeViewController: TableViewController {
if let navigationBar = self.navigationController?.navigationBar as? NavigationBar {
navigationBar.passthroughView = nil
}
}
override func makeUI() {
@ -76,6 +84,11 @@ class HomeViewController: TableViewController {
tableView.tableHeaderView = headerView
tableView.separatorColor = .clear
tableView.register(HomeViewCell.self, forCellReuseIdentifier: "HomeViewCell")
}
@ -194,12 +207,9 @@ class HomeViewController: TableViewController {
make.left.equalTo(view)
make.right.equalTo(view)
make.top.equalTo(view).offset(-BaseDimensions.statusBarHeight)
if AudioManager.sharedInstance.playlist?.count ?? 0 > 0 {
make.bottom.equalTo(view).offset(-66)
} else {
make.bottom.equalTo(view)
}
}
}

@ -11,55 +11,203 @@ import RxCocoa
import RxDataSources
class CommentDetailViewController: TableViewController {
let commentDetailHeaderView: CommentDetailHeaderView = {
let commentDetailHeaderView = CommentDetailHeaderView.init()
return commentDetailHeaderView
}()
var commentToolView: CommentToolView = {
let commentToolView = CommentToolView.init(isShowButton: false, frame: CGRect.zero)
return commentToolView
}()
override func viewDidLoad() {
super.viewDidLoad()
tableView.register(CommentViewCell.self, forCellReuseIdentifier: "CommentViewCell")
}
//
// 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()
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
tableView.separatorColor = .clear
tableView.backgroundColor = .white
tableView.layer.cornerRadius = 8
tableView.layer.masksToBounds = true
tableView.layer.maskedCorners = [.layerMinXMinYCorner, .layerMaxXMinYCorner]
view.addSubview(commentToolView)
}
override func bindViewModel() {
super.bindViewModel()
guard let viewModel = viewModel as? CommentViewModel else { return }
guard let viewModel = viewModel as? CommentDetailViewModel else { return }
// let input = CommentViewModel.Input.init(viewWillAppear: rx.viewWillAppear,
// selection: tableView.rx.itemSelected.asDriver(),
// currentCommentListType: current)
// let output = viewModel.transform(input: input)
//
// let dataSource = CommentDetailViewController.dataSource()
//
// output.items.bind(to: tableView.rx.items(dataSource: dataSource)).disposed(by: rx.disposeBag)
//
//
//
//
// output.itemSelected.subscribe { [weak self] sectionItem in
//
// }.disposed(by: rx.disposeBag)
//
let input = CommentDetailViewModel.Input.init(viewWillAppear: rx.viewWillAppear,
modelSelected: tableView.rx.modelSelected(CommentType.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()
output.items.bind(to: tableView.rx.items(dataSource: dataSource)).disposed(by: rx.disposeBag)
output.modelSelected.drive { [weak self] sectionItem in
}.disposed(by: rx.disposeBag)
output.comment.drive { comment in
self.commentDetailHeaderView.comment = comment
self.updateHeaderViewFrame()
}.disposed(by: rx.disposeBag)
//
NotificationCenter.default.rx.notification(UIResponder.keyboardWillShowNotification)
.subscribe(onNext: { notification in
if let keyboardSize = (notification.userInfo?[UIResponder.keyboardFrameEndUserInfoKey] as? NSValue)?.cgRectValue {
let keyboardHeight = keyboardSize.height
self.commentToolView.snp.remakeConstraints { make in
make.left.equalTo(self.view)
make.right.equalTo(self.view)
make.bottom.equalTo(self.view).offset(-keyboardHeight)
make.height.equalTo(BaseDimensions.bottomHeight + 48)
}
UIView.animate(withDuration: 0.25) {
self.view.layoutIfNeeded()
}
}
})
.disposed(by: rx.disposeBag)
//
NotificationCenter.default.rx.notification(UIResponder.keyboardWillHideNotification)
.subscribe(onNext: { notification in
self.commentToolView.snp.remakeConstraints { make in
make.left.equalTo(self.view)
make.right.equalTo(self.view)
make.bottom.equalTo(self.view)
make.height.equalTo(BaseDimensions.bottomHeight + 48)
}
UIView.animate(withDuration: 0.25) {
self.view.layoutIfNeeded()
}
})
.disposed(by: rx.disposeBag)
let tapGesture = UITapGestureRecognizer()
tableView.addGestureRecognizer(tapGesture)
tapGesture.rx.event
.bind(onNext: { [weak self] _ in
self?.view.endEditing(true)
self?.navigator.dismiss(sender: self)
})
.disposed(by: rx.disposeBag)
tableView.mj_header = nil
}
func updateHeaderViewFrame() {
guard let headerView = tableView.tableHeaderView else {
return
}
// headerView
headerView.layoutIfNeeded()
let height = headerView.systemLayoutSizeFitting(UIView.layoutFittingCompressedSize).height
var frame = headerView.frame
frame.size.height = height
headerView.frame = frame
// : tableViewtableHeaderViewframe
tableView.tableHeaderView = headerView
}
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
commentToolView.snp.makeConstraints { make in
make.left.equalTo(view)
make.right.equalTo(view)
make.bottom.equalTo(view)
make.height.equalTo(BaseDimensions.bottomHeight + 48)
}
tableView.snp.remakeConstraints { make in
make.left.equalTo(view)
make.right.equalTo(view)
make.top.equalTo(view)
make.bottom.equalTo(view).offset(-BaseDimensions.bottomHeight - 48)
}
}
}
extension CommentDetailViewController {
func tableView(_ tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat {
return 60
}
func tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? {
let header = CommentDetailSectionView.init()
header.titleLabel.text = "全部回复"
return header
}
}
extension CommentDetailViewController {
static func dataSource() -> RxTableViewSectionedReloadDataSource<CommentSection> {
return RxTableViewSectionedReloadDataSource<CommentSection>(
@ -69,23 +217,17 @@ extension CommentDetailViewController {
case .comment(let comment):
let cell: CommentViewCell = tableView.dequeueReusableCell(withIdentifier: "CommentViewCell", for: indexPath) as! CommentViewCell
// cell.setting = item.setting
cell.comment = comment
return cell
case .quote(let commentQuote):
let cell: CommentQuoteViewCell = tableView.dequeueReusableCell(withIdentifier: "CommentQuoteViewCell", for: indexPath) as! CommentQuoteViewCell
// cell.setting = item.setting
cell.comment = commentQuote
return cell
}
}
)
})
}
}
@ -94,6 +236,9 @@ extension CommentDetailViewController {
class CommentDetailHeaderView: UIView {
let avatarView: UIImageView = {
let avatarView = UIImageView.init()
avatarView.layer.cornerRadius = 20
avatarView.layer.masksToBounds = true
avatarView.backgroundColor = .init(hex: 0xf5f5f5)
return avatarView
}()
@ -107,6 +252,7 @@ class CommentDetailHeaderView: UIView {
let commentLabel: UILabel = {
let commentLabel = UILabel.init()
commentLabel.numberOfLines = 0
commentLabel.textColor = .primaryText()
commentLabel.font = UIFont.systemFont(ofSize: 14)
@ -121,6 +267,18 @@ class CommentDetailHeaderView: UIView {
}()
var comment: Comment? {
didSet {
guard let comment = comment else { return }
avatarView.kf.setImage(with: URL.init(string: comment.avatar ?? ""))
nameLabel.text = comment.nickName
commentLabel.text = comment.content
}
}
override init(frame: CGRect) {
super.init(frame: frame)
@ -142,7 +300,9 @@ 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.centerY.equalTo(self)
make.top.equalTo(self).offset(24)
// make.bottom.equalTo(self).offset(-21)
}
nameLabel.snp.makeConstraints { make in
@ -167,3 +327,35 @@ class CommentDetailHeaderView: UIView {
}
}
class CommentDetailSectionView: UIView {
var titleLabel: UILabel = {
let titleLabel = UILabel.init()
titleLabel.font = UIFont.systemFont(ofSize: 17)
return titleLabel
}()
override init(frame: CGRect) {
super.init(frame: frame)
makeUI()
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
func makeUI() {
addSubview(titleLabel)
titleLabel.snp.makeConstraints { make in
make.top.equalTo(self).offset(18)
make.left.equalTo(self).offset(20)
}
}
}

@ -8,59 +8,138 @@
import Foundation
import RxSwift
import RxCocoa
import SVProgressHUD
class CommentDetailViewModel: ViewModel, ViewModelType {
struct Input {
let viewWillAppear: ControlEvent<Bool>
let selection: Driver<IndexPath>
let modelSelected: Driver<CommentType>
let footerRefresh: Observable<Void>
let commentText: Driver<String?>
let sendCommentTrigger: Driver<Void>
}
struct Output {
let comment: Driver<Comment>
let items: BehaviorRelay<[CommentSection]>
let itemSelected: PublishSubject<Comment>
let modelSelected: Driver<CommentType>
}
let items = BehaviorRelay<[CommentSection]>.init(value: [])
let itemSelected = PublishSubject<Comment>()
let commentID: String?
let comment: Comment
init(commentID: String, provider: IndieMusicAPI) {
self.commentID = commentID
init(comment: Comment, provider: IndieMusicAPI) {
self.comment = comment
super.init(provider: provider)
}
func transform(input: Input) -> Output {
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)
}
// let commentQuote = CommentQuote.init(avatar: "", name: "1233", comment: "dfdsadsdads", haveMore: false)
self.items.accept([CommentSection.init(items: newArray)])
} onError: { error in
}.disposed(by: self.rx.disposeBag)
}.disposed(by: rx.disposeBag)
input.footerRefresh.flatMapLatest({ [weak self] () -> Observable<[Comment]> in
guard let self = self else { return Observable.just([]) }
self.page += 1
return self.requestSubCommentData(parentId: self.comment.id, page: self.page, size: 10)
.trackActivity(self.footerLoading)
})
.subscribe(onNext: { (commentArray) in
let newArray = commentArray.map { comment in
return CommentType.comment(comment)
}
// let comment = Comment.init(avatar: "", name: "nike", date: 10000, isLike: false, comment: "test", commentQuotes: [commentQuote, commentQuote])
//
//
// let journalSection = CommentSection.init(items: [.comment(comment)])
//
// items.accept([journalSection])
guard let new = self.items.value.first else { return }
self.items.accept([CommentSection.init(items: new.items + newArray)])
}).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)
input.modelSelected.drive { commentType in
}.disposed(by: rx.disposeBag)
let sendComment = input.sendCommentTrigger
.withLatestFrom(input.commentText)
.drive(onNext: { comment in
if let comment = comment, !comment.isEmpty {
self.sendCommentData(content: comment, journalId: self.comment.journalId, parentId: self.comment.id, journalImage: self.comment.journalImage)
.subscribe { comment in
//TODO cell
SVProgressHUD.showText(withStatus: "发送成功")
} onError: { error in
}.disposed(by: self.rx.disposeBag)
} else {
print("评论为空,不发送")
}
})
return Output.init(items: items,
itemSelected: itemSelected)
return Output.init(comment: Driver.just(comment),
items: items,
modelSelected: input.modelSelected)
}
func requestSubCommentData(parentId: String, page: Int, size: Int) -> Observable<[Comment]> {
return self.provider.subCommentList(parentId: parentId, page: page, size: size)
.trackActivity(loading)
.trackError(error)
}
func requestCommentLike(commentID: String) -> Observable<CommentThumbState> {
return self.provider.commentLike(commentId: commentID)
.trackError(error)
}
func insertComment(in index: Int, with newCommentType: CommentType) {
var firstSection = items.value.first
firstSection?.items.insert(newCommentType, at: index + 1)
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)
}
}

@ -138,6 +138,16 @@ class CommentViewCell: UITableViewCell {
return commentLabel
}()
let moreButton: UIButton = {
let moreButton = UIButton.init()
moreButton.setTitle("更多回复", for: .normal)
moreButton.titleLabel?.font = UIFont.systemFont(ofSize: 14)
moreButton.setTitleColor(.primary(), for: .normal)
moreButton.isHidden = true
return moreButton
}()
var comment: Comment? {
didSet {
guard let comment = comment else { return }
@ -154,6 +164,31 @@ class CommentViewCell: UITableViewCell {
likeButton.isSelected = comment.haveThumbup == true ? true : false
if let commentCount = comment.commentCount, commentCount > 0 {
moreButton.setTitle("展开\(commentCount)条回复", for: .normal)
moreButton.isHidden = false
commentLabel.snp.remakeConstraints { make in
make.left.equalTo(avatarView.snp.right).offset(14)
make.right.equalTo(contentView).offset(-18)
make.top.equalTo(dateLabel.snp.bottom).offset(10)
}
moreButton.snp.remakeConstraints { make in
make.left.equalTo(avatarView.snp.right).offset(14)
make.top.equalTo(commentLabel.snp.bottom).offset(12)
make.bottom.equalTo(contentView).offset(-12)
}
} else {
moreButton.isHidden = true
commentLabel.snp.remakeConstraints { make in
make.left.equalTo(avatarView.snp.right).offset(14)
make.top.equalTo(dateLabel.snp.bottom).offset(10)
make.bottom.equalTo(contentView).offset(-12)
}
}
}
}
@ -189,6 +224,7 @@ class CommentViewCell: UITableViewCell {
contentView.addSubview(dateLabel)
contentView.addSubview(likeButton)
contentView.addSubview(commentLabel)
contentView.addSubview(moreButton)
avatarView.snp.makeConstraints { make in
make.left.equalTo(contentView).offset(18)
@ -219,6 +255,7 @@ class CommentViewCell: UITableViewCell {
make.bottom.equalTo(contentView).offset(-12)
}
}

@ -24,6 +24,7 @@ class CommentViewController: ViewController {
tableView.separatorColor = .clear
tableView.register(CommentViewCell.self, forCellReuseIdentifier: "CommentViewCell")
tableView.register(CommentQuoteViewCell.self, forCellReuseIdentifier: "CommentQuoteViewCell")
tableView.keyboardDismissMode = .onDrag
return tableView
}()
@ -64,6 +65,16 @@ class CommentViewController: ViewController {
}
}
let tapGesture = UITapGestureRecognizer()
tableView.addGestureRecognizer(tapGesture)
tapGesture.rx.event
.bind(onNext: { [weak self] _ in
self?.view.endEditing(true)
})
.disposed(by: rx.disposeBag)
}
@ -85,7 +96,12 @@ class CommentViewController: ViewController {
selection: tableView.rx.itemSelected.asDriver(),
currentCommentListType: currentCommentListType,
commentText: commentToolView.textField.rx.text.asDriver(),
sendCommentTrigger: commentToolView.textField.rx.controlEvent(.editingDidEndOnExit).asDriver())
sendCommentTrigger: commentToolView.textField.rx.controlEvent(.editingDidEndOnExit).asDriver(),
keyboardWillShowNotification: NotificationCenter.default.rx.notification(UIResponder.keyboardWillShowNotification),
keyboardWillHideNotification: NotificationCenter.default.rx.notification(UIResponder.keyboardWillHideNotification)
)
let output = viewModel.transform(input: input)
let dataSource = RxTableViewSectionedReloadDataSource<CommentSection>(
@ -112,6 +128,15 @@ class CommentViewController: ViewController {
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)
self.navigator.show(segue: .commentDetail(viewModel:commentDetailViewModel), sender: self, transition: .modal)
}.disposed(by: self.rx.disposeBag)
cell.rx.longPressGesture().when(.recognized)
.subscribe { [weak self] tap in
@ -144,8 +169,8 @@ class CommentViewController: ViewController {
}
cell.moreClousres = {[weak self] commentID in
let commentDetailViewModel = CommentDetailViewModel.init(commentID: commentID, provider: viewModel.provider)
self?.navigator.show(segue: .commentDetail(viewModel: commentDetailViewModel), sender: self)
// let commentDetailViewModel = CommentDetailViewModel.init(commentID: commentID, provider: viewModel.provider)
// self?.navigator.show(segue: .commentDetail(viewModel: commentDetailViewModel), sender: self)
}
return cell
@ -163,6 +188,53 @@ class CommentViewController: ViewController {
}.disposed(by: rx.disposeBag)
//
NotificationCenter.default.rx.notification(UIResponder.keyboardWillShowNotification)
.subscribe(onNext: { notification in
if let keyboardSize = (notification.userInfo?[UIResponder.keyboardFrameEndUserInfoKey] as? NSValue)?.cgRectValue {
let keyboardHeight = keyboardSize.height
self.commentToolView.snp.remakeConstraints { make in
make.left.equalTo(self.view)
make.right.equalTo(self.view)
make.bottom.equalTo(self.view).offset(-keyboardHeight)
make.height.equalTo(BaseDimensions.bottomHeight + 48)
}
UIView.animate(withDuration: 0.25) {
self.view.layoutIfNeeded()
}
}
})
.disposed(by: rx.disposeBag)
//
NotificationCenter.default.rx.notification(UIResponder.keyboardWillHideNotification)
.subscribe(onNext: { notification in
self.commentToolView.snp.remakeConstraints { make in
make.left.equalTo(self.view)
make.right.equalTo(self.view)
make.bottom.equalTo(self.view)
make.height.equalTo(BaseDimensions.bottomHeight + 48)
}
UIView.animate(withDuration: 0.25) {
self.view.layoutIfNeeded()
}
})
.disposed(by: rx.disposeBag)
}
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
}
override func viewDidLayoutSubviews() {
@ -186,7 +258,7 @@ class CommentViewController: ViewController {
make.left.equalTo(view)
make.right.equalTo(view)
make.top.equalTo(commentHeaderView.snp.bottom)
make.bottom.equalTo(commentToolView.snp.top)
make.bottom.equalTo(view).offset(-BaseDimensions.bottomHeight - 48)
}

@ -18,6 +18,10 @@ class CommentViewModel: ViewModel, ViewModelType {
let currentCommentListType: BehaviorRelay<CommentListType>
let commentText: Driver<String?>
let sendCommentTrigger: Driver<Void>
let keyboardWillShowNotification: Observable<Notification>
let keyboardWillHideNotification: Observable<Notification>
}
struct Output {
@ -26,6 +30,8 @@ class CommentViewModel: ViewModel, ViewModelType {
let itemSelected: PublishSubject<Comment>
let insertCommment: PublishSubject<Comment>
let toCommentDetail: PublishRelay<Comment>
}
@ -39,18 +45,21 @@ class CommentViewModel: ViewModel, ViewModelType {
let insertCommment: PublishSubject<Comment> = .init()
var journalID: String
let toCommentDetail: PublishRelay<Comment> = .init()
var journal: Journal
var subPage: Int = 1
init(journalID: String, provider: IndieMusicAPI) {
self.journalID = journalID
init(journal: Journal, provider: IndieMusicAPI) {
self.journal = journal
super.init(provider: provider)
}
func transform(input: Input) -> Output {
input.viewWillAppear.subscribe { _ in
self.requestHotCommentListData(journalID: self.journalID, page: self.page, size: 10)
self.requestHotCommentListData(journalID: self.journal.id, page: self.page, size: 10)
.subscribe { [weak self] commentArray in
guard let self = self else { return }
@ -67,7 +76,7 @@ class CommentViewModel: ViewModel, ViewModelType {
switch commentListType {
case .hot:
self.requestHotCommentListData(journalID: self.journalID, page: self.page, size: 10)
self.requestHotCommentListData(journalID: self.journal.id, page: self.page, size: 10)
.subscribe { [weak self] comments in
guard let self = self else { return }
@ -80,7 +89,7 @@ class CommentViewModel: ViewModel, ViewModelType {
case .latest:
self.requestLatestCommentListData(journalID: self.journalID, page: self.page, size: 10)
self.requestLatestCommentListData(journalID: self.journal.id, page: self.page, size: 10)
.subscribe { [weak self] comments in
guard let self = self else { return }
@ -135,11 +144,11 @@ class CommentViewModel: ViewModel, ViewModelType {
let sendComment = input.sendCommentTrigger
.withLatestFrom(input.commentText) // 使 commentText
.withLatestFrom(input.commentText)
.drive(onNext: { comment in
if let comment = comment, !comment.isEmpty {
self.sendCommentData(content: comment, journalId: self.journalID, parentId: nil, journalImage: nil)
self.sendCommentData(content: comment, journalId: self.journal.id, parentId: nil, journalImage: self.journal.image)
.subscribe { comment in
//TODO cell
@ -159,7 +168,8 @@ class CommentViewModel: ViewModel, ViewModelType {
return Output.init(items: items,
itemSelected: itemSelected,
insertCommment: insertCommment)
insertCommment: insertCommment,
toCommentDetail: toCommentDetail)
}
private func handleReceivedComments(comments: [Comment]) {
@ -167,18 +177,18 @@ class CommentViewModel: ViewModel, ViewModelType {
var commentSections = [CommentSection(items: commentTypes)]
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)
}
}
// 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)
// }
// }
}

@ -51,6 +51,7 @@ class JournalDetailController: ViewController, UIScrollViewDelegate {
doubleColumnSection.boundarySupplementaryItems = [header]
doubleColumnSection.contentInsets = NSDirectionalEdgeInsets(top: 0, leading: 18, bottom: 0, trailing: 18)
doubleColumnSection.interGroupSpacing = 24
return doubleColumnSection
@ -360,7 +361,7 @@ class JournalDetailController: ViewController, UIScrollViewDelegate {
.subscribe { [weak self] tap in
guard let self = self else { return }
let commentViewModel = CommentViewModel.init(journalID: viewModel.journal.id, provider: viewModel.provider)
let commentViewModel = CommentViewModel.init(journal: viewModel.journal, provider: viewModel.provider)
self.navigator.show(segue: .comment(viewModel: commentViewModel), sender: self)

@ -78,9 +78,7 @@ class LoginViewController: ViewController {
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
if let tabbar = self.tabBarController as? HomeTabBarController {
tabbar.showAllBar(false, animated: true)
}
}
override func makeUI() {
@ -260,4 +258,6 @@ class LoginViewController: ViewController {
}
}

@ -122,7 +122,7 @@ class MessageViewController: ViewController, UIScrollViewDelegate {
headerRefresh: refresh,
footerRefresh: footerRefreshTrigger,
messageType: messageType,
selection: tableView.rx.itemSelected.asDriver())
modelSelected: tableView.rx.modelSelected(MessageSectionItem.self).asDriver())
@ -134,14 +134,9 @@ class MessageViewController: ViewController, UIScrollViewDelegate {
output.items.bind(to: tableView.rx.items(dataSource: dataSource)).disposed(by: rx.disposeBag)
//
// output.tableViewItemSelected.subscribe { [weak self] message in
// let commentListViewModel = CommentListViewModel.init(provider: viewModel.provider)
//
// self?.navigator.show(segue: .commentList(viewModel: commentListViewModel), sender: self)
//
//
// }.disposed(by: rx.disposeBag)
output.modelSelected.drive { messageSectionItem in
}.disposed(by: rx.disposeBag)
}
@ -213,6 +208,11 @@ extension MessageViewController {
}
)
}
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
self.navigator.pop(sender: self, toRoot: true)
}
}

@ -17,41 +17,32 @@ class MessageViewModel: ViewModel, ViewModelType {
let headerRefresh: Observable<Void>
let footerRefresh: Observable<Void>
let messageType: BehaviorRelay<MessageType>
let selection: Driver<IndexPath>
let modelSelected: Driver<MessageSectionItem>
}
struct Output {
let messageItems: BehaviorRelay<[MessageSection]>
let activitiesItems: BehaviorRelay<[MessageSection]>
let messageItems: PublishRelay<[MessageSection]>
let activitiesItems: PublishRelay<[MessageSection]>
let items: BehaviorRelay<[MessageSection]>
let selection: PublishSubject<MessageSectionItem>
let itemSelected: PublishSubject<MessageSectionItem>
let modelSelected: Driver<MessageSectionItem>
}
let messageItems = BehaviorRelay<[MessageSection]>.init(value: [])
let activitiesItems = BehaviorRelay<[MessageSection]>.init(value: [])
let messageItems = PublishRelay<[MessageSection]>.init()
let activitiesItems = PublishRelay<[MessageSection]>.init()
let items = BehaviorRelay<[MessageSection]>.init(value: [])
let selection = PublishSubject<MessageSectionItem>()
let messageType = BehaviorRelay<MessageType>.init(value: .message)
let itemSelected = PublishSubject<MessageSectionItem>()
func transform(input: Input) -> Output {
input.messageType.subscribe { [weak self] followersType in
guard let self = self else { return }
self.messageType.accept(followersType)
}.disposed(by: rx.disposeBag)
input.headerRefresh.withLatestFrom(input.messageType)
.flatMapLatest({ [weak self] (messageType) -> Observable<[Message]> in
guard let self = self else { return Observable.just([]) }
@ -119,6 +110,7 @@ class MessageViewModel: ViewModel, ViewModelType {
input.messageType.subscribe { [weak self] messageType in
guard let self = self else { return }
self.messageType.accept(messageType)
print("messageType \(messageType)")
self.page = 1
@ -176,7 +168,13 @@ class MessageViewModel: ViewModel, ViewModelType {
cuttentItems.subscribe { [weak self] followersSections in
guard let self = self else { return }
if self.messageType.value == .activities {
} else {
self.items.accept(followersSections)
}
}.disposed(by: rx.disposeBag)
@ -187,7 +185,7 @@ class MessageViewModel: ViewModel, ViewModelType {
activitiesItems: activitiesItems,
items: items,
selection: selection,
itemSelected: itemSelected)
modelSelected: input.modelSelected)
}

@ -73,6 +73,7 @@ class MineSingleController: ViewController {
}.disposed(by: rx.disposeBag)
}
override func bindViewModel() {
@ -121,6 +122,28 @@ class MineSingleController: ViewController {
}.disposed(by: rx.disposeBag)
headerView.playAllButton.rx.tap.subscribe { _ in
viewModel.playSubject.accept(())
}.disposed(by: rx.disposeBag)
output.toPlay.subscribe { audioTracks in
guard let audioTracks = audioTracks.element,
let audioTrack = audioTracks.first else { return }
let playerViewModel = PlayerViewModel.init(track: audioTrack, provider: viewModel.provider)
self.navigator.show(segue: .player(viewModel: playerViewModel), sender: self, transition: .modal)
DispatchQueue.main.asyncAfter(deadline: .now() + 1) {
//
AudioManager.sharedInstance.setPlaylist(list: audioTracks)
AudioManager.sharedInstance.playTrack(track: audioTrack)
}
}.disposed(by: rx.disposeBag)
}

@ -25,6 +25,7 @@ class MineSingleViewModel: ViewModel, ViewModelType {
let items: BehaviorRelay<[MineSingleSection]>
let selection: Driver<IndexPath>
let itemSelected: PublishSubject<AudioTrack>
let toPlay: PublishRelay<[AudioTrack]>
}
@ -33,7 +34,10 @@ class MineSingleViewModel: ViewModel, ViewModelType {
let needRefresh: BehaviorRelay<AudioTrack?> = .init(value: nil)
let playSubject = PublishRelay<Void>.init()
func transform(input: Input) -> Output {
let toPlay = PublishRelay<[AudioTrack]>.init()
input.viewWillAppear.subscribe { (_) in
@ -48,10 +52,14 @@ class MineSingleViewModel: ViewModel, ViewModelType {
return self.requestSingleList(page: self.page, size: 10)
.trackActivity(self.headerLoading)
})
.subscribe(onNext: { (items) in
.subscribe(onNext: { items in
self.items.accept([MineSingleSection.init(items: items)])
}, onError: { error in
}).disposed(by: rx.disposeBag)
input.footerRefresh.flatMapLatest({ [weak self] () -> Observable<[AudioTrack]> in
guard let self = self else { return Observable.just([]) }
self.page += 1
@ -78,10 +86,24 @@ class MineSingleViewModel: ViewModel, ViewModelType {
}.disposed(by: rx.disposeBag)
playSubject.subscribe { _ in
self.requestSingleList(page: 0, size: 0)
.subscribe { audioTracks in
toPlay.accept(audioTracks)
} onError: { error in
}.disposed(by: self.rx.disposeBag)
}.disposed(by: rx.disposeBag)
return Output.init(items: items,
selection: input.selection,
itemSelected: itemSelected)
itemSelected: itemSelected,
toPlay: toPlay)
}

@ -315,6 +315,7 @@ class MineViewCell: UITableViewCell {
return playButton
}()
var playClousres: (()->())?
var mine: Mine? {
@ -337,6 +338,8 @@ class MineViewCell: UITableViewCell {
selectionStyle = .none
makeUI()
playButton.addTarget(self, action: #selector(playButtonClick), for: .touchUpInside)
}
required init?(coder: NSCoder) {
@ -390,5 +393,11 @@ class MineViewCell: UITableViewCell {
}
}
@objc func playButtonClick() {
if let playClousres = self.playClousres {
playClousres()
}
}
}

@ -38,15 +38,33 @@ class MineViewController: TableViewController {
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
self.navigationController?.setNavigationBarHidden(false, animated: true)
if let tabbar = self.tabBarController as? HomeTabBarController {
tabbar.showAllBar(true, animated: true)
}
if AudioManager.sharedInstance.playlist?.count ?? 0 > 0 {
tableView.contentInset = .init(top: 0, left: 0, bottom: BaseDimensions.tabBarHeight + 66, right: 0)
} else {
tableView.contentInset = .init(top: 0, left: 0, bottom: BaseDimensions.tabBarHeight, right: 0)
}
}
override func viewDidLoad() {
super.viewDidLoad()
let fixedSpaceBarButtonItem = UIBarButtonItem.init(barButtonSystemItem: .fixedSpace, target: nil, action: nil)
fixedSpaceBarButtonItem.width = -10
let spacerView = UIView(frame: CGRect(x: 0, y: 0, width: 0, height: 1))
let spacer = UIBarButtonItem(customView: spacerView)
navigationItem.rightBarButtonItems = [settingBarButton, fixedSpaceBarButtonItem, messageBarButton]
}
@ -66,16 +84,6 @@ class MineViewController: TableViewController {
let fixedSpaceBarButtonItem = UIBarButtonItem.init(barButtonSystemItem: .fixedSpace, target: nil, action: nil)
fixedSpaceBarButtonItem.width = -10
let spacerView = UIView(frame: CGRect(x: 0, y: 0, width: 0, height: 1))
let spacer = UIBarButtonItem(customView: spacerView)
navigationItem.rightBarButtonItems = [settingBarButton, fixedSpaceBarButtonItem, messageBarButton]
}
@ -91,6 +99,17 @@ class MineViewController: TableViewController {
let output = viewModel.transform(input: input)
headerView.nameButton.rx.tap.subscribe { [weak self] _ in
if let tabBar = self?.tabBarController as? HomeTabBarController {
for subview in tabBar.customeTabBar.subviews {
// UITabBarButton is a private class of UIKit, so we check by converting the class to a String
let viewClassName = NSStringFromClass(subview.classForCoder)
if viewClassName.contains("UITabBarButton") {
print("Tab Bar Item Frame: \(subview.frame)")
}
}
}
guard AuthManager.shared.token?.isValid == false else { return }
let loginViewModel = LoginViewModel.init(provider: viewModel.provider)
@ -148,7 +167,9 @@ class MineViewController: TableViewController {
let dataSource = MineViewController.dataSource()
let dataSource = MineViewController.dataSource { cell, mine in
viewModel.playSubject.accept(())
}
output.items.bind(to: tableView.rx.items(dataSource: dataSource)).disposed(by: rx.disposeBag)
@ -204,34 +225,41 @@ class MineViewController: TableViewController {
self?.headerView.user = user
}.disposed(by: rx.disposeBag)
output.toPlay.subscribe { audioTracks in
guard let audioTracks = audioTracks.element,
let audioTrack = audioTracks.first else { return }
let playerViewModel = PlayerViewModel.init(track: audioTrack, provider: viewModel.provider)
self.navigator.show(segue: .player(viewModel: playerViewModel), sender: self, transition: .modal)
DispatchQueue.main.asyncAfter(deadline: .now() + 1) {
//
AudioManager.sharedInstance.setPlaylist(list: audioTracks)
AudioManager.sharedInstance.playTrack(track: audioTrack)
}
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
tableView.snp.remakeConstraints { make in
make.edges.equalTo(view)
if AudioManager.sharedInstance.playlist?.count ?? 0 > 0 {
make.bottom.equalTo(view).offset(-66 - BaseDimensions.tabBarHeight)
} else {
make.bottom.equalTo(view)
}
}
}.disposed(by: rx.disposeBag)
}
}
extension MineViewController {
static func dataSource() -> RxTableViewSectionedReloadDataSource<MineSection> {
static func dataSource(_ buttonTapHandler: @escaping (UITableViewCell, Mine) -> Void) -> RxTableViewSectionedReloadDataSource<MineSection> {
return RxTableViewSectionedReloadDataSource<MineSection>(
configureCell: { dataSource, tableView, indexPath, item in
let cell: MineViewCell = tableView.dequeueReusableCell(withIdentifier: "MineViewCell", for: indexPath) as! MineViewCell
cell.mine = item
cell.playClousres = {
buttonTapHandler(cell, item)
}
return cell
}
)

@ -23,6 +23,7 @@ class MineViewModel: ViewModel, ViewModelType {
let selection: Driver<IndexPath>
let itemSelected: PublishSubject<Mine>
let user: BehaviorRelay<User?>
let toPlay: PublishRelay<[AudioTrack]>
}
@ -30,8 +31,12 @@ class MineViewModel: ViewModel, ViewModelType {
let items = BehaviorRelay<[MineSection]>.init(value: [])
let user = BehaviorRelay<User?>.init(value: nil)
let playSubject = PublishRelay<Void>.init()
func transform(input: Input) -> Output {
let toPlay = PublishRelay<[AudioTrack]>.init()
input.viewWillAppear.subscribe { _ in
self.requestUserInfoData().subscribe { user in
self.user.accept(user)
@ -75,11 +80,25 @@ class MineViewModel: ViewModel, ViewModelType {
}.disposed(by: rx.disposeBag)
playSubject.subscribe { _ in
self.requestAllSingleList()
.subscribe { audioTracks in
toPlay.accept(audioTracks)
} onError: { error in
}.disposed(by: self.rx.disposeBag)
}.disposed(by: rx.disposeBag)
return Output.init(items: items,
selection: input.selection,
itemSelected: itemSelected,
user: user)
user: user,
toPlay: toPlay)
}
@ -89,4 +108,12 @@ class MineViewModel: ViewModel, ViewModelType {
.trackError(error)
}
func requestAllSingleList() -> Observable<[AudioTrack]> {
return self.provider.collectSongList(userId: UserDefaults.AccountInfo.string(forKey: .userID) ?? "", page: 0, size: 0)
.trackActivity(loading)
.trackError(error)
}
}

@ -172,9 +172,6 @@ extension FollowersViewController {
} else {
cell.followingButton.setTitle("取消", for: .normal)
cell.followingButton.setTitle("添加", for: .selected)
}

@ -52,14 +52,12 @@ class FollowingViewController: TableViewController {
let input = FollowingViewModel.Input.init(viewWillAppear: rx.viewWillAppear,
headerRefresh: refresh,
footerRefresh: footerRefreshTrigger,
selection: tableView.rx.itemSelected.asDriver())
modelSelected: tableView.rx.modelSelected(User.self).asDriver())
let output = viewModel.transform(input: input)
let dataSource = FollowingViewController.dataSource { cell, user in
viewModel.itemSelected.onNext(user)
}
output.items.bind(to: tableView.rx.items(dataSource: dataSource)).disposed(by: rx.disposeBag)
@ -67,26 +65,13 @@ class FollowingViewController: TableViewController {
output.itemSelected.subscribe { [weak self] sectionItem in
guard let userID = sectionItem.element?.id else { return }
let personalViewModel = PersonalViewModel.init(userID: userID, provider: viewModel.provider)
self?.navigator.show(segue: .personal(viewModel: personalViewModel), sender: self)
}.disposed(by: rx.disposeBag)
output.selection.drive { [weak self] indexPath in
guard let user = output.items.value.first?.items[indexPath.row] else { return }
output.modelSelected.drive { [weak self] user in
let personalViewModel = PersonalViewModel.init(userID: user.id, provider: viewModel.provider)
self?.navigator.show(segue: .personal(viewModel: personalViewModel), sender: self)
}.disposed(by: rx.disposeBag)
}

@ -17,14 +17,13 @@ class FollowingViewModel: ViewModel, ViewModelType {
let headerRefresh: Observable<Void>
let footerRefresh: Observable<Void>
let selection: Driver<IndexPath>
let modelSelected: Driver<User>
}
struct Output {
let items: BehaviorRelay<[FollowingSection]>
let selection: Driver<IndexPath>
let itemSelected: PublishSubject<User>
let modelSelected: Driver<User>
}
@ -134,8 +133,8 @@ class FollowingViewModel: ViewModel, ViewModelType {
return Output.init(items: items,
selection: input.selection,
itemSelected: itemSelected)
// selection: input.selection,
modelSelected: input.modelSelected)
}

@ -113,8 +113,10 @@ class PersonalViewController: ViewController {
guard let viewModel = viewModel as? PersonalViewModel else { return }
let refresh = Observable.of(Observable.just(()), headerRefreshTrigger).merge()
let input = PersonalViewModel.Input.init(viewWillAppear: rx.viewWillAppear,
headerRefresh: headerRefreshTrigger,
headerRefresh: refresh,
footerRefresh: footerRefreshTrigger,
personInfoLikeType: personInfoLikeType,
selection: collectionView.rx.itemSelected.asDriver(),
@ -433,6 +435,7 @@ class PersonalHeaderView: UICollectionReusableView {
let messageButton: BadgeButton = {
let messageButton = BadgeButton.init()
messageButton.setImage(UIImage.init(named: "personal_message_btn"), for: .normal)
messageButton.isHidden = true
return messageButton
}()

@ -92,11 +92,15 @@ class PersonalViewModel: ViewModel, ViewModelType {
}
})
.subscribe(onNext: { (items) in
.subscribe(onNext: { items in
self.items.accept([PersonInfoSection.audio(personInfoLikeType: self.personInfoLikeType.value, items: items)])
}, onError: { error in
self.items.accept([PersonInfoSection.audio(personInfoLikeType: self.personInfoLikeType.value, items: [])])
}).disposed(by: rx.disposeBag)
input.footerRefresh.withLatestFrom(input.personInfoLikeType)
.flatMapLatest({ [weak self] (personInfoLikeType) -> Observable<[PersonInfoItem]> in
guard let self = self else { return Observable.just([]) }

@ -57,6 +57,15 @@ class SearchViewController: ViewController, UIScrollViewDelegate {
}
self.navigationController?.setNavigationBarHidden(true, animated: false)
if AudioManager.sharedInstance.playlist?.count ?? 0 > 0 {
collectionView.contentInset = .init(top: 0, left: 0, bottom: BaseDimensions.tabBarHeight + 66, right: 0)
} else {
collectionView.contentInset = .init(top: 0, left: 0, bottom: BaseDimensions.tabBarHeight, right: 0)
}
}
override func viewWillDisappear(_ animated: Bool) {
@ -136,11 +145,7 @@ class SearchViewController: ViewController, UIScrollViewDelegate {
make.left.equalTo(view)
make.right.equalTo(view)
make.top.equalTo(view).offset(-BaseDimensions.navBarHeight)
if AudioManager.sharedInstance.playlist?.count ?? 0 > 0 {
make.bottom.equalTo(view).offset(-66 - BaseDimensions.tabBarHeight)
} else {
make.bottom.equalTo(view).offset(-BaseDimensions.tabBarHeight)
}
make.bottom.equalTo(view)
}
}

@ -43,6 +43,9 @@ class EditInfoViewController: TableViewController {
super.bindViewModel()
guard let viewModel = viewModel as? EditInfoViewModel else { return }
var nikeName = ""
var signature = ""
let input = EditInfoViewModel.Input.init(viewWillAppear: rx.viewWillAppear,
selection: tableView.rx.itemSelected.asDriver(),
@ -65,13 +68,14 @@ class EditInfoViewController: TableViewController {
output.selection.drive { [weak self] sectionItem in
self?.deselectSelectedRow()
switch sectionItem.row {
case 0:
let editNameViewModel = EditNameViewModel.init(provider: viewModel.provider)
let editNameViewModel = EditNameViewModel.init(nickName: nikeName, provider: viewModel.provider)
self?.navigator.show(segue: .editName(viewModel: editNameViewModel), sender: self)
case 1:
let editSignatureViewModel = EditSignatureViewModel.init(provider: viewModel.provider)
let editSignatureViewModel = EditSignatureViewModel.init(signature: signature, provider: viewModel.provider)
self?.navigator.show(segue: .editSignature(viewModel: editSignatureViewModel), sender: self)
case 2:
let editDate = EditDateViewModel.init(birthDayText: viewModel.birthDayText, provider: viewModel.provider)
@ -83,13 +87,14 @@ class EditInfoViewController: TableViewController {
default: break
}
}.disposed(by: rx.disposeBag)
output.user.subscribe { [weak self] user in
guard let user = user.element else { return }
nikeName = user.nickName ?? ""
signature = user.signature ?? ""
self?.headerView.avatarView.kf.setImage(with: URL.init(string: user.avatar ?? ""))
}.disposed(by: rx.disposeBag)
@ -97,15 +102,11 @@ class EditInfoViewController: TableViewController {
self?.headerView.avatarView.kf.setImage(with: URL.init(string: url))
}.disposed(by: rx.disposeBag)
let avatarViewTap = headerView.avatarView.rx.tapGesture().when(.recognized)
let avatarLabelTap = headerView.tipsLabel.rx.tapGesture().when(.recognized)
headerView.avatarView.rx.tapGesture().when(.recognized)
Observable.merge(avatarViewTap, avatarLabelTap)
.subscribe { gestureRecognizer in
// self.navigator.show(segue: .photoConfirm, sender: self, transition: .navigationPresent(type: .photoConfirm))
// let configuration = PHPickerConfiguration()
// configuration.filter = .images //
RxModal.photoPicker(presenter: .viewController(self)) { config in
config.filter = .images
config.selectionLimit = 1
@ -128,6 +129,7 @@ class EditInfoViewController: TableViewController {
}.disposed(by: self.rx.disposeBag)
}.disposed(by: rx.disposeBag)
tableView.mj_footer = nil

@ -23,9 +23,17 @@ class EditNameViewModel: ViewModel, ViewModelType {
let error: Driver<String>
let textCount: Driver<Int>
let dismiss: PublishSubject<Void>
}
let dismiss = PublishSubject<Void>.init()
let nickName: String
init(nickName: String, provider: IndieMusicAPI) {
self.nickName = nickName
super.init(provider: provider)
}
func transform(input: Input) -> Output {
let errorSubject = PublishSubject<String>()
@ -98,7 +106,11 @@ class EditNameController: ViewController {
super.viewDidLoad()
// viewModel.nickNameSubject
// .take(1) //
// .asDriver(onErrorJustReturn: "")
// .drive(editNameTextFieldView.textFieldView.rx.text)
// .disposed(by: rx.disposeBag)
}
@ -109,7 +121,7 @@ class EditNameController: ViewController {
view.addSubview(editNameTextFieldView)
confirmBarButton
self.navigationItem.rightBarButtonItem = confirmBarButton
}
@ -117,6 +129,7 @@ class EditNameController: ViewController {
super.bindViewModel()
guard let viewModel = viewModel as? EditNameViewModel else { return }
editNameTextFieldView.textFieldView.text = viewModel.nickName
let input = EditNameViewModel.Input.init(viewWillAppear: rx.viewWillAppear,
nickName: editNameTextFieldView.textFieldView.rx.text.filterNil().asDriver(onErrorJustReturn: ""),
@ -126,6 +139,10 @@ class EditNameController: ViewController {
let output = viewModel.transform(input: input)
output.textCount.drive { textCount in
self.editNameTextFieldView.countTipsLabel.addAttributed(attributedes: [("\(textCount)", [NSAttributedString.Key.foregroundColor: UIColor.primaryText()]),
("/10", [NSAttributedString.Key.foregroundColor: UIColor.tertiaryText()])
@ -144,7 +161,6 @@ class EditNameController: ViewController {
self?.navigator.pop(sender: self)
}.disposed(by: rx.disposeBag)
}

@ -133,7 +133,7 @@ class EditSexViewController: ViewController, UIScrollViewDelegate {
output.popView.subscribe { [weak self] _ in
self?.navigator.pop(sender: self)
self?.navigator.dismiss(sender: self)
}.disposed(by: rx.disposeBag)
dismissButton.rx.tap.subscribe { [weak self] _ in

@ -26,6 +26,12 @@ class EditSignatureViewModel: ViewModel, ViewModelType {
}
let dismiss = PublishSubject<Void>.init()
let signature: String
init(signature: String, provider: IndieMusicAPI) {
self.signature = signature
super.init(provider: provider)
}
func transform(input: Input) -> Output {
let errorSubject = PublishSubject<String>()
@ -118,6 +124,8 @@ class EditSignatureViewController: ViewController {
guard let viewModel = viewModel as? EditSignatureViewModel else { return }
editNameTextFieldView.textFieldView.text = viewModel.signature
let input = EditSignatureViewModel.Input.init(viewWillAppear: rx.viewWillAppear,
signature: editNameTextFieldView.textFieldView.rx.text.filterNil().asDriver(onErrorJustReturn: ""),
saveTrigger: confirmBarButton.rx.tap.asDriver())

@ -10,6 +10,7 @@ import GrowingTextView
import RxSwift
import RxCocoa
import RxDataSources
import RxModal
class FeedbackViewController: ViewController {
let feedbackTypeView: FeedbackTypeView = {
@ -42,6 +43,9 @@ class FeedbackViewController: ViewController {
return confirmButton
}()
let photoItems = PublishRelay<[UIImage]>.init()
let category = PublishRelay<String>.init()
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
@ -78,13 +82,62 @@ class FeedbackViewController: ViewController {
guard let viewModel = viewModel as? FeedbackViewModel else { return }
let input = FeedbackViewModel.Input.init(viewWillAppear: rx.viewWillAppear)
let input = FeedbackViewModel.Input.init(viewWillAppear: rx.viewWillAppear,
modelSelected: feedbackDetailView.feedbackDetailSubView.collectionView.rx.modelSelected(FeedbackPhotoType.self).asDriver(),
photoItems: photoItems,
category: category,
content: feedbackDetailView.feedbackDetailSubView.textView.rx.text.asDriver(),
contact: feedbackContactView.textFieldView.rx.text.asDriver(),
confirmTrigger: confirmButton.rx.tap.asDriver())
let output = viewModel.transform(input: input)
let dataSource = FeedbackDetailSubView.dataSource()
let dataSource = FeedbackDetailSubView.dataSource { cell, image in
viewModel.deleteImage(image, from: viewModel.photoItems)
}
output.photoItems.bind(to: feedbackDetailView.feedbackDetailSubView.collectionView.rx.items(dataSource: dataSource)).disposed(by: rx.disposeBag)
output.photoItems.subscribe { feedbackSection in
if feedbackSection.first?.items.isEmpty == false {
self.feedbackDetailView.feedbackDetailSubView.setNeedsLayout()
self.feedbackDetailView.feedbackDetailSubView.layoutIfNeeded()
}
}.disposed(by: rx.disposeBag)
output.toPhotoSelect.subscribe { imageArray in
RxModal.photoPicker(presenter: .viewController(self)) { config in
config.filter = .images
config.selectionLimit = 9
}.subscribe { results in
var images = [UIImage]()
let itemProviders = results.map(\.itemProvider)
for item in itemProviders {
if item.canLoadObject(ofClass: UIImage.self) {
item.loadObject(ofClass: UIImage.self) { (image, error) in
DispatchQueue.main.async {
if let image = image as? UIImage {
images.append(image)
self.photoItems.accept(images)
}
}
}
}
}
} onFailure: { error in
}.disposed(by: self.rx.disposeBag)
}.disposed(by: rx.disposeBag)
}
@ -313,35 +366,36 @@ class FeedbackDetailSubView: UIView {
var collectionView: UICollectionView = {
let layout = UICollectionViewCompositionalLayout { (sectionIndex, environment) -> NSCollectionLayoutSection? in
// item
let itemSize = NSCollectionLayoutSize(widthDimension: .absolute(84), heightDimension: .absolute(84))
let item = NSCollectionLayoutItem(layoutSize: itemSize)
// Item
let collectionViewWidth = BaseDimensions.screenWidth - 2 * 18
//
let totalSpacingWidth: CGFloat = (2 * 18) + (2 * 18) // item
// item
let adjustedItemWidth = (collectionViewWidth - totalSpacingWidth) / 3
// group
let groupSize = NSCollectionLayoutSize(widthDimension: .absolute(84), heightDimension: .absolute(84))
// 使 adjustedItemWidth itemSize
let itemSize = NSCollectionLayoutSize(widthDimension: .absolute(adjustedItemWidth), heightDimension: .absolute(adjustedItemWidth))
// let itemSize = NSCollectionLayoutSize(widthDimension: .absolute(84), heightDimension: .absolute(84))
let item = NSCollectionLayoutItem(layoutSize: itemSize)
// group使.horizontal使
let group = NSCollectionLayoutGroup.horizontal(layoutSize: groupSize, subitems: [item])
//
let verticalGroupSize = NSCollectionLayoutSize(widthDimension: .fractionalWidth(1.0), heightDimension: .estimated(84))
let verticalGroup = NSCollectionLayoutGroup.horizontal(layoutSize: verticalGroupSize, subitem: item, count: 3)
verticalGroup.interItemSpacing = .fixed(18)
// section
let section = NSCollectionLayoutSection(group: group)
//
let section = NSCollectionLayoutSection(group: verticalGroup)
section.contentInsets = .init(top: 18, leading: 18, bottom: 18, trailing: 18)
section.interGroupSpacing = 18
//
section.orthogonalScrollingBehavior = .continuous
return section
}
let collectionView = UICollectionView.init(frame: CGRect.zero, collectionViewLayout: layout)
collectionView.register(FeedbackDetailViewCell.self, forCellWithReuseIdentifier: "FeedbackDetailViewCell")
collectionView.showsHorizontalScrollIndicator = false
collectionView.showsVerticalScrollIndicator = false
// collectionView.backgroundColor = .init(hex: 0xfbfbfb)
return collectionView
}()
@ -387,38 +441,46 @@ class FeedbackDetailSubView: UIView {
}
// Item
let collectionViewWidth = BaseDimensions.screenWidth - 2 * 18
//
let totalSpacingWidth: CGFloat = (2 * 18) + (2 * 18) // item
// item
let adjustedItemWidth = (collectionViewWidth - totalSpacingWidth) / 3
let rowsPerSection = ceil(Double(collectionView.numberOfItems(inSection: 0)) / Double(3))
let totalSectionHeight = CGFloat(rowsPerSection) * adjustedItemWidth + CGFloat(rowsPerSection - 1) * 18 + 36
print("collectionView height \(rowsPerSection) \(totalSectionHeight)")
collectionView.snp.remakeConstraints { make in
make.left.equalTo(self)
make.right.equalTo(self)
make.top.equalTo(countLabel.snp.bottom).offset(53)
make.bottom.equalTo(self).offset(0)
make.height.equalTo(120)
make.height.equalTo(totalSectionHeight)
}
// print("collectionView height \(84 * collectionView.numberOfItems(inSection: 0) % 3) \(collectionView.numberOfItems(inSection: 0))")
}
}
extension FeedbackDetailSubView {
static func dataSource() -> RxCollectionViewSectionedReloadDataSource<FeedbackSection> {
static func dataSource(_ buttonTapHandler: @escaping (UICollectionViewCell, UIImage) -> Void) -> RxCollectionViewSectionedReloadDataSource<FeedbackSection> {
return RxCollectionViewSectionedReloadDataSource<FeedbackSection>(
configureCell: { dataSource, collectionView, indexPath, item in
switch dataSource[indexPath] {
case .add:
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "FeedbackDetailViewCell", for: indexPath) as? FeedbackDetailViewCell
cell?.feedbackPhotoType = item
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "FeedbackDetailViewCell", for: indexPath) as! FeedbackDetailViewCell
cell.feedbackPhotoType = item
return cell!
case .photo(let photo):
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "FeedbackDetailViewCell", for: indexPath) as? FeedbackDetailViewCell
cell?.feedbackPhotoType = item
return cell!
cell.closeClosures = { image in
buttonTapHandler(cell, image)
}
return cell
}
)
}
@ -450,6 +512,7 @@ class FeedbackDetailViewCell: UICollectionViewCell {
return addImageView
}()
var closeClosures: ((UIImage) -> ())?
var feedbackPhotoType: FeedbackPhotoType? {
didSet {
@ -470,6 +533,7 @@ class FeedbackDetailViewCell: UICollectionViewCell {
addImageView.isHidden = true
imageView.isHidden = false
closeButton.isHidden = false
imageView.image = image
contentView.layer.borderColor = UIColor.clear.cgColor
contentView.layer.borderWidth = 1
@ -486,6 +550,8 @@ class FeedbackDetailViewCell: UICollectionViewCell {
super.init(frame: frame)
makeUI()
closeButton.addTarget(self, action: #selector(closeButtonClick), for: .touchUpInside)
}
required init?(coder: NSCoder) {
@ -513,6 +579,15 @@ class FeedbackDetailViewCell: UICollectionViewCell {
}
}
@objc func closeButtonClick() {
guard let feedbackPhotoType = feedbackPhotoType,
let closeClosures = self.closeClosures else { return }
if case FeedbackPhotoType.photo(let image) = feedbackPhotoType{
closeClosures(image)
}
}
}

@ -13,18 +13,21 @@ class FeedbackViewModel: ViewModel, ViewModelType {
struct Input {
let viewWillAppear: ControlEvent<Bool>
let modelSelected: Driver<FeedbackPhotoType>
let photoItems: PublishRelay<[UIImage]>
let category: PublishRelay<String>
let content: Driver<String?>
let contact: Driver<String?>
let confirmTrigger: Driver<Void>
}
struct Output {
let photoItems: BehaviorRelay<[FeedbackSection]>
// let itemSelected: PublishSubject<Thanks>
let toPhotoSelect: PublishRelay<[UIImage]>
}
// let itemSelected = PublishSubject<Thanks>()
let items = BehaviorRelay<[FeedbackSection]>.init(value: [])
let photoItems = BehaviorRelay<[FeedbackSection]>.init(value: [.init(items: [.add])])
let toPhotoSelect = PublishRelay<[UIImage]>.init()
func transform(input: Input) -> Output {
@ -33,12 +36,103 @@ class FeedbackViewModel: ViewModel, ViewModelType {
}.disposed(by: rx.disposeBag)
let add = FeedbackPhotoType.add
self.items.accept([FeedbackSection.init(items: [.add, .add, .add, .add, .add, .add, .add ,.add, .add])])
input.photoItems.subscribe { imageArray in
guard let imageArray = imageArray.element else { return }
var photos = imageArray.map { image in
return FeedbackPhotoType.photo(image)
}
return Output.init(photoItems: items)
if imageArray.count < 9 {
photos.append(.add)
}
self.photoItems.accept([FeedbackSection.init(items: photos)])
}.disposed(by: rx.disposeBag)
input.modelSelected.drive { feedbackPhotoType in
switch feedbackPhotoType {
case .add:
self.toPhotoSelect.accept([])
case .photo(let image):
break
}
}.disposed(by: rx.disposeBag)
// input.confirmTrigger.drive { _ in
// self.uploadFeedback(type: 0, content: "", images: [], contact: "")
// .subscribe { _ in
//
// } onError: { error in
//
// }.disposed(by: self.rx.disposeBag)
// }.disposed(by: rx.disposeBag)
let parms = Driver.combineLatest(
input.photoItems.startWith([]).asDriver(onErrorJustReturn: []),
input.category.startWith("").asDriver(onErrorJustReturn: ""),
input.content,
input.contact
)
input.confirmTrigger
.withLatestFrom(parms) //
.flatMapLatest { tuple in // 使
//
print("点击发送")
let (photoItems, category, content, contact) = tuple
// contentcontactOptional
return self.uploadFeedback(type: 0, content: "", images: [], contact: "")
.asDriver(onErrorJustReturn: ())
}
.drive(onNext: { _ in
})
.disposed(by: rx.disposeBag)
return Output.init(photoItems: self.photoItems,
toPhotoSelect: toPhotoSelect)
}
func deleteImage(_ imageToDelete: UIImage, from photoItems: BehaviorRelay<[FeedbackSection]>) {
var sections = photoItems.value
var didDeleteImage = false
for sectionIndex in sections.indices where !didDeleteImage {
let items = sections[sectionIndex].items
if let itemIndex = items.firstIndex(where: { if case .photo(let image) = $0, image == imageToDelete { return true } else { return false } }) {
sections[sectionIndex].items.remove(at: itemIndex)
didDeleteImage = true
}
}
if didDeleteImage {
photoItems.accept(sections)
}
}
func uploadFeedback(type: Int, content: String, images: [UIImage], contact: String) -> Observable<Void> {
return self.provider.feedback(type: type, content: content, images: images, contact: contact)
.trackActivity(loading)
.trackError(error)
}
}

@ -95,5 +95,7 @@ protocol IndieMusicAPI {
///
func myCommentList(page: Int, size: Int) -> Single<[Comment]>
///
func feedback(type: Int, content: String, images: [UIImage], contact: String) -> Single<Void>
}

@ -35,7 +35,7 @@ enum APIConfig {
case journal(String)
case messageList(Int, Int)
case collectSongList(String, Int, Int)
case collectSongList([String: Any])
case journalCollectList(String, Int, Int)
case followingList(String, Int, Int)
case followerList(String, Int, Int)
@ -58,6 +58,7 @@ enum APIConfig {
case myThumbupList(Int, Int)
case myCommentReplyList(Int, Int)
case feedback([Data], [String: Any])
}
@ -103,8 +104,8 @@ extension APIConfig: TargetType {
return "luoo-music/song/getByJournalNo/\(journalNo)"
case .messageList(let page, let size):
return "luoo-user/userMessage/list/\(page)/\(size)"
case .collectSongList(let userId, let page, let size):
return "luoo-music/song/collect/\(userId)/\(page)/\(size)"
case .collectSongList:
return "luoo-music/song/collect"
case .journalCollectList(let userId, let page, let size):
return "luoo-music/journal/collect/\(userId)/\(page)/\(size)"
case .followingList(let userId, let page, let size):
@ -143,6 +144,9 @@ extension APIConfig: TargetType {
return "luoo-user/my/myThumbupList/\(page)/\(size)"
case .myCommentReplyList(let page, let size):
return "luoo-user/my/myCommentReplyList/\(page)/\(size)"
case .feedback:
return "luoo-user/my/feedback"
}
}
@ -150,7 +154,7 @@ extension APIConfig: TargetType {
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:
return .get
case .sendsms, .login, .autoLogin, .editAvatar, .like, .checkVersion, .logout, .sendComment:
case .sendsms, .login, .autoLogin, .editAvatar, .like, .checkVersion, .logout, .sendComment, .feedback:
return .post
case .editUserInfo, .commentLike:
return .put
@ -165,7 +169,7 @@ extension APIConfig: TargetType {
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:
return URLEncoding.default
case .autoLogin, .editUserInfo, .editAvatar, .checkVersion, .logout, .commentLike, .sendComment:
case .autoLogin, .editUserInfo, .editAvatar, .checkVersion, .logout, .commentLike, .sendComment, .feedback:
return JSONEncoding.default
}
}
@ -174,10 +178,10 @@ extension APIConfig: TargetType {
var task: Task {
var parameters: [String: Any] = [:]
switch self {
case .wechatAccessToken, .countryCode, .journalMusic, .imageCheckCode, .getUserInfo, .carousel, .otherUserInfo, .single, .journal, .messageList, .collectSongList, .journalCollectList, .followingList, .followerList, .blackList, .hotCommentList, .latestCommentList, .subCommentList, .filterMenu, .journalRecommend, .searchCategory, .serach, .randomAudioTrack, .myThumbupList, .myCommentReplyList:
case .wechatAccessToken, .countryCode, .journalMusic, .imageCheckCode, .getUserInfo, .carousel, .otherUserInfo, .single, .journal, .messageList, .journalCollectList, .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):
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):
parameters = dic
return .requestParameters(parameters: parameters, encoding: parameterEncoding)
@ -185,6 +189,23 @@ extension APIConfig: TargetType {
case .editAvatar(let imageData):
let formData = [MultipartFormData(provider: .data(imageData), name: "file", fileName: Date.init().description, mimeType: "image/png")]
return .uploadMultipart(formData)
case .feedback(let imageDataArray, let dic):
var formData = imageDataArray.map { imageData in
return MultipartFormData(provider: .data(imageData),
name: "images_feedback", //
fileName: Date.init().description,
mimeType: "image/png")
}
for (key, value) in dic {
if let stringValue = value as? String,
let data = stringValue.data(using: .utf8) {
formData.append(MultipartFormData(provider: .data(data), name: key))
}
}
return .uploadMultipart(formData)
}
}
@ -203,7 +224,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:
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:
return ["Authorization": AuthManager.shared.token?.basicToken ?? ""]
default:
return nil

@ -59,7 +59,6 @@ extension ObservableType where Element == Response {
//code0data西
if let errodModel = try? JSONDecoder().decode(ErrorResponse.self, from: response.data) {
print("ssss")
if let msg = errodModel.message, msg == "处理失败,未知异常" {
print("接口错误:\(String(describing: response.request?.url))")
}

@ -9,6 +9,7 @@ import Foundation
import RxSwift
import RxCocoa
import Moya
import RxOptional
typealias MoyaError = Moya.MoyaError
@ -142,7 +143,6 @@ extension RestApi {
func editAvatar(imageData: Data) -> Single<String> {
return requestObject(.editAvatar(imageData), with: "data", type: String.self)
}
func journalList(categoryId: String?, journalNoRange: String?, pageNum: Int, pageSize: Int) -> Single<[Journal]> {
@ -208,7 +208,11 @@ extension RestApi {
func collectSongList(userId: String, page: Int, size: Int) -> Single<[AudioTrack]> {
return requestObject(.collectSongList(userId, page, size), with: "data.rows", type: [AudioTrack].self)
let dic = ["userId": userId,
"pageNum": page,
"pageSize": size
] as [String : Any]
return requestObject(.collectSongList(dic), with: "data.rows", type: [AudioTrack].self)
}
func journalCollectList(userId: String, page: Int, size: Int) -> Single<[Journal]> {
@ -307,4 +311,17 @@ extension RestApi {
}
func feedback(type: Int, content: String, images: [UIImage], contact: String) -> Single<Void> {
let dic = ["type": type,
"content": content,
"contact": contact] as [String : Any]
let imageDataArray = images.map { image in
return image.pngData()
}
let nonNilDataArray = imageDataArray.compactMap { $0 }
return requestWithoutMapping(.feedback(nonNilDataArray, dic)).map { _ in }
}
}

Loading…
Cancel
Save