Thanks Module and feedback module interface building

dev
wenlei 11 months ago
parent 8738905797
commit 0aaddb5136

@ -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 = "<group>"; };
776A1F772B7A18F000F613EB /* zh-Hans */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "zh-Hans"; path = "zh-Hans.lproj/ChinaLocalizable.strings"; sourceTree = "<group>"; };
776A1F7A2B7A192000F613EB /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = en; path = en.lproj/ChinaLocalizable.strings; sourceTree = "<group>"; };
776A1F7B2B7B38DF00F613EB /* Thanks.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Thanks.swift; sourceTree = "<group>"; };
776A1F7D2B7B3EE600F613EB /* FeedbackViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FeedbackViewModel.swift; sourceTree = "<group>"; };
776A1F7F2B7BA99A00F613EB /* Feedback.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Feedback.swift; sourceTree = "<group>"; };
778638932B4D123D00B00AF9 /* CommentDetailViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CommentDetailViewModel.swift; sourceTree = "<group>"; };
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 = "<group>"; };
@ -803,6 +809,8 @@
770F6CA12B64F1DE0082F53A /* Carousel.swift */,
77620D912B68E13300798861 /* Sheet.swift */,
77620D952B68E96C00798861 /* DateItem.swift */,
776A1F7B2B7B38DF00F613EB /* Thanks.swift */,
776A1F7F2B7BA99A00F613EB /* Feedback.swift */,
);
path = Models;
sourceTree = "<group>";
@ -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 */,

@ -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):

@ -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
}
}

@ -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
}
}

@ -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))
}

@ -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)

@ -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)

@ -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)

@ -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 }

@ -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<FeedbackSection> {
return RxCollectionViewSectionedReloadDataSource<FeedbackSection>(
configureCell: { dataSource, collectionView, indexPath, item in
switch dataSource[indexPath] {
case .add:
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "FeedbackDetailViewCell", for: indexPath) as? FeedbackDetailViewCell
cell?.feedbackPhotoType = item
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)
}
}

@ -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<Bool>
}
struct Output {
let photoItems: BehaviorRelay<[FeedbackSection]>
// let itemSelected: PublishSubject<Thanks>
}
// let itemSelected = PublishSubject<Thanks>()
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)
}
}

@ -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)

@ -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<ThanksSection> {
return RxCollectionViewSectionedReloadDataSource<ThanksSection>(
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)
}
}
}

@ -18,14 +18,14 @@ class ThanksViewModel: ViewModel, ViewModelType {
}
struct Output {
let items: BehaviorRelay<[SettingSection]>
let items: BehaviorRelay<[ThanksSection]>
let selection: Driver<IndexPath>
let itemSelected: PublishSubject<Setting>
let itemSelected: PublishSubject<Thanks>
}
let itemSelected = PublishSubject<Setting>()
let items = BehaviorRelay<[SettingSection]>.init(value: [])
let itemSelected = PublishSubject<Thanks>()
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)

@ -0,0 +1,6 @@
{
"info" : {
"author" : "xcode",
"version" : 1
}
}

@ -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
}
}

@ -0,0 +1,4 @@
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<rect width="24" height="24" fill="white"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M12.7508 3.75C12.7508 3.33579 12.415 3 12.0008 3C11.5866 3 11.2508 3.33579 11.2508 3.75V11.2508H3.75C3.33579 11.2508 3 11.5866 3 12.0008C3 12.415 3.33579 12.7508 3.75 12.7508H11.2508V20.2516C11.2508 20.6658 11.5866 21.0016 12.0008 21.0016C12.415 21.0016 12.7508 20.6658 12.7508 20.2516V12.7508H20.2516C20.6658 12.7508 21.0016 12.415 21.0016 12.0008C21.0016 11.5866 20.6658 11.2508 20.2516 11.2508H12.7508V3.75Z" fill="black" fill-opacity="0.95"/>
</svg>

After

Width:  |  Height:  |  Size: 643 B

@ -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
}
}

@ -0,0 +1,3 @@
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" clip-rule="evenodd" d="M7.53033 6.46967C7.23744 6.17678 6.76256 6.17678 6.46967 6.46967C6.17678 6.76256 6.17678 7.23744 6.46967 7.53033L10.9393 12L6.46967 16.4697C6.17678 16.7626 6.17678 17.2374 6.46967 17.5303C6.76256 17.8232 7.23744 17.8232 7.53033 17.5303L12 13.0607L16.4697 17.5303C16.7626 17.8232 17.2374 17.8232 17.5303 17.5303C17.8232 17.2374 17.8232 16.7626 17.5303 16.4697L13.0607 12L17.5303 7.53033C17.8232 7.23744 17.8232 6.76256 17.5303 6.46967C17.2374 6.17678 16.7626 6.17678 16.4697 6.46967L12 10.9393L7.53033 6.46967Z" fill="black" fill-opacity="0.95"/>
</svg>

After

Width:  |  Height:  |  Size: 698 B

Loading…
Cancel
Save