Set the module interaction logic to write

dev
wenlei 11 months ago
parent c8a3ef59ca
commit 1062a4c0e3

@ -108,6 +108,10 @@
778B8AC12AF8ED280034AFD4 /* TableView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 778B8AB82AF8ED280034AFD4 /* TableView.swift */; }; 778B8AC12AF8ED280034AFD4 /* TableView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 778B8AB82AF8ED280034AFD4 /* TableView.swift */; };
778B8AC22AF8ED280034AFD4 /* TableViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 778B8AB92AF8ED280034AFD4 /* TableViewController.swift */; }; 778B8AC22AF8ED280034AFD4 /* TableViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 778B8AB92AF8ED280034AFD4 /* TableViewController.swift */; };
778B8AC32AF8ED280034AFD4 /* TabViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 778B8ABA2AF8ED280034AFD4 /* TabViewController.swift */; }; 778B8AC32AF8ED280034AFD4 /* TabViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 778B8ABA2AF8ED280034AFD4 /* TabViewController.swift */; };
77C9B9BE2B4AB4FA0006C83F /* TimingViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 77C9B9BD2B4AB4FA0006C83F /* TimingViewModel.swift */; };
77C9B9C02B4AB5B50006C83F /* PrivacyViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 77C9B9BF2B4AB5B50006C83F /* PrivacyViewModel.swift */; };
77C9B9C22B4AB6C10006C83F /* AboutViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 77C9B9C12B4AB6C10006C83F /* AboutViewModel.swift */; };
77C9B9C42B4AB75E0006C83F /* CacheViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 77C9B9C32B4AB75E0006C83F /* CacheViewModel.swift */; };
77FA0B282B0B3E1E00404C5E /* Journal.swift in Sources */ = {isa = PBXBuildFile; fileRef = 77FA0B272B0B3E1E00404C5E /* Journal.swift */; }; 77FA0B282B0B3E1E00404C5E /* Journal.swift in Sources */ = {isa = PBXBuildFile; fileRef = 77FA0B272B0B3E1E00404C5E /* Journal.swift */; };
77FA0B2A2B0B5F0D00404C5E /* AudioMoreActionView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 77FA0B292B0B5F0D00404C5E /* AudioMoreActionView.swift */; }; 77FA0B2A2B0B5F0D00404C5E /* AudioMoreActionView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 77FA0B292B0B5F0D00404C5E /* AudioMoreActionView.swift */; };
77FA0B2C2B0C480B00404C5E /* AudioMoreActionViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 77FA0B2B2B0C480B00404C5E /* AudioMoreActionViewModel.swift */; }; 77FA0B2C2B0C480B00404C5E /* AudioMoreActionViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 77FA0B2B2B0C480B00404C5E /* AudioMoreActionViewModel.swift */; };
@ -140,6 +144,11 @@
77FB7A702B48074600B64030 /* MusicStyleViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 77FB7A6F2B48074600B64030 /* MusicStyleViewModel.swift */; }; 77FB7A702B48074600B64030 /* MusicStyleViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 77FB7A6F2B48074600B64030 /* MusicStyleViewModel.swift */; };
77FB7A722B48E93100B64030 /* SearchResultsViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 77FB7A712B48E93100B64030 /* SearchResultsViewModel.swift */; }; 77FB7A722B48E93100B64030 /* SearchResultsViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 77FB7A712B48E93100B64030 /* SearchResultsViewModel.swift */; };
77FB7A742B49944E00B64030 /* ScrollSegmentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 77FB7A732B49944E00B64030 /* ScrollSegmentView.swift */; }; 77FB7A742B49944E00B64030 /* ScrollSegmentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 77FB7A732B49944E00B64030 /* ScrollSegmentView.swift */; };
77FB7A762B4A4B5600B64030 /* MineJournalViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 77FB7A752B4A4B5600B64030 /* MineJournalViewController.swift */; };
77FB7A782B4A4B6400B64030 /* MineJournalViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 77FB7A772B4A4B6400B64030 /* MineJournalViewModel.swift */; };
77FB7A7B2B4A4FC900B64030 /* MessageViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 77FB7A7A2B4A4FC900B64030 /* MessageViewController.swift */; };
77FB7A7D2B4A4FD400B64030 /* MessageViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 77FB7A7C2B4A4FD400B64030 /* MessageViewModel.swift */; };
77FB7A7F2B4A630100B64030 /* ThanksViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 77FB7A7E2B4A630100B64030 /* ThanksViewModel.swift */; };
B570F70B7040469E9D729E7E /* Pods_IndieMusic.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 3BA421D41748C113CCE36A32 /* Pods_IndieMusic.framework */; }; B570F70B7040469E9D729E7E /* Pods_IndieMusic.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 3BA421D41748C113CCE36A32 /* Pods_IndieMusic.framework */; };
/* End PBXBuildFile section */ /* End PBXBuildFile section */
@ -270,6 +279,10 @@
778B8AB82AF8ED280034AFD4 /* TableView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TableView.swift; sourceTree = "<group>"; }; 778B8AB82AF8ED280034AFD4 /* TableView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TableView.swift; sourceTree = "<group>"; };
778B8AB92AF8ED280034AFD4 /* TableViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TableViewController.swift; sourceTree = "<group>"; }; 778B8AB92AF8ED280034AFD4 /* TableViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TableViewController.swift; sourceTree = "<group>"; };
778B8ABA2AF8ED280034AFD4 /* TabViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TabViewController.swift; sourceTree = "<group>"; }; 778B8ABA2AF8ED280034AFD4 /* TabViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TabViewController.swift; sourceTree = "<group>"; };
77C9B9BD2B4AB4FA0006C83F /* TimingViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TimingViewModel.swift; sourceTree = "<group>"; };
77C9B9BF2B4AB5B50006C83F /* PrivacyViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PrivacyViewModel.swift; sourceTree = "<group>"; };
77C9B9C12B4AB6C10006C83F /* AboutViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AboutViewModel.swift; sourceTree = "<group>"; };
77C9B9C32B4AB75E0006C83F /* CacheViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CacheViewModel.swift; sourceTree = "<group>"; };
77FA0B272B0B3E1E00404C5E /* Journal.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Journal.swift; sourceTree = "<group>"; }; 77FA0B272B0B3E1E00404C5E /* Journal.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Journal.swift; sourceTree = "<group>"; };
77FA0B292B0B5F0D00404C5E /* AudioMoreActionView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AudioMoreActionView.swift; sourceTree = "<group>"; }; 77FA0B292B0B5F0D00404C5E /* AudioMoreActionView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AudioMoreActionView.swift; sourceTree = "<group>"; };
77FA0B2B2B0C480B00404C5E /* AudioMoreActionViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AudioMoreActionViewModel.swift; sourceTree = "<group>"; }; 77FA0B2B2B0C480B00404C5E /* AudioMoreActionViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AudioMoreActionViewModel.swift; sourceTree = "<group>"; };
@ -302,6 +315,11 @@
77FB7A6F2B48074600B64030 /* MusicStyleViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MusicStyleViewModel.swift; sourceTree = "<group>"; }; 77FB7A6F2B48074600B64030 /* MusicStyleViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MusicStyleViewModel.swift; sourceTree = "<group>"; };
77FB7A712B48E93100B64030 /* SearchResultsViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SearchResultsViewModel.swift; sourceTree = "<group>"; }; 77FB7A712B48E93100B64030 /* SearchResultsViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SearchResultsViewModel.swift; sourceTree = "<group>"; };
77FB7A732B49944E00B64030 /* ScrollSegmentView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ScrollSegmentView.swift; sourceTree = "<group>"; }; 77FB7A732B49944E00B64030 /* ScrollSegmentView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ScrollSegmentView.swift; sourceTree = "<group>"; };
77FB7A752B4A4B5600B64030 /* MineJournalViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MineJournalViewController.swift; sourceTree = "<group>"; };
77FB7A772B4A4B6400B64030 /* MineJournalViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MineJournalViewModel.swift; sourceTree = "<group>"; };
77FB7A7A2B4A4FC900B64030 /* MessageViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MessageViewController.swift; sourceTree = "<group>"; };
77FB7A7C2B4A4FD400B64030 /* MessageViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MessageViewModel.swift; sourceTree = "<group>"; };
77FB7A7E2B4A630100B64030 /* ThanksViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ThanksViewModel.swift; sourceTree = "<group>"; };
A90C808FB1B74BB851FB67CB /* Pods-IndieMusic-IndieMusicUITests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-IndieMusic-IndieMusicUITests.debug.xcconfig"; path = "Target Support Files/Pods-IndieMusic-IndieMusicUITests/Pods-IndieMusic-IndieMusicUITests.debug.xcconfig"; sourceTree = "<group>"; }; A90C808FB1B74BB851FB67CB /* Pods-IndieMusic-IndieMusicUITests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-IndieMusic-IndieMusicUITests.debug.xcconfig"; path = "Target Support Files/Pods-IndieMusic-IndieMusicUITests/Pods-IndieMusic-IndieMusicUITests.debug.xcconfig"; sourceTree = "<group>"; };
B4D1DC4EEC82560A8FAECF4A /* Pods-IndieMusic-IndieMusicUITests.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-IndieMusic-IndieMusicUITests.release.xcconfig"; path = "Target Support Files/Pods-IndieMusic-IndieMusicUITests/Pods-IndieMusic-IndieMusicUITests.release.xcconfig"; sourceTree = "<group>"; }; B4D1DC4EEC82560A8FAECF4A /* Pods-IndieMusic-IndieMusicUITests.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-IndieMusic-IndieMusicUITests.release.xcconfig"; path = "Target Support Files/Pods-IndieMusic-IndieMusicUITests/Pods-IndieMusic-IndieMusicUITests.release.xcconfig"; sourceTree = "<group>"; };
C8F1B981837A2D1AAE58D709 /* Pods-IndieMusic.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-IndieMusic.release.xcconfig"; path = "Target Support Files/Pods-IndieMusic/Pods-IndieMusic.release.xcconfig"; sourceTree = "<group>"; }; C8F1B981837A2D1AAE58D709 /* Pods-IndieMusic.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-IndieMusic.release.xcconfig"; path = "Target Support Files/Pods-IndieMusic/Pods-IndieMusic.release.xcconfig"; sourceTree = "<group>"; };
@ -529,6 +547,7 @@
778B8A582AF8EAED0034AFD4 /* Modules */ = { 778B8A582AF8EAED0034AFD4 /* Modules */ = {
isa = PBXGroup; isa = PBXGroup;
children = ( children = (
77FB7A792B4A4FB600B64030 /* Message */,
774A180C2B06FFF500F56DF1 /* JournalDetail */, 774A180C2B06FFF500F56DF1 /* JournalDetail */,
7743999C2AFA18B0006F8EEA /* Player */, 7743999C2AFA18B0006F8EEA /* Player */,
778B8A762AF8ECE50034AFD4 /* Home */, 778B8A762AF8ECE50034AFD4 /* Home */,
@ -614,6 +633,8 @@
77FA0B512B0F3BC700404C5E /* MineView.swift */, 77FA0B512B0F3BC700404C5E /* MineView.swift */,
77FA0B552B0F4ABF00404C5E /* MineSingleController.swift */, 77FA0B552B0F4ABF00404C5E /* MineSingleController.swift */,
77FA0B572B0F4C0800404C5E /* MineSingleViewModel.swift */, 77FA0B572B0F4C0800404C5E /* MineSingleViewModel.swift */,
77FB7A752B4A4B5600B64030 /* MineJournalViewController.swift */,
77FB7A772B4A4B6400B64030 /* MineJournalViewModel.swift */,
); );
path = Mine; path = Mine;
sourceTree = "<group>"; sourceTree = "<group>";
@ -650,10 +671,15 @@
7751D3632B42BC2E00F1F2BD /* AccountViewModel.swift */, 7751D3632B42BC2E00F1F2BD /* AccountViewModel.swift */,
7751D3652B42BE7F00F1F2BD /* FeedbackViewController.swift */, 7751D3652B42BE7F00F1F2BD /* FeedbackViewController.swift */,
7751D3672B42E96200F1F2BD /* ThanksViewController.swift */, 7751D3672B42E96200F1F2BD /* ThanksViewController.swift */,
77FB7A7E2B4A630100B64030 /* ThanksViewModel.swift */,
7751D3692B42ED6C00F1F2BD /* TimingViewController.swift */, 7751D3692B42ED6C00F1F2BD /* TimingViewController.swift */,
77C9B9BD2B4AB4FA0006C83F /* TimingViewModel.swift */,
7751D36D2B43A03E00F1F2BD /* PrivacyViewController.swift */, 7751D36D2B43A03E00F1F2BD /* PrivacyViewController.swift */,
77C9B9BF2B4AB5B50006C83F /* PrivacyViewModel.swift */,
7751D36B2B439F0000F1F2BD /* AboutViewController.swift */, 7751D36B2B439F0000F1F2BD /* AboutViewController.swift */,
77C9B9C12B4AB6C10006C83F /* AboutViewModel.swift */,
7751D36F2B43A4FC00F1F2BD /* CacheViewController.swift */, 7751D36F2B43A4FC00F1F2BD /* CacheViewController.swift */,
77C9B9C32B4AB75E0006C83F /* CacheViewModel.swift */,
); );
path = Setting; path = Setting;
sourceTree = "<group>"; sourceTree = "<group>";
@ -680,6 +706,15 @@
path = "RxSwift+Extension"; path = "RxSwift+Extension";
sourceTree = "<group>"; sourceTree = "<group>";
}; };
77FB7A792B4A4FB600B64030 /* Message */ = {
isa = PBXGroup;
children = (
77FB7A7A2B4A4FC900B64030 /* MessageViewController.swift */,
77FB7A7C2B4A4FD400B64030 /* MessageViewModel.swift */,
);
path = Message;
sourceTree = "<group>";
};
8E5A57ACCF64265CA660700B /* Pods */ = { 8E5A57ACCF64265CA660700B /* Pods */ = {
isa = PBXGroup; isa = PBXGroup;
children = ( children = (
@ -964,6 +999,7 @@
7751D35C2B42B5E900F1F2BD /* EditInfoViewModel.swift in Sources */, 7751D35C2B42B5E900F1F2BD /* EditInfoViewModel.swift in Sources */,
7751D3602B42BA3D00F1F2BD /* EditNameController.swift in Sources */, 7751D3602B42BA3D00F1F2BD /* EditNameController.swift in Sources */,
778B8A212AF8E36D0034AFD4 /* AppDelegate.swift in Sources */, 778B8A212AF8E36D0034AFD4 /* AppDelegate.swift in Sources */,
77FB7A762B4A4B5600B64030 /* MineJournalViewController.swift in Sources */,
778B8AC32AF8ED280034AFD4 /* TabViewController.swift in Sources */, 778B8AC32AF8ED280034AFD4 /* TabViewController.swift in Sources */,
77FAF7622B07434A00FC2CA1 /* AudioMoreActionController.swift in Sources */, 77FAF7622B07434A00FC2CA1 /* AudioMoreActionController.swift in Sources */,
774A18122B07327C00F56DF1 /* CommentCountButton.swift in Sources */, 774A18122B07327C00F56DF1 /* CommentCountButton.swift in Sources */,
@ -1010,13 +1046,17 @@
7751D37E2B451EA800F1F2BD /* UserDefaultKeys.swift in Sources */, 7751D37E2B451EA800F1F2BD /* UserDefaultKeys.swift in Sources */,
778B8A8E2AF8ECF20034AFD4 /* Artist.swift in Sources */, 778B8A8E2AF8ECF20034AFD4 /* Artist.swift in Sources */,
774A17F32B0459C900F56DF1 /* PlayerViewModel.swift in Sources */, 774A17F32B0459C900F56DF1 /* PlayerViewModel.swift in Sources */,
77FB7A782B4A4B6400B64030 /* MineJournalViewModel.swift in Sources */,
778B8A6D2AF8ECD30034AFD4 /* Observable+Operators.swift in Sources */, 778B8A6D2AF8ECD30034AFD4 /* Observable+Operators.swift in Sources */,
77FA0B542B0F447400404C5E /* Mine.swift in Sources */, 77FA0B542B0F447400404C5E /* Mine.swift in Sources */,
77C9B9C02B4AB5B50006C83F /* PrivacyViewModel.swift in Sources */,
77FB7A7F2B4A630100B64030 /* ThanksViewModel.swift in Sources */,
778B8ABB2AF8ED280034AFD4 /* WebViewController.swift in Sources */, 778B8ABB2AF8ED280034AFD4 /* WebViewController.swift in Sources */,
774A17F72B04932100F56DF1 /* SegmentControl.swift in Sources */, 774A17F72B04932100F56DF1 /* SegmentControl.swift in Sources */,
77FA0B282B0B3E1E00404C5E /* Journal.swift in Sources */, 77FA0B282B0B3E1E00404C5E /* Journal.swift in Sources */,
77FA0B5A2B147EC900404C5E /* CommentViewController.swift in Sources */, 77FA0B5A2B147EC900404C5E /* CommentViewController.swift in Sources */,
77FA0B402B0D8E9300404C5E /* LayoutableButton.swift in Sources */, 77FA0B402B0D8E9300404C5E /* LayoutableButton.swift in Sources */,
77C9B9C22B4AB6C10006C83F /* AboutViewModel.swift in Sources */,
778B8A712AF8ECD30034AFD4 /* Api.swift in Sources */, 778B8A712AF8ECD30034AFD4 /* Api.swift in Sources */,
77FA0B582B0F4C0800404C5E /* MineSingleViewModel.swift in Sources */, 77FA0B582B0F4C0800404C5E /* MineSingleViewModel.swift in Sources */,
77FA0B342B0C50BA00404C5E /* ShareActionViewModel.swift in Sources */, 77FA0B342B0C50BA00404C5E /* ShareActionViewModel.swift in Sources */,
@ -1035,6 +1075,7 @@
7751D3582B42B5A200F1F2BD /* EditInfoViewController.swift in Sources */, 7751D3582B42B5A200F1F2BD /* EditInfoViewController.swift in Sources */,
774A180B2B06F8B900F56DF1 /* JournalDetailController.swift in Sources */, 774A180B2B06F8B900F56DF1 /* JournalDetailController.swift in Sources */,
7751D3522B42AC2B00F1F2BD /* SettingView.swift in Sources */, 7751D3522B42AC2B00F1F2BD /* SettingView.swift in Sources */,
77C9B9BE2B4AB4FA0006C83F /* TimingViewModel.swift in Sources */,
77FA0B362B0C50D800404C5E /* Share.swift in Sources */, 77FA0B362B0C50D800404C5E /* Share.swift in Sources */,
778B8A622AF8ECC20034AFD4 /* ErrorTracker.swift in Sources */, 778B8A622AF8ECC20034AFD4 /* ErrorTracker.swift in Sources */,
774A18102B070A6900F56DF1 /* SongViewCell.swift in Sources */, 774A18102B070A6900F56DF1 /* SongViewCell.swift in Sources */,
@ -1050,6 +1091,7 @@
778B8A702AF8ECD30034AFD4 /* ErrorResponse.swift in Sources */, 778B8A702AF8ECD30034AFD4 /* ErrorResponse.swift in Sources */,
77FA0B2A2B0B5F0D00404C5E /* AudioMoreActionView.swift in Sources */, 77FA0B2A2B0B5F0D00404C5E /* AudioMoreActionView.swift in Sources */,
778B8A6C2AF8ECD30034AFD4 /* Networking.swift in Sources */, 778B8A6C2AF8ECD30034AFD4 /* Networking.swift in Sources */,
77C9B9C42B4AB75E0006C83F /* CacheViewModel.swift in Sources */,
77FA0B2E2B0C485D00404C5E /* AudioMoreAction.swift in Sources */, 77FA0B2E2B0C485D00404C5E /* AudioMoreAction.swift in Sources */,
7751D37C2B4516BE00F1F2BD /* UILabel+IndieMusic.swift in Sources */, 7751D37C2B4516BE00F1F2BD /* UILabel+IndieMusic.swift in Sources */,
778B8AAC2AF8ED0E0034AFD4 /* UIViewController+Rx.swift in Sources */, 778B8AAC2AF8ED0E0034AFD4 /* UIViewController+Rx.swift in Sources */,
@ -1063,10 +1105,12 @@
77FA0B3E2B0C573600404C5E /* Filter.swift in Sources */, 77FA0B3E2B0C573600404C5E /* Filter.swift in Sources */,
77165D742B464493002AE0A5 /* BarButtonItem.swift in Sources */, 77165D742B464493002AE0A5 /* BarButtonItem.swift in Sources */,
7751D3662B42BE7F00F1F2BD /* FeedbackViewController.swift in Sources */, 7751D3662B42BE7F00F1F2BD /* FeedbackViewController.swift in Sources */,
77FB7A7B2B4A4FC900B64030 /* MessageViewController.swift in Sources */,
77FB7A722B48E93100B64030 /* SearchResultsViewModel.swift in Sources */, 77FB7A722B48E93100B64030 /* SearchResultsViewModel.swift in Sources */,
774A18142B07329600F56DF1 /* MultiUserAvatarView.swift in Sources */, 774A18142B07329600F56DF1 /* MultiUserAvatarView.swift in Sources */,
778B8AAA2AF8ED0E0034AFD4 /* AVPlayer.swift in Sources */, 778B8AAA2AF8ED0E0034AFD4 /* AVPlayer.swift in Sources */,
77FA0B442B0DFABD00404C5E /* LoginView.swift in Sources */, 77FA0B442B0DFABD00404C5E /* LoginView.swift in Sources */,
77FB7A7D2B4A4FD400B64030 /* MessageViewModel.swift in Sources */,
7743999E2AFA18C3006F8EEA /* PlayerViewController.swift in Sources */, 7743999E2AFA18C3006F8EEA /* PlayerViewController.swift in Sources */,
); );
runOnlyForDeploymentPostprocessing = 0; runOnlyForDeploymentPostprocessing = 0;

@ -26,6 +26,19 @@ class Navigator {
case audioMore(viewModel: AudioMoreActionViewModel) case audioMore(viewModel: AudioMoreActionViewModel)
case share(viewModel: ShareActionViewModel) case share(viewModel: ShareActionViewModel)
case musicStyle(viewModel: MusicStyleViewModel) case musicStyle(viewModel: MusicStyleViewModel)
case mineSingle(viewModel: MineViewModel)
case mineJourna(viewModel: MineJournalViewModel)
case setting(viewModel: SettingViewMdel)
case message(viewModel: MessageViewModel)
case editInfo(viewModel: EditInfoViewModel)
case editName
case account(viewModel: AccountViewModel)
case thanks(viewModel: ThanksViewModel)
case feedback
case timing(viewModel: TimingViewModel)
case privacy(viewModel: PrivacyViewModel)
case about(viewModel: AboutViewModel)
case cache(viewModel: CacheViewModel)
case searchResults(viewModel: SearchResultsViewModel) case searchResults(viewModel: SearchResultsViewModel)
@ -76,6 +89,33 @@ class Navigator {
return MusicStyleViewController.init(viewModel: viewModel, navigator: self) return MusicStyleViewController.init(viewModel: viewModel, navigator: self)
case .searchResults(viewModel: let viewModel): case .searchResults(viewModel: let viewModel):
return SearchResultsController.init(viewModel: viewModel, navigator: self) return SearchResultsController.init(viewModel: viewModel, navigator: self)
case .mineSingle(viewModel: let viewModel):
return MineSingleController.init(viewModel: viewModel, navigator: self)
case .mineJourna(viewModel: let viewModel):
return MineJournalViewController.init(viewModel: viewModel, navigator: self)
case .setting(viewModel: let viewModel):
return SettingViewController.init(viewModel: viewModel, navigator: self)
case .message(viewModel: let viewModel):
return MessageViewController.init(viewModel: viewModel, navigator: self)
case .editInfo(viewModel: let viewModel):
return EditInfoViewController.init(viewModel: viewModel, navigator: self)
case .editName:
return EditNameController.init()
case .account(let viewModel):
return AccountViewController.init(viewModel: viewModel, navigator: self)
case .thanks(let viewModel):
return ThanksViewController.init(viewModel: viewModel, navigator: self)
case .feedback:
return FeedbackViewController.init()
case .timing(viewModel: let viewModel):
return TimingViewController.init(viewModel: viewModel, navigator: self)
case .about(viewModel: let viewModel):
return AboutViewController.init(viewModel: viewModel, navigator: self)
case .cache(viewModel: let viewModel):
return CacheViewController.init(viewModel: viewModel, navigator: self)
case .privacy(viewModel: let viewModel):
return PrivacyViewController.init(viewModel: viewModel, navigator: self)
case .test: case .test:
let test = UIViewController.init() let test = UIViewController.init()
@ -94,6 +134,7 @@ class Navigator {
let vc = WebViewController(viewModel: nil, navigator: self) let vc = WebViewController(viewModel: nil, navigator: self)
vc.load(url: url) vc.load(url: url)
return vc return vc
} }
} }

@ -309,7 +309,7 @@ extension ScrollSegmentView{
let currWidth = currentWidth - 2 * segmentStyle.titleMargin let currWidth = currentWidth - 2 * segmentStyle.titleMargin
titleW = currWidth/CGFloat(labelsArray.count) titleW = currWidth/CGFloat(labelsArray.count)
titleX = segmentStyle.titleMargin titleX = 0
if index != 0 { if index != 0 {
let lastLabel = labelsArray[index - 1] let lastLabel = labelsArray[index - 1]
titleX = lastLabel.frame.maxX + segmentStyle.titleMargin titleX = lastLabel.frame.maxX + segmentStyle.titleMargin

@ -52,7 +52,7 @@ class ViewController: UIViewController, Navigatable {
let motionShakeEvent = PublishSubject<Void>() let motionShakeEvent = PublishSubject<Void>()
lazy var backBarButton: BarButtonItem = { lazy var backBarButton: BarButtonItem = {
let view = BarButtonItem() let view = BarButtonItem.init(image: UIImage.init(named: "nav_back_btn"), style: .plain, target: self, action: nil)
view.title = "" view.title = ""
return view return view
}() }()
@ -86,6 +86,10 @@ class ViewController: UIViewController, Navigatable {
self?.navigator.dismiss(sender: self) self?.navigator.dismiss(sender: self)
}).disposed(by: rx.disposeBag) }).disposed(by: rx.disposeBag)
backBarButton.rx.tap.asObservable().subscribe(onNext: { [weak self] () in
self?.navigator.pop(sender: self)
}).disposed(by: rx.disposeBag)
NotificationCenter.default NotificationCenter.default
.rx.notification(UIDevice.orientationDidChangeNotification).mapToVoid() .rx.notification(UIDevice.orientationDidChangeNotification).mapToVoid()
.bind(to: orientationEvent).disposed(by: rx.disposeBag) .bind(to: orientationEvent).disposed(by: rx.disposeBag)
@ -138,7 +142,6 @@ class ViewController: UIViewController, Navigatable {
func makeUI() { func makeUI() {
navigationItem.backBarButtonItem = backBarButton
updateUI() updateUI()
} }
@ -187,7 +190,7 @@ class ViewController: UIViewController, Navigatable {
func adjustLeftBarButtonItem() { func adjustLeftBarButtonItem() {
if self.navigationController?.viewControllers.count ?? 0 > 1 { // Pushed if self.navigationController?.viewControllers.count ?? 0 > 1 { // Pushed
self.navigationItem.leftBarButtonItem = nil self.navigationItem.leftBarButtonItem = backBarButton
} else if self.presentingViewController != nil { // presented } else if self.presentingViewController != nil { // presented
self.navigationItem.leftBarButtonItem = closeBarButton self.navigationItem.leftBarButtonItem = closeBarButton
} }

@ -11,6 +11,7 @@ import RxDataSources
struct Mine: Codable { struct Mine: Codable {
let title: String let title: String
let detail: String let detail: String
let isPlaying: Bool
} }
struct MineSection { struct MineSection {

@ -32,6 +32,35 @@ enum SettingType {
case setting(Setting) case setting(Setting)
} }
extension SettingType {
var setting: Setting {
switch self {
case .editInfo(let setting):
return setting
case .account(let setting):
return setting
case .privacy(let setting):
return setting
case .timing(let setting):
return setting
case .cache(let setting):
return setting
case .permission(let setting):
return setting
case .feedback(let setting):
return setting
case .about(let setting):
return setting
case .contributors(let setting):
return setting
case .version(let setting):
return setting
case .setting(let setting):
return setting
}
}
}
struct SettingSection { struct SettingSection {
var items: [SettingType] var items: [SettingType]

@ -0,0 +1,19 @@
//
// MessageViewController.swift
// IndieMusic
//
// Created by WenLei on 2024/1/7.
//
import UIKit
class MessageViewController: ViewController {
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
}
}

@ -0,0 +1,94 @@
//
// MessageViewModel.swift
// IndieMusic
//
// Created by WenLei on 2024/1/7.
//
import Foundation
import RxSwift
import RxCocoa
class MessageViewModel: ViewModel, ViewModelType {
struct Input {
let viewWillAppear: ControlEvent<Bool>
let selection: Driver<IndexPath>
let dropButtonTrigger: Driver<Void>
let playButtonTrigger: Driver<Void>
let shareButtonTrigger: Driver<Void>
// let moreButtonTrigger: Driver<AudioTrack>
}
struct Output {
let items: BehaviorRelay<[JournalSection]>
let selection: Driver<IndexPath>
let itemSelected: PublishSubject<AudioTrack>
let journalDetail: PublishSubject<JournalDetail>
let currentPlaying: PublishSubject<AudioTrack>
let dowloadState: Driver<Float>
let isLike: BehaviorRelay<Bool>
let isExpand: BehaviorRelay<Bool>
}
let itemSelected = PublishSubject<AudioTrack>()
let items = BehaviorRelay<[JournalSection]>.init(value: [])
let isExpand = BehaviorRelay<Bool>.init(value: false)
func transform(input: Input) -> Output {
input.viewWillAppear.subscribe { (_) in
}.disposed(by: rx.disposeBag)
input.dropButtonTrigger.drive { _ in
self.isExpand.accept(!self.isExpand.value)
}.disposed(by: rx.disposeBag)
let journalDetail = JournalDetail.init(audio: "", cover: "", title: "", artist: "", number: "", tags: [], date: 0, content: "", isExpand: false)
let item = AudioTrack.init(artists: [], availableMarkets: [""], discNumber: 0, durationMs: 0, explicit: false, externalUrls: ["": ""], id: "", name: "123", previewUrl: "")
let journalSection = JournalSection.init(items: [item, item, item, item], journalDetail: journalDetail)
items.accept([journalSection])
input.selection.drive { indexPath in
guard let sectionItem = self.items.value.first?.items[indexPath.row] else { return }
self.itemSelected.onNext(sectionItem)
}.disposed(by: rx.disposeBag)
let journal = PublishSubject<JournalDetail>.init()
let currentPlaying: PublishSubject<AudioTrack> = .init()
let dowloadState: Driver<Float> = .just(0)
let isLike = BehaviorRelay<Bool>.init(value: false)
let isLick = BehaviorRelay<Bool>.init(value: false)
return Output.init(items: items,
selection: input.selection,
itemSelected: itemSelected,
journalDetail: journal,
currentPlaying: currentPlaying,
dowloadState: dowloadState,
isLike: isLike,
isExpand: isExpand
)
}
}

@ -0,0 +1,123 @@
//
// MineJournalViewController.swift
// IndieMusic
//
// Created by WenLei on 2024/1/7.
//
import UIKit
import RxSwift
import RxCocoa
import RxDataSources
class MineJournalViewController: ViewController {
let mineSingleNoDataView: MineSingleNoDataView = {
let mineSingleNoDataView = MineSingleNoDataView.init()
mineSingleNoDataView.isHidden = true
return mineSingleNoDataView
}()
let collectionView: UICollectionView = {
let layout = UICollectionViewFlowLayout()
layout.minimumInteritemSpacing = 15
layout.minimumLineSpacing = 24
layout.sectionInset = UIEdgeInsets.init(top: 0, left: 18, bottom: 0, right: 18)
layout.itemSize = CGSize(width: (BaseDimensions.screenWidth - 18 * 2 - 15) / 2, height: 147)
let collectionView = UICollectionView.init(frame: CGRect.zero, collectionViewLayout: layout)
collectionView.register(MusicStyleCellView.self, forCellWithReuseIdentifier: "MusicStyleCellView")
return collectionView
}()
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
}
override func makeUI() {
super.makeUI()
self.navigationItem.title = "期刊"
view.backgroundColor = .white
view.addSubview(mineSingleNoDataView)
view.addSubview(collectionView)
}
override func bindViewModel() {
super.bindViewModel()
guard let viewModel = viewModel as? MineJournalViewModel else { return }
let input = MineJournalViewModel.Input.init(viewWillAppear: rx.viewWillAppear,
selection: collectionView.rx.itemSelected.asDriver())
let output = viewModel.transform(input: input)
let dataSource = MusicStyleViewController.dataSource()
output.items.bind(to: collectionView.rx.items(dataSource: dataSource)).disposed(by: rx.disposeBag)
output.itemSelected.subscribe { sectionItem in
switch sectionItem {
default: break
}
}.disposed(by: rx.disposeBag)
}
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
mineSingleNoDataView.snp.makeConstraints { make in
make.left.equalTo(view)
make.right.equalTo(view)
make.top.equalTo(view)
make.height.equalTo(200)
}
collectionView.snp.makeConstraints { make in
make.left.equalTo(view)
make.right.equalTo(view)
make.top.equalTo(view)
make.bottom.equalTo(view)
}
}
}
extension MineJournalViewController {
static func dataSource() -> RxCollectionViewSectionedReloadDataSource<MusicStyleSection> {
return RxCollectionViewSectionedReloadDataSource<MusicStyleSection>(
configureCell: { dataSource, collectionView, indexPath, item in
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "MusicStyleCellView", for: indexPath) as! MusicStyleCellView
cell.titleLabel.text = item.title
cell.volLabel.text = item.subTitle
return cell
}
)
}
}

@ -0,0 +1,60 @@
//
// MineJournalViewModel.swift
// IndieMusic
//
// Created by WenLei on 2024/1/7.
//
import Foundation
import RxSwift
import RxCocoa
class MineJournalViewModel: ViewModel, ViewModelType {
struct Input {
let viewWillAppear: ControlEvent<Bool>
let selection: Driver<IndexPath>
}
struct Output {
let items: BehaviorRelay<[MusicStyleSection]>
let selection: Driver<IndexPath>
let itemSelected: PublishSubject<MusicStyle>
}
let itemSelected = PublishSubject<MusicStyle>()
let items = BehaviorRelay<[MusicStyleSection]>.init(value: [])
func transform(input: Input) -> Output {
input.viewWillAppear.subscribe { (_) in
}.disposed(by: rx.disposeBag)
let item = MusicStyle.init(title: "测试", subTitle: "123", backgroundImage: "")
let musicStyleSection = MusicStyleSection.init(items: [item, item, item, item], header: "后摇", headerSub: "Post Rock", content: "总在不经意间获得简单朴素且乐趣其中的感怀,这种感怀的妙处在于它没有试图去提炼出任何的真理,他就像我们恬然的谈话里总夹杂着“那我懂你的意思了”,但是否是真的明白,却不然得知。即", isExpand: false)
items.accept([musicStyleSection])
input.selection.drive { indexPath in
guard let sectionItem = self.items.value.first?.items[indexPath.row] else { return }
self.itemSelected.onNext(sectionItem)
}.disposed(by: rx.disposeBag)
let journal = PublishSubject<JournalDetail>.init()
return Output.init(items: items,
selection: input.selection,
itemSelected: itemSelected
)
}
}

@ -10,7 +10,13 @@ import RxSwift
import RxCocoa import RxCocoa
import RxDataSources import RxDataSources
class MineSingleController: TableViewController { class MineSingleController: TableViewController, UITableViewDataSource {
let mineSingleNoDataView: MineSingleNoDataView = {
let mineSingleNoDataView = MineSingleNoDataView.init()
mineSingleNoDataView.isHidden = true
return mineSingleNoDataView
}()
override func viewDidLoad() { override func viewDidLoad() {
super.viewDidLoad() super.viewDidLoad()
@ -22,7 +28,18 @@ class MineSingleController: TableViewController {
override func makeUI() { override func makeUI() {
super.makeUI() super.makeUI()
self.navigationItem.title = "单曲"
view.backgroundColor = .white
// tableView.delegate = self
tableView.dataSource = self
tableView.register(SongViewCell.self
, forCellReuseIdentifier: "SongViewCell")
tableView.register(MineSingleHeaderView.self, forHeaderFooterViewReuseIdentifier: "MineSingleHeaderView")
view.addSubview(mineSingleNoDataView)
} }
override func bindViewModel() { override func bindViewModel() {
@ -34,7 +51,14 @@ class MineSingleController: TableViewController {
selection: tableView.rx.itemSelected.asDriver()) selection: tableView.rx.itemSelected.asDriver())
let output = viewModel.transform(input: input) let output = viewModel.transform(input: input)
let dataSource = MineSingleController.dataSource() let dataSource = MineSingleController.dataSource { cell, audioTrack in
let audioMoreActionViewModel = AudioMoreActionViewModel.init(provider: viewModel.provider)
self.navigator.show(segue: .audioMore(viewModel: audioMoreActionViewModel), sender: self, transition: .navigationPresent(type: .audioMore))
}
output.items.bind(to: tableView.rx.items(dataSource: dataSource)).disposed(by: rx.disposeBag) output.items.bind(to: tableView.rx.items(dataSource: dataSource)).disposed(by: rx.disposeBag)
@ -47,20 +71,154 @@ class MineSingleController: TableViewController {
} }
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
mineSingleNoDataView.snp.makeConstraints { make in
make.left.equalTo(view)
make.right.equalTo(view)
make.top.equalTo(view)
make.height.equalTo(200)
}
}
}
extension MineSingleController {
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return 1
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
if let cell = tableView.dequeueReusableCell(withIdentifier: "SongViewCell", for: indexPath) as? SongViewCell {
cell.titleLabel.text = "123"
cell.detailLabel.text = "321"
cell.buttonTapCallback = {[weak self] audioTrack in
guard let viewModel = self?.viewModel as? MineSingleViewModel else { return }
let audioMoreActionViewModel = AudioMoreActionViewModel.init(provider: viewModel.provider)
self?.navigator.show(segue: .audioMore(viewModel: audioMoreActionViewModel), sender: self, transition: .navigationPresent(type: .audioMore))
}
return cell
}
return UITableViewCell()
} }
func tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? {
let header = MineSingleHeaderView.init()
return header
}
}
extension MineSingleController { extension MineSingleController {
static func dataSource() -> RxTableViewSectionedReloadDataSource<MineSingleSection> { static func dataSource(_ buttonTapHandler: @escaping (UITableViewCell, AudioTrack) -> Void) -> RxTableViewSectionedReloadDataSource<MineSingleSection> {
return RxTableViewSectionedReloadDataSource<MineSingleSection>( return RxTableViewSectionedReloadDataSource<MineSingleSection>(
configureCell: { dataSource, tableView, indexPath, item in configureCell: { dataSource, tableView, indexPath, item in
let cell: SongViewCell = tableView.dequeueReusableCell(withIdentifier: "SongViewCell", for: indexPath) as! SongViewCell let cell: SongViewCell = tableView.dequeueReusableCell(withIdentifier: "SongViewCell", for: indexPath) as! SongViewCell
let it = AudioTrack.init(artists: [], availableMarkets: [""], discNumber: 0, durationMs: 0, explicit: false, externalUrls: ["": ""], id: "", name: "123", previewUrl: "")
// cell.titleLabel.text = "123"
// cell.detailLabel.text = "321" cell.audioTrack = it
cell.buttonTapCallback = {audioTrack in
buttonTapHandler(cell, audioTrack)
}
return cell return cell
} }
) )
} }
} }
class MineSingleHeaderView: UITableViewHeaderFooterView {
let playAllButton: UIButton = {
let playAllButton = UIButton.init()
playAllButton.setImage(UIImage.init(named: "audio_playAll_btn"), for: .normal)
playAllButton.setTitle("播放全部", for: .normal)
playAllButton.setTitleColor(.primaryText(), for: .normal)
playAllButton.titleLabel?.font = UIFont.systemFont(ofSize: 15, weight: .medium)
return playAllButton
}()
override init(reuseIdentifier: String?) {
super.init(reuseIdentifier: reuseIdentifier)
makeUI()
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
// override init(frame: CGRect) {
// super.init(frame: frame)
//
// makeUI()
// }
func makeUI() {
contentView.backgroundColor = .white
contentView.addSubview(playAllButton)
}
override func layoutSubviews() {
super.layoutSubviews()
playAllButton.snp.makeConstraints { make in
make.left.equalTo(contentView).offset(18)
make.centerY.equalTo(contentView)
}
}
}
class MineSingleNoDataView: UIView {
let titleLabel: UILabel = {
let titleLabel = UILabel.init()
titleLabel.font = UIFont.systemFont(ofSize: 15)
titleLabel.textColor = .init(hex: 0x000000, alpha: 0.6)
titleLabel.text = "你还没收藏过单曲!"
return titleLabel
}()
override init(frame: CGRect) {
super.init(frame: frame)
makeUI()
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
func makeUI() {
addSubview(titleLabel)
}
override func layoutSubviews() {
super.layoutSubviews()
titleLabel.snp.makeConstraints { make in
make.center.equalTo(self)
}
}
}

@ -297,6 +297,7 @@ class MineViewCell: UITableViewCell {
override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) { override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
super.init(style: style, reuseIdentifier: reuseIdentifier) super.init(style: style, reuseIdentifier: reuseIdentifier)
selectionStyle = .none
makeUI() makeUI()
} }
@ -353,3 +354,4 @@ class MineViewCell: UITableViewCell {
} }
} }

@ -17,8 +17,35 @@ class MineViewController: TableViewController {
return headerView return headerView
}() }()
lazy var messageBarButton: BarButtonItem = {
let view = BarButtonItem(image: UIImage.init(named: "mine_message_btn"), style: .plain, target: nil, action: nil)
return view
}()
lazy var settingBarButton: BarButtonItem = {
let view = BarButtonItem(image: UIImage.init(named: "mine_setting_btn"), style: .plain, target: nil, action: nil)
return view
}()
override func viewWillDisappear(_ animated: Bool) {
super.viewWillDisappear(animated)
if let tabbar = self.tabBarController as? HomeTabBarController {
tabbar.showTabBar(false, animated: true)
}
}
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
if let tabbar = self.tabBarController as? HomeTabBarController {
tabbar.showTabBar(true, animated: true)
}
}
override func viewDidLoad() { override func viewDidLoad() {
@ -30,12 +57,26 @@ class MineViewController: TableViewController {
override func makeUI() { override func makeUI() {
super.makeUI() super.makeUI()
view.backgroundColor = .blue
self.tableView.mj_header = nil
self.tableView.mj_footer = nil
view.backgroundColor = .init(hex: 0xfefefe)
tableView.tableHeaderView = headerView tableView.tableHeaderView = headerView
tableView.separatorColor = .clear tableView.separatorColor = .clear
tableView.register(MineViewCell.self, forCellReuseIdentifier: "MineViewCell") tableView.register(MineViewCell.self, forCellReuseIdentifier: "MineViewCell")
let fixedSpaceBarButtonItem = UIBarButtonItem.init(barButtonSystemItem: .fixedSpace, target: nil, action: nil)
fixedSpaceBarButtonItem.width = -10
navigationItem.rightBarButtonItems = [settingBarButton, fixedSpaceBarButtonItem, messageBarButton]
} }
@ -56,6 +97,30 @@ class MineViewController: TableViewController {
output.itemSelected.subscribe { sectionItem in output.itemSelected.subscribe { sectionItem in
//
// let mineViewModel = MineViewModel.init(provider: viewModel.provider)
// self.navigator.show(segue: .mineSingle(viewModel: mineViewModel), sender: self)
let mineJournalViewModel = MineJournalViewModel.init(provider: viewModel.provider)
self.navigator.show(segue: .mineJourna(viewModel: mineJournalViewModel), sender: self)
}.disposed(by: rx.disposeBag)
settingBarButton.rx.tap.subscribe { _ in
let settingViewModel = SettingViewMdel.init(provider: viewModel.provider)
self.navigator.show(segue: .setting(viewModel: settingViewModel), sender: self)
}.disposed(by: rx.disposeBag)
messageBarButton.rx.tap.subscribe { _ in
let messageViewModel = MessageViewModel.init(provider: viewModel.provider)
self.navigator.show(segue: .message(viewModel: messageViewModel), sender: self)
}.disposed(by: rx.disposeBag) }.disposed(by: rx.disposeBag)
@ -71,8 +136,9 @@ extension MineViewController {
configureCell: { dataSource, tableView, indexPath, item in configureCell: { dataSource, tableView, indexPath, item in
let cell: MineViewCell = tableView.dequeueReusableCell(withIdentifier: "MineViewCell", for: indexPath) as! MineViewCell let cell: MineViewCell = tableView.dequeueReusableCell(withIdentifier: "MineViewCell", for: indexPath) as! MineViewCell
cell.titleLabel.text = "123" cell.titleLabel.text = item.title
cell.detailLabel.text = "321" cell.detailLabel.text = item.detail
cell.playButton.isHidden = !item.isPlaying
return cell return cell
} }

@ -23,6 +23,7 @@ class MineViewModel: ViewModel, ViewModelType {
let selection: Driver<IndexPath> let selection: Driver<IndexPath>
let itemSelected: PublishSubject<Mine> let itemSelected: PublishSubject<Mine>
} }
let itemSelected = PublishSubject<Mine>() let itemSelected = PublishSubject<Mine>()
@ -34,9 +35,11 @@ class MineViewModel: ViewModel, ViewModelType {
}.disposed(by: rx.disposeBag) }.disposed(by: rx.disposeBag)
let mine = Mine.init(title: "ttt", detail: "123") let single = Mine.init(title: "单曲", detail: "123", isPlaying: true)
let journal = Mine.init(title: "期刊", detail: "123", isPlaying: false)
let download = Mine.init(title: "下载", detail: "123", isPlaying: false)
items.accept([MineSection.init(items: [mine, mine, mine])]) items.accept([MineSection.init(items: [single, journal, download])])
input.selection.drive { indexPath in input.selection.drive { indexPath in

@ -28,14 +28,14 @@ class SearchResultsController: ViewController {
let searchResultsView: SearchResultsView = { let searchResultsView: SearchResultsView = {
let searchResultsView = SearchResultsView.init() let searchResultsView = SearchResultsView.init()
searchResultsView.tableView.register(SongViewCell.self, forCellReuseIdentifier: "SongViewCell")
searchResultsView.collectionView.register(MusicStyleCellView.self, forCellWithReuseIdentifier: "MusicStyleCellView")
return searchResultsView return searchResultsView
}() }()
override func viewDidLoad() { override func viewDidLoad() {
super.viewDidLoad() super.viewDidLoad()
@ -53,10 +53,55 @@ class SearchResultsController: ViewController {
} }
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
self.navigationController?.setNavigationBarHidden(true, animated: true)
}
override func viewWillDisappear(_ animated: Bool) {
super.viewWillDisappear(animated)
self.navigationController?.setNavigationBarHidden(false, animated: true)
}
override func bindViewModel() { override func bindViewModel() {
super.bindViewModel() super.bindViewModel()
self.searchResultsView.currentSearchType = .single
guard let viewModel = viewModel as? SearchResultsViewModel else { return }
let input = SearchResultsViewModel.Input.init(viewWillAppear: rx.viewWillAppear,
closeButtonTrigger: self.searchTopBar.cancelButton.rx.tap.asDriver(),
collectionViewSelection: searchResultsView.collectionView.rx.itemSelected.asDriver())
let output = viewModel.transform(input: input)
let tableViewDataSource = SearchResultsController.tableViewDataSource { cell, audioTrack in
let audioMoreActionViewModel = AudioMoreActionViewModel.init(provider: viewModel.provider)
self.navigator.show(segue: .audioMore(viewModel: audioMoreActionViewModel), sender: self, transition: .navigationPresent(type: .audioMore))
}
let collectionViewDataSource = SearchResultsController.collectionViewDataSource()
output.collectionViewItems.bind(to: searchResultsView.collectionView.rx.items(dataSource: collectionViewDataSource)).disposed(by: rx.disposeBag)
output.tableViewItems.bind(to: searchResultsView.tableView.rx.items(dataSource: tableViewDataSource)).disposed(by: rx.disposeBag)
searchTopBar.cancelButton.rx.tap.subscribe { _ in
self.navigator.pop(sender: self)
}.disposed(by: rx.disposeBag)
} }
@ -67,7 +112,7 @@ class SearchResultsController: ViewController {
searchTopBar.snp.makeConstraints { make in searchTopBar.snp.makeConstraints { make in
make.left.equalTo(view) make.left.equalTo(view)
make.right.equalTo(view) make.right.equalTo(view)
make.top.equalTo(view).offset(15) make.top.equalTo(view).offset(BaseDimensions.statusBarHeight + 15)
make.height.equalTo(40) make.height.equalTo(40)
} }
@ -89,6 +134,49 @@ class SearchResultsController: ViewController {
} }
extension SearchResultsController {
//TODO
static func tableViewDataSource(_ buttonTapHandler: @escaping (UITableViewCell, AudioTrack) -> Void) -> RxTableViewSectionedReloadDataSource<JournalSection> {
return RxTableViewSectionedReloadDataSource<JournalSection>(
configureCell: { dataSource, tableView, indexPath, item in
let cell: SongViewCell = tableView.dequeueReusableCell(withIdentifier: "SongViewCell", for: indexPath) as! SongViewCell
cell.audioTrack = item
cell.buttonTapCallback = {audioTrack in
buttonTapHandler(cell, audioTrack)
}
return cell
}
)
}
}
extension SearchResultsController {
static func collectionViewDataSource() -> RxCollectionViewSectionedReloadDataSource<MusicStyleSection> {
return RxCollectionViewSectionedReloadDataSource<MusicStyleSection>(
configureCell: { dataSource, collectionView, indexPath, item in
// cell
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "MusicStyleCellView", for: indexPath) as! MusicStyleCellView
// cell
cell.titleLabel.text = item.title
cell.volLabel.text = item.subTitle
return cell
}
)
}
}
class SearchTopBar: UIView { class SearchTopBar: UIView {
let searchControl: SearchControl = { let searchControl: SearchControl = {
@ -262,6 +350,17 @@ class SearchResultsView: UIView {
} }
func makeUI() { func makeUI() {
segmentControl.titleBtnOnClick = { [weak self] (label, index) in
if index == 0 {
self?.currentSearchType = .single
} else {
self?.currentSearchType = .journal
}
}
addSubview(segmentControl) addSubview(segmentControl)
addSubview(tableView) addSubview(tableView)
addSubview(collectionView) addSubview(collectionView)
@ -298,32 +397,3 @@ class SearchResultsView: UIView {
} }
extension SearchResultsView {
static func dataSource() -> RxCollectionViewSectionedReloadDataSource<MusicStyleSection> {
return RxCollectionViewSectionedReloadDataSource<MusicStyleSection>(
configureCell: { dataSource, collectionView, indexPath, item in
// cell
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "MusicStyleCellView", for: indexPath)
// cell
return cell
}
)
}
}
extension SearchResultsView {
//TODO
static func dataSource() -> RxTableViewSectionedReloadDataSource<JournalSection> {
return RxTableViewSectionedReloadDataSource<JournalSection>(
configureCell: { dataSource, tableView, indexPath, item in
let cell: SongViewCell = tableView.dequeueReusableCell(withIdentifier: "SongViewCell", for: indexPath) as! SongViewCell
cell.audioTrack = item
return cell
}
)
}
}

@ -14,39 +14,60 @@ class SearchResultsViewModel: ViewModel, ViewModelType {
struct Input { struct Input {
let viewWillAppear: ControlEvent<Bool> let viewWillAppear: ControlEvent<Bool>
let selection: Driver<IndexPath> let closeButtonTrigger: Driver<Void>
let collectionViewSelection: Driver<IndexPath>
} }
struct Output { struct Output {
let items: BehaviorRelay<[SearchSection]> let tableViewItems: BehaviorRelay<[JournalSection]>
let selection: Driver<IndexPath> let collectionViewItems: BehaviorRelay<[MusicStyleSection]>
let itemSelected: PublishSubject<Search>
let collectionViewItemSelected: PublishSubject<MusicStyle>
} }
let itemSelected = PublishSubject<Search>() let tableViewItems = BehaviorRelay<[JournalSection]>.init(value: [])
let items = BehaviorRelay<[SearchSection]>.init(value: []) let collectionViewItemstems = BehaviorRelay<[MusicStyleSection]>.init(value: [])
let collectionViewSelection = PublishSubject<MusicStyle>()
func transform(input: Input) -> Output { func transform(input: Input) -> Output {
let search = Search.init(title: "硬核", subTitle: "Hardcore", backgroundImage: "") input.closeButtonTrigger.drive { _ in
let searchSection = SearchSection.init(items: [search, search, search, search, search])
}.disposed(by: rx.disposeBag)
let journalDetail = JournalDetail.init(audio: "", cover: "", title: "", artist: "", number: "", tags: [], date: 0, content: "", isExpand: false)
let item = AudioTrack.init(artists: [], availableMarkets: [""], discNumber: 0, durationMs: 0, explicit: false, externalUrls: ["": ""], id: "", name: "123", previewUrl: "")
let journalSection = JournalSection.init(items: [item, item, item, item], journalDetail: journalDetail)
items.accept([searchSection]) tableViewItems.accept([journalSection])
input.selection.drive { indexPath in
guard let sectionItem = self.items.value.first?.items[indexPath.row] else { return }
self.itemSelected.onNext(sectionItem) let musicStyleItem = MusicStyle.init(title: "测试", subTitle: "123", backgroundImage: "")
let musicStyleSection = MusicStyleSection.init(items: [musicStyleItem, musicStyleItem, musicStyleItem, musicStyleItem], header: "后摇", headerSub: "Post Rock", content: "总在不经意间获得简单朴素且乐趣其中的感怀,这种感怀的妙处在于它没有试图去提炼出任何的真理,他就像我们恬然的谈话里总夹杂着“那我懂你的意思了”,但是否是真的明白,却不然得知。即", isExpand: false)
collectionViewItemstems.accept([musicStyleSection])
input.collectionViewSelection.drive { indexPath in
guard let sectionItem = self.collectionViewItemstems.value.first?.items[indexPath.row] else { return }
self.collectionViewSelection.onNext(sectionItem)
}.disposed(by: rx.disposeBag) }.disposed(by: rx.disposeBag)
return Output(items: items, return Output.init(tableViewItems: tableViewItems,
selection: input.selection, collectionViewItems: collectionViewItemstems, collectionViewItemSelected: collectionViewSelection)
itemSelected: itemSelected)
} }
} }

@ -13,6 +13,7 @@ import RxDataSources
class AboutViewController: TableViewController { class AboutViewController: TableViewController {
let headerView: EditInfoHeaderView = { let headerView: EditInfoHeaderView = {
let headerView = EditInfoHeaderView.init(frame: CGRect.init(x: 0, y: 0, width: BaseDimensions.screenWidth, height: 191)) let headerView = EditInfoHeaderView.init(frame: CGRect.init(x: 0, y: 0, width: BaseDimensions.screenWidth, height: 191))
headerView.tipsLabel.text = "版本号1.0.1"
return headerView return headerView
}() }()
@ -32,9 +33,14 @@ class AboutViewController: TableViewController {
override func makeUI() { override func makeUI() {
super.makeUI() super.makeUI()
view.backgroundColor = .blue view.backgroundColor = .white
tableView.register(MineViewCell.self, forCellReuseIdentifier: "SettingViewCell") self.navigationItem.title = "关于雀乐"
self.tableView.mj_header = nil
self.tableView.mj_footer = nil
tableView.register(SettingViewCell.self, forCellReuseIdentifier: "SettingViewCell")
tableView.tableHeaderView = headerView tableView.tableHeaderView = headerView
@ -46,13 +52,13 @@ class AboutViewController: TableViewController {
override func bindViewModel() { override func bindViewModel() {
super.bindViewModel() super.bindViewModel()
guard let viewModel = viewModel as? MineViewModel else { return } guard let viewModel = viewModel as? AboutViewModel else { return }
let input = MineViewModel.Input.init(viewWillAppear: rx.viewWillAppear, let input = AboutViewModel.Input.init(viewWillAppear: rx.viewWillAppear,
selection: tableView.rx.itemSelected.asDriver()) selection: tableView.rx.itemSelected.asDriver())
let output = viewModel.transform(input: input) let output = viewModel.transform(input: input)
let dataSource = MineViewController.dataSource() let dataSource = AboutViewController.dataSource()
output.items.bind(to: tableView.rx.items(dataSource: dataSource)).disposed(by: rx.disposeBag) output.items.bind(to: tableView.rx.items(dataSource: dataSource)).disposed(by: rx.disposeBag)
@ -73,6 +79,7 @@ class AboutViewController: TableViewController {
make.bottom.equalTo(view).offset(-BaseDimensions.bottomHeight) make.bottom.equalTo(view).offset(-BaseDimensions.bottomHeight)
make.left.equalTo(view) make.left.equalTo(view)
make.right.equalTo(view) make.right.equalTo(view)
make.height.equalTo(100)
} }
} }
@ -80,13 +87,12 @@ class AboutViewController: TableViewController {
extension AboutViewController { extension AboutViewController {
static func dataSource() -> RxTableViewSectionedReloadDataSource<MineSection> { static func dataSource() -> RxTableViewSectionedReloadDataSource<SettingSection> {
return RxTableViewSectionedReloadDataSource<MineSection>( return RxTableViewSectionedReloadDataSource<SettingSection>(
configureCell: { dataSource, tableView, indexPath, item in configureCell: { dataSource, tableView, indexPath, item in
let cell: MineViewCell = tableView.dequeueReusableCell(withIdentifier: "SettingViewCell", for: indexPath) as! MineViewCell let cell: SettingViewCell = tableView.dequeueReusableCell(withIdentifier: "SettingViewCell", for: indexPath) as! SettingViewCell
cell.setting = item.setting
cell.titleLabel.text = "123"
cell.detailLabel.text = "321"
return cell return cell
} }
@ -99,9 +105,15 @@ extension AboutViewController {
class AbloutFooterView: UIView, UITextViewDelegate { class AbloutFooterView: UIView, UITextViewDelegate {
let textView: UITextView = { let textView: UITextView = {
let textView = UITextView.init() let textView = UITextView.init()
textView.addAttributed(attributedes: [("服务条款 ", [NSAttributedString.Key.foregroundColor: UIColor.primary(), NSAttributedString.Key.link: URL.init(string: Configs.App.aggrementUrl) ?? ""]), textView.tintColor = UIColor.primary()
(" 版权声明 ", [NSAttributedString.Key.foregroundColor: UIColor.primary(), NSAttributedString.Key.link: URL.init(string: Configs.App.aggrementUrl) ?? ""]),
(" 许可协议", [NSAttributedString.Key.foregroundColor: UIColor.primary(), NSAttributedString.Key.link: URL.init(string: Configs.App.aggrementUrl) ?? ""]) let paragraphStyle = NSMutableParagraphStyle()
paragraphStyle.alignment = .center
paragraphStyle.lineSpacing = 5
textView.addAttributed(attributedes: [("服务条款 ", [NSAttributedString.Key.foregroundColor: UIColor.primary(), NSAttributedString.Key.link: URL.init(string: Configs.App.aggrementUrl) ?? "", NSAttributedString.Key.paragraphStyle: paragraphStyle]),
(" 版权声明 ", [NSAttributedString.Key.foregroundColor: UIColor.primary(), NSAttributedString.Key.link: URL.init(string: Configs.App.aggrementUrl) ?? "" , NSAttributedString.Key.paragraphStyle: paragraphStyle]),
(" 许可协议", [NSAttributedString.Key.foregroundColor: UIColor.primary(), NSAttributedString.Key.link: URL.init(string: Configs.App.aggrementUrl) ?? "", NSAttributedString.Key.paragraphStyle: paragraphStyle])
]) ])
return textView return textView
@ -119,6 +131,7 @@ class AbloutFooterView: UIView, UITextViewDelegate {
} }
func makeUI() { func makeUI() {
backgroundColor = .white
addSubview(textView) addSubview(textView)
} }
@ -127,7 +140,7 @@ class AbloutFooterView: UIView, UITextViewDelegate {
super.layoutSubviews() super.layoutSubviews()
textView.snp.makeConstraints { make in textView.snp.makeConstraints { make in
make.edges.equalTo(self).offset(18) make.edges.equalTo(self)
} }
} }

@ -0,0 +1,77 @@
//
// AboutViewModel.swift
// IndieMusic
//
// Created by WenLei on 2024/1/7.
//
import Foundation
import RxSwift
import RxCocoa
class AboutViewModel: ViewModel, ViewModelType {
struct Input {
let viewWillAppear: ControlEvent<Bool>
let selection: Driver<IndexPath>
}
struct Output {
let items: BehaviorRelay<[SettingSection]>
let selection: Driver<IndexPath>
let itemSelected: PublishSubject<Setting>
}
let itemSelected = PublishSubject<Setting>()
let items = BehaviorRelay<[SettingSection]>.init(value: [])
func transform(input: Input) -> Output {
input.viewWillAppear.subscribe { (_) in
}.disposed(by: rx.disposeBag)
let score = Setting.init(title: "给我们评分", detail: "", arrowIcon: "setting_arrow")
let service = Setting.init(title: "在线客服", detail: "400609213", arrowIcon: "")
let wechat = Setting.init(title: "微信公众号", detail: "雀乐", arrowIcon: "")
//TODO
items.accept([SettingSection.init(items: [.setting(score), .setting(service), .setting(wechat)])])
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)
return Output.init(items: items,
selection: input.selection,
itemSelected: itemSelected)
}
}

@ -6,6 +6,9 @@
// //
import UIKit import UIKit
import RxSwift
import RxCocoa
import RxDataSources
class AccountViewController: TableViewController { class AccountViewController: TableViewController {
@ -16,4 +19,72 @@ class AccountViewController: TableViewController {
} }
override func makeUI() {
super.makeUI()
navigationItem.title = "账户与安全"
self.tableView.mj_header = nil
self.tableView.mj_footer = nil
tableView.register(SettingViewCell.self, forCellReuseIdentifier: "SettingViewCell")
view.backgroundColor = .white
}
override func bindViewModel() {
super.bindViewModel()
guard let viewModel = viewModel as? AccountViewModel else { return }
let input = AccountViewModel.Input.init(viewWillAppear: rx.viewWillAppear,
selection: tableView.rx.itemSelected.asDriver())
let output = viewModel.transform(input: input)
let dataSource = AccountViewController.dataSource()
output.items.bind(to: tableView.rx.items(dataSource: dataSource)).disposed(by: rx.disposeBag)
output.itemSelected.subscribe { sectionItem in
}.disposed(by: rx.disposeBag)
output.selection.drive { 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()
}
}
extension AccountViewController {
static func dataSource() -> RxTableViewSectionedReloadDataSource<SettingSection> {
return RxTableViewSectionedReloadDataSource<SettingSection>(
configureCell: { dataSource, tableView, indexPath, item in
let cell: SettingViewCell = tableView.dequeueReusableCell(withIdentifier: "SettingViewCell", for: indexPath) as! SettingViewCell
cell.titleLabel.text = item.setting.title
cell.detailLabel.text = item.setting.detail
return cell
}
)
}
} }

@ -0,0 +1,77 @@
//
// CacheViewModel.swift
// IndieMusic
//
// Created by WenLei on 2024/1/7.
//
import Foundation
import RxSwift
import RxCocoa
class CacheViewModel: ViewModel, ViewModelType {
struct Input {
let viewWillAppear: ControlEvent<Bool>
let selection: Driver<IndexPath>
}
struct Output {
let items: BehaviorRelay<[SettingSection]>
let selection: Driver<IndexPath>
let itemSelected: PublishSubject<Setting>
}
let itemSelected = PublishSubject<Setting>()
let items = BehaviorRelay<[SettingSection]>.init(value: [])
func transform(input: Input) -> Output {
input.viewWillAppear.subscribe { (_) in
}.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)
return Output.init(items: items,
selection: input.selection,
itemSelected: itemSelected)
}
}

@ -12,6 +12,7 @@ class EditInfoHeaderView: UIView {
let avatorView = UIImageView.init() let avatorView = UIImageView.init()
avatorView.layer.cornerRadius = 45 avatorView.layer.cornerRadius = 45
avatorView.layer.masksToBounds = true avatorView.layer.masksToBounds = true
avatorView.backgroundColor = .red
return avatorView return avatorView
}() }()

@ -25,9 +25,13 @@ class EditInfoViewController: TableViewController {
override func makeUI() { override func makeUI() {
super.makeUI() super.makeUI()
view.backgroundColor = .blue view.backgroundColor = .white
navigationItem.title = "编辑资料"
tableView.register(MineViewCell.self, forCellReuseIdentifier: "SettingViewCell") tableView.mj_footer = nil
tableView.mj_header = nil
tableView.register(SettingViewCell.self, forCellReuseIdentifier: "SettingViewCell")
tableView.tableHeaderView = headerView tableView.tableHeaderView = headerView
} }
@ -37,36 +41,44 @@ class EditInfoViewController: TableViewController {
override func bindViewModel() { override func bindViewModel() {
super.bindViewModel() super.bindViewModel()
guard let viewModel = viewModel as? MineViewModel else { return } guard let viewModel = viewModel as? EditInfoViewModel else { return }
let input = MineViewModel.Input.init(viewWillAppear: rx.viewWillAppear, let input = EditInfoViewModel.Input.init(viewWillAppear: rx.viewWillAppear,
selection: tableView.rx.itemSelected.asDriver()) selection: tableView.rx.itemSelected.asDriver())
let output = viewModel.transform(input: input) let output = viewModel.transform(input: input)
let dataSource = MineViewController.dataSource() let dataSource = EditInfoViewController.dataSource()
output.items.bind(to: tableView.rx.items(dataSource: dataSource)).disposed(by: rx.disposeBag) output.items.bind(to: tableView.rx.items(dataSource: dataSource)).disposed(by: rx.disposeBag)
output.itemSelected.subscribe { sectionItem in output.itemSelected.subscribe { sectionItem in
}.disposed(by: rx.disposeBag) }.disposed(by: rx.disposeBag)
output.selection.drive { sectionItem in
let accountViewModel = AccountViewModel.init(provider: viewModel.provider)
self.navigator.show(segue: .account(viewModel: accountViewModel), sender: self)
}.disposed(by: rx.disposeBag)
} }
} }
extension EditInfoViewController { extension EditInfoViewController {
static func dataSource() -> RxTableViewSectionedReloadDataSource<MineSection> { static func dataSource() -> RxTableViewSectionedReloadDataSource<SettingSection> {
return RxTableViewSectionedReloadDataSource<MineSection>( return RxTableViewSectionedReloadDataSource<SettingSection>(
configureCell: { dataSource, tableView, indexPath, item in configureCell: { dataSource, tableView, indexPath, item in
let cell: MineViewCell = tableView.dequeueReusableCell(withIdentifier: "SettingViewCell", for: indexPath) as! MineViewCell let cell: SettingViewCell = tableView.dequeueReusableCell(withIdentifier: "SettingViewCell", for: indexPath) as! SettingViewCell
cell.titleLabel.text = "123" cell.titleLabel.text = item.setting.title
cell.detailLabel.text = "321" cell.detailLabel.text = item.setting.detail
return cell return cell
} }

@ -77,3 +77,4 @@ class EditInfoViewModel: ViewModel, ViewModelType {
} }

@ -7,22 +7,32 @@
import UIKit import UIKit
class EditNameController: ViewController { class EditNameController: UIViewController {
let editNameTextField: EditNameTextField = { let editNameTextFieldView: EditNameTextFieldView = {
let editNameTextField = EditNameTextField.init(frame: CGRect.init(x: 0, y: 0, width: BaseDimensions.screenWidth, height: 52)) let editNameTextFieldView = EditNameTextFieldView.init(frame: CGRect.init(x: 0, y: 0, width: BaseDimensions.screenWidth, height: 52))
return editNameTextField return editNameTextFieldView
}()
lazy var confirmBarButton: BarButtonItem = {
let confirmBarButton = BarButtonItem.init(title: "提交", style: .plain, target: self, action: nil)
return confirmBarButton
}() }()
override func viewDidLoad() { override func viewDidLoad() {
super.viewDidLoad() super.viewDidLoad()
// Do any additional setup after loading the view. makeUI()
} }
override func makeUI() { func makeUI() {
super.makeUI() navigationItem.title = "设置用户名"
view.backgroundColor = .white
view.addSubview(editNameTextFieldView)
view.addSubview(editNameTextField) self.navigationItem.rightBarButtonItem = confirmBarButton
} }
@ -31,9 +41,10 @@ class EditNameController: ViewController {
} }
class EditNameTextField: UIView { class EditNameTextFieldView: UIView {
let textFieldView: UITextField = { let textFieldView: UITextField = {
let textFieldView = UITextField.init() let textFieldView = UITextField.init()
textFieldView.placeholder = "修改用户名"
return textFieldView return textFieldView
}() }()
@ -42,11 +53,17 @@ class EditNameTextField: UIView {
let countTipsLabel = UILabel.init() let countTipsLabel = UILabel.init()
countTipsLabel.font = UIFont.systemFont(ofSize: 15) countTipsLabel.font = UIFont.systemFont(ofSize: 15)
countTipsLabel.text = "4/12"
return countTipsLabel return countTipsLabel
}() }()
let lineView: UIView = {
let lineView = UIView.init()
lineView.backgroundColor = .separator()
return lineView
}()
override init(frame: CGRect) { override init(frame: CGRect) {
super.init(frame: frame) super.init(frame: frame)
makeUI() makeUI()
@ -59,6 +76,7 @@ class EditNameTextField: UIView {
func makeUI() { func makeUI() {
addSubview(textFieldView) addSubview(textFieldView)
addSubview(countTipsLabel) addSubview(countTipsLabel)
addSubview(lineView)
} }
@ -68,6 +86,7 @@ class EditNameTextField: UIView {
countTipsLabel.snp.makeConstraints { make in countTipsLabel.snp.makeConstraints { make in
make.right.equalTo(self).offset(-18) make.right.equalTo(self).offset(-18)
make.centerY.equalTo(self) make.centerY.equalTo(self)
make.width.equalTo(35)
} }
@ -76,5 +95,11 @@ class EditNameTextField: UIView {
make.right.equalTo(countTipsLabel.snp.left).offset(-18) make.right.equalTo(countTipsLabel.snp.left).offset(-18)
make.centerY.equalTo(self) make.centerY.equalTo(self)
} }
lineView.snp.makeConstraints { make in
make.bottom.equalTo(self)
make.height.equalTo(1)
make.left.equalTo(self).offset(18)
make.right.equalTo(self).offset(-18)
}
} }
} }

@ -7,7 +7,7 @@
import UIKit import UIKit
class FeedbackViewController: ViewController { class FeedbackViewController: UIViewController {
let feedbackTypeView: FeedbackTypeView = { let feedbackTypeView: FeedbackTypeView = {
let feedbackTypeView = FeedbackTypeView.init() let feedbackTypeView = FeedbackTypeView.init()
@ -39,12 +39,11 @@ class FeedbackViewController: ViewController {
override func viewDidLoad() { override func viewDidLoad() {
super.viewDidLoad() super.viewDidLoad()
// Do any additional setup after loading the view. makeUI()
} }
override func makeUI() { func makeUI() {
super.makeUI()
view.addSubview(feedbackTypeView) view.addSubview(feedbackTypeView)
view.addSubview(feedbackDetailView) view.addSubview(feedbackDetailView)

@ -6,18 +6,85 @@
// //
import UIKit import UIKit
import RxSwift
import RxCocoa
import RxDataSources
class PrivacyViewController: TableViewController { class PrivacyViewController: TableViewController {
override func viewDidLoad() { override func viewDidLoad() {
super.viewDidLoad() super.viewDidLoad()
// Do any additional setup after loading the view.
} }
override func makeUI() { override func makeUI() {
super.makeUI() super.makeUI()
navigationItem.title = "消息和隐私设置"
self.tableView.mj_header = nil
self.tableView.mj_footer = nil
tableView.register(SettingViewCell.self, forCellReuseIdentifier: "SettingViewCell")
view.backgroundColor = .white
}
override func bindViewModel() {
super.bindViewModel()
guard let viewModel = viewModel as? PrivacyViewModel else { return }
let input = PrivacyViewModel.Input.init(viewWillAppear: rx.viewWillAppear,
selection: tableView.rx.itemSelected.asDriver())
let output = viewModel.transform(input: input)
let dataSource = PrivacyViewController.dataSource()
output.items.bind(to: tableView.rx.items(dataSource: dataSource)).disposed(by: rx.disposeBag)
output.itemSelected.subscribe { sectionItem in
}.disposed(by: rx.disposeBag)
output.selection.drive { 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()
}
}
extension PrivacyViewController {
static func dataSource() -> RxTableViewSectionedReloadDataSource<SettingSection> {
return RxTableViewSectionedReloadDataSource<SettingSection>(
configureCell: { dataSource, tableView, indexPath, item in
let cell: SettingViewCell = tableView.dequeueReusableCell(withIdentifier: "SettingViewCell", for: indexPath) as! SettingViewCell
cell.titleLabel.text = item.setting.title
cell.detailLabel.text = item.setting.detail
return cell
}
)
} }
} }

@ -0,0 +1,78 @@
//
// PrivacyViewModel.swift
// IndieMusic
//
// Created by WenLei on 2024/1/7.
//
import Foundation
import RxSwift
import RxCocoa
class PrivacyViewModel: ViewModel, ViewModelType {
struct Input {
let viewWillAppear: ControlEvent<Bool>
let selection: Driver<IndexPath>
}
struct Output {
let items: BehaviorRelay<[SettingSection]>
let selection: Driver<IndexPath>
let itemSelected: PublishSubject<Setting>
}
let itemSelected = PublishSubject<Setting>()
let items = BehaviorRelay<[SettingSection]>.init(value: [])
func transform(input: Input) -> Output {
input.viewWillAppear.subscribe { (_) in
}.disposed(by: rx.disposeBag)
let like = Setting.init(title: "点赞通知", detail: "", arrowIcon: "setting_arrow")
let follower = Setting.init(title: "新增关注通知", detail: "", arrowIcon: "setting_arrow")
let privateMessages = Setting.init(title: "私信", detail: "", arrowIcon: "setting_arrow")
let push = Setting.init(title: "系统推送", detail: "", arrowIcon: "setting_arrow")
//TODO
items.accept([SettingSection.init(items: [.setting(like), .setting(follower), .setting(privateMessages), .setting(push)])])
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)
return Output.init(items: items,
selection: input.selection,
itemSelected: itemSelected)
}
}

@ -37,9 +37,21 @@ class SettingViewCell: UITableViewCell {
return switchButton return switchButton
}() }()
var setting: Setting? {
didSet {
titleLabel.text = setting?.title
detailLabel.text = setting?.detail
}
}
override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) { override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
super.init(style: style, reuseIdentifier: reuseIdentifier) super.init(style: style, reuseIdentifier: reuseIdentifier)
makeUI()
} }
required init?(coder: NSCoder) { required init?(coder: NSCoder) {
@ -90,6 +102,7 @@ class SettingViewFooterView: UIView {
let logoutButton = UIButton.init() let logoutButton = UIButton.init()
logoutButton.titleLabel?.font = UIFont.systemFont(ofSize: 15) logoutButton.titleLabel?.font = UIFont.systemFont(ofSize: 15)
logoutButton.setTitle("退出登录", for: .normal) logoutButton.setTitle("退出登录", for: .normal)
logoutButton.setTitleColor(.primary(), for: .normal)
return logoutButton return logoutButton
}() }()
@ -99,6 +112,7 @@ class SettingViewFooterView: UIView {
super.init(frame: frame) super.init(frame: frame)
addSubview(logoutButton) addSubview(logoutButton)
backgroundColor = .white
logoutButton.snp.makeConstraints { make in logoutButton.snp.makeConstraints { make in
make.edges.equalTo(self) make.edges.equalTo(self)

@ -26,9 +26,9 @@ class SettingViewController: TableViewController {
override func makeUI() { override func makeUI() {
super.makeUI() super.makeUI()
view.backgroundColor = .blue view.backgroundColor = .init(hex: 0xf5f5f5)
tableView.register(MineViewCell.self, forCellReuseIdentifier: "SettingViewCell") tableView.register(SettingViewCell.self, forCellReuseIdentifier: "SettingViewCell")
tableView.tableFooterView = footerView tableView.tableFooterView = footerView
} }
@ -38,18 +38,84 @@ class SettingViewController: TableViewController {
override func bindViewModel() { override func bindViewModel() {
super.bindViewModel() super.bindViewModel()
guard let viewModel = viewModel as? MineViewModel else { return } guard let viewModel = viewModel as? SettingViewMdel else { return }
let input = MineViewModel.Input.init(viewWillAppear: rx.viewWillAppear, let input = SettingViewMdel.Input.init(viewWillAppear: rx.viewWillAppear,
selection: tableView.rx.itemSelected.asDriver()) selection: tableView.rx.itemSelected.asDriver())
let output = viewModel.transform(input: input) let output = viewModel.transform(input: input)
let dataSource = MineViewController.dataSource() let dataSource = SettingViewController.dataSource()
output.items.bind(to: tableView.rx.items(dataSource: dataSource)).disposed(by: rx.disposeBag) output.items.bind(to: tableView.rx.items(dataSource: dataSource)).disposed(by: rx.disposeBag)
output.itemSelected.subscribe { sectionItem in output.itemSelected.subscribe { sectionItem in
guard let sectionItem = sectionItem.element else { return }
switch sectionItem {
case .editInfo(let setting):
let editInfo = EditInfoViewModel.init(provider: viewModel.provider)
self.navigator.show(segue: .editInfo(viewModel: editInfo), sender: self)
case .account(let setting):
let accountViewModel = AccountViewModel.init(provider: viewModel.provider)
self.navigator.show(segue: .account(viewModel: accountViewModel), sender: self)
case .privacy(let setting):
let privacyViewModel = PrivacyViewModel.init(provider: viewModel.provider)
self.navigator.show(segue: .privacy(viewModel: privacyViewModel), sender: self)
case .timing(let setting):
let timingViewModel = TimingViewModel.init(provider: viewModel.provider)
self.navigator.show(segue: .timing(viewModel: timingViewModel), sender: self)
case .cache(let setting):
let cacheViewModel = CacheViewModel.init(provider: viewModel.provider)
self.navigator.show(segue: .cache(viewModel: cacheViewModel), sender: self)
case .permission(let setting):
let accountViewModel = AccountViewModel.init(provider: viewModel.provider)
self.navigator.show(segue: .account(viewModel: accountViewModel), sender: self)
case .feedback(let setting):
// let accountViewModel = Feedbackview.init(provider: viewModel.provider)
self.navigator.show(segue: .feedback, sender: self)
case .about(let setting):
let aboutViewModel = AboutViewModel.init(provider: viewModel.provider)
self.navigator.show(segue: .about(viewModel: aboutViewModel), sender: self)
case .contributors(let setting):
let thanksViewModel = ThanksViewModel.init(provider: viewModel.provider)
self.navigator.show(segue: .thanks(viewModel: thanksViewModel), sender: self)
case .version(let setting):
break
case .setting(_):
break
}
// let editInfoViewModel = EditInfoViewModel.init(provider: viewModel.provider)
//
// self.navigator.show(segue: .editInfo(viewModel: editInfoViewModel), sender: self)
}.disposed(by: rx.disposeBag)
output.selection.drive { sectionItem in
// let editInfoViewModel = EditInfoViewModel.init(provider: viewModel.provider)
//
// self.navigator.show(segue: .editInfo(viewModel: editInfoViewModel), sender: self)
// let thanksViewModel = ThanksViewModel.init(provider: viewModel.provider)
//
// self.navigator.show(segue: .thanks(viewModel: thanksViewModel), sender: self)
}.disposed(by: rx.disposeBag) }.disposed(by: rx.disposeBag)
@ -61,13 +127,12 @@ class SettingViewController: TableViewController {
extension SettingViewController { extension SettingViewController {
static func dataSource() -> RxTableViewSectionedReloadDataSource<MineSection> { static func dataSource() -> RxTableViewSectionedReloadDataSource<SettingSection> {
return RxTableViewSectionedReloadDataSource<MineSection>( return RxTableViewSectionedReloadDataSource<SettingSection>(
configureCell: { dataSource, tableView, indexPath, item in configureCell: { dataSource, tableView, indexPath, item in
let cell: MineViewCell = tableView.dequeueReusableCell(withIdentifier: "SettingViewCell", for: indexPath) as! MineViewCell let cell: SettingViewCell = tableView.dequeueReusableCell(withIdentifier: "SettingViewCell", for: indexPath) as! SettingViewCell
cell.titleLabel.text = "123" cell.setting = item.setting
cell.detailLabel.text = "321"
return cell return cell
} }

@ -22,11 +22,11 @@ class SettingViewMdel: ViewModel, ViewModelType {
struct Output { struct Output {
let items: BehaviorRelay<[SettingSection]> let items: BehaviorRelay<[SettingSection]>
let selection: Driver<IndexPath> let selection: Driver<IndexPath>
let itemSelected: PublishSubject<Setting> let itemSelected: PublishSubject<SettingType>
} }
let itemSelected = PublishSubject<Setting>() let itemSelected = PublishSubject<SettingType>()
let items = BehaviorRelay<[SettingSection]>.init(value: []) let items = BehaviorRelay<[SettingSection]>.init(value: [])
func transform(input: Input) -> Output { func transform(input: Input) -> Output {
@ -51,15 +51,17 @@ class SettingViewMdel: ViewModel, ViewModelType {
let version = Setting.init(title: "检查版本", detail: "", arrowIcon: "setting_arrow") let version = Setting.init(title: "检查版本", detail: "", arrowIcon: "setting_arrow")
items.accept([SettingSection.init(items: [.editInfo(editInfo), .account(account), .privacy(privacy)])])
items.accept([SettingSection.init(items: [.editInfo(editInfo), .account(account), .privacy(privacy), .timing(timing), .cache(cache), .privacy(privacy), .feedback(feedback), .about(about), .contributors(contributors), .version(version)])])
input.selection.drive { indexPath in input.selection.drive { indexPath in
guard let sectionItem = self.items.value.first?.items[indexPath.row] else { return } guard let sectionItem = self.items.value.first?.items[indexPath.row] else { return }
self.itemSelected.onNext(sectionItem)
switch sectionItem { // switch sectionItem {
case .about(let setting): // case .about(let setting):
// case .account(let setting): // case .account(let setting):
// case .privacy(let setting): // case .privacy(let setting):
@ -71,11 +73,10 @@ class SettingViewMdel: ViewModel, ViewModelType {
// case .contributors(let setting): // case .contributors(let setting):
// case .version(let setting): // case .version(let setting):
self.itemSelected.onNext(setting)
default: break
} // default: break
//
// }
}.disposed(by: rx.disposeBag) }.disposed(by: rx.disposeBag)

@ -0,0 +1,77 @@
//
// ThanksViewModel.swift
// IndieMusic
//
// Created by WenLei on 2024/1/7.
//
import Foundation
import RxSwift
import RxCocoa
class ThanksViewModel: ViewModel, ViewModelType {
struct Input {
let viewWillAppear: ControlEvent<Bool>
let selection: Driver<IndexPath>
}
struct Output {
let items: BehaviorRelay<[SettingSection]>
let selection: Driver<IndexPath>
let itemSelected: PublishSubject<Setting>
}
let itemSelected = PublishSubject<Setting>()
let items = BehaviorRelay<[SettingSection]>.init(value: [])
func transform(input: Input) -> Output {
input.viewWillAppear.subscribe { (_) in
}.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)
return Output.init(items: items,
selection: input.selection,
itemSelected: itemSelected)
}
}

@ -0,0 +1,77 @@
//
// TimingViewModel.swift
// IndieMusic
//
// Created by WenLei on 2024/1/7.
//
import Foundation
import RxSwift
import RxCocoa
class TimingViewModel: ViewModel, ViewModelType {
struct Input {
let viewWillAppear: ControlEvent<Bool>
let selection: Driver<IndexPath>
}
struct Output {
let items: BehaviorRelay<[SettingSection]>
let selection: Driver<IndexPath>
let itemSelected: PublishSubject<Setting>
}
let itemSelected = PublishSubject<Setting>()
let items = BehaviorRelay<[SettingSection]>.init(value: [])
func transform(input: Input) -> Output {
input.viewWillAppear.subscribe { (_) in
}.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)
return Output.init(items: items,
selection: input.selection,
itemSelected: itemSelected)
}
}

@ -0,0 +1,21 @@
{
"images" : [
{
"filename" : "song_playAll_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">
<circle cx="12" cy="12" r="12" fill="#B44343"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M15.3791 11.5642C15.7189 11.7553 15.7189 12.2446 15.3791 12.4358L9.51432 15.7347C9.18102 15.9222 8.76919 15.6813 8.76919 15.2989L8.76919 8.70106C8.76919 8.31864 9.18102 8.07778 9.51432 8.26527L15.3791 11.5642Z" fill="white" fill-opacity="0.95"/>
</svg>

After

Width:  |  Height:  |  Size: 446 B

@ -0,0 +1,21 @@
{
"images" : [
{
"filename" : "mine_message_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 d="M21 7.49999L12.927 12.63C12.6491 12.8041 12.3279 12.8964 12 12.8964C11.6721 12.8964 11.3509 12.8041 11.073 12.63L3 7.49999M4.8 4.79999H19.2C20.1941 4.79999 21 5.60588 21 6.59999V17.4C21 18.3941 20.1941 19.2 19.2 19.2H4.8C3.80589 19.2 3 18.3941 3 17.4V6.59999C3 5.60588 3.80589 4.79999 4.8 4.79999Z" stroke="#17171A" stroke-width="1.35" stroke-linecap="round" stroke-linejoin="round"/>
</svg>

After

Width:  |  Height:  |  Size: 497 B

@ -0,0 +1,21 @@
{
"images" : [
{
"filename" : "mine_setting_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">
<path d="M11.4659 3.17438C11.7964 2.98357 12.2036 2.98357 12.5341 3.17438L19.3762 7.12466C19.7067 7.31547 19.9103 7.6681 19.9103 8.04973V15.9503C19.9103 16.3319 19.7067 16.6845 19.3762 16.8753L12.5341 20.8256C12.2036 21.0164 11.7964 21.0164 11.4659 20.8256L4.62384 16.8753C4.29334 16.6845 4.08975 16.3319 4.08975 15.9503V8.04973C4.08975 7.6681 4.29334 7.31547 4.62384 7.12466L11.4659 3.17438Z" fill="white" fill-opacity="0.95" stroke="#17171A" stroke-width="1.5"/>
<circle cx="12" cy="12" r="3.25" stroke="#17171A" stroke-width="1.5"/>
</svg>

After

Width:  |  Height:  |  Size: 639 B

Loading…
Cancel
Save