diff --git a/IndieMusic/IndieMusic.xcodeproj/project.pbxproj b/IndieMusic/IndieMusic.xcodeproj/project.pbxproj index 626f78b..fad14a1 100644 --- a/IndieMusic/IndieMusic.xcodeproj/project.pbxproj +++ b/IndieMusic/IndieMusic.xcodeproj/project.pbxproj @@ -88,6 +88,9 @@ 77620D962B68E96C00798861 /* DateItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = 77620D952B68E96C00798861 /* DateItem.swift */; }; 77620D9A2B69DA1A00798861 /* EditSignatureViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 77620D992B69DA1A00798861 /* EditSignatureViewController.swift */; }; 776A1F762B7A18F000F613EB /* ChinaLocalizable.strings in Resources */ = {isa = PBXBuildFile; fileRef = 776A1F782B7A18F000F613EB /* ChinaLocalizable.strings */; }; + 776A1F7C2B7B38DF00F613EB /* Thanks.swift in Sources */ = {isa = PBXBuildFile; fileRef = 776A1F7B2B7B38DF00F613EB /* Thanks.swift */; }; + 776A1F7E2B7B3EE600F613EB /* FeedbackViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 776A1F7D2B7B3EE600F613EB /* FeedbackViewModel.swift */; }; + 776A1F802B7BA99A00F613EB /* Feedback.swift in Sources */ = {isa = PBXBuildFile; fileRef = 776A1F7F2B7BA99A00F613EB /* Feedback.swift */; }; 778638942B4D123D00B00AF9 /* CommentDetailViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 778638932B4D123D00B00AF9 /* CommentDetailViewModel.swift */; }; 778B8A212AF8E36D0034AFD4 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 778B8A202AF8E36D0034AFD4 /* AppDelegate.swift */; }; 778B8A232AF8E36D0034AFD4 /* SceneDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 778B8A222AF8E36D0034AFD4 /* SceneDelegate.swift */; }; @@ -330,6 +333,9 @@ 776A1F752B7A184F00F613EB /* zh-Hans */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "zh-Hans"; path = "zh-Hans.lproj/LaunchScreen.strings"; sourceTree = ""; }; 776A1F772B7A18F000F613EB /* zh-Hans */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "zh-Hans"; path = "zh-Hans.lproj/ChinaLocalizable.strings"; sourceTree = ""; }; 776A1F7A2B7A192000F613EB /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = en; path = en.lproj/ChinaLocalizable.strings; sourceTree = ""; }; + 776A1F7B2B7B38DF00F613EB /* Thanks.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Thanks.swift; sourceTree = ""; }; + 776A1F7D2B7B3EE600F613EB /* FeedbackViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FeedbackViewModel.swift; sourceTree = ""; }; + 776A1F7F2B7BA99A00F613EB /* Feedback.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Feedback.swift; sourceTree = ""; }; 778638932B4D123D00B00AF9 /* CommentDetailViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CommentDetailViewModel.swift; sourceTree = ""; }; 778B8A1D2AF8E36D0034AFD4 /* IndieMusic.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = IndieMusic.app; sourceTree = BUILT_PRODUCTS_DIR; }; 778B8A202AF8E36D0034AFD4 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; @@ -803,6 +809,8 @@ 770F6CA12B64F1DE0082F53A /* Carousel.swift */, 77620D912B68E13300798861 /* Sheet.swift */, 77620D952B68E96C00798861 /* DateItem.swift */, + 776A1F7B2B7B38DF00F613EB /* Thanks.swift */, + 776A1F7F2B7BA99A00F613EB /* Feedback.swift */, ); path = Models; sourceTree = ""; @@ -940,6 +948,7 @@ 7751D3612B42BC0C00F1F2BD /* AccountViewController.swift */, 7751D3632B42BC2E00F1F2BD /* AccountViewModel.swift */, 7751D3652B42BE7F00F1F2BD /* FeedbackViewController.swift */, + 776A1F7D2B7B3EE600F613EB /* FeedbackViewModel.swift */, 7751D3672B42E96200F1F2BD /* ThanksViewController.swift */, 77FB7A7E2B4A630100B64030 /* ThanksViewModel.swift */, 7751D3692B42ED6C00F1F2BD /* TimingViewController.swift */, @@ -1389,6 +1398,7 @@ 77C9B9E92B4BE04C0006C83F /* MyCommentListController.swift in Sources */, 77C9B9ED2B4BEA610006C83F /* MyCommentListViewModel.swift in Sources */, 77C9B9C82B4B8E200006C83F /* AlertViewController.swift in Sources */, + 776A1F802B7BA99A00F613EB /* Feedback.swift in Sources */, 778B8A862AF8ECE50034AFD4 /* SearchViewModel.swift in Sources */, 77C9B9DD2B4BC5D20006C83F /* FollowersViewModel.swift in Sources */, 778B8A802AF8ECE50034AFD4 /* MineViewController.swift in Sources */, @@ -1433,6 +1443,7 @@ 77FA0B5C2B147EE800404C5E /* CommentView.swift in Sources */, 77620D942B68E65300798861 /* EditDateViewController.swift in Sources */, 778B8A902AF8ECF20034AFD4 /* Token.swift in Sources */, + 776A1F7C2B7B38DF00F613EB /* Thanks.swift in Sources */, 778B8A6B2AF8ECD30034AFD4 /* IndieMusicAPI.swift in Sources */, 7751D36E2B43A03E00F1F2BD /* PrivacyViewController.swift in Sources */, 7751D3582B42B5A200F1F2BD /* EditInfoViewController.swift in Sources */, @@ -1442,6 +1453,7 @@ 77C9B9F12B4C2B3A0006C83F /* AudioTrackListViewModel.swift in Sources */, 778638942B4D123D00B00AF9 /* CommentDetailViewModel.swift in Sources */, 775D075A2B5E47E1009270D3 /* ETPopupView.swift in Sources */, + 776A1F7E2B7B3EE600F613EB /* FeedbackViewModel.swift in Sources */, 77C9B9BE2B4AB4FA0006C83F /* TimingViewModel.swift in Sources */, 77FA0B362B0C50D800404C5E /* Share.swift in Sources */, 778B8A622AF8ECC20034AFD4 /* ErrorTracker.swift in Sources */, diff --git a/IndieMusic/IndieMusic/Application/Navigator.swift b/IndieMusic/IndieMusic/Application/Navigator.swift index b8e693a..44a7d4b 100644 --- a/IndieMusic/IndieMusic/Application/Navigator.swift +++ b/IndieMusic/IndieMusic/Application/Navigator.swift @@ -40,7 +40,7 @@ class Navigator { case account(viewModel: AccountViewModel) case thanks(viewModel: ThanksViewModel) - case feedback + case feedback(viewModel: FeedbackViewModel) case timing(viewModel: TimingViewModel) case privacy(viewModel: PrivacyViewModel) case about(viewModel: AboutViewModel) @@ -140,8 +140,8 @@ class Navigator { return AccountViewController.init(viewModel: viewModel, navigator: self) case .thanks(let viewModel): return ThanksViewController.init(viewModel: viewModel, navigator: self) - case .feedback: - return FeedbackViewController.init() + case .feedback(let viewModel): + return FeedbackViewController.init(viewModel: viewModel, navigator: self) case .timing(viewModel: let viewModel): return TimingViewController.init(viewModel: viewModel, navigator: self) case .about(viewModel: let viewModel): diff --git a/IndieMusic/IndieMusic/Models/Feedback.swift b/IndieMusic/IndieMusic/Models/Feedback.swift new file mode 100644 index 0000000..229e124 --- /dev/null +++ b/IndieMusic/IndieMusic/Models/Feedback.swift @@ -0,0 +1,34 @@ +// +// Feedback.swift +// IndieMusic +// +// Created by WenLei on 2024/2/13. +// + +import Foundation +import RxDataSources + +//struct Feedback: Codable { +// let icon: String +// let title: String +// +//} + +enum FeedbackPhotoType { + case add + case photo(String) +} + +struct FeedbackSection { + var items: [FeedbackPhotoType] + +} + +extension FeedbackSection: SectionModelType { + typealias Item = FeedbackPhotoType + + init(original: FeedbackSection, items: [Item]) { + self = original + self.items = items + } +} diff --git a/IndieMusic/IndieMusic/Models/Thanks.swift b/IndieMusic/IndieMusic/Models/Thanks.swift new file mode 100644 index 0000000..cfb267e --- /dev/null +++ b/IndieMusic/IndieMusic/Models/Thanks.swift @@ -0,0 +1,30 @@ +// +// Thanks.swift +// IndieMusic +// +// Created by WenLei on 2024/2/13. +// + +import Foundation +import RxDataSources + +struct Thanks: Codable { + let icon: String + let title: String + let detail: String + +} + +struct ThanksSection { + var items: [Thanks] + +} + +extension ThanksSection: SectionModelType { + typealias Item = Thanks + + init(original: ThanksSection, items: [Item]) { + self = original + self.items = items + } +} diff --git a/IndieMusic/IndieMusic/Modules/Home/HomeViewController.swift b/IndieMusic/IndieMusic/Modules/Home/HomeViewController.swift index 710c02e..5fc8569 100644 --- a/IndieMusic/IndieMusic/Modules/Home/HomeViewController.swift +++ b/IndieMusic/IndieMusic/Modules/Home/HomeViewController.swift @@ -158,6 +158,12 @@ class HomeViewController: TableViewController { guard let audioTrackArray = audioTrackArray.element, let audioTrack = audioTrackArray.first else { return } + guard AuthManager.shared.token?.isValid == true else { + let loginViewModel = LoginViewModel.init(provider: viewModel.provider) + self?.navigator.show(segue: .login(viewModel: loginViewModel), sender: self) + return + } + let playerViewModel = PlayerViewModel.init(track: audioTrack, provider: viewModel.provider) self?.navigator.show(segue: .player(viewModel: playerViewModel), sender: self, transition: .modal) @@ -312,13 +318,24 @@ extension HomeViewController { self.headerView = header self.headerView?.filterButton.filter = viewModel.filter.value header.theFMButtonClosures = {[weak self] in + guard AuthManager.shared.token?.isValid == true else { + let loginViewModel = LoginViewModel.init(provider: viewModel.provider) + self?.navigator.show(segue: .login(viewModel: loginViewModel), sender: self) + return + } + self?.randomAudioTrackTrigger.accept(()) } header.filterButtonClosures = {[weak self] in guard let viewModel = self?.viewModel as? HomeViewModel, let navigator = self?.navigator else { return } - + guard AuthManager.shared.token?.isValid == true else { + let loginViewModel = LoginViewModel.init(provider: viewModel.provider) + self?.navigator.show(segue: .login(viewModel: loginViewModel), sender: self) + return + } + let filterViewModel = FilterViewModel.init(filter: viewModel.filter, provider: viewModel.provider) navigator.show(segue: .filter(viewModel: filterViewModel), sender: self, transition: .navigationPresent(type: .filter)) } diff --git a/IndieMusic/IndieMusic/Modules/JournalDetail/JournalDetailController.swift b/IndieMusic/IndieMusic/Modules/JournalDetail/JournalDetailController.swift index e4befdc..180c971 100644 --- a/IndieMusic/IndieMusic/Modules/JournalDetail/JournalDetailController.swift +++ b/IndieMusic/IndieMusic/Modules/JournalDetail/JournalDetailController.swift @@ -316,6 +316,12 @@ class JournalDetailController: ViewController, UIScrollViewDelegate { case .journaItem(let journal): + guard AuthManager.shared.token?.isValid == true else { + let loginViewModel = LoginViewModel.init(provider: viewModel.provider) + self?.navigator.show(segue: .login(viewModel: loginViewModel), sender: self) + return + } + let journalDetailViewModel = JournalDetailViewModel.init(journal: journal, provider: viewModel.provider) self?.navigator.show(segue: .journalDetail(viewModel: journalDetailViewModel), sender: self) diff --git a/IndieMusic/IndieMusic/Modules/Mine/MineViewController.swift b/IndieMusic/IndieMusic/Modules/Mine/MineViewController.swift index 12f8766..0b584cb 100644 --- a/IndieMusic/IndieMusic/Modules/Mine/MineViewController.swift +++ b/IndieMusic/IndieMusic/Modules/Mine/MineViewController.swift @@ -99,28 +99,48 @@ class MineViewController: TableViewController { headerView.mineMenuView.followingView.rx.controlEvent(.touchUpInside).subscribe { [weak self] _ in - + guard AuthManager.shared.token?.isValid == true else { + let loginViewModel = LoginViewModel.init(provider: viewModel.provider) + self?.navigator.show(segue: .login(viewModel: loginViewModel), sender: self) + return + } + let followingViewModel = FollowingViewModel.init(provider: viewModel.provider) self?.navigator.show(segue: .following(viewModel: followingViewModel), sender: self) }.disposed(by: rx.disposeBag) headerView.mineMenuView.followersView.rx.controlEvent(.touchUpInside).subscribe { [weak self] _ in - + guard AuthManager.shared.token?.isValid == true else { + let loginViewModel = LoginViewModel.init(provider: viewModel.provider) + self?.navigator.show(segue: .login(viewModel: loginViewModel), sender: self) + return + } + let followersViewModel = FollowersViewModel.init(provider: viewModel.provider) self?.navigator.show(segue: .followers(viewModel: followersViewModel), sender: self) }.disposed(by: rx.disposeBag) headerView.mineMenuView.likeView.rx.controlEvent(.touchUpInside).subscribe { [weak self] _ in - + guard AuthManager.shared.token?.isValid == true else { + let loginViewModel = LoginViewModel.init(provider: viewModel.provider) + self?.navigator.show(segue: .login(viewModel: loginViewModel), sender: self) + return + } + let myLikeListViewModel = MyLikeListViewModel.init(provider: viewModel.provider) self?.navigator.show(segue: .myLikeList(viewModel: myLikeListViewModel), sender: self) }.disposed(by: rx.disposeBag) headerView.mineMenuView.commentView.rx.controlEvent(.touchUpInside).subscribe { [weak self] _ in - + guard AuthManager.shared.token?.isValid == true else { + let loginViewModel = LoginViewModel.init(provider: viewModel.provider) + self?.navigator.show(segue: .login(viewModel: loginViewModel), sender: self) + return + } + let myCommentListViewModel = MyCommentListViewModel.init(provider: viewModel.provider) self?.navigator.show(segue: .myCommentList(viewModel: myCommentListViewModel), sender: self) @@ -134,6 +154,13 @@ class MineViewController: TableViewController { output.selection.drive { [weak self] indexPath in + guard AuthManager.shared.token?.isValid == true else { + let loginViewModel = LoginViewModel.init(provider: viewModel.provider) + self?.navigator.show(segue: .login(viewModel: loginViewModel), sender: self) + return + } + + switch indexPath.row { case 0: let mineSingleViewModel = MineSingleViewModel.init(provider: viewModel.provider) @@ -160,10 +187,16 @@ class MineViewController: TableViewController { }.disposed(by: rx.disposeBag) messageBarButton.rx.tap.subscribe { [weak self] _ in - let messageViewModel = MessageViewModel.init(provider: viewModel.provider) - - self?.navigator.show(segue: .message(viewModel: messageViewModel), sender: self) + if AuthManager.shared.token?.isValid == true { + let messageViewModel = MessageViewModel.init(provider: viewModel.provider) + + self?.navigator.show(segue: .message(viewModel: messageViewModel), sender: self) + } else { + let loginViewModel = LoginViewModel.init(provider: viewModel.provider) + self?.navigator.show(segue: .login(viewModel: loginViewModel), sender: self) + + } }.disposed(by: rx.disposeBag) diff --git a/IndieMusic/IndieMusic/Modules/Search/SearchViewController.swift b/IndieMusic/IndieMusic/Modules/Search/SearchViewController.swift index f97c383..16d398c 100644 --- a/IndieMusic/IndieMusic/Modules/Search/SearchViewController.swift +++ b/IndieMusic/IndieMusic/Modules/Search/SearchViewController.swift @@ -96,9 +96,14 @@ class SearchViewController: ViewController, UIScrollViewDelegate { selection: collectionView.rx.itemSelected.asDriver()) let output = viewModel.transform(input: input) - let dataSource = SearchViewController.dataSource { + let dataSource = SearchViewController.dataSource { [weak self] in + guard AuthManager.shared.token?.isValid == true else { + let loginViewModel = LoginViewModel.init(provider: viewModel.provider) + self?.navigator.show(segue: .login(viewModel: loginViewModel), sender: self) + return + } let searchResultsViewModel = SearchResultsViewModel.init(provider: viewModel.provider) - self.navigator.show(segue: .searchResults(viewModel: searchResultsViewModel), sender: self) + self?.navigator.show(segue: .searchResults(viewModel: searchResultsViewModel), sender: self) } @@ -106,11 +111,16 @@ class SearchViewController: ViewController, UIScrollViewDelegate { output.itemSelected.subscribe { [weak self] searchCategory in + guard AuthManager.shared.token?.isValid == true else { + let loginViewModel = LoginViewModel.init(provider: viewModel.provider) + self?.navigator.show(segue: .login(viewModel: loginViewModel), sender: self) + return + } + guard let searchCategory = searchCategory.element else { return } let musicStyleViewModel = MusicStyleViewModel.init(searchCategory: searchCategory, provider: viewModel.provider) - self?.navigator.show(segue: .musicStyle(viewModel: musicStyleViewModel), sender: self) }.disposed(by: rx.disposeBag) diff --git a/IndieMusic/IndieMusic/Modules/Search/SearchViewModel.swift b/IndieMusic/IndieMusic/Modules/Search/SearchViewModel.swift index 17415ba..05af5ae 100644 --- a/IndieMusic/IndieMusic/Modules/Search/SearchViewModel.swift +++ b/IndieMusic/IndieMusic/Modules/Search/SearchViewModel.swift @@ -40,11 +40,6 @@ class SearchViewModel: ViewModel, ViewModelType { }.disposed(by: rx.disposeBag) - - - - - input.selection.drive { [weak self] indexPath in guard let self = self else { return } diff --git a/IndieMusic/IndieMusic/Modules/Setting/FeedbackViewController.swift b/IndieMusic/IndieMusic/Modules/Setting/FeedbackViewController.swift index 81ade47..bcdf985 100644 --- a/IndieMusic/IndieMusic/Modules/Setting/FeedbackViewController.swift +++ b/IndieMusic/IndieMusic/Modules/Setting/FeedbackViewController.swift @@ -6,8 +6,12 @@ // import UIKit +import GrowingTextView +import RxSwift +import RxCocoa +import RxDataSources -class FeedbackViewController: UIViewController { +class FeedbackViewController: ViewController { let feedbackTypeView: FeedbackTypeView = { let feedbackTypeView = FeedbackTypeView.init() @@ -32,18 +36,34 @@ class FeedbackViewController: UIViewController { confirmButton.setTitle("提交", for: .normal) confirmButton.titleLabel?.font = UIFont.systemFont(ofSize: 15) confirmButton.backgroundColor = .init(hex: 0x000000) + confirmButton.layer.cornerRadius = 22 + confirmButton.layer.masksToBounds = true + return confirmButton }() + + override func viewWillAppear(_ animated: Bool) { + super.viewWillAppear(animated) + + navBarBgAlpha = 0 + } + + override func viewWillDisappear(_ animated: Bool) { + super.viewWillDisappear(animated) + + navBarBgAlpha = 1 + } override func viewDidLoad() { super.viewDidLoad() - makeUI() } - func makeUI() { + override func makeUI() { + view.backgroundColor = .init(hex: 0xf5f5f5) + view.addSubview(feedbackTypeView) view.addSubview(feedbackDetailView) @@ -53,6 +73,23 @@ class FeedbackViewController: UIViewController { } + override func bindViewModel() { + super.bindViewModel() + + guard let viewModel = viewModel as? FeedbackViewModel else { return } + + let input = FeedbackViewModel.Input.init(viewWillAppear: rx.viewWillAppear) + let output = viewModel.transform(input: input) + + let dataSource = FeedbackDetailSubView.dataSource() + + output.photoItems.bind(to: feedbackDetailView.feedbackDetailSubView.collectionView.rx.items(dataSource: dataSource)).disposed(by: rx.disposeBag) + + + + } + + override func viewDidLayoutSubviews() { super.viewDidLayoutSubviews() @@ -65,19 +102,18 @@ class FeedbackViewController: UIViewController { feedbackDetailView.snp.makeConstraints { make in make.left.equalTo(view) make.right.equalTo(view) - make.top.equalTo(feedbackTypeView.snp.bottom) + make.top.equalTo(feedbackTypeView.snp.bottom).offset(12) } feedbackContactView.snp.makeConstraints { make in make.left.equalTo(view) make.right.equalTo(view) - make.top.equalTo(feedbackDetailView.snp.bottom) - + make.top.equalTo(feedbackDetailView.snp.bottom).offset(12) } confirmButton.snp.makeConstraints { make in - make.left.equalTo(view) - make.right.equalTo(view) + make.left.equalTo(view).offset(46) + make.right.equalTo(view).offset(-46) make.top.equalTo(feedbackContactView.snp.bottom).offset(30) make.height.equalTo(44) } @@ -90,6 +126,7 @@ class FeedbackViewController: UIViewController { class FeedbackTypeView: UIView { let titleLabel: UILabel = { let titleLabel = UILabel.init() + titleLabel.text = "反馈类型*" return titleLabel }() @@ -109,7 +146,8 @@ class FeedbackTypeView: UIView { func makeUI() { addSubview(titleLabel) - + addSubview(feedbackTypeControl) + } required init?(coder: NSCoder) { @@ -119,6 +157,18 @@ class FeedbackTypeView: UIView { override func layoutSubviews() { super.layoutSubviews() + titleLabel.snp.makeConstraints { make in + make.left.equalTo(self).offset(18) + make.top.equalTo(self).offset(12) + } + + feedbackTypeControl.snp.makeConstraints { make in + make.left.equalTo(self).offset(18) + make.height.equalTo(45) + make.right.equalTo(self).offset(-18) + make.top.equalTo(titleLabel.snp.bottom).offset(9) + make.bottom.equalTo(self) + } } } @@ -127,6 +177,7 @@ class FeedbackTypeControl: UIControl { let titleLabel: UILabel = { let titleLabel = UILabel.init() titleLabel.font = UIFont.systemFont(ofSize: 15) + titleLabel.text = "请选择" return titleLabel }() @@ -140,7 +191,7 @@ class FeedbackTypeControl: UIControl { let arrowView: UIImageView = { let arrowView = UIImageView .init() - arrowView.image = UIImage.init(named: "") + arrowView.image = UIImage.init(named: "setting_arrow") return arrowView }() @@ -153,6 +204,10 @@ class FeedbackTypeControl: UIControl { } func makeUI() { + backgroundColor = .white + layer.cornerRadius = 22 + layer.masksToBounds = true + addSubview(titleLabel) addSubview(detailLabel) addSubview(arrowView) @@ -189,6 +244,7 @@ class FeedbackDetailView: UIView { let titleLabel: UILabel = { let titleLabel = UILabel.init() titleLabel.font = UIFont.systemFont(ofSize: 15) + titleLabel.text = "问题描述*" return titleLabel }() @@ -207,6 +263,7 @@ class FeedbackDetailView: UIView { } func makeUI() { + addSubview(titleLabel) addSubview(feedbackDetailSubView) @@ -220,13 +277,25 @@ class FeedbackDetailView: UIView { override func layoutSubviews() { super.layoutSubviews() + titleLabel.snp.makeConstraints { make in + make.left.equalTo(self).offset(18) + make.top.equalTo(self).offset(12) + } + feedbackDetailSubView.snp.makeConstraints { make in + make.left.equalTo(self).offset(18) + make.top.equalTo(titleLabel.snp.bottom).offset(9) + make.right.equalTo(self).offset(-18) + make.bottom.equalTo(self) + } } } class FeedbackDetailSubView: UIView { - let textView: UITextView = { - let textView = UITextView.init() + let textView: GrowingTextView = { + let textView = GrowingTextView.init() + textView.font = UIFont.systemFont(ofSize: 14) + textView.placeholder = "请详细描述问题或建议" return textView }() @@ -234,11 +303,49 @@ class FeedbackDetailSubView: UIView { let countLabel: UILabel = { let countLabel = UILabel.init() countLabel.font = UIFont.systemFont(ofSize: 14) + countLabel.text = "0/200" + countLabel.textColor = .tertiaryText() return countLabel }() - // TODO:相册 + + 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) + + // 创建group大小 + let groupSize = NSCollectionLayoutSize(widthDimension: .absolute(84), heightDimension: .absolute(84)) + + // 创建group,这里使用.horizontal来使其横向滚动 + let group = NSCollectionLayoutGroup.horizontal(layoutSize: groupSize, subitems: [item]) + + // 创建section + let section = NSCollectionLayoutSection(group: group) + 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 + }() + + override init(frame: CGRect) { @@ -252,11 +359,19 @@ class FeedbackDetailSubView: UIView { } func makeUI() { + backgroundColor = .white + layer.cornerRadius = 9 + layer.masksToBounds = true + addSubview(textView) addSubview(countLabel) + addSubview(collectionView) } + + + override func layoutSubviews() { super.layoutSubviews() @@ -271,6 +386,131 @@ class FeedbackDetailSubView: UIView { make.top.equalTo(textView.snp.bottom).offset(20) } + + + 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) + } + + } + +} + +extension FeedbackDetailSubView { + static func dataSource() -> RxCollectionViewSectionedReloadDataSource { + return RxCollectionViewSectionedReloadDataSource( + configureCell: { dataSource, collectionView, indexPath, item in + + + switch dataSource[indexPath] { + case .add: + 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! + } + + } + ) + } + +} + + + + +class FeedbackDetailViewCell: UICollectionViewCell { + var imageView: UIImageView = { + let imageView = UIImageView.init() + + return imageView + }() + + var closeButton: UIButton = { + let closeButton = UIButton.init() + closeButton.setImage(UIImage.init(named: "feedback_close_btn"), for: .normal) + + return closeButton + }() + + + var addImageView: UIImageView = { + let addImageView = UIImageView.init() + addImageView.image = UIImage.init(named: "feedback_add_btn") + + return addImageView + }() + + + var feedbackPhotoType: FeedbackPhotoType? { + didSet { + guard let feedbackPhotoType = feedbackPhotoType else { return } + + switch feedbackPhotoType { + case .add: + addImageView.isHidden = false + imageView.isHidden = true + closeButton.isHidden = true + + contentView.layer.borderColor = UIColor.separator().cgColor + contentView.layer.borderWidth = 1 + contentView.layer.cornerRadius = 3 + contentView.layer.masksToBounds = true + + case .photo(let image): + addImageView.isHidden = true + imageView.isHidden = false + closeButton.isHidden = false + + contentView.layer.borderColor = UIColor.clear.cgColor + contentView.layer.borderWidth = 1 + contentView.layer.cornerRadius = 3 + contentView.layer.masksToBounds = true + + } + + } + } + + + override init(frame: CGRect) { + super.init(frame: frame) + + makeUI() + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + func makeUI() { + contentView.addSubview(imageView) + contentView.addSubview(addImageView) + contentView.addSubview(closeButton) + + + imageView.snp.makeConstraints { make in + make.edges.equalTo(contentView) + } + + + closeButton.snp.makeConstraints { make in + make.right.equalTo(imageView) + make.top.equalTo(imageView) + } + + addImageView.snp.makeConstraints { make in + make.center.equalTo(contentView) + } } } @@ -280,14 +520,26 @@ class FeedbackContactView: UIView { let titleLabel: UILabel = { let titleLabel = UILabel.init() titleLabel.font = UIFont.systemFont(ofSize: 15) + titleLabel.text = "联系方式" return titleLabel }() + let containerView: UIView = { + let containerView = UIView.init() + containerView.backgroundColor = .white + containerView.layer.cornerRadius = 22 + containerView.layer.masksToBounds = true + + return containerView + }() + let textFieldView: UITextField = { let textFieldView = UITextField.init() textFieldView.font = UIFont.systemFont(ofSize: 15) textFieldView.placeholder = "手机/邮箱" + textFieldView.backgroundColor = .white + return textFieldView }() @@ -305,7 +557,8 @@ class FeedbackContactView: UIView { func makeUI() { addSubview(titleLabel) - addSubview(textFieldView) + addSubview(containerView) + containerView.addSubview(textFieldView) } @@ -317,11 +570,19 @@ class FeedbackContactView: UIView { make.top.equalTo(self).offset(12) } - textFieldView.snp.makeConstraints { make in + containerView.snp.makeConstraints { make in make.left.equalTo(self).offset(18) make.right.equalTo(self).offset(-18) make.top.equalTo(titleLabel.snp.bottom).offset(9) make.height.equalTo(45) + make.bottom.equalTo(self) + } + + textFieldView.snp.makeConstraints { make in + make.left.equalTo(containerView).offset(12) + make.top.equalTo(containerView) + make.bottom.equalTo(containerView) + make.right.equalTo(containerView).offset(-12) } } diff --git a/IndieMusic/IndieMusic/Modules/Setting/FeedbackViewModel.swift b/IndieMusic/IndieMusic/Modules/Setting/FeedbackViewModel.swift new file mode 100644 index 0000000..7e84117 --- /dev/null +++ b/IndieMusic/IndieMusic/Modules/Setting/FeedbackViewModel.swift @@ -0,0 +1,44 @@ +// +// FeedbackViewModel.swift +// IndieMusic +// +// Created by WenLei on 2024/2/13. +// + +import Foundation +import RxSwift +import RxCocoa + +class FeedbackViewModel: ViewModel, ViewModelType { + + struct Input { + let viewWillAppear: ControlEvent + + + } + + struct Output { + let photoItems: BehaviorRelay<[FeedbackSection]> +// let itemSelected: PublishSubject + + } + +// let itemSelected = PublishSubject() + let items = BehaviorRelay<[FeedbackSection]>.init(value: []) + + func transform(input: Input) -> Output { + + input.viewWillAppear.subscribe { (_) in + + }.disposed(by: rx.disposeBag) + + + let add = FeedbackPhotoType.add + + self.items.accept([FeedbackSection.init(items: [.add, .add, .add, .add, .add, .add, .add ,.add, .add])]) + + + return Output.init(photoItems: items) + } + +} diff --git a/IndieMusic/IndieMusic/Modules/Setting/SettingViewController.swift b/IndieMusic/IndieMusic/Modules/Setting/SettingViewController.swift index 44271cd..57ab40f 100644 --- a/IndieMusic/IndieMusic/Modules/Setting/SettingViewController.swift +++ b/IndieMusic/IndieMusic/Modules/Setting/SettingViewController.swift @@ -88,7 +88,8 @@ class SettingViewController: TableViewController { self?.navigator.show(segue: .account(viewModel: accountViewModel), sender: self) case .feedback: - self?.navigator.show(segue: .feedback, sender: self) + let feedbackViewModel = FeedbackViewModel.init(provider: viewModel.provider) + self?.navigator.show(segue: .feedback(viewModel: feedbackViewModel), sender: self) case .about: let aboutViewModel = AboutViewModel.init(provider: viewModel.provider) diff --git a/IndieMusic/IndieMusic/Modules/Setting/ThanksViewController.swift b/IndieMusic/IndieMusic/Modules/Setting/ThanksViewController.swift index 608bf76..51bed3d 100644 --- a/IndieMusic/IndieMusic/Modules/Setting/ThanksViewController.swift +++ b/IndieMusic/IndieMusic/Modules/Setting/ThanksViewController.swift @@ -6,6 +6,9 @@ // import UIKit +import RxSwift +import RxCocoa +import RxDataSources class ThanksViewController: ViewController { let headerView: ThanksHeaderView = { @@ -14,8 +17,38 @@ class ThanksViewController: ViewController { return headerView }() - let collectionView: UICollectionView = { - let collectionView = UICollectionView.init() + let thanksBottomView: ThanksBottomView = { + let thanksBottomView = ThanksBottomView.init() + + return thanksBottomView + }() + + var collectionView: UICollectionView = { + let layout = UICollectionViewCompositionalLayout { (sectionIndex, environment) -> NSCollectionLayoutSection? in + + // 创建Item + let itemSize = NSCollectionLayoutSize(widthDimension: .absolute(104), heightDimension: .absolute(151)) + let item = NSCollectionLayoutItem(layoutSize: itemSize) + + // 创建水平子组(每排) + let verticalGroupSize = NSCollectionLayoutSize(widthDimension: .fractionalWidth(1.0), heightDimension: .estimated(151)) + let verticalGroup = NSCollectionLayoutGroup.horizontal(layoutSize: verticalGroupSize, subitem: item, count: 5) + verticalGroup.interItemSpacing = .fixed(12) + + + + // 创建分区 + let section = NSCollectionLayoutSection(group: verticalGroup) + section.contentInsets = .init(top: 18, leading: 18, bottom: 18, trailing: 18) + section.interGroupSpacing = 12 + + return section + } + + let collectionView = UICollectionView.init(frame: CGRect.zero, collectionViewLayout: layout) + collectionView.register(ThanksViewCell.self, forCellWithReuseIdentifier: "ThanksViewCell") + collectionView.showsHorizontalScrollIndicator = false + collectionView.backgroundColor = .init(hex: 0xfbfbfb) return collectionView }() @@ -47,6 +80,40 @@ class ThanksViewController: ViewController { } + override func bindViewModel() { + super.bindViewModel() + + guard let viewModel = viewModel as? ThanksViewModel else { return } + + let input = ThanksViewModel.Input.init(viewWillAppear: rx.viewWillAppear, + selection: collectionView.rx.itemSelected.asDriver()) + let output = viewModel.transform(input: input) + + let dataSource = ThanksViewController.dataSource() + + output.items.bind(to: collectionView.rx.items(dataSource: dataSource)).disposed(by: rx.disposeBag) + + + + output.itemSelected.subscribe { [weak self] sectionItem in + + + }.disposed(by: rx.disposeBag) + + + output.selection.drive { [weak self] sectionItem in + + let accountViewModel = AccountViewModel.init(provider: viewModel.provider) + self?.navigator.show(segue: .account(viewModel: accountViewModel), sender: self) + + }.disposed(by: rx.disposeBag) + + + + + } + + override func viewDidLayoutSubviews() { super.viewDidLayoutSubviews() @@ -62,7 +129,31 @@ class ThanksViewController: ViewController { } -class ThanksHeaderView: UIView { +extension ThanksViewController { + + static func dataSource() -> RxCollectionViewSectionedReloadDataSource { + return RxCollectionViewSectionedReloadDataSource( + configureCell: { dataSource, collectionView, indexPath, item in + let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "ThanksViewCell", for: indexPath) as? ThanksViewCell + + cell?.thanks = item + return cell! + }, + configureSupplementaryView: { dataSource, collectionView, kind, indexPath in + guard kind == UICollectionView.elementKindSectionHeader else { + return UICollectionReusableView() + } + let section = dataSource[indexPath.section] + let resuableView = collectionView.dequeueReusableSupplementaryView(ofKind: "UICollectionElementKindSectionHeader", withReuseIdentifier: "ThanksHeaderView", for: indexPath) as! ThanksHeaderView + + return resuableView + } + ) + } +} + + +class ThanksHeaderView: UICollectionReusableView { let titleImageView: UIImageView = { let titleImageView = UIImageView.init(image: UIImage.init(named: "")) @@ -112,3 +203,120 @@ class ThanksHeaderView: UIView { } + +class ThanksViewCell: UICollectionViewCell { + + var avatarView: UIImageView = { + let avatarView = UIImageView.init() + avatarView.backgroundColor = .init(hex: 0xf5f5f5) + avatarView.layer.cornerRadius = 27 + avatarView.layer.masksToBounds = true + + return avatarView + }() + + var titleLabel: UILabel = { + let titleLabel = UILabel.init() + titleLabel.font = UIFont.systemFont(ofSize: 15, weight: .medium) + titleLabel.textColor = .primaryText() + titleLabel.textAlignment = .center + + return titleLabel + }() + + var detailLabel: UILabel = { + let detailLabel = UILabel.init() + detailLabel.font = UIFont.systemFont(ofSize: 12) + detailLabel.textColor = .tertiaryText() + detailLabel.textAlignment = .center + + return detailLabel + }() + + var thanks: Thanks? { + didSet { + avatarView.kf.setImage(with: URL.init(string: thanks?.icon ?? "")) + + titleLabel.text = thanks?.title + + detailLabel.text = thanks?.detail + } + } + + override init(frame: CGRect) { + super.init(frame: frame) + + makeUI() + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + func makeUI() { + contentView.addSubview(avatarView) + contentView.addSubview(titleLabel) + contentView.addSubview(detailLabel) + + + avatarView.snp.makeConstraints { make in + make.centerX.equalTo(contentView) + make.top.equalTo(contentView).offset(21) + make.size.equalTo(CGSize.init(width: 54, height: 54)) + } + + titleLabel.snp.makeConstraints { make in + make.top.equalTo(avatarView.snp.bottom).offset(18) + make.left.equalTo(contentView).offset(18) + make.right.equalTo(contentView).offset(-18) + } + + detailLabel.snp.makeConstraints { make in + make.top.equalTo(titleLabel.snp.bottom).offset(1) + make.left.equalTo(contentView).offset(18) + make.right.equalTo(contentView).offset(-18) + + } + + } + +} + + +class ThanksBottomView: UIView { + var button: UIButton = { + let button = UIButton.init() + button.backgroundColor = .primary() + button.setTitle("成为贡献者", for: .normal) + button.titleLabel?.font = UIFont.systemFont(ofSize: 15) + button.layer.cornerRadius = 22 + button.layer.masksToBounds = true + + return button + }() + + override init(frame: CGRect) { + super.init(frame: frame) + + makeUI() + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + func makeUI() { + addSubview(button) + } + + override func layoutSubviews() { + super.layoutSubviews() + + button.snp.makeConstraints { make in + make.left.equalTo(self).offset(46) + make.right.equalTo(self).offset(-46) + make.top.equalTo(self).offset(46) + make.height.equalTo(44) + } + } +} diff --git a/IndieMusic/IndieMusic/Modules/Setting/ThanksViewModel.swift b/IndieMusic/IndieMusic/Modules/Setting/ThanksViewModel.swift index b8d6a79..5194385 100644 --- a/IndieMusic/IndieMusic/Modules/Setting/ThanksViewModel.swift +++ b/IndieMusic/IndieMusic/Modules/Setting/ThanksViewModel.swift @@ -18,14 +18,14 @@ class ThanksViewModel: ViewModel, ViewModelType { } struct Output { - let items: BehaviorRelay<[SettingSection]> + let items: BehaviorRelay<[ThanksSection]> let selection: Driver - let itemSelected: PublishSubject + let itemSelected: PublishSubject } - let itemSelected = PublishSubject() - let items = BehaviorRelay<[SettingSection]>.init(value: []) + let itemSelected = PublishSubject() + let items = BehaviorRelay<[ThanksSection]>.init(value: []) func transform(input: Input) -> Output { @@ -33,38 +33,10 @@ class ThanksViewModel: ViewModel, ViewModelType { }.disposed(by: rx.disposeBag) - let phone = Setting.init(title: "手机号", detail: "去绑定", arrowIcon: "setting_arrow") - let wechatBinding = Setting.init(title: "绑定微信", detail: "", arrowIcon: "setting_arrow") - let privacy = Setting.init(title: "注销账户", detail: "", arrowIcon: "setting_arrow") - - - //TODO - items.accept([SettingSection.init(items: [.setting(phone), .setting(wechatBinding), .setting(privacy)])]) - - + input.selection.drive { indexPath in guard let sectionItem = self.items.value.first?.items[indexPath.row] else { return } - - switch sectionItem { - case .about(let setting): - -// case .account(let setting): -// case .privacy(let setting): -// case .timing(let setting): -// case .cache(let setting): -// case .permission(let setting): -// case .feedback(let setting): -// case .about(let setting): -// case .contributors(let setting): -// case .version(let setting): - - self.itemSelected.onNext(setting) - - default: break - - } - }.disposed(by: rx.disposeBag) diff --git a/IndieMusic/IndieMusic/Resources/Assets.xcassets/feedback/Contents.json b/IndieMusic/IndieMusic/Resources/Assets.xcassets/feedback/Contents.json new file mode 100644 index 0000000..73c0059 --- /dev/null +++ b/IndieMusic/IndieMusic/Resources/Assets.xcassets/feedback/Contents.json @@ -0,0 +1,6 @@ +{ + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/IndieMusic/IndieMusic/Resources/Assets.xcassets/feedback/feedback_add_btn.imageset/Contents.json b/IndieMusic/IndieMusic/Resources/Assets.xcassets/feedback/feedback_add_btn.imageset/Contents.json new file mode 100644 index 0000000..a6b2f7a --- /dev/null +++ b/IndieMusic/IndieMusic/Resources/Assets.xcassets/feedback/feedback_add_btn.imageset/Contents.json @@ -0,0 +1,21 @@ +{ + "images" : [ + { + "filename" : "feedback_add_btn.svg", + "idiom" : "universal", + "scale" : "1x" + }, + { + "idiom" : "universal", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/IndieMusic/IndieMusic/Resources/Assets.xcassets/feedback/feedback_add_btn.imageset/feedback_add_btn.svg b/IndieMusic/IndieMusic/Resources/Assets.xcassets/feedback/feedback_add_btn.imageset/feedback_add_btn.svg new file mode 100644 index 0000000..593ea5e --- /dev/null +++ b/IndieMusic/IndieMusic/Resources/Assets.xcassets/feedback/feedback_add_btn.imageset/feedback_add_btn.svg @@ -0,0 +1,4 @@ + + + + diff --git a/IndieMusic/IndieMusic/Resources/Assets.xcassets/feedback/feedback_close_btn.imageset/Contents.json b/IndieMusic/IndieMusic/Resources/Assets.xcassets/feedback/feedback_close_btn.imageset/Contents.json new file mode 100644 index 0000000..6d54e91 --- /dev/null +++ b/IndieMusic/IndieMusic/Resources/Assets.xcassets/feedback/feedback_close_btn.imageset/Contents.json @@ -0,0 +1,21 @@ +{ + "images" : [ + { + "filename" : "feedback_close_btn.svg", + "idiom" : "universal", + "scale" : "1x" + }, + { + "idiom" : "universal", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/IndieMusic/IndieMusic/Resources/Assets.xcassets/feedback/feedback_close_btn.imageset/feedback_close_btn.svg b/IndieMusic/IndieMusic/Resources/Assets.xcassets/feedback/feedback_close_btn.imageset/feedback_close_btn.svg new file mode 100644 index 0000000..1fdf748 --- /dev/null +++ b/IndieMusic/IndieMusic/Resources/Assets.xcassets/feedback/feedback_close_btn.imageset/feedback_close_btn.svg @@ -0,0 +1,3 @@ + + +