diff --git a/Dear-World/Dear-World.xcodeproj/project.pbxproj b/Dear-World/Dear-World.xcodeproj/project.pbxproj index d953dfc..50605d0 100644 --- a/Dear-World/Dear-World.xcodeproj/project.pbxproj +++ b/Dear-World/Dear-World.xcodeproj/project.pbxproj @@ -15,12 +15,13 @@ 121BDB712597A5D60062B15A /* APIMock.swift in Sources */ = {isa = PBXBuildFile; fileRef = 121BDB702597A5D60062B15A /* APIMock.swift */; }; 121BDB8E259829840062B15A /* CountrySelectController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 121BDB8D259829840062B15A /* CountrySelectController.swift */; }; 128313E32598A08700BDF8A3 /* Message.swift in Sources */ = {isa = PBXBuildFile; fileRef = 128313E22598A08700BDF8A3 /* Message.swift */; }; - 128313EE2598A14E00BDF8A3 /* List.swift in Sources */ = {isa = PBXBuildFile; fileRef = 128313ED2598A14E00BDF8A3 /* List.swift */; }; + 128313EE2598A14E00BDF8A3 /* Message.API.Messages.swift in Sources */ = {isa = PBXBuildFile; fileRef = 128313ED2598A14E00BDF8A3 /* Message.API.Messages.swift */; }; 3902F1052596F26D00A3DF8C /* CheeringMapViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3902F1042596F26D00A3DF8C /* CheeringMapViewController.swift */; }; 3902F1132597049D00A3DF8C /* SendMessageViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3902F1122597049D00A3DF8C /* SendMessageViewController.swift */; }; 3902F118259704AF00A3DF8C /* AboutViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3902F117259704AF00A3DF8C /* AboutViewController.swift */; }; 3902F12425970E5600A3DF8C /* Number+.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3902F12325970E5600A3DF8C /* Number+.swift */; }; 3902F12A259714D800A3DF8C /* RankerTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3902F129259714D800A3DF8C /* RankerTableViewCell.swift */; }; + 3914D9E6259F3FA1009765B0 /* Message.Model.Messages.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3914D9E5259F3FA1009765B0 /* Message.Model.Messages.swift */; }; 3958257B25948E41007325AB /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3958257A25948E41007325AB /* AppDelegate.swift */; }; 3958258425948E43007325AB /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 3958258325948E43007325AB /* Assets.xcassets */; }; 3958258725948E43007325AB /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 3958258525948E43007325AB /* LaunchScreen.storyboard */; }; @@ -92,17 +93,18 @@ 121BDB292597259E0062B15A /* MessageCountBadgeView.swift */ = {isa = PBXFileReference; indentWidth = 2; lastKnownFileType = sourcecode.swift; name = MessageCountBadgeView.swift; path = "Dear-World.xcodeproj/MessageCountBadgeView.swift"; sourceTree = SOURCE_ROOT; tabWidth = 2; }; 121BDB43259735200062B15A /* UIColor+.swift */ = {isa = PBXFileReference; indentWidth = 2; lastKnownFileType = sourcecode.swift; path = "UIColor+.swift"; sourceTree = ""; tabWidth = 2; }; 121BDB582597652A0062B15A /* MessageTableViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MessageTableViewCell.swift; sourceTree = ""; }; - 121BDB662597982F0062B15A /* DiscoverReactor.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DiscoverReactor.swift; sourceTree = ""; }; + 121BDB662597982F0062B15A /* DiscoverReactor.swift */ = {isa = PBXFileReference; indentWidth = 2; lastKnownFileType = sourcecode.swift; path = DiscoverReactor.swift; sourceTree = ""; tabWidth = 2; }; 121BDB6B2597A49B0062B15A /* MessageMock.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MessageMock.swift; sourceTree = ""; }; 121BDB702597A5D60062B15A /* APIMock.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = APIMock.swift; sourceTree = ""; }; 121BDB8D259829840062B15A /* CountrySelectController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CountrySelectController.swift; sourceTree = ""; }; 128313E22598A08700BDF8A3 /* Message.swift */ = {isa = PBXFileReference; indentWidth = 2; lastKnownFileType = sourcecode.swift; name = Message.swift; path = "Dear-World/Source/Domain/Message/Message.swift"; sourceTree = SOURCE_ROOT; tabWidth = 2; }; - 128313ED2598A14E00BDF8A3 /* List.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = List.swift; sourceTree = ""; }; + 128313ED2598A14E00BDF8A3 /* Message.API.Messages.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Message.API.Messages.swift; sourceTree = ""; }; 3902F1042596F26D00A3DF8C /* CheeringMapViewController.swift */ = {isa = PBXFileReference; indentWidth = 2; lastKnownFileType = sourcecode.swift; path = CheeringMapViewController.swift; sourceTree = ""; tabWidth = 2; }; 3902F1122597049D00A3DF8C /* SendMessageViewController.swift */ = {isa = PBXFileReference; indentWidth = 2; lastKnownFileType = sourcecode.swift; path = SendMessageViewController.swift; sourceTree = ""; tabWidth = 2; }; 3902F117259704AF00A3DF8C /* AboutViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AboutViewController.swift; sourceTree = ""; }; 3902F12325970E5600A3DF8C /* Number+.swift */ = {isa = PBXFileReference; indentWidth = 2; lastKnownFileType = sourcecode.swift; path = "Number+.swift"; sourceTree = ""; tabWidth = 2; }; 3902F129259714D800A3DF8C /* RankerTableViewCell.swift */ = {isa = PBXFileReference; indentWidth = 2; lastKnownFileType = sourcecode.swift; path = RankerTableViewCell.swift; sourceTree = ""; tabWidth = 2; }; + 3914D9E5259F3FA1009765B0 /* Message.Model.Messages.swift */ = {isa = PBXFileReference; indentWidth = 2; lastKnownFileType = sourcecode.swift; name = Message.Model.Messages.swift; path = "Dear-World/Source/Presentation/Scene/Main/Message.Model.Messages.swift"; sourceTree = SOURCE_ROOT; tabWidth = 2; }; 3958257725948E41007325AB /* Dear-World.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = "Dear-World.app"; sourceTree = BUILT_PRODUCTS_DIR; }; 3958257A25948E41007325AB /* AppDelegate.swift */ = {isa = PBXFileReference; indentWidth = 2; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; tabWidth = 2; }; 3958258325948E43007325AB /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; @@ -115,7 +117,7 @@ 3958259C25948E43007325AB /* Dear_WorldUITests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Dear_WorldUITests.swift; sourceTree = ""; }; 3958259E25948E43007325AB /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 395825B625948EE4007325AB /* Colors.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Colors.xcassets; sourceTree = ""; }; - 395826112596322B007325AB /* DiscoverViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DiscoverViewController.swift; sourceTree = ""; }; + 395826112596322B007325AB /* DiscoverViewController.swift */ = {isa = PBXFileReference; indentWidth = 2; lastKnownFileType = sourcecode.swift; path = DiscoverViewController.swift; sourceTree = ""; tabWidth = 2; }; 395826322596E693007325AB /* Logger.swift */ = {isa = PBXFileReference; indentWidth = 2; lastKnownFileType = sourcecode.swift; path = Logger.swift; sourceTree = ""; tabWidth = 2; }; 395A4176259DF9F900F10531 /* MainTabBarController.swift */ = {isa = PBXFileReference; indentWidth = 2; lastKnownFileType = sourcecode.swift; path = MainTabBarController.swift; sourceTree = ""; tabWidth = 2; }; 39658F8C259AD9900050D180 /* SplashViewController.swift */ = {isa = PBXFileReference; indentWidth = 2; lastKnownFileType = sourcecode.swift; path = SplashViewController.swift; sourceTree = ""; tabWidth = 2; }; @@ -214,7 +216,7 @@ 128313EB2598A12300BDF8A3 /* API */ = { isa = PBXGroup; children = ( - 128313ED2598A14E00BDF8A3 /* List.swift */, + 128313ED2598A14E00BDF8A3 /* Message.API.Messages.swift */, 3971EB36259A9C550084E6DC /* Message.API.SendMessage.swift */, ); path = API; @@ -224,6 +226,7 @@ isa = PBXGroup; children = ( 3971EB3B259A9C860084E6DC /* Message.Model.SendMessage.swift */, + 3914D9E5259F3FA1009765B0 /* Message.Model.Messages.swift */, ); path = Model; sourceTree = ""; @@ -256,7 +259,9 @@ 3958259B25948E43007325AB /* Dear-WorldUITests */, 3958257825948E41007325AB /* Products */, ); + indentWidth = 2; sourceTree = ""; + tabWidth = 2; }; 3958257825948E41007325AB /* Products */ = { isa = PBXGroup; @@ -695,6 +700,7 @@ 3971EB18259A7BC10084E6DC /* Emoji.swift in Sources */, 39EED22E259CFEB8007452E1 /* World.Model.Ranker.swift in Sources */, 3902F1132597049D00A3DF8C /* SendMessageViewController.swift in Sources */, + 3914D9E6259F3FA1009765B0 /* Message.Model.Messages.swift in Sources */, 39F0C1A02598877600A7001F /* NetworkError.swift in Sources */, 121BDB592597652A0062B15A /* MessageTableViewCell.swift in Sources */, 3902F12A259714D800A3DF8C /* RankerTableViewCell.swift in Sources */, @@ -702,7 +708,7 @@ 39F0C19025987A2B00A7001F /* ServiceAPI.swift in Sources */, 3958257B25948E41007325AB /* AppDelegate.swift in Sources */, 39F465822597811900621327 /* CheerButton.swift in Sources */, - 128313EE2598A14E00BDF8A3 /* List.swift in Sources */, + 128313EE2598A14E00BDF8A3 /* Message.API.Messages.swift in Sources */, 128313E32598A08700BDF8A3 /* Message.swift in Sources */, 121BDB44259735200062B15A /* UIColor+.swift in Sources */, 39658FBB259AE55C0050D180 /* World.Model.Rank.swift in Sources */, diff --git a/Dear-World/Dear-World/Source/Domain/Message/API/List.swift b/Dear-World/Dear-World/Source/Domain/Message/API/List.swift deleted file mode 100644 index 20bc3c6..0000000 --- a/Dear-World/Dear-World/Source/Domain/Message/API/List.swift +++ /dev/null @@ -1,19 +0,0 @@ -// -// List.swift -// Dear-World -// -// Created by rookie.w on 2020/12/27. -// - -import Foundation -extension Message.Model { - struct List { - - } -} -extension Message.API { - struct List: Decodable { - - } -} - diff --git a/Dear-World/Dear-World/Source/Domain/Message/API/Message.API.Messages.swift b/Dear-World/Dear-World/Source/Domain/Message/API/Message.API.Messages.swift new file mode 100644 index 0000000..9411e8b --- /dev/null +++ b/Dear-World/Dear-World/Source/Domain/Message/API/Message.API.Messages.swift @@ -0,0 +1,32 @@ +// +// List.swift +// Dear-World +// +// Created by rookie.w on 2020/12/27. +// + +import Alamofire +import Foundation + +extension Message.API { + struct Messages: ServiceAPI { + typealias Response = Message.Model.Messages + + // MARK: Parameters + private let countryId: Int + private let lastMsgId: Int + + var method: HTTPMethod { .get } + var path: String { "/api/v1/messages" } + var parameters: [String : Any]? { + ["countryId": countryId, + "lastId": lastMsgId] + } + + init(countryId: Int, lastMsgId: Int) { + self.countryId = countryId + self.lastMsgId = lastMsgId + } + } +} + diff --git a/Dear-World/Dear-World/Source/Domain/Message/API/Message.API.SendMessage.swift b/Dear-World/Dear-World/Source/Domain/Message/API/Message.API.SendMessage.swift index 830a54b..f18ac90 100644 --- a/Dear-World/Dear-World/Source/Domain/Message/API/Message.API.SendMessage.swift +++ b/Dear-World/Dear-World/Source/Domain/Message/API/Message.API.SendMessage.swift @@ -12,6 +12,7 @@ extension Message.API { struct SendMessage: ServiceAPI { typealias Response = Message.Model.SendMessage + // MARK: Parameters private let countryCode: String private let emojiId: Int private let name: String diff --git a/Dear-World/Dear-World/Source/Presentation/Scene/Discover/DiscoverReactor.swift b/Dear-World/Dear-World/Source/Presentation/Scene/Discover/DiscoverReactor.swift index 629a7fa..fad5e31 100644 --- a/Dear-World/Dear-World/Source/Presentation/Scene/Discover/DiscoverReactor.swift +++ b/Dear-World/Dear-World/Source/Presentation/Scene/Discover/DiscoverReactor.swift @@ -8,76 +8,83 @@ import Foundation import ReactorKit -class DiscoverReactor: Reactor { - enum Action { - case countryDidChanged(country: String) - case refresh - case loadMore - - } - enum Mutation { - case setMessages(result: [MessageMock]) - case setRefreshing(Bool) - case addMessages(result: [MessageMock], page: Int) - case setCountry(country: String) - case setLoading(Bool) - } - struct State { - var messageCount: Int = 0 - var country: String = "Whole World" - var messages: [MessageMock] = [] - var isRefreshing: Bool = false - var isLoading: Bool = false - var isAnimating: Bool = false - var currentPage: Int = 1 - } - var initialState: State - - init() { - self.initialState = State() +final class DiscoverReactor: Reactor { + + enum Action { + case countryDidChanged(country: String) + case refresh + case loadMore + } + + enum Mutation { + case setMessages(result: [MessageMock]) + case setRefreshing(Bool) + case addMessages(result: [MessageMock], page: Int) + case setCountry(country: String) + case setLoading(Bool) + } + + struct State { + var messageCount: Int = 0 + var country: String = "Whole World" + var messages: [MessageMock] = [] + var isRefreshing: Bool = false + var isLoading: Bool = false + var isAnimating: Bool = false + var currentPage: Int = 1 + } + + var initialState: State + + init() { + self.initialState = State() + } + + func mutate(action: Action) -> Observable { + switch action { + case let .countryDidChanged(country): + return .concat( + .just(Mutation.setCountry(country: country)), + .just(.setLoading(true)), + APIMock().getMessages(page: 1, country: country) + .map { .setMessages(result: $0) }, + .just(.setLoading(false)) + ) + case .refresh: + return .concat([ + .just(Mutation.setRefreshing(true)), + APIMock().getMessages(page: 1, country: currentState.country) + .map { .setMessages(result: $0) }, + .just(Mutation.setRefreshing(false)) + ]) + case .loadMore: + return Observable.concat([ + APIMock().getMessages(page: 2, country: currentState.country) + .map { Mutation.addMessages(result: $0, page: 2) } + ]) } + } + + func reduce(state: State, mutation: Mutation) -> State { + var newState: State = state + switch mutation { + case let .setMessages(results): + newState.messages = results + newState.currentPage = 1 + + case let .setRefreshing(flag): + newState.isRefreshing = flag - func mutate(action: Action) -> Observable { - switch action { - case let .countryDidChanged(country): - return .concat( - .just(Mutation.setCountry(country: country)), - .just(.setLoading(true)), - APIMock().getMessages(page: 1, country: country) - .map { .setMessages(result: $0) }, - .just(.setLoading(false)) - ) - case .refresh: - return .concat([ - .just(Mutation.setRefreshing(true)), - APIMock().getMessages(page: 1, country: currentState.country) - .map { .setMessages(result: $0) }, - .just(Mutation.setRefreshing(false)) - ]) - case .loadMore: - return Observable.concat([ - APIMock().getMessages(page: 2, country: currentState.country) - .map { Mutation.addMessages(result: $0, page: 2) } - ]) - } - } + case let .addMessages(result: results, page: page): + newState.messages = state.messages + results + newState.currentPage = page + + case let .setCountry(country: country): + newState.country = country - func reduce(state: State, mutation: Mutation) -> State { - var newState: State = state - switch mutation { - case let .setMessages(results): - newState.messages = results - newState.currentPage = 1 - case let .setRefreshing(flag): - newState.isRefreshing = flag - case let .addMessages(result: results, page: page): - newState.messages = state.messages + results - newState.currentPage = page - case let .setCountry(country: country): - newState.country = country - case let .setLoading(flag): - newState.isLoading = flag - } - return newState + case let .setLoading(flag): + newState.isLoading = flag } + return newState + } } diff --git a/Dear-World/Dear-World/Source/Presentation/Scene/Discover/DiscoverViewController.swift b/Dear-World/Dear-World/Source/Presentation/Scene/Discover/DiscoverViewController.swift index 4081c59..9fd0026 100644 --- a/Dear-World/Dear-World/Source/Presentation/Scene/Discover/DiscoverViewController.swift +++ b/Dear-World/Dear-World/Source/Presentation/Scene/Discover/DiscoverViewController.swift @@ -13,242 +13,242 @@ import Then import UIKit final class DiscoverViewController: UIViewController, View { - private let messageCountBadgeView: MessageCountBadgeView = MessageCountBadgeView() - private let filterContainerView: UIView = UIView() - private let countryLabel: UILabel = UILabel() - private let messageCollectionView: UICollectionView = UICollectionView(frame: .zero, collectionViewLayout: UICollectionViewFlowLayout()) - private var messages: [MessageMock] = [] - private var outerScrollView: UIScrollView = UIScrollView() - private var scrollOuter: Bool = true - private var scrollRecentConvertTime: Date = Date() + private let messageCountBadgeView: MessageCountBadgeView = MessageCountBadgeView() + private let filterContainerView: UIView = UIView() + private let countryLabel: UILabel = UILabel() + private let messageCollectionView: UICollectionView = UICollectionView(frame: .zero, collectionViewLayout: UICollectionViewFlowLayout()) + private var messages: [MessageMock] = [] + private var outerScrollView: UIScrollView = UIScrollView() + private var scrollOuter: Bool = true + private var scrollRecentConvertTime: Date = Date() + + var disposeBag: DisposeBag = DisposeBag() + + init() { + super.init(nibName: nil, bundle: nil) + self.view.backgroundColor = .breathingWhite + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + override func viewDidLoad() { + super.viewDidLoad() + setupUI() + setupCollectionView() + self.reactor = DiscoverReactor() + startInitAnimation() + } + + private func startInitAnimation() { + animate(view: messageCountBadgeView, alpha: 0.4, length: 20, duration: 0.4) + animate(view: filterContainerView, alpha: 0.4, length: 20, duration: 0.4) + } + + private func animate(view: UIView, alpha: CGFloat, length: CGFloat, duration: Double, delay: Double = 0) { + view.alpha = alpha + view.frame.origin.y += length + UIView.animate(withDuration: duration) { + view.alpha = 1 + view.frame.origin.y -= length + } + } + + func bind(reactor: DiscoverReactor) { + reactor.state + .map(\.messageCount) + .subscribe { [weak self] count in + self?.messageCountBadgeView.count = count + } + .disposed(by: self.disposeBag) - var disposeBag: DisposeBag = DisposeBag() + reactor.state + .map(\.messages) + .distinctUntilChanged() + .subscribe(onNext: {[weak self] mess in + self?.messages = mess + self?.messageCollectionView.reloadData() + }) + .disposed(by: self.disposeBag) - init() { - super.init(nibName: nil, bundle: nil) - self.view.backgroundColor = .breathingWhite - } + reactor.state + .map(\.isRefreshing) + .distinctUntilChanged() + .filter { !$0 } + .bind {[weak self] _ in + self?.messageCollectionView.refreshControl?.endRefreshing() + } + .disposed(by: self.disposeBag) - required init?(coder: NSCoder) { - fatalError("init(coder:) has not been implemented") - } + reactor.state + .map(\.country) + .distinctUntilChanged() + .bind(to: self.countryLabel.rx.text) + .disposed(by: self.disposeBag) + + reactor.state + .map(\.country) + .distinctUntilChanged() + .bind { _ in + self.messageCollectionView.setContentOffset(.zero, animated: false) + } + .disposed(by: self.disposeBag) + + self.countryLabel + .rx.observe(String.self, "text") + .filter { $0 != nil } + .map { $0! } + .distinctUntilChanged() + .map { Reactor.Action.countryDidChanged(country: $0) } + .bind(to: reactor.action) + .disposed(by: self.disposeBag) + + self.messageCollectionView + .refreshControl?.rx + .controlEvent(.valueChanged) + .map { Reactor.Action.refresh } + .bind(to: reactor.action) + .disposed(by: self.disposeBag) + + self.messageCollectionView + .rx.isReachedBottom + .map { Reactor.Action.loadMore } + .bind(to: reactor.action) + .disposed(by: self.disposeBag) - override func viewDidLoad() { - super.viewDidLoad() - setupUI() - setupCollectionView() - self.reactor = DiscoverReactor() - startInitAnimation() + self.filterContainerView + .rx.tapGesture() + .skip(1) + .flatMap { [weak self] _ -> Observable in + guard let self = self else { return Observable.just("") } + return CountrySelectController.selectCountry(presenting: self, disposeBag: self.disposeBag) + } + .map { Reactor.Action.countryDidChanged(country: $0) } + .bind(to: reactor.action) + .disposed(by: self.disposeBag) + } + + private func setupUI() { + self.outerScrollView.do { + $0.isScrollEnabled = true + $0.showsVerticalScrollIndicator = false + $0.contentSize.height = self.view.frame.height + 264 + $0.delegate = self } - private func startInitAnimation() { - animate(view: messageCountBadgeView, alpha: 0.4, length: 20, duration: 0.4) - animate(view: filterContainerView, alpha: 0.4, length: 20, duration: 0.4) + self.view.addSubview(outerScrollView) + self.outerScrollView.snp.makeConstraints { + $0.top.bottom.leading.trailing.equalToSuperview() } - private func animate(view: UIView, alpha: CGFloat, length: CGFloat, duration: Double, delay: Double = 0) { - view.alpha = alpha - view.frame.origin.y += length - UIView.animate(withDuration: duration) { - view.alpha = 1 - view.frame.origin.y -= length - } + self.outerScrollView.addSubview(self.messageCountBadgeView) + self.outerScrollView.addSubview(self.filterContainerView) + self.filterContainerView.snp.makeConstraints { + $0.centerX.equalToSuperview() + $0.top.equalTo(self.messageCountBadgeView.snp.bottom).offset(30) + $0.height.equalTo(26) } - func bind(reactor: DiscoverReactor) { - reactor.state - .map(\.messageCount) - .subscribe { [weak self] count in - self?.messageCountBadgeView.count = count - } - .disposed(by: self.disposeBag) - - reactor.state - .map(\.messages) - .distinctUntilChanged() - .subscribe(onNext: {[weak self] mess in - self?.messages = mess - self?.messageCollectionView.reloadData() - }) - .disposed(by: self.disposeBag) - - reactor.state - .map(\.isRefreshing) - .distinctUntilChanged() - .filter { !$0 } - .bind {[weak self] _ in - self?.messageCollectionView.refreshControl?.endRefreshing() - } - .disposed(by: self.disposeBag) - - reactor.state - .map(\.country) - .distinctUntilChanged() - .bind(to: self.countryLabel.rx.text) - .disposed(by: self.disposeBag) - - reactor.state - .map(\.country) - .distinctUntilChanged() - .bind { _ in - self.messageCollectionView.setContentOffset(.zero, animated: false) - } - .disposed(by: self.disposeBag) - - self.countryLabel - .rx.observe(String.self, "text") - .filter { $0 != nil } - .map { $0! } - .distinctUntilChanged() - .map { Reactor.Action.countryDidChanged(country: $0) } - .bind(to: reactor.action) - .disposed(by: self.disposeBag) - - self.messageCollectionView - .refreshControl?.rx - .controlEvent(.valueChanged) - .map { Reactor.Action.refresh } - .bind(to: reactor.action) - .disposed(by: self.disposeBag) - - self.messageCollectionView - .rx.isReachedBottom - .map { Reactor.Action.loadMore } - .bind(to: reactor.action) - .disposed(by: self.disposeBag) - - self.filterContainerView - .rx.tapGesture() - .skip(1) - .flatMap { [weak self] _ -> Observable in - guard let self = self else { return Observable.just("") } - return CountrySelectController.selectCountry(presenting: self, disposeBag: self.disposeBag) - } - .map { Reactor.Action.countryDidChanged(country: $0) } - .bind(to: reactor.action) - .disposed(by: self.disposeBag) + countryLabel.do { + $0.font = .boldSystemFont(ofSize: 22) + $0.textColor = .warmBlue + $0.text = "Whole world" } - private func setupUI() { - self.outerScrollView.do { - $0.isScrollEnabled = true - $0.showsVerticalScrollIndicator = false - $0.contentSize.height = self.view.frame.height + 264 - $0.delegate = self - } - - self.view.addSubview(outerScrollView) - self.outerScrollView.snp.makeConstraints { - $0.top.bottom.leading.trailing.equalToSuperview() - } - - self.outerScrollView.addSubview(self.messageCountBadgeView) - self.outerScrollView.addSubview(self.filterContainerView) - self.filterContainerView.snp.makeConstraints { - $0.centerX.equalToSuperview() - $0.top.equalTo(self.messageCountBadgeView.snp.bottom).offset(30) - $0.height.equalTo(26) - } - - countryLabel.do { - $0.font = .boldSystemFont(ofSize: 22) - $0.textColor = .warmBlue - $0.text = "Whole world" - } - - filterContainerView.addSubview(countryLabel) - countryLabel.snp.makeConstraints { - $0.centerY.equalToSuperview() - $0.leading.equalTo(filterContainerView.snp.leading) - } - - let select: UIImageView = UIImageView().then { - $0.image = UIImage(named: "select") - } - filterContainerView.addSubview(select) - select.snp.makeConstraints { - $0.centerY.equalToSuperview() - $0.width.equalTo(14) - $0.height.equalTo(8) - $0.trailing.equalTo(filterContainerView.snp.trailing) - $0.leading.equalTo(countryLabel.snp.trailing).offset(5) - } - - messageCollectionView.do { - $0.backgroundColor = .breathingWhite - $0.isScrollEnabled = false - } - self.outerScrollView.addSubview(self.messageCollectionView) - self.messageCollectionView.snp.makeConstraints { - $0.top.equalTo(filterContainerView.snp.bottom).offset(30) - $0.trailing.leading.equalTo(self.view.safeAreaLayoutGuide).inset(20) - $0.bottom.equalTo(self.outerScrollView.frameLayoutGuide.snp.bottom) - } + filterContainerView.addSubview(countryLabel) + countryLabel.snp.makeConstraints { + $0.centerY.equalToSuperview() + $0.leading.equalTo(filterContainerView.snp.leading) } - private func setupCollectionView() { - self.messageCollectionView.register(MessageTableViewCell.self, forCellWithReuseIdentifier: "messageCell") - self.messageCollectionView.delegate = self - self.messageCollectionView.dataSource = self - - let layout: UICollectionViewFlowLayout = self.messageCollectionView.collectionViewLayout as! UICollectionViewFlowLayout - layout.minimumLineSpacing = 20 + let select: UIImageView = UIImageView().then { + $0.image = UIImage(named: "select") } -} -extension DiscoverViewController: UICollectionViewDelegate, UICollectionViewDataSource, UICollectionViewDelegateFlowLayout{ - func collectionView( - _ collectionView: UICollectionView, - numberOfItemsInSection section: Int - ) -> Int { - return self.messages.count + filterContainerView.addSubview(select) + select.snp.makeConstraints { + $0.centerY.equalToSuperview() + $0.width.equalTo(14) + $0.height.equalTo(8) + $0.trailing.equalTo(filterContainerView.snp.trailing) + $0.leading.equalTo(countryLabel.snp.trailing).offset(5) } - func collectionView( - _ collectionView: UICollectionView, - cellForItemAt indexPath: IndexPath - ) -> UICollectionViewCell { - guard let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "messageCell", for: indexPath) as? MessageTableViewCell else {return UICollectionViewCell()} - cell.nameLabel.text = self.messages[indexPath.row].name - cell.emojiLabel.text = self.messages[indexPath.row].emoji - cell.detailTextView.text = self.messages[indexPath.row].detail - cell.likeCountLabel.text = self.messages[indexPath.row].likes.formatted - cell.countryLabel.text = self.messages[indexPath.row].countryName - bindShareButton(button: cell.shareButton) - if reactor?.currentState.currentPage == 1 { - self.animate(view: cell, alpha: 0.3, length: 50, duration: 0.5, delay: Double(indexPath.row)) - } - return cell + messageCollectionView.do { + $0.backgroundColor = .breathingWhite + $0.isScrollEnabled = false } - func collectionView( - _ collectionView: UICollectionView, - layout collectionViewLayout: UICollectionViewLayout, - sizeForItemAt indexPath: IndexPath - ) -> CGSize { - return CGSize(width: collectionView.frame.width, height: 192) + self.outerScrollView.addSubview(self.messageCollectionView) + self.messageCollectionView.snp.makeConstraints { + $0.top.equalTo(filterContainerView.snp.bottom).offset(30) + $0.trailing.leading.equalTo(self.view.safeAreaLayoutGuide).inset(20) + $0.bottom.equalTo(self.outerScrollView.frameLayoutGuide.snp.bottom) } + } + + private func setupCollectionView() { + self.messageCollectionView.register(MessageTableViewCell.self, forCellWithReuseIdentifier: "messageCell") + self.messageCollectionView.delegate = self + self.messageCollectionView.dataSource = self - private func bindShareButton(button: UIButton) { - button - .rx.tap - .bind { - let activityVC: UIActivityViewController = UIActivityViewController(activityItems: ["hi"], applicationActivities: nil) - activityVC.popoverPresentationController?.sourceView = self.view - self.present(activityVC, animated: true) - } - .disposed(by: self.disposeBag) + let layout: UICollectionViewFlowLayout = self.messageCollectionView.collectionViewLayout as! UICollectionViewFlowLayout + layout.minimumLineSpacing = 20 + } +} +extension DiscoverViewController: UICollectionViewDelegate, UICollectionViewDataSource, UICollectionViewDelegateFlowLayout{ + func collectionView( + _ collectionView: UICollectionView, + numberOfItemsInSection section: Int + ) -> Int { + return self.messages.count + } + + func collectionView( + _ collectionView: UICollectionView, + cellForItemAt indexPath: IndexPath + ) -> UICollectionViewCell { + guard let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "messageCell", for: indexPath) as? MessageTableViewCell else {return UICollectionViewCell()} + cell.nameLabel.text = self.messages[indexPath.row].name + cell.emojiLabel.text = self.messages[indexPath.row].emoji + cell.detailTextView.text = self.messages[indexPath.row].detail + cell.likeCountLabel.text = self.messages[indexPath.row].likes.formatted + cell.countryLabel.text = self.messages[indexPath.row].countryName + bindShareButton(button: cell.shareButton) + if reactor?.currentState.currentPage == 1 { + self.animate(view: cell, alpha: 0.3, length: 50, duration: 0.5, delay: Double(indexPath.row)) } + return cell + } + func collectionView( + _ collectionView: UICollectionView, + layout collectionViewLayout: UICollectionViewLayout, + sizeForItemAt indexPath: IndexPath + ) -> CGSize { + return CGSize(width: collectionView.frame.width, height: 192) + } + + private func bindShareButton(button: UIButton) { + button + .rx.tap + .bind { + let activityVC: UIActivityViewController = UIActivityViewController(activityItems: ["hi"], applicationActivities: nil) + activityVC.popoverPresentationController?.sourceView = self.view + self.present(activityVC, animated: true) + } + .disposed(by: self.disposeBag) + } } extension DiscoverViewController: UIScrollViewDelegate { - func scrollViewDidScroll(_ scrollView: UIScrollView) { - let flag = scrollView.contentOffset.y <= 225 - if flag != self.scrollOuter && self.scrollRecentConvertTime.timeIntervalSinceNow < -5 { - print(flag) - self.outerScrollView.isScrollEnabled = flag - self.messageCollectionView.isScrollEnabled = !flag - self.scrollOuter.toggle() - self.scrollRecentConvertTime = Date() - } + func scrollViewDidScroll(_ scrollView: UIScrollView) { + let flag = scrollView.contentOffset.y <= 225 + if flag != self.scrollOuter && self.scrollRecentConvertTime.timeIntervalSinceNow < -5 { + print(flag) + self.outerScrollView.isScrollEnabled = flag + self.messageCollectionView.isScrollEnabled = !flag + self.scrollOuter.toggle() + self.scrollRecentConvertTime = Date() } + } } extension Reactive where Base: UIScrollView { diff --git a/Dear-World/Dear-World/Source/Presentation/Scene/Main/Message.Model.Messages.swift b/Dear-World/Dear-World/Source/Presentation/Scene/Main/Message.Model.Messages.swift new file mode 100644 index 0000000..62e91ba --- /dev/null +++ b/Dear-World/Dear-World/Source/Presentation/Scene/Main/Message.Model.Messages.swift @@ -0,0 +1,41 @@ +// +// Message.Model.Messages.swift +// Dear-World +// +// Created by dongyoung.lee on 2021/01/01. +// + +import Foundation + +extension Message.Model { + struct Messages: Decodable { + let firstMsgId: Int + let lastMsgId: Int + let messageCount: Int + let messages: [Message] + + enum CodingKeys: String, CodingKey { + case firstMsgId = "firstId" + case lastMsgId = "lastId" + case messageCount + case messages + } + } + + struct Message: Decodable { + let id: String + // TODO: 🔮 anonymousUser 모델 구현 + let isLiked: Bool + let likeCount: Int + let content: String + let createAt: Date + + enum CodingKeys: String, CodingKey{ + case id = "uuid" + case isLiked = "like" + case likeCount + case content + case createAt + } + } +}