diff --git a/Dear-World/Dear-World.xcodeproj/project.pbxproj b/Dear-World/Dear-World.xcodeproj/project.pbxproj index 92073ed..4630c40 100644 --- a/Dear-World/Dear-World.xcodeproj/project.pbxproj +++ b/Dear-World/Dear-World.xcodeproj/project.pbxproj @@ -76,6 +76,7 @@ 39965E8825ABE32400069860 /* BottomSheetItemHeaderView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 39965E8725ABE32400069860 /* BottomSheetItemHeaderView.swift */; }; 39965E8D25ACA87A00069860 /* BottomSheetItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = 39965E8C25ACA87A00069860 /* BottomSheetItem.swift */; }; 39965F2525B2E20A00069860 /* heart_fill.json in Resources */ = {isa = PBXBuildFile; fileRef = 39965F2425B2E20900069860 /* heart_fill.json */; }; + 39965F3325B308B000069860 /* UIApplication+.swift in Sources */ = {isa = PBXBuildFile; fileRef = 39965F3225B308B000069860 /* UIApplication+.swift */; }; 39C832282597651F00236DDF /* RxRelay in Frameworks */ = {isa = PBXBuildFile; productRef = 39C832272597651F00236DDF /* RxRelay */; }; 39C8322A2597651F00236DDF /* RxSwift in Frameworks */ = {isa = PBXBuildFile; productRef = 39C832292597651F00236DDF /* RxSwift */; }; 39C8322C2597651F00236DDF /* RxCocoa in Frameworks */ = {isa = PBXBuildFile; productRef = 39C8322B2597651F00236DDF /* RxCocoa */; }; @@ -178,6 +179,7 @@ 39965E8725ABE32400069860 /* BottomSheetItemHeaderView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BottomSheetItemHeaderView.swift; sourceTree = ""; }; 39965E8C25ACA87A00069860 /* BottomSheetItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BottomSheetItem.swift; sourceTree = ""; }; 39965F2425B2E20900069860 /* heart_fill.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = heart_fill.json; sourceTree = ""; }; + 39965F3225B308B000069860 /* UIApplication+.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIApplication+.swift"; sourceTree = ""; }; 39E9F7CF25A1738F00BC2CC2 /* AboutReactor.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AboutReactor.swift; sourceTree = ""; }; 39E9F7DA25A1BBED00BC2CC2 /* PixelWorldMapView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PixelWorldMapView.swift; sourceTree = ""; }; 39E9F7DF25A1BC3C00BC2CC2 /* NoticeBadge.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NoticeBadge.swift; sourceTree = ""; }; @@ -307,6 +309,7 @@ 39F0C1622597B34F00A7001F /* UIView+.swift */, 39F0C16A2597C7DF00A7001F /* UIImage+.swift */, 39F0C175259860D700A7001F /* UIControl+Rx.swift */, + 39965F3225B308B000069860 /* UIApplication+.swift */, ); path = Extension; sourceTree = ""; @@ -894,6 +897,7 @@ 39658FAF259AE4E40050D180 /* World.swift in Sources */, 3971EB24259A7C420084E6DC /* Emoji.Model.Random.swift in Sources */, 39F0C1632597B34F00A7001F /* UIView+.swift in Sources */, + 39965F3325B308B000069860 /* UIApplication+.swift in Sources */, 3971EB1F259A7C0E0084E6DC /* Emoji.API.Random.swift in Sources */, 393E0D4025A23A12000DB3B9 /* World.Model.Map.swift in Sources */, 3902F1052596F26D00A3DF8C /* CheeringMapViewController.swift in Sources */, diff --git a/Dear-World/Dear-World.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved b/Dear-World/Dear-World.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved new file mode 100644 index 0000000..f31d32c --- /dev/null +++ b/Dear-World/Dear-World.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -0,0 +1,268 @@ +{ + "object": { + "pins": [ + { + "package": "abseil", + "repositoryURL": "https://github.com/firebase/abseil-cpp-SwiftPM.git", + "state": { + "branch": null, + "revision": "05d8107f2971a37e6c77245b7c4c6b0a7e97bc99", + "version": "0.20200225.0" + } + }, + { + "package": "Alamofire", + "repositoryURL": "https://github.com/Alamofire/Alamofire", + "state": { + "branch": null, + "revision": "eaf6e622dd41b07b251d8f01752eab31bc811493", + "version": "5.4.1" + } + }, + { + "package": "BoringSSL-GRPC", + "repositoryURL": "https://github.com/firebase/boringssl-SwiftPM.git", + "state": { + "branch": null, + "revision": "7bcafa2660bc58715c39637494550d1ed7cd7229", + "version": "0.0.7" + } + }, + { + "package": "Firebase", + "repositoryURL": "https://github.com/firebase/firebase-ios-sdk.git", + "state": { + "branch": null, + "revision": "c67d97dc802aafe5474042156dce828e1c4145ca", + "version": "7.3.1" + } + }, + { + "package": "gRPC", + "repositoryURL": "https://github.com/firebase/grpc-SwiftPM.git", + "state": { + "branch": null, + "revision": "91b62619e6c83bc5f1b99d9d60fe46b2862d3a5a", + "version": "1.28.2" + } + }, + { + "package": "GTMSessionFetcher", + "repositoryURL": "https://github.com/google/gtm-session-fetcher.git", + "state": { + "branch": null, + "revision": "91ed3d188eb95705fef3c249453b81f32dc557d1", + "version": "1.5.0" + } + }, + { + "package": "Kingfisher", + "repositoryURL": "https://github.com/onevcat/Kingfisher", + "state": { + "branch": null, + "revision": "0be276f6a7fd54af4c3eb03f2d12e12d8c8a1a1d", + "version": "6.0.1" + } + }, + { + "package": "leveldb", + "repositoryURL": "https://github.com/firebase/leveldb.git", + "state": { + "branch": null, + "revision": "fa1f25f296a766e5a789c4dacd4798dea798b2c2", + "version": "1.22.1" + } + }, + { + "package": "Logger", + "repositoryURL": "https://github.com/f-meloni/Logger", + "state": { + "branch": null, + "revision": "53c3ecca5abe8cf46697e33901ee774236d94cce", + "version": "0.2.3" + } + }, + { + "package": "Lottie", + "repositoryURL": "https://github.com/airbnb/lottie-ios", + "state": { + "branch": null, + "revision": "e02e82c7c1b472e85e641a7624e99c855e21add1", + "version": "3.1.9" + } + }, + { + "package": "nanopb", + "repositoryURL": "https://github.com/firebase/nanopb.git", + "state": { + "branch": null, + "revision": "8119dfe5631f2616d11e50ead95448d12e816062", + "version": "2.30906.0" + } + }, + { + "package": "Nimble", + "repositoryURL": "https://github.com/Quick/Nimble.git", + "state": { + "branch": null, + "revision": "7a46a5fc86cb917f69e3daf79fcb045283d8f008", + "version": "8.1.2" + } + }, + { + "package": "PackageConfig", + "repositoryURL": "https://github.com/shibapm/PackageConfig.git", + "state": { + "branch": null, + "revision": "bf90dc69fa0792894b08a0b74cf34029694ae486", + "version": "0.13.0" + } + }, + { + "package": "Promises", + "repositoryURL": "https://github.com/google/promises.git", + "state": { + "branch": null, + "revision": "afa9a1ace74e116848d4f743599ab83e584ff8cb", + "version": "1.2.12" + } + }, + { + "package": "Quick", + "repositoryURL": "https://github.com/Quick/Quick.git", + "state": { + "branch": null, + "revision": "09b3becb37cb2163919a3842a4c5fa6ec7130792", + "version": "2.2.1" + } + }, + { + "package": "ReactorKit", + "repositoryURL": "https://github.com/ReactorKit/ReactorKit", + "state": { + "branch": null, + "revision": "fc392a1dc4c98a496089b8bd091cd92f608fd299", + "version": "2.1.1" + } + }, + { + "package": "Rocket", + "repositoryURL": "https://github.com/shibapm/Rocket", + "state": { + "branch": null, + "revision": "f75c9736733b489a3456b4f3a47cf13adb99f197", + "version": "0.9.2" + } + }, + { + "package": "RxExpect", + "repositoryURL": "https://github.com/devxoul/RxExpect.git", + "state": { + "branch": null, + "revision": "c3a3bb3d46ee831582c6619ecc48cda1cdbff890", + "version": "2.0.0" + } + }, + { + "package": "RxGesture", + "repositoryURL": "https://github.com/RxSwiftCommunity/RxGesture", + "state": { + "branch": null, + "revision": "867f176b6695829e350fafc00b5a849bb46a1857", + "version": "3.0.3" + } + }, + { + "package": "RxKeyboard", + "repositoryURL": "https://github.com/RxSwiftCommunity/RxKeyboard", + "state": { + "branch": null, + "revision": "ed5940a7e29c3a4b4925b2d76260f793d311d58e", + "version": "1.0.0" + } + }, + { + "package": "RxOptional", + "repositoryURL": "https://github.com/RxSwiftCommunity/RxOptional", + "state": { + "branch": null, + "revision": "98a1895918e9ba9735a15207dd9c7120e9f51843", + "version": "4.1.0" + } + }, + { + "package": "RxSwift", + "repositoryURL": "https://github.com/ReactiveX/RxSwift", + "state": { + "branch": null, + "revision": "002d325b0bdee94e7882e1114af5ff4fe1e96afa", + "version": "5.1.1" + } + }, + { + "package": "SnapKit", + "repositoryURL": "https://github.com/SnapKit/SnapKit", + "state": { + "branch": null, + "revision": "d458564516e5676af9c70b4f4b2a9178294f1bc6", + "version": "5.0.1" + } + }, + { + "package": "SwiftRichString", + "repositoryURL": "https://github.com/malcommac/SwiftRichString", + "state": { + "branch": null, + "revision": "9bf4b5af6bb4386865636fc504d6c588c2b65040", + "version": "3.7.2" + } + }, + { + "package": "SwiftShell", + "repositoryURL": "https://github.com/kareman/SwiftShell", + "state": { + "branch": null, + "revision": "beebe43c986d89ea5359ac3adcb42dac94e5e08a", + "version": "4.1.2" + } + }, + { + "package": "Then", + "repositoryURL": "https://github.com/devxoul/Then", + "state": { + "branch": null, + "revision": "e421a7b3440a271834337694e6050133a3958bc7", + "version": "2.7.0" + } + }, + { + "package": "UITextView+Placeholder", + "repositoryURL": "https://github.com/devxoul/UITextView-Placeholder", + "state": { + "branch": null, + "revision": "5a133b7efc4e8dd9de91c9eada48270d90c83ef0", + "version": "1.4.0" + } + }, + { + "package": "WeakMapTable", + "repositoryURL": "https://github.com/ReactorKit/WeakMapTable.git", + "state": { + "branch": null, + "revision": "9580560169b4b48ba2affe7badba6a7f360495f4", + "version": "1.2.0" + } + }, + { + "package": "Yams", + "repositoryURL": "https://github.com/jpsim/Yams", + "state": { + "branch": null, + "revision": "c947a306d2e80ecb2c0859047b35c73b8e1ca27f", + "version": "2.0.0" + } + } + ] + }, + "version": 1 +} diff --git a/Dear-World/Dear-World/Resource/Assets.xcassets/left_arrow.imageset/Contents.json b/Dear-World/Dear-World/Resource/Assets.xcassets/left_arrow.imageset/Contents.json new file mode 100644 index 0000000..4e72abf --- /dev/null +++ b/Dear-World/Dear-World/Resource/Assets.xcassets/left_arrow.imageset/Contents.json @@ -0,0 +1,21 @@ +{ + "images" : [ + { + "filename" : "left_arrow.pdf", + "idiom" : "universal", + "scale" : "1x" + }, + { + "idiom" : "universal", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Dear-World/Dear-World/Resource/Assets.xcassets/left_arrow.imageset/left_arrow.pdf b/Dear-World/Dear-World/Resource/Assets.xcassets/left_arrow.imageset/left_arrow.pdf new file mode 100644 index 0000000..5693448 Binary files /dev/null and b/Dear-World/Dear-World/Resource/Assets.xcassets/left_arrow.imageset/left_arrow.pdf differ diff --git a/Dear-World/Dear-World/Resource/Assets.xcassets/location.imageset/Contents.json b/Dear-World/Dear-World/Resource/Assets.xcassets/location.imageset/Contents.json index 2b123ab..beba7a0 100644 --- a/Dear-World/Dear-World/Resource/Assets.xcassets/location.imageset/Contents.json +++ b/Dear-World/Dear-World/Resource/Assets.xcassets/location.imageset/Contents.json @@ -1,7 +1,7 @@ { "images" : [ { - "filename" : "location.png", + "filename" : "location.pdf", "idiom" : "universal", "scale" : "1x" }, diff --git a/Dear-World/Dear-World/Resource/Assets.xcassets/location.imageset/location.pdf b/Dear-World/Dear-World/Resource/Assets.xcassets/location.imageset/location.pdf new file mode 100644 index 0000000..f52a1c4 Binary files /dev/null and b/Dear-World/Dear-World/Resource/Assets.xcassets/location.imageset/location.pdf differ diff --git a/Dear-World/Dear-World/Resource/Assets.xcassets/location.imageset/location.png b/Dear-World/Dear-World/Resource/Assets.xcassets/location.imageset/location.png deleted file mode 100644 index f7b67a8..0000000 Binary files a/Dear-World/Dear-World/Resource/Assets.xcassets/location.imageset/location.png and /dev/null differ diff --git a/Dear-World/Dear-World/Resource/Assets.xcassets/ufo.imageset/Contents.json b/Dear-World/Dear-World/Resource/Assets.xcassets/ufo.imageset/Contents.json new file mode 100644 index 0000000..64ce47e --- /dev/null +++ b/Dear-World/Dear-World/Resource/Assets.xcassets/ufo.imageset/Contents.json @@ -0,0 +1,21 @@ +{ + "images" : [ + { + "filename" : "ufo.pdf", + "idiom" : "universal", + "scale" : "1x" + }, + { + "idiom" : "universal", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Dear-World/Dear-World/Resource/Assets.xcassets/ufo.imageset/ufo.pdf b/Dear-World/Dear-World/Resource/Assets.xcassets/ufo.imageset/ufo.pdf new file mode 100644 index 0000000..f5a7c1f Binary files /dev/null and b/Dear-World/Dear-World/Resource/Assets.xcassets/ufo.imageset/ufo.pdf differ diff --git a/Dear-World/Dear-World/Source/Common/Extension/UIApplication+.swift b/Dear-World/Dear-World/Source/Common/Extension/UIApplication+.swift new file mode 100644 index 0000000..a16e688 --- /dev/null +++ b/Dear-World/Dear-World/Source/Common/Extension/UIApplication+.swift @@ -0,0 +1,17 @@ +// +// UIApplication+.swift +// Dear-World +// +// Created by dongyoung.lee on 2021/01/16. +// + +import UIKit + +extension UIApplication { + var version: String? { + guard let dictionary = Bundle.main.infoDictionary, + let version = dictionary["CFBundleShortVersionString"] as? String, + let build = dictionary["CFBundleVersion"] as? String else { return nil } + return "\(version).\(build)" + } +} diff --git a/Dear-World/Dear-World/Source/Presentation/Scene/About/AboutReactor.swift b/Dear-World/Dear-World/Source/Presentation/Scene/About/AboutReactor.swift index 06b7831..816132f 100644 --- a/Dear-World/Dear-World/Source/Presentation/Scene/About/AboutReactor.swift +++ b/Dear-World/Dear-World/Source/Presentation/Scene/About/AboutReactor.swift @@ -7,12 +7,14 @@ import Foundation import ReactorKit +import UIKit final class AboutReactor: Reactor { enum Action { case initalize case tapClose + case tapContact case tapCrewInfo case tapNotice case tapVersion @@ -20,6 +22,7 @@ final class AboutReactor: Reactor { enum Mutation { case setPresentCrewInfo(Bool) + case setPresentEmail(Bool) case setPresentNotice(Bool) case setPresentAppStore(Bool) case setWillDismiss(Bool) @@ -31,9 +34,10 @@ final class AboutReactor: Reactor { @Revision var isPresentCrewInfo: Bool = false @Revision var isPresentNotice: Bool = false @Revision var isPresentAppStore: Bool = false + @Revision var isPresentEmail: Bool = false @Revision var willDismiss: Bool = false var noticeCount: Int? - var currentVersion: String? = "10.1.1" + var currentVersion: String? } var initialState: State @@ -47,11 +51,14 @@ final class AboutReactor: Reactor { func mutate(action: Action) -> Observable { switch action { case .initalize: - return .empty() + return .just(.setCurrentVersion(UIApplication.shared.version ?? "")) case .tapCrewInfo: return .just(.setPresentCrewInfo(true)) + case .tapContact: + return .just(.setPresentEmail(true)) + case .tapNotice: return .just(.setPresentNotice(true)) @@ -70,6 +77,9 @@ final class AboutReactor: Reactor { case .setPresentCrewInfo(let isPresentCrewInfo): newState.isPresentCrewInfo = isPresentCrewInfo + case .setPresentEmail(let isPresentEmail): + newState.isPresentEmail = isPresentEmail + case .setPresentNotice(let isPresentNotice): newState.isPresentNotice = isPresentNotice diff --git a/Dear-World/Dear-World/Source/Presentation/Scene/About/AboutViewController.swift b/Dear-World/Dear-World/Source/Presentation/Scene/About/AboutViewController.swift index 1080ad7..d4cacdc 100644 --- a/Dear-World/Dear-World/Source/Presentation/Scene/About/AboutViewController.swift +++ b/Dear-World/Dear-World/Source/Presentation/Scene/About/AboutViewController.swift @@ -5,17 +5,18 @@ // Created by dongyoung.lee on 2020/12/26. // +import MessageUI import ReactorKit import UIKit final class AboutViewController: UIViewController, View { - typealias Reactor = AboutReactor typealias Action = Reactor.Action // MARK: πŸ–Ό UI private let stackView: UIStackView = UIStackView() private let crewInfoView: UIView = UIView() + private let contactInfoView: UIView = UIView() private let noticeInfoView: UIView = UIView() private let noticeBadge: NoticeBadge = NoticeBadge() private let versionInfoView: UIView = UIView() @@ -26,7 +27,6 @@ final class AboutViewController: UIViewController, View { target: nil, action: nil ) - var disposeBag: DisposeBag = DisposeBag() // MARK: 🏁 Initialize @@ -54,6 +54,7 @@ final class AboutViewController: UIViewController, View { $0.axis = .vertical $0.spacing = 20 $0.addArrangedSubview(crewInfoView) + $0.addArrangedSubview(contactInfoView) $0.addArrangedSubview(noticeInfoView) $0.addArrangedSubview(versionInfoView) } @@ -63,33 +64,64 @@ final class AboutViewController: UIViewController, View { let crewInfoTitleLabel: UILabel = UILabel().then { $0.text = "Dear world, we are OFU Crew." - $0.font = .systemFont(ofSize: 14) + $0.font = .boldSystemFont(ofSize: 14) $0.textColor = .warmBlue } - let crewContentLabel: UILabel = UILabel().then { - $0.text = "πŸ›Έ" + let crewContentImageView: UIImageView = UIImageView().then { + $0.image = UIImage(named: "ufo") } crewInfoView.do { $0.backgroundColor = .grayWhite $0.addSubview(crewInfoTitleLabel) - $0.addSubview(crewContentLabel) + $0.addSubview(crewContentImageView) $0.layer.cornerRadius = 10 + $0.layer.shadowOffset = CGSize(width: 0, height: 4) + $0.layer.shadowOpacity = 0.1 + $0.layer.shadowColor = UIColor.black.cgColor } crewInfoTitleLabel.snp.makeConstraints { $0.leading.equalToSuperview().inset(20) $0.centerY.equalToSuperview() } - crewContentLabel.snp.makeConstraints { + crewContentImageView.snp.makeConstraints { $0.trailing.equalToSuperview().inset(20) - $0.centerY.equalToSuperview() + $0.bottom.equalToSuperview() } crewInfoView.snp.makeConstraints { $0.height.equalTo(57) } + let contactInfoTitleLabel: UILabel = UILabel().then { + $0.text = "Contact" + $0.font = .boldSystemFont(ofSize: 14) + $0.textColor = .warmBlue + } + let contactContentImageView: UIImageView = UIImageView().then { + $0.image = UIImage(named: "left_arrow") + } + contactInfoView.do { + $0.backgroundColor = .grayWhite + $0.addSubview(contactInfoTitleLabel) + $0.addSubview(contactContentImageView) + $0.layer.cornerRadius = 10 + $0.layer.shadowOffset = CGSize(width: 0, height: 4) + $0.layer.shadowOpacity = 0.1 + $0.layer.shadowColor = UIColor.black.cgColor + } + contactInfoTitleLabel.snp.makeConstraints { + $0.leading.equalToSuperview().inset(20) + $0.centerY.equalToSuperview() + } + contactInfoView.snp.makeConstraints { + $0.height.equalTo(57) + } + contactContentImageView.snp.makeConstraints { + $0.trailing.equalToSuperview().inset(20) + $0.centerY.equalToSuperview() + } let noticeInfoTitleLabel: UILabel = UILabel().then { $0.text = "Notice" - $0.font = .systemFont(ofSize: 14) + $0.font = .boldSystemFont(ofSize: 14) $0.textColor = .warmBlue } noticeBadge.do { @@ -100,6 +132,9 @@ final class AboutViewController: UIViewController, View { $0.addSubview(noticeInfoTitleLabel) $0.addSubview(noticeBadge) $0.layer.cornerRadius = 10 + $0.layer.shadowOffset = CGSize(width: 0, height: 4) + $0.layer.shadowOpacity = 0.1 + $0.layer.shadowColor = UIColor.black.cgColor } noticeInfoTitleLabel.snp.makeConstraints { $0.leading.equalToSuperview().inset(20) @@ -112,14 +147,15 @@ final class AboutViewController: UIViewController, View { noticeInfoView.snp.makeConstraints { $0.height.equalTo(57) } - + noticeInfoView.isHidden = true let versionInfoTitleLabel: UILabel = UILabel().then { $0.text = "Version Info." - $0.font = .systemFont(ofSize: 14) + $0.font = .boldSystemFont(ofSize: 14) $0.textColor = .warmBlue } versionLabel.do { $0.text = "New Version" + $0.font = .boldSystemFont(ofSize: 16) $0.textColor = .livelyBlue } versionInfoView.do { @@ -127,6 +163,9 @@ final class AboutViewController: UIViewController, View { $0.addSubview(versionInfoTitleLabel) $0.addSubview(versionLabel) $0.layer.cornerRadius = 10 + $0.layer.shadowOffset = CGSize(width: 0, height: 4) + $0.layer.shadowOpacity = 0.1 + $0.layer.shadowColor = UIColor.black.cgColor } versionInfoTitleLabel.snp.makeConstraints { $0.leading.equalToSuperview().inset(20) @@ -150,6 +189,13 @@ final class AboutViewController: UIViewController, View { .bind(to: reactor.action) .disposed(by: disposeBag) + contactInfoView.rx.tapGesture() + .throttle(.milliseconds(300), scheduler: MainScheduler.instance) + .skip(1) + .map { _ in Action.tapContact } + .bind(to: reactor.action) + .disposed(by: disposeBag) + backButton.rx.tap .throttle(.milliseconds(300), scheduler: MainScheduler.instance) .map { Action.tapClose } @@ -199,6 +245,22 @@ final class AboutViewController: UIViewController, View { }) .disposed(by: disposeBag) + reactor.state + .distinctUntilChanged(\.$isPresentEmail) + .map { $0.isPresentEmail } + .filter { $0 } + .filter { _ in MFMailComposeViewController.canSendMail() } + .subscribe(onNext: { _ in + let viewController = MFMailComposeViewController().then { + $0.setToRecipients(["dearworld.crew@gmail.com"]) + $0.setSubject("Dear world μ—κ²Œ λ¬Έμ˜λ“œλ¦½λ‹ˆλ‹€.") + $0.setMessageBody("λ¬Έμ˜μ‚¬ν•­", isHTML: false) + } + viewController.mailComposeDelegate = self + self.present(viewController, animated: true, completion: nil) + }) + .disposed(by: disposeBag) + reactor.state .distinctUntilChanged(\.$isPresentAppStore) .map { $0.isPresentAppStore } @@ -221,5 +283,8 @@ final class AboutViewController: UIViewController, View { .distinctUntilChanged() .bind(to: versionLabel.rx.text) .disposed(by: disposeBag) + + reactor.action.onNext(.initalize) } } +extension AboutViewController: MFMailComposeViewControllerDelegate {} diff --git a/Dear-World/Dear-World/Source/Presentation/Scene/Discover/Cell/MessageTableViewCell.swift b/Dear-World/Dear-World/Source/Presentation/Scene/Discover/Cell/MessageTableViewCell.swift index c7ac7fa..be1f151 100644 --- a/Dear-World/Dear-World/Source/Presentation/Scene/Discover/Cell/MessageTableViewCell.swift +++ b/Dear-World/Dear-World/Source/Presentation/Scene/Discover/Cell/MessageTableViewCell.swift @@ -43,7 +43,7 @@ final class MessageTableViewCell: UITableViewCell { self.likeCountLabel.textColor = isLike ? .loveRed : .grayWhite } } - var messageId: Int? + private var messageId: Int? // MARK: 🏁 Initialize override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) { @@ -220,4 +220,19 @@ final class MessageTableViewCell: UITableViewCell { }) .disposed(by: self.disposeBag) } + + func configure(_ message: Message.Model.Message) { + nameLabel.text = message.user.nickname + detailTextView.text = message.content + likeCount = message.likeCount + isLike = message.isLiked + countryLabel.text = message.user.country.fullName + if let emojiImageURL: URL = URL(string: message.user.emoji.imageURL) { + emojiImageView.kf.setImage(with: emojiImageURL) + } + if let countryImageURL: URL = URL(string: message.user.country.imageURL) { + countryFlagImageView.kf.setImage(with: countryImageURL) + } + messageId = message.id + } } 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 538c038..d0e4f79 100644 --- a/Dear-World/Dear-World/Source/Presentation/Scene/Discover/DiscoverReactor.swift +++ b/Dear-World/Dear-World/Source/Presentation/Scene/Discover/DiscoverReactor.swift @@ -18,6 +18,7 @@ final class DiscoverReactor: Reactor { case tapAbout case tapFilter case tapSort + case tapShare(at: Int) case countryDidChanged(Model.Country?) case sortTypeDidChanged(Model.Sort?) case refresh @@ -25,9 +26,12 @@ final class DiscoverReactor: Reactor { } enum Mutation { - case setMessages(Model.Messages) + case setFirstMsgId(Int?) + case setLastMsgId(Int?) + case setMessages([Model.Message]) case setRefreshing(Bool) - case addMessages(Model.Messages) + case addMessages([Model.Message]) + case setShareURL(URL) case setCountry(Model.Country?) case setCountries([Model.Country]) case setLoading(Bool) @@ -46,12 +50,10 @@ final class DiscoverReactor: Reactor { @Revision var selectedCountry: Model.Country? = .wholeWorld @Revision var countries: [Model.Country] = [] @Revision var selectedSortType: Model.Sort = .recent - @Revision var messages: Model.Messages = .init( - firstMsgId: nil, - lastMsgId: nil, - messageCount: 0, - messages: [] - ) + @Revision var shareURL: URL? + @Revision var firstMsgId: Int? + @Revision var lastMsgId: Int? + @Revision var messages: [Model.Message] = [] var messageCount: Int = 0 var isLoading: Bool = false var isAnimating: Bool = false @@ -84,7 +86,13 @@ final class DiscoverReactor: Reactor { ) ) .filterNil() - .map { Mutation.setMessages($0) }, + .flatMap { response -> Observable in + .from([ + .setFirstMsgId(response.firstMsgId), + .setLastMsgId(response.lastMsgId), + .setMessages(response.messages) + ]) + }, Network.request(API.Countries()) .filterNil() .map { .setCountries($0.countries) } @@ -96,7 +104,12 @@ final class DiscoverReactor: Reactor { case .tapSort: return .just(.setPresentSort(true)) - case let .countryDidChanged(country): + case .tapShare(let index): + let urlString = currentState.messages[index].shareURL + guard let shareURL = URL(string: urlString) else { return .empty() } + return .just(.setShareURL(shareURL)) + + case .countryDidChanged(let country): return .merge( Network.request( API.MessageCount( @@ -116,11 +129,18 @@ final class DiscoverReactor: Reactor { ) ) .filterNil() - .map{ Mutation.setMessages($0) }, + .flatMap { response -> Observable in + .from([ + .setMessages(response.messages), + .setFirstMsgId(response.firstMsgId), + .setLastMsgId(response.lastMsgId) + ]) + }, .just(.setLoading(false)) ) ) - case let .sortTypeDidChanged(sortType: sortType) : + + case .sortTypeDidChanged(let sortType): return .merge( Network.request( API.MessageCount( @@ -140,24 +160,48 @@ final class DiscoverReactor: Reactor { ) ) .filterNil() - .map{ Mutation.setMessages($0) }, - .just(.setLoading(false)) + .flatMap { response -> Observable in + .from([ + .setMessages(response.messages), + .setFirstMsgId(response.firstMsgId), + .setLastMsgId(response.lastMsgId) + ]) + } ) ) + case .refresh: guard currentState.isRefreshing == false else { return .empty() } return .merge( - Network.request(Message.API.MessageCount(countryCode: currentState.selectedCountry?.code)) - .filterNil() - .map(\.messageCount) - .map{.setMessageCount($0)} - ,.concat([ + Network.request( + Message.API.MessageCount( + countryCode: currentState.selectedCountry?.code + ) + ) + .filterNil() + .map(\.messageCount) + .map { .setMessageCount($0) }, + .concat([ .just(.setRefreshing(true)), - Network.request(Message.API.Messages(countryCode: currentState.selectedCountry?.code, lastMsgId: nil, type: currentState.selectedSortType)) - .filterNil() - .map{.setMessages($0)}, + Network.request( + Message.API.Messages( + countryCode: currentState.selectedCountry?.code, + lastMsgId: nil, + type: currentState.selectedSortType + ) + ) + .filterNil() + .flatMap { response -> Observable in + .from([ + .setMessages(response.messages), + .setFirstMsgId(response.firstMsgId), + .setLastMsgId(response.lastMsgId) + ]) + }, .just(.setRefreshing(false)) - ])) + ]) + ) + case .loadMore: guard !currentState.isLoading else { return .empty() } return Observable.concat([ @@ -165,12 +209,18 @@ final class DiscoverReactor: Reactor { Network.request( API.Messages( countryCode: currentState.selectedCountry?.code, - lastMsgId: currentState.messages.lastMsgId, + lastMsgId: currentState.lastMsgId, type: currentState.selectedSortType ) ) .filterNil() - .map { .addMessages($0) }, + .flatMap { response -> Observable in + .from([ + .setMessages(response.messages), + .setFirstMsgId(response.firstMsgId), + .setLastMsgId(response.lastMsgId) + ]) + }, .just(.setLoading(false)) ]) @@ -189,13 +239,8 @@ final class DiscoverReactor: Reactor { case .setRefreshing(let flag): newState.isRefreshing = flag - case .addMessages(let results): - newState.messages = Model.Messages( - firstMsgId: state.messages.firstMsgId, - lastMsgId: results.lastMsgId, - messageCount: state.messageCount + results.messageCount, - messages: currentState.messages.messages + results.messages - ) + case .addMessages(let messages): + newState.messages = currentState.messages + messages case .setCountry(let country): newState.selectedCountry = country @@ -220,6 +265,15 @@ final class DiscoverReactor: Reactor { case .setPresentSort(let isPresentSort): newState.isPresentSort = isPresentSort + + case .setFirstMsgId(let firstMsgId): + newState.firstMsgId = firstMsgId + + case .setLastMsgId(let lastMsgId): + newState.lastMsgId = lastMsgId + + case .setShareURL(let shareURL): + newState.shareURL = shareURL } 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 63f41af..2485a0f 100644 --- a/Dear-World/Dear-World/Source/Presentation/Scene/Discover/DiscoverViewController.swift +++ b/Dear-World/Dear-World/Source/Presentation/Scene/Discover/DiscoverViewController.swift @@ -163,13 +163,7 @@ final class DiscoverViewController: UIViewController, View { // MARK: πŸ”— Bind func bind(reactor: Reactor) { _ = AllCountries.shared - reactor.action.onNext(.countryDidChanged( - Model.Country( - code: nil, - fullName: "Whole World", - emojiUnicode: "🍎", imageURL: nil - ) - )) + reactor.action.onNext(.countryDidChanged(.wholeWorld)) reactor.state .map(\.messageCount) @@ -181,8 +175,8 @@ final class DiscoverViewController: UIViewController, View { reactor.state .distinctUntilChanged(\.$messages) .map(\.messages) - .subscribe(onNext: {[weak self] mess in - self?.messages = mess.messages + .subscribe(onNext: {[weak self] in + self?.messages = $0 self?.messageTableView.reloadData() }) .disposed(by: self.disposeBag) @@ -302,7 +296,21 @@ final class DiscoverViewController: UIViewController, View { .filter{!$0} .bind(to: self.refreshControl.rx.isRefreshing) .disposed(by: self.disposeBag) + + reactor.state.distinctUntilChanged(\.$shareURL) + .map { $0.shareURL } + .subscribe(onNext: { shareURL in + let shareViewController: UIActivityViewController = UIActivityViewController( + activityItems: ["πŸ›Έμš°μ›…πŸ›Έ\n지ꡬ μ–΄λ””μ„ κ°€ 메세지가 λ„μ°©ν–ˆμ–΄μš”πŸ’Œ", + shareURL], + applicationActivities: nil + ) + shareViewController.popoverPresentationController?.sourceView = self.view + self.present(shareViewController, animated: true) + }) + .disposed(by: self.disposeBag) } + private func presentFilter() -> Observable { guard let reactor = self.reactor else { return .empty() } let selected: Model.Country? = reactor.currentState.selectedCountry @@ -369,48 +377,26 @@ extension DiscoverViewController: UITableViewDelegate, UITableViewDataSource { } func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { - self.messages.count + self.reactor?.currentState.messages.count ?? 0 } func tableView( _ tableView: UITableView, cellForRowAt indexPath: IndexPath ) -> UITableViewCell { - guard let cell = tableView.dequeueReusableCell(withIdentifier: "MessageCell", for: indexPath) as? MessageTableViewCell else { return UITableViewCell() } - cell.do { - let cellMessage = self.messages[indexPath.row] - $0.nameLabel.text = cellMessage.user.nickname - - $0.detailTextView.text = cellMessage.content - $0.likeCount = cellMessage.likeCount - $0.isLike = cellMessage.isLiked - $0.countryLabel.text = cellMessage.user.country.fullName - if let emojiImageURL: URL = URL(string: cellMessage.user.emoji.imageURL) { - $0.emojiImageView.kf.setImage(with: emojiImageURL) - } - if let countryImageURL: URL = URL(string: cellMessage.user.country.imageURL) { - $0.countryFlagImageView.kf.setImage(with: countryImageURL) - } - $0.messageId = cellMessage.id + guard let cell = tableView.dequeueReusableCell(withIdentifier: "MessageCell", for: indexPath) as? MessageTableViewCell, + let message = reactor?.currentState.messages[indexPath.row] + else { return UITableViewCell() } + cell.configure(message) + if let reactor = self.reactor { + cell.shareButton.rx.tap + .throttle(.milliseconds(300), scheduler: MainScheduler.instance) + .map { Action.tapShare(at: indexPath.row) } + .bind(to: reactor.action) + .disposed(by: self.disposeBag) } - bindShareButton(button: cell.shareButton) return cell } - - private func bindShareButton(button: UIButton) { - button - .rx.tap - .throttle(.milliseconds(300), scheduler: MainScheduler.instance) - .subscribe (onNext: { - self.filterView.snp.makeConstraints { - $0.top.greaterThanOrEqualTo(self.view.safeAreaLayoutGuide) - } - let activityVC: UIActivityViewController = UIActivityViewController(activityItems: ["hi"], applicationActivities: nil) - activityVC.popoverPresentationController?.sourceView = self.view - self.present(activityVC, animated: true) - }) - .disposed(by: self.disposeBag) - } } 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 index 497c67e..7db55fd 100644 --- 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 @@ -29,6 +29,7 @@ extension Message.Model { let likeCount: Int let content: String let createdAt: String + let shareURL: String enum CodingKeys: String, CodingKey { case id @@ -37,6 +38,7 @@ extension Message.Model { case likeCount case content case createdAt + case shareURL = "shareLink" } } diff --git a/Dear-World/Dear-World/Source/Presentation/Scene/Send Message/SendMessageReactor.swift b/Dear-World/Dear-World/Source/Presentation/Scene/Send Message/SendMessageReactor.swift index 7bfa7ee..a679b2f 100644 --- a/Dear-World/Dear-World/Source/Presentation/Scene/Send Message/SendMessageReactor.swift +++ b/Dear-World/Dear-World/Source/Presentation/Scene/Send Message/SendMessageReactor.swift @@ -83,9 +83,12 @@ final class SendMessageReactor: Reactor { func mutate(action: Action) -> Observable { switch action { case .initialize: - return Network.request(API.Countries()) - .filterNil() - .map { .setCountries($0.countries) } + return .merge( + Network.request(API.Countries()) + .filterNil() + .map { .setCountries($0.countries) }, + .just(.setCountry(.current)) + ) case .tapFilter: return .just(.setPresentFilter(true)) @@ -239,3 +242,13 @@ final class SendMessageReactor: Reactor { } } +extension Message.Model.Country { + static var current: Message.Model.Country { + Message.Model.Country( + code: Locale.current.regionCode, + fullName: Locale.current.localizedString(forRegionCode: Locale.current.regionCode ?? "") ?? "", + emojiUnicode: nil, + imageURL: nil + ) + } +} diff --git a/Dear-World/Dear-World/Source/Presentation/Scene/Send Message/SendMessageViewController.swift b/Dear-World/Dear-World/Source/Presentation/Scene/Send Message/SendMessageViewController.swift index fd45a54..b4bee69 100644 --- a/Dear-World/Dear-World/Source/Presentation/Scene/Send Message/SendMessageViewController.swift +++ b/Dear-World/Dear-World/Source/Presentation/Scene/Send Message/SendMessageViewController.swift @@ -453,7 +453,7 @@ final class SendMessageViewController: UIViewController, View { $0.reactor = ItemBottomSheetReactor( items: items, selectedItem: selected, - headerItem: .wholeWorld + headerItem: nil ) $0.modalPresentationStyle = .overFullScreen }