├── .github ├── ISSUE_TEMPLATE │ ├── bug-issue.md │ └── feature-issue.md └── pull_request_template.md ├── .gitignore ├── Projects ├── App │ ├── App.xcodeproj │ │ ├── project.pbxproj │ │ ├── project.xcworkspace │ │ │ └── contents.xcworkspacedata │ │ ├── xcshareddata │ │ │ └── xcschemes │ │ │ │ └── App.xcscheme │ │ └── xcuserdata │ │ │ └── yeseul.xcuserdatad │ │ │ └── xcschemes │ │ │ └── xcschememanagement.plist │ ├── InfoPlist.xcstrings │ ├── Localizable.xcstrings │ ├── Project.swift │ ├── Resources │ │ ├── Assets.xcassets │ │ │ ├── AccentColor.colorset │ │ │ │ └── Contents.json │ │ │ ├── AppIcon.appiconset │ │ │ │ ├── Contents.json │ │ │ │ └── Instagram post - 9.png │ │ │ ├── Contents.json │ │ │ ├── logo.imageset │ │ │ │ ├── Contents.json │ │ │ │ ├── logo 1.svg │ │ │ │ ├── logo 2.svg │ │ │ │ ├── logo 3.svg │ │ │ │ ├── logo 4.svg │ │ │ │ ├── logo.png │ │ │ │ ├── logo.svg │ │ │ │ ├── logoDark 1.svg │ │ │ │ ├── logoDark 2.svg │ │ │ │ └── logoDark.svg │ │ │ └── logo.png │ │ ├── GoogleService-Info.plist │ │ └── LaunchScreen.storyboard │ ├── Seta.entitlements │ ├── Setlist.xcodeproj │ │ ├── project.pbxproj │ │ └── xcuserdata │ │ │ ├── a_mcflurry.xcuserdatad │ │ │ └── xcschemes │ │ │ │ └── xcschememanagement.plist │ │ │ ├── kohyeji.xcuserdatad │ │ │ └── xcschemes │ │ │ │ └── xcschememanagement.plist │ │ │ └── yeseul.xcuserdatad │ │ │ └── xcschemes │ │ │ └── xcschememanagement.plist │ ├── Sources │ │ └── App.swift │ └── Support │ │ └── Info.plist ├── Core │ ├── Core.xcodeproj │ │ ├── project.pbxproj │ │ └── xcuserdata │ │ │ ├── a_mcflurry.xcuserdatad │ │ │ └── xcschemes │ │ │ │ └── xcschememanagement.plist │ │ │ ├── kohyeji.xcuserdatad │ │ │ └── xcschemes │ │ │ │ └── xcschememanagement.plist │ │ │ └── yeseul.xcuserdatad │ │ │ └── xcschemes │ │ │ └── xcschememanagement.plist │ ├── Project.swift │ └── Sources │ │ ├── Model │ │ ├── AccessTokenModel.swift │ │ ├── AccessTokenRequestModel.swift │ │ ├── ArchivedConcertInfo │ │ │ ├── ArchivedConcertInfo.swift │ │ │ └── SaveSetlist.swift │ │ ├── ArtistInfoModel.swift │ │ ├── ArtistInfoModel │ │ │ └── SaveArtistInfo.swift │ │ ├── ArtistListModel.swift │ │ ├── GeniusArtistsModel.swift │ │ ├── GeniusSongsModel.swift │ │ ├── LikeArtist │ │ │ └── LikeArtistModel.swift │ │ ├── OnboardingModel.swift │ │ ├── SearchHistory │ │ │ └── SearchHistoryModel.swift │ │ ├── SearchModel.swift │ │ └── SetlistListModel.swift │ │ └── Service │ │ ├── APIKeys.swift │ │ ├── AnalyticsEvent.swift │ │ ├── AppState.swift │ │ ├── AppleMusicService.swift │ │ ├── ArtistFetchService.swift │ │ ├── NetworkManager.swift │ │ ├── SearchHistoryDataManager │ │ ├── ArtistDataManager.swift │ │ └── KoreanConverter.swift │ │ ├── SetlistDataService.swift │ │ ├── SpotifyManager │ │ └── SpotifyManager.swift │ │ ├── SwiftDataManager │ │ └── SwiftDataManager.swift │ │ └── UserDefaultsManger.swift ├── Feature │ ├── Feature.xcodeproj │ │ ├── project.pbxproj │ │ └── xcuserdata │ │ │ ├── a_mcflurry.xcuserdatad │ │ │ └── xcschemes │ │ │ │ └── xcschememanagement.plist │ │ │ ├── kohyeji.xcuserdatad │ │ │ └── xcschemes │ │ │ │ └── xcschememanagement.plist │ │ │ └── yeseul.xcuserdatad │ │ │ └── xcschemes │ │ │ └── xcschememanagement.plist │ ├── Localizable.xcstrings │ ├── Project.swift │ └── Scenes │ │ ├── ArchiveScene │ │ ├── Component │ │ │ ├── ArchiveArtistCell.swift │ │ │ ├── ArchiveConcertInfoCell.swift │ │ │ ├── ArtistSetCell.swift │ │ │ ├── EmptyArtistImage.swift │ │ │ ├── IsEmptyCell.swift │ │ │ └── MenuButton.swift │ │ ├── View │ │ │ ├── ArchivingArtistView.swift │ │ │ └── ArchivingView.swift │ │ └── ViewModel │ │ │ └── ArchivingViewModel.swift │ │ ├── ArtistScene │ │ ├── View │ │ │ ├── AllSetlistsView.swift │ │ │ ├── ArtistInfoView.swift │ │ │ ├── ArtistView.swift │ │ │ └── BookmarkedSetlistsView.swift │ │ └── ViewModel │ │ │ └── ArtistViewModel.swift │ │ ├── MainScene │ │ ├── Component │ │ │ ├── ArtistImage.swift │ │ │ ├── ArtistMainSetlistView.swift │ │ │ ├── ArtistNameView.swift │ │ │ ├── ArtistsContentView.swift │ │ │ ├── EmptyMainView.swift │ │ │ └── MainToolTipView.swift │ │ ├── View │ │ │ ├── MainView.swift │ │ │ └── TabBarView.swift │ │ └── ViewModel │ │ │ └── MainViewModel.swift │ │ ├── NetworkUnavailableView.swift │ │ ├── OnboardingScene │ │ ├── View │ │ │ └── OnboardingView.swift │ │ └── ViewModel │ │ │ └── OnboardingViewModel.swift │ │ ├── SearchScene │ │ ├── Component │ │ │ ├── SearchArtistCell.swift │ │ │ ├── SearchArtistList.swift │ │ │ ├── SearchHistoryCell.swift │ │ │ └── SearchbarCustom.swift │ │ ├── View │ │ │ └── SearchView.swift │ │ └── ViewModel │ │ │ └── SearchViewModel.swift │ │ ├── SetlistScene │ │ ├── Component │ │ │ ├── CaptureSetlist │ │ │ │ └── CaptureSetlistView.swift │ │ │ ├── CustomTitleAlert.swift │ │ │ ├── ExportPlaylistButtonView.swift │ │ │ ├── MusicButtonView.swift │ │ │ ├── SetlistFMLinkButtonView.swift │ │ │ ├── ShareOptionButtonView.swift │ │ │ ├── ShareSetlistView.swift │ │ │ └── ToastMessageView.swift │ │ ├── View │ │ │ ├── BottomModalView.swift │ │ │ ├── EmptySetlistView.swift │ │ │ ├── ListView.swift │ │ │ ├── SetlistImageShareView.swift │ │ │ ├── SetlistInfoView.swift │ │ │ └── SetlistView.swift │ │ └── ViewModel │ │ │ ├── ExportPlaylistViewModel.swift │ │ │ └── SetlistViewModel.swift │ │ ├── SettingScene │ │ └── View │ │ │ ├── AskView.swift │ │ │ └── SettingView.swift │ │ └── SummarizedSetlistInfoView.swift └── UI │ ├── .DS_Store │ ├── Project.swift │ ├── Resources │ ├── Colors.xcassets │ │ ├── Contents.json │ │ ├── ellipsis.colorset │ │ │ └── Contents.json │ │ ├── gray6.colorset │ │ │ └── Contents.json │ │ ├── gray600.colorset │ │ │ └── Contents.json │ │ ├── mainBlack.colorset │ │ │ └── Contents.json │ │ ├── mainOrange.colorset │ │ │ └── Contents.json │ │ ├── mainWhite.colorset │ │ │ └── Contents.json │ │ ├── orange100.colorset │ │ │ └── Contents.json │ │ ├── shareBG.colorset │ │ │ └── Contents.json │ │ ├── toast1.colorset │ │ │ └── Contents.json │ │ ├── toast2.colorset │ │ │ └── Contents.json │ │ └── toastBG.colorset │ │ │ └── Contents.json │ └── Images.xcassets │ │ ├── AppImages │ │ ├── Contents.json │ │ ├── appleMusic.imageset │ │ │ ├── Contents.json │ │ │ └── appleMusic.png │ │ ├── spotify.imageset │ │ │ ├── Contents.json │ │ │ └── spotify.png │ │ └── youtubeMusic.imageset │ │ │ ├── Contents.json │ │ │ └── youtubeMusic.png │ │ ├── Contents.json │ │ ├── ForeignArtist │ │ ├── AJR.imageset │ │ │ ├── AJR.jpg │ │ │ └── Contents.json │ │ ├── BrunoMars.imageset │ │ │ ├── BrunoMars.jpg │ │ │ └── Contents.json │ │ ├── Contents.json │ │ ├── DojaCat.imageset │ │ │ ├── Contents.json │ │ │ └── DojaCat.jpg │ │ ├── Drake.imageset │ │ │ ├── Contents.json │ │ │ └── Drake.png │ │ ├── DuaLipa.imageset │ │ │ ├── Contents.json │ │ │ └── DuaLipa.jpg │ │ ├── Eminem.imageset │ │ │ ├── Contents.json │ │ │ └── Eminem.jpg │ │ ├── JohnK.imageset │ │ │ ├── Contents.json │ │ │ └── JohnK.jpg │ │ ├── Maroon5.imageset │ │ │ ├── Contents.json │ │ │ └── Maroon5.jpg │ │ ├── PostMalone.imageset │ │ │ ├── Contents.json │ │ │ └── PostMalone.jpg │ │ ├── SamSmith.imageset │ │ │ ├── Contents.json │ │ │ └── SamSmith.jpg │ │ ├── TheWeekend.imageset │ │ │ ├── Contents.json │ │ │ └── TheWeekend.jpg │ │ └── TroyeSivan.imageset │ │ │ ├── Contents.json │ │ │ └── TroyeSivan.jpg │ │ ├── KoreanArtist │ │ ├── BTS.imageset │ │ │ ├── BTS.jpg │ │ │ └── Contents.json │ │ ├── CarTheGarden.imageset │ │ │ ├── CarTheGarden.jpg │ │ │ └── Contents.json │ │ ├── Contents.json │ │ ├── DAY6.imageset │ │ │ ├── Contents.json │ │ │ └── DAY6.jpg │ │ ├── JayPark.imageset │ │ │ ├── Contents.json │ │ │ └── JayPark.jpg │ │ ├── LeeYoungJi.imageset │ │ │ ├── Contents.json │ │ │ └── LeeYoungJi.jpg │ │ ├── NCTDream.imageset │ │ │ ├── Contents.json │ │ │ └── NCTDream.jpg │ │ ├── NewJeans.imageset │ │ │ ├── Contents.json │ │ │ └── NewJeans.jpg │ │ ├── PaulKim.imageset │ │ │ ├── Contents.json │ │ │ └── PaulKim.jpg │ │ ├── StrayKids.imageset │ │ │ ├── Contents.json │ │ │ └── StrayKids.jpg │ │ ├── Woodz.imageset │ │ │ ├── Contents.json │ │ │ └── Woodz.jpg │ │ └── Yoonha.imageset │ │ │ ├── Contents.json │ │ │ └── Yoonha.jpg │ │ ├── archiveIcon.imageset │ │ ├── Contents.json │ │ └── archiveIcon.svg │ │ ├── artistViewTicket.imageset │ │ ├── Contents.json │ │ ├── Group 1000002051.png │ │ └── darkArtistViewTicket.png │ │ ├── darkArtistViewTicket.imageset │ │ ├── Contents.json │ │ └── darkArtistViewTicket.png │ │ ├── darkTicket.imageset │ │ ├── Contents.json │ │ └── darkTicket.png │ │ ├── homeIcon.imageset │ │ ├── Contents.json │ │ └── homeIcon.svg │ │ ├── mainViewTicket.imageset │ │ ├── Contents.json │ │ └── Ticket.svg │ │ ├── modalCloseButton.imageset │ │ ├── Contents.json │ │ └── modal_close_button.png │ │ ├── screenshotforOCR.imageset │ │ ├── Contents.json │ │ └── screenshotforOCR.png │ │ ├── searchIcon.imageset │ │ ├── Contents.json │ │ └── searchIcon.svg │ │ ├── serviceForConcertgoers.imageset │ │ ├── Contents.json │ │ └── serviceForConcertgoers.png │ │ ├── seta.imageset │ │ ├── Contents.json │ │ └── Seta.svg │ │ ├── ticket.imageset │ │ ├── Contents.json │ │ └── ticket.png │ │ └── whiteTicket.imageset │ │ ├── Contents.json │ │ └── whiteTicket.png │ ├── Sources │ └── Extensions │ │ ├── ArchiveExtension.swift │ │ ├── CellFrameExtension.swift │ │ ├── ColorExtension.swift │ │ ├── DateFormatterExtension.swift │ │ ├── ImageCropExtension.swift │ │ └── UIScreenExtension.swift │ └── UI.xcodeproj │ ├── project.pbxproj │ └── xcuserdata │ ├── a_mcflurry.xcuserdatad │ └── xcschemes │ │ └── xcschememanagement.plist │ ├── choihyowon.xcuserdatad │ └── xcschemes │ │ └── xcschememanagement.plist │ ├── kohyeji.xcuserdatad │ └── xcschemes │ │ └── xcschememanagement.plist │ └── yeseul.xcuserdatad │ └── xcschemes │ └── xcschememanagement.plist ├── README.md ├── Setlist.xcworkspace ├── contents.xcworkspacedata ├── xcshareddata │ └── xcschemes │ │ └── Setlist-Workspace.xcscheme └── xcuserdata │ ├── a_mcflurry.xcuserdatad │ ├── UserInterfaceState.xcuserstate │ ├── xcdebugger │ │ └── Breakpoints_v2.xcbkptlist │ └── xcschemes │ │ └── xcschememanagement.plist │ ├── choihyowon.xcuserdatad │ └── UserInterfaceState.xcuserstate │ ├── kohyeji.xcuserdatad │ ├── UserInterfaceState.xcuserstate │ ├── xcdebugger │ │ └── Breakpoints_v2.xcbkptlist │ └── xcschemes │ │ └── xcschememanagement.plist │ └── yeseul.xcuserdatad │ ├── UserInterfaceState.xcuserstate │ ├── xcdebugger │ └── Breakpoints_v2.xcbkptlist │ └── xcschemes │ └── xcschememanagement.plist ├── Tuist ├── Config.swift └── ProjectDescriptionHelpers │ ├── Dependency+Project.swift │ ├── Dependency+Spm.swift │ └── Project+Templates.swift └── Workspace.swift /.github/ISSUE_TEMPLATE/bug-issue.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: bug issue 3 | about: IssueTemplate 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | ## 🐛 Description 11 | [이런 상황]에서 [이런 버그]가 발생합니다. 12 | 13 | ## 📝 TODO 14 | - [ ] 15 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature-issue.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: feature issue 3 | about: IssueTemplate 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | ## 🛠 Issue 11 | - 12 | 13 | ## 📝 To-do 14 | - [ ] 15 | -------------------------------------------------------------------------------- /.github/pull_request_template.md: -------------------------------------------------------------------------------- 1 | ## 🔥 *Pull requests* 2 | 3 | ⛳️ **작업한 브랜치** 4 | - # 5 | 6 | 👷 **작업한 내용** 7 | - 8 | 9 | ## 🚨 참고 사항 10 | - 11 | 12 | ## 📸 스크린샷 13 | |기능|스크린샷| 14 | |:--:|:--:| 15 | ||| 16 | 17 | ## 📟 관련 이슈 18 | - Resolved: # 19 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | ### macOS ### 2 | # General 3 | .DS_Store 4 | .AppleDouble 5 | .LSOverride 6 | 7 | # Icon must end with two 8 | Icon 9 | 10 | # Thumbnails 11 | ._* 12 | 13 | # Files that might appear in the root of a volume 14 | .DocumentRevisions-V100 15 | .fseventsd 16 | .Spotlight-V100 17 | .TemporaryItems 18 | .Trashes 19 | .VolumeIcon.icns 20 | .com.apple.timemachine.donotpresent 21 | 22 | # Directories potentially created on remote AFP share 23 | .AppleDB 24 | .AppleDesktop 25 | Network Trash Folder 26 | Temporary Items 27 | .apdisk 28 | 29 | ### Xcode ### 30 | # Xcode 31 | # 32 | # gitignore contributors: remember to update Global/Xcode.gitignore, Objective-C.gitignore & Swift.gitignore 33 | 34 | ## User settings 35 | xcuserdata/ 36 | 37 | ## compatibility with Xcode 8 and earlier (ignoring not required starting Xcode 9) 38 | *.xcscmblueprint 39 | *.xccheckout 40 | 41 | ## compatibility with Xcode 3 and earlier (ignoring not required starting Xcode 4) 42 | build/ 43 | DerivedData/ 44 | *.moved-aside 45 | *.pbxuser 46 | !default.pbxuser 47 | *.mode1v3 48 | !default.mode1v3 49 | *.mode2v3 50 | !default.mode2v3 51 | *.perspectivev3 52 | !default.perspectivev3 53 | 54 | ### Xcode Patch ### 55 | *.xcodeproj/* 56 | !*.xcodeproj/project.pbxproj 57 | !*.xcodeproj/xcshareddata/ 58 | !*.xcworkspace/contents.xcworkspacedata 59 | /*.gcno 60 | 61 | ### Projects ### 62 | *.xcodeproj 63 | *.xcworkspace 64 | 65 | ### Tuist derived files ### 66 | graph.dot 67 | Derived/ 68 | 69 | ### Tuist managed dependencies ### 70 | Tuist/Dependencies 71 | 72 | APIKeys.swift 73 | -------------------------------------------------------------------------------- /Projects/App/App.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /Projects/App/App.xcodeproj/xcshareddata/xcschemes/App.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 32 | 33 | 35 | 41 | 42 | 43 | 44 | 45 | 51 | 52 | 53 | 54 | 65 | 67 | 73 | 74 | 75 | 76 | 82 | 84 | 90 | 91 | 92 | 93 | 95 | 96 | 99 | 100 | 101 | -------------------------------------------------------------------------------- /Projects/App/App.xcodeproj/xcuserdata/yeseul.xcuserdatad/xcschemes/xcschememanagement.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | SchemeUserState 6 | 7 | App.xcscheme_^#shared#^_ 8 | 9 | isShown 10 | 11 | orderHint 12 | 0 13 | 14 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /Projects/App/InfoPlist.xcstrings: -------------------------------------------------------------------------------- 1 | { 2 | "sourceLanguage" : "en", 3 | "strings" : { 4 | "CFBundleName" : { 5 | "comment" : "Bundle name", 6 | "extractionState" : "extracted_with_value", 7 | "localizations" : { 8 | "en" : { 9 | "stringUnit" : { 10 | "state" : "new", 11 | "value" : "Seta" 12 | } 13 | } 14 | } 15 | }, 16 | "NSAppleMusicUsageDescription" : { 17 | "extractionState" : "manual", 18 | "localizations" : { 19 | "en" : { 20 | "stringUnit" : { 21 | "state" : "translated", 22 | "value" : "To enable the Export Playlist feature, You must allow access to 'Apple Music'" 23 | } 24 | }, 25 | "ko" : { 26 | "stringUnit" : { 27 | "state" : "needs_review", 28 | "value" : "공연의 세트리스트를 Apple Music 플레이리스트에 넣기 위해 접근을 허용해주세요" 29 | } 30 | } 31 | } 32 | }, 33 | "NSPhotoLibraryUsageDescription" : { 34 | "extractionState" : "manual", 35 | "localizations" : { 36 | "en" : { 37 | "stringUnit" : { 38 | "state" : "translated", 39 | "value" : "To use the Photography feature, You must allow 'Photo/Video' access" 40 | } 41 | }, 42 | "ko" : { 43 | "stringUnit" : { 44 | "state" : "needs_review", 45 | "value" : "세트리스트를 사진첩에 저장하려면 권한이 필요합니다." 46 | } 47 | } 48 | } 49 | } 50 | }, 51 | "version" : "1.0" 52 | } -------------------------------------------------------------------------------- /Projects/App/Project.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Project.swift 3 | // ProjectDescriptionHelpers 4 | // 5 | // Created by 최효원 on 2023/10/06. 6 | // 7 | 8 | import ProjectDescription 9 | import ProjectDescriptionHelpers 10 | 11 | let project = Project.makeModule( 12 | name: "Seta", 13 | platform: .iOS, 14 | product: .app, 15 | packages: [ 16 | .Firebase, 17 | .SpotifyAPI, 18 | .KeychainAccess 19 | ], 20 | dependencies: [ 21 | .Projcet.Feature, 22 | .SPM.Firebase, 23 | .SPM.SpotifyAPI, 24 | .SPM.KeychainAccess 25 | ], 26 | sources: ["Sources/**"], 27 | resources: ["Resources/**"], 28 | infoPlist: .file(path: "Support/Info.plist")) 29 | -------------------------------------------------------------------------------- /Projects/App/Resources/Assets.xcassets/AccentColor.colorset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "colors" : [ 3 | { 4 | "color" : { 5 | "color-space" : "srgb", 6 | "components" : { 7 | "alpha" : "1.000", 8 | "blue" : "0x00", 9 | "green" : "0x00", 10 | "red" : "0x00" 11 | } 12 | }, 13 | "idiom" : "universal" 14 | }, 15 | { 16 | "appearances" : [ 17 | { 18 | "appearance" : "luminosity", 19 | "value" : "dark" 20 | } 21 | ], 22 | "color" : { 23 | "color-space" : "srgb", 24 | "components" : { 25 | "alpha" : "1.000", 26 | "blue" : "0xEE", 27 | "green" : "0xEA", 28 | "red" : "0xE9" 29 | } 30 | }, 31 | "idiom" : "universal" 32 | } 33 | ], 34 | "info" : { 35 | "author" : "xcode", 36 | "version" : 1 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /Projects/App/Resources/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "Instagram post - 9.png", 5 | "idiom" : "universal", 6 | "platform" : "ios", 7 | "size" : "1024x1024" 8 | } 9 | ], 10 | "info" : { 11 | "author" : "xcode", 12 | "version" : 1 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /Projects/App/Resources/Assets.xcassets/AppIcon.appiconset/Instagram post - 9.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DeveloperAcademy-POSTECH/2023-MacC-Team12-Creative8/4b68cf6d2cee5683693269560ffbd23162034ec0/Projects/App/Resources/Assets.xcassets/AppIcon.appiconset/Instagram post - 9.png -------------------------------------------------------------------------------- /Projects/App/Resources/Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "author" : "xcode", 4 | "version" : 1 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /Projects/App/Resources/Assets.xcassets/logo.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "logo.png", 5 | "idiom" : "universal", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "appearances" : [ 10 | { 11 | "appearance" : "luminosity", 12 | "value" : "light" 13 | } 14 | ], 15 | "filename" : "logo 2.svg", 16 | "idiom" : "universal", 17 | "scale" : "1x" 18 | }, 19 | { 20 | "appearances" : [ 21 | { 22 | "appearance" : "luminosity", 23 | "value" : "dark" 24 | } 25 | ], 26 | "filename" : "logoDark.svg", 27 | "idiom" : "universal", 28 | "scale" : "1x" 29 | }, 30 | { 31 | "filename" : "logo.svg", 32 | "idiom" : "universal", 33 | "scale" : "2x" 34 | }, 35 | { 36 | "appearances" : [ 37 | { 38 | "appearance" : "luminosity", 39 | "value" : "light" 40 | } 41 | ], 42 | "filename" : "logo 3.svg", 43 | "idiom" : "universal", 44 | "scale" : "2x" 45 | }, 46 | { 47 | "appearances" : [ 48 | { 49 | "appearance" : "luminosity", 50 | "value" : "dark" 51 | } 52 | ], 53 | "filename" : "logoDark 1.svg", 54 | "idiom" : "universal", 55 | "scale" : "2x" 56 | }, 57 | { 58 | "filename" : "logo 1.svg", 59 | "idiom" : "universal", 60 | "scale" : "3x" 61 | }, 62 | { 63 | "appearances" : [ 64 | { 65 | "appearance" : "luminosity", 66 | "value" : "light" 67 | } 68 | ], 69 | "filename" : "logo 4.svg", 70 | "idiom" : "universal", 71 | "scale" : "3x" 72 | }, 73 | { 74 | "appearances" : [ 75 | { 76 | "appearance" : "luminosity", 77 | "value" : "dark" 78 | } 79 | ], 80 | "filename" : "logoDark 2.svg", 81 | "idiom" : "universal", 82 | "scale" : "3x" 83 | } 84 | ], 85 | "info" : { 86 | "author" : "xcode", 87 | "version" : 1 88 | } 89 | } 90 | -------------------------------------------------------------------------------- /Projects/App/Resources/Assets.xcassets/logo.imageset/logo 1.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /Projects/App/Resources/Assets.xcassets/logo.imageset/logo 2.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /Projects/App/Resources/Assets.xcassets/logo.imageset/logo 3.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /Projects/App/Resources/Assets.xcassets/logo.imageset/logo 4.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /Projects/App/Resources/Assets.xcassets/logo.imageset/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DeveloperAcademy-POSTECH/2023-MacC-Team12-Creative8/4b68cf6d2cee5683693269560ffbd23162034ec0/Projects/App/Resources/Assets.xcassets/logo.imageset/logo.png -------------------------------------------------------------------------------- /Projects/App/Resources/Assets.xcassets/logo.imageset/logo.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /Projects/App/Resources/Assets.xcassets/logo.imageset/logoDark 1.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /Projects/App/Resources/Assets.xcassets/logo.imageset/logoDark 2.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /Projects/App/Resources/Assets.xcassets/logo.imageset/logoDark.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /Projects/App/Resources/Assets.xcassets/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DeveloperAcademy-POSTECH/2023-MacC-Team12-Creative8/4b68cf6d2cee5683693269560ffbd23162034ec0/Projects/App/Resources/Assets.xcassets/logo.png -------------------------------------------------------------------------------- /Projects/App/Resources/GoogleService-Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | API_KEY 6 | AIzaSyCCgP87-LaBtaVdwsmoAxwpTl_b8MLbiRY 7 | GCM_SENDER_ID 8 | 126154503966 9 | PLIST_VERSION 10 | 1 11 | BUNDLE_ID 12 | com.creative8.seta.Seta 13 | PROJECT_ID 14 | seta-e6522 15 | STORAGE_BUCKET 16 | seta-e6522.appspot.com 17 | IS_ADS_ENABLED 18 | 19 | IS_ANALYTICS_ENABLED 20 | 21 | IS_APPINVITE_ENABLED 22 | 23 | IS_GCM_ENABLED 24 | 25 | IS_SIGNIN_ENABLED 26 | 27 | GOOGLE_APP_ID 28 | 1:126154503966:ios:27c1f4b0d3093c2b45fb0f 29 | 30 | -------------------------------------------------------------------------------- /Projects/App/Resources/LaunchScreen.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | -------------------------------------------------------------------------------- /Projects/App/Seta.entitlements: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | aps-environment 6 | development 7 | com.apple.developer.icloud-container-identifiers 8 | 9 | iCloud.creative8.Seta 10 | 11 | com.apple.developer.icloud-services 12 | 13 | CloudDocuments 14 | CloudKit 15 | 16 | com.apple.developer.ubiquity-container-identifiers 17 | 18 | iCloud.creative8.Seta 19 | 20 | com.apple.security.app-sandbox 21 | 22 | keychain-access-groups 23 | 24 | $(AppIdentifierPrefix)com.creative8.seta.Seta 25 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /Projects/App/Setlist.xcodeproj/xcuserdata/a_mcflurry.xcuserdatad/xcschemes/xcschememanagement.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | SchemeUserState 6 | 7 | Setlist.xcscheme_^#shared#^_ 8 | 9 | isShown 10 | 11 | orderHint 12 | 2 13 | 14 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /Projects/App/Setlist.xcodeproj/xcuserdata/kohyeji.xcuserdatad/xcschemes/xcschememanagement.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | SchemeUserState 6 | 7 | Setlist.xcscheme_^#shared#^_ 8 | 9 | isShown 10 | 11 | orderHint 12 | 2 13 | 14 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /Projects/App/Setlist.xcodeproj/xcuserdata/yeseul.xcuserdatad/xcschemes/xcschememanagement.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | SchemeUserState 6 | 7 | Setlist.xcscheme_^#shared#^_ 8 | 9 | isShown 10 | 11 | orderHint 12 | 2 13 | 14 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /Projects/App/Sources/App.swift: -------------------------------------------------------------------------------- 1 | // 2 | // App.swift 3 | // ProjectDescriptionHelpers 4 | // 5 | // Created by 최효원 on 2023/10/06. 6 | // 7 | 8 | import SwiftUI 9 | import SwiftData 10 | import Feature 11 | import Core 12 | import Firebase 13 | 14 | @main 15 | struct SetlistApp: App { 16 | @State var networkMonitor = NetworkMonitor() 17 | @StateObject private var appState = AppState() 18 | 19 | var sharedModelContainer: ModelContainer = { 20 | let schema = Schema([ 21 | ArchivedConcertInfo.self, LikeArtist.self, SearchHistory.self 22 | ]) 23 | let modelConfiguration = ModelConfiguration(schema: schema, isStoredInMemoryOnly: false) 24 | 25 | do { 26 | return try ModelContainer(for: schema, configurations: [modelConfiguration]) 27 | } catch { 28 | fatalError("Could not create ModelContainer: \(error)") 29 | } 30 | }() 31 | 32 | init() { 33 | FirebaseApp.configure() 34 | Thread.sleep(forTimeInterval: 2) 35 | } 36 | 37 | var body: some Scene { 38 | WindowGroup { 39 | if appState.isOnboarding { 40 | OnboardingView() 41 | .environmentObject(appState) 42 | .onAppear() { 43 | AnalyticsEvent.trackScreen(screenName: AnalyticsEvent.Screen.onboarding, screenClass: "OnboardingView") 44 | } 45 | } else { 46 | TabBarView() 47 | } 48 | } 49 | .modelContainer(sharedModelContainer) 50 | .environmentObject(networkMonitor) 51 | 52 | } 53 | } 54 | 55 | extension UINavigationController { 56 | open override func viewWillLayoutSubviews() { 57 | navigationBar.topItem?.backBarButtonItem = UIBarButtonItem(title: "", style: .plain, target: nil, action: nil) 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /Projects/App/Support/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | $(DEVELOPMENT_LANGUAGE) 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleLocalizations 14 | 15 | ko 16 | 17 | CFBundleName 18 | $(PRODUCT_NAME) 19 | CFBundlePackageType 20 | $(PRODUCT_BUNDLE_PACKAGE_TYPE) 21 | CFBundleShortVersionString 22 | 2.0.0 23 | CFBundleURLTypes 24 | 25 | 26 | CFBundleURLSchemes 27 | 28 | fbAPP-ID 29 | 30 | 31 | 32 | CFBundleTypeRole 33 | Editor 34 | CFBundleURLName 35 | com.create8.seta.Seta 36 | CFBundleURLSchemes 37 | 38 | seta-login 39 | 40 | 41 | 42 | CFBundleVersion 43 | 2 44 | LSApplicationQueriesSchemes 45 | 46 | instagram-stories 47 | 48 | LSRequiresIPhoneOS 49 | 50 | NSAppleMusicUsageDescription 51 | NSAppleMusicUsageDescription 52 | NSPhotoLibraryUsageDescription 53 | NSPhotoLibraryUsageDescription 54 | UIApplicationSceneManifest 55 | 56 | UIApplicationSupportsMultipleScenes 57 | 58 | UISceneConfigurations 59 | 60 | UIWindowSceneSessionRoleApplication 61 | 62 | 63 | UISceneConfigurationName 64 | Default Configuration 65 | UISceneDelegateClassName 66 | $(PRODUCT_MODULE_NAME).SceneDelegate 67 | 68 | 69 | 70 | 71 | UIApplicationSupportsIndirectInputEvents 72 | 73 | UIBackgroundModes 74 | 75 | remote-notification 76 | 77 | UILaunchStoryboardName 78 | LaunchScreen 79 | UIRequiredDeviceCapabilities 80 | 81 | armv7 82 | 83 | UISupportedInterfaceOrientations 84 | 85 | UIInterfaceOrientationPortrait 86 | 87 | UISupportedInterfaceOrientations~ipad 88 | 89 | UIInterfaceOrientationLandscapeLeft 90 | UIInterfaceOrientationLandscapeRight 91 | UIInterfaceOrientationPortrait 92 | UIInterfaceOrientationPortraitUpsideDown 93 | 94 | 95 | 96 | -------------------------------------------------------------------------------- /Projects/Core/Core.xcodeproj/xcuserdata/a_mcflurry.xcuserdatad/xcschemes/xcschememanagement.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | SchemeUserState 6 | 7 | Core.xcscheme_^#shared#^_ 8 | 9 | isShown 10 | 11 | orderHint 12 | 0 13 | 14 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /Projects/Core/Core.xcodeproj/xcuserdata/kohyeji.xcuserdatad/xcschemes/xcschememanagement.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | SchemeUserState 6 | 7 | Core.xcscheme_^#shared#^_ 8 | 9 | isShown 10 | 11 | orderHint 12 | 0 13 | 14 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /Projects/Core/Core.xcodeproj/xcuserdata/yeseul.xcuserdatad/xcschemes/xcschememanagement.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | SchemeUserState 6 | 7 | Core.xcscheme_^#shared#^_ 8 | 9 | isShown 10 | 11 | orderHint 12 | 0 13 | 14 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /Projects/Core/Project.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Project.swift 3 | // ProjectDescriptionHelpers 4 | // 5 | // Created by 최효원 on 2023/10/06. 6 | // 7 | 8 | import ProjectDescription 9 | import ProjectDescriptionHelpers 10 | 11 | let project = Project.makeModule( 12 | name: "Core", 13 | product: .staticFramework, 14 | dependencies: [ 15 | .SPM.KeychainAccess, 16 | .SPM.SpotifyAPI 17 | ], 18 | sources: ["Sources/**"] 19 | ) 20 | -------------------------------------------------------------------------------- /Projects/Core/Sources/Model/AccessTokenModel.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AccessTokenModel.swift 3 | // Core 4 | // 5 | // Created by A_Mcflurry on 6/30/24. 6 | // Copyright © 2024 com.creative8.seta. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | public struct AccessTokenModel: Decodable { 12 | private let accessToken: String 13 | private let tokenType: String 14 | 15 | public var token: String { 16 | return "\(tokenType) \(accessToken)" 17 | } 18 | 19 | enum CodingKeys: String, CodingKey { 20 | case accessToken = "access_token" 21 | case tokenType = "token_type" 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /Projects/Core/Sources/Model/AccessTokenRequestModel.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AccessTokenRequestModel.swift 3 | // Core 4 | // 5 | // Created by A_Mcflurry on 6/30/24. 6 | // Copyright © 2024 com.creative8.seta. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | -------------------------------------------------------------------------------- /Projects/Core/Sources/Model/ArchivedConcertInfo/ArchivedConcertInfo.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ArchivedConcertInfo.swift 3 | // Core 4 | // 5 | // Created by A_Mcflurry on 10/22/23. 6 | // Copyright © 2023 com.creative8.seta. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import SwiftData 11 | 12 | @Model 13 | public class ArchivedConcertInfo { 14 | public var id: UUID? 15 | public var artistInfo: SaveArtistInfo = SaveArtistInfo(name: "", country: "", alias: "", mbid: "", gid: 0, imageUrl: "", songList: []) 16 | public var setlist: SaveSetlist = SaveSetlist(setlistId: "", date: Date(), venue: "", title: "", city: "", country: "") 17 | 18 | public init(artistInfo: SaveArtistInfo, setlist: SaveSetlist) { 19 | self.id = UUID() 20 | self.artistInfo = artistInfo 21 | self.setlist = setlist 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /Projects/Core/Sources/Model/ArchivedConcertInfo/SaveSetlist.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SaveSetlist.swift 3 | // Core 4 | // 5 | // Created by A_Mcflurry on 10/22/23. 6 | // Copyright © 2023 com.creative8.seta. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | public struct SaveSetlist: Codable { 12 | public var setlistId: String 13 | public var date: Date 14 | public var venue: String 15 | public var title: String 16 | public var city: String 17 | public var country: String 18 | 19 | public init(setlistId: String, date: Date, venue: String, title: String, city: String, country: String) { 20 | self.setlistId = setlistId 21 | self.date = date 22 | self.venue = venue 23 | self.title = title 24 | self.city = city 25 | self.country = country 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /Projects/Core/Sources/Model/ArtistInfoModel.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ArtistInfoModel.swift 3 | // Core 4 | // 5 | // Created by 고혜지 on 10/18/23. 6 | // Copyright © 2023 com.creative8. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | public struct ArtistInfo { 12 | public var name: String 13 | public var alias: String? 14 | public var mbid: String 15 | public var gid: Int? 16 | public var imageUrl: String? 17 | public var songList: [Titles]? 18 | 19 | public init(name: String, alias: String? = nil, mbid: String, gid: Int? = nil, imageUrl: String? = nil, songList: [Titles]? = nil) { 20 | self.name = name 21 | self.alias = alias 22 | self.mbid = mbid 23 | self.gid = gid 24 | self.imageUrl = imageUrl 25 | self.songList = songList 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /Projects/Core/Sources/Model/ArtistInfoModel/SaveArtistInfo.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ArtistInfoModel.swift 3 | // Core 4 | // 5 | // Created by A_Mcflurry on 10/22/23. 6 | // Copyright © 2023 com.creative8.seta. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import SwiftData 11 | 12 | public struct SaveArtistInfo: Codable, Hashable { 13 | public var name: String 14 | public var country: String 15 | public var alias: String 16 | public var mbid: String 17 | public var gid: Int 18 | public var imageUrl: String 19 | public var songList: [Titles] 20 | 21 | public init(name: String, country: String, alias: String, mbid: String, gid: Int, imageUrl: String, songList: [Titles]) { 22 | self.name = name 23 | self.country = country 24 | self.alias = alias 25 | self.mbid = mbid 26 | self.gid = gid 27 | self.imageUrl = imageUrl 28 | self.songList = songList 29 | } 30 | } 31 | 32 | public struct Titles: Codable, Hashable { 33 | public var title: String 34 | public var subTitle: String 35 | 36 | public init(title: String, subTitle: String) { 37 | self.title = title 38 | self.subTitle = subTitle 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /Projects/Core/Sources/Model/ArtistListModel.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ArtistListModel.swift 3 | // Core 4 | // 5 | // Created by 고혜지 on 10/9/23. 6 | // Copyright © 2023 com.creative8. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | // MARK: - ArtistListModel 12 | public struct ArtistListModel: Codable { 13 | public let created: String? 14 | public let count: Int? 15 | public let offset: Int? 16 | public let artists: [MusicBrainzArtist]? 17 | } 18 | 19 | // MARK: - Artist 20 | public struct MusicBrainzArtist: Codable { 21 | public let id: String? 22 | public let name: String? 23 | public let sortName: String? 24 | public let country: String? 25 | public let area: Area? 26 | public let beginArea: Area? 27 | public let aliases: [Alias]? 28 | } 29 | 30 | // MARK: - Alias 31 | public struct Alias: Codable { 32 | public let sortName: String? 33 | public let typeID: String? 34 | public let name: String? 35 | public let locale: String? 36 | public let type: String? 37 | public let primary: Bool? 38 | } 39 | 40 | // MARK: - Area 41 | public struct Area: Codable { 42 | public let id: String? 43 | public let type: String? 44 | public let typeID: String? 45 | public let name: String? 46 | public let sortName: String? 47 | } 48 | -------------------------------------------------------------------------------- /Projects/Core/Sources/Model/GeniusArtistsModel.swift: -------------------------------------------------------------------------------- 1 | // 2 | // GeniusArtistsModel.swift 3 | // Core 4 | // 5 | // Created by 고혜지 on 10/9/23. 6 | // Copyright © 2023 com.creative8. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | // MARK: - GeniusArtistsModel 12 | public struct GeniusArtistsModel: Codable { 13 | public let response: ArtistsResponse? 14 | } 15 | 16 | // MARK: - Response 17 | public struct ArtistsResponse: Codable { 18 | public let hits: [Hit]? 19 | } 20 | 21 | // MARK: - Hit 22 | public struct Hit: Codable { 23 | public let result: Result? 24 | } 25 | 26 | // MARK: - Result 27 | public struct Result: Codable { 28 | public let artistNames: String? 29 | public let headerImageThumbnailURL: String? 30 | public let headerImageURL: String? 31 | public let songArtImageThumbnailURL: String? 32 | public let songArtImageURL: String? 33 | public let primaryArtist: PrimaryArtist? 34 | 35 | enum CodingKeys: String, CodingKey { 36 | case artistNames = "artist_names" 37 | case headerImageThumbnailURL = "header_image_thumbnail_url" 38 | case headerImageURL = "header_image_url" 39 | case songArtImageThumbnailURL = "song_art_image_thumbnail_url" 40 | case songArtImageURL = "song_art_image_url" 41 | case primaryArtist = "primary_artist" 42 | } 43 | } 44 | 45 | // MARK: - PrimaryArtist 46 | public struct PrimaryArtist: Codable { 47 | public let headerImageURL: String? 48 | public let id: Int? 49 | public let imageURL: String? 50 | public let name: String? 51 | 52 | enum CodingKeys: String, CodingKey { 53 | case headerImageURL = "header_image_url" 54 | case id 55 | case imageURL = "image_url" 56 | case name 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /Projects/Core/Sources/Model/GeniusSongsModel.swift: -------------------------------------------------------------------------------- 1 | // 2 | // GeniusSongsModel.swift 3 | // Core 4 | // 5 | // Created by 고혜지 on 10/9/23. 6 | // Copyright © 2023 com.creative8. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | // MARK: - GeniusSongsModel 12 | public struct GeniusSongsModel: Codable { 13 | public let response: SongsResponse? 14 | } 15 | 16 | // MARK: - Response 17 | public struct SongsResponse: Codable { 18 | public let songs: [GeniusSong]? 19 | public let nextPage: Int? 20 | 21 | enum CodingKeys: String, CodingKey { 22 | case songs 23 | case nextPage = "next_page" 24 | } 25 | } 26 | 27 | // MARK: - Song 28 | public struct GeniusSong: Codable { 29 | public let headerImageThumbnailURL: String? 30 | public let headerImageURL: String? 31 | public let id: Int? 32 | public let songArtImageThumbnailURL: String? 33 | public let songArtImageURL: String? 34 | public let title, titleWithFeatured: String? 35 | 36 | enum CodingKeys: String, CodingKey { 37 | case headerImageThumbnailURL = "header_image_thumbnail_url" 38 | case headerImageURL = "header_image_url" 39 | case id 40 | case songArtImageThumbnailURL = "song_art_image_thumbnail_url" 41 | case songArtImageURL = "song_art_image_url" 42 | case title 43 | case titleWithFeatured = "title_with_featured" 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /Projects/Core/Sources/Model/LikeArtist/LikeArtistModel.swift: -------------------------------------------------------------------------------- 1 | // 2 | // LikeArtistModel.swift 3 | // Feature 4 | // 5 | // Created by A_Mcflurry on 10/17/23. 6 | // Copyright © 2023 com.creative8. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import SwiftData 11 | 12 | @Model 13 | public final class LikeArtist { 14 | public var id: UUID? 15 | public var artistInfo: SaveArtistInfo = SaveArtistInfo(name: "", country: "", alias: "", mbid: "", gid: 0, imageUrl: "", songList: []) 16 | public var orderIndex: Int = 0 17 | 18 | public init(artistInfo: SaveArtistInfo, orderIndex: Int) { 19 | self.id = UUID() 20 | self.artistInfo = artistInfo 21 | self.orderIndex = orderIndex 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /Projects/Core/Sources/Model/OnboardingModel.swift: -------------------------------------------------------------------------------- 1 | // 2 | // OnboardingModel.swift 3 | // Core 4 | // 5 | // Created by 최효원 on 11/2/23. 6 | // Copyright © 2023 com.creative8.seta. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | public struct OnboardingModel: Decodable, Hashable { 12 | public var name: String 13 | public var mbid: String 14 | public var alias: String 15 | public var url: String? 16 | public var gid: Int 17 | public var country: String 18 | public var tags: [String]? 19 | } 20 | 21 | public enum OnboardingFilterType: LocalizedStringResource, CaseIterable { 22 | case all = "전체" // 전체 23 | case kpop = "케이팝" // 케이팝 24 | case pop = "팝" // 팝 25 | case hiphop = "힙합" // 힙합 26 | case rnb = "알앤비" // 알앤비 27 | case rock = "락" // 락 28 | case metal = "메탈" // 메탈 29 | case elctronic = "일렉트로닉" // 일렉트로닉 30 | case folk = "포크" // 포크 31 | case jpop = "제이팝" // 제이팝 32 | } 33 | -------------------------------------------------------------------------------- /Projects/Core/Sources/Model/SearchHistory/SearchHistoryModel.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SearchHistoryModel.swift 3 | // Core 4 | // 5 | // Created by A_Mcflurry on 10/10/23. 6 | // Copyright © 2023 com.creative8. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import SwiftData 11 | 12 | @Model 13 | public final class SearchHistory { 14 | public var id: UUID? 15 | public var artistInfo: SaveArtistInfo = SaveArtistInfo(name: "", country: "", alias: "", mbid: "", gid: 0, imageUrl: "", songList: []) 16 | public var createdDate: Date = Date() 17 | 18 | init(artistInfo: SaveArtistInfo) { 19 | self.id = UUID() 20 | self.artistInfo = artistInfo 21 | self.createdDate = Date() 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /Projects/Core/Sources/Model/SearchModel.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SearchModel.swift 3 | // Core 4 | // 5 | // Created by 최효원 on 11/8/23. 6 | // Copyright © 2023 com.creative8.seta. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | public let koreanArtistModel: [ArtistInfo] = [ 12 | ArtistInfo(name: "BTS", mbid: "0d79fe8e-ba27-4859-bb8c-2f255f346853", imageUrl: "BTS"), 13 | ArtistInfo(name: "NewJeans", mbid: "49204a7a-ed85-407a-828f-6fd46f1d8126", imageUrl: "NewJeans"), 14 | ArtistInfo(name: "Jay Park", mbid: "3dda8202-ce15-4031-862a-77bc6759d15e", imageUrl: "JayPark"), 15 | ArtistInfo(name: "DAY6", mbid: "77191d5f-1045-4b3b-ad7f-12ea398db929", imageUrl: "DAY6"), 16 | ArtistInfo(name: "카더가든", mbid: "9f2862c0-22a9-4fd2-9f1c-07e17f356137", imageUrl: "CarTheGarden"), 17 | ArtistInfo(name: "Paul Kim", mbid: "8968d1ad-c830-4ab2-92d2-bd6bea0b6f42", imageUrl: "PaulKim"), 18 | ArtistInfo(name: "Stray Kids", mbid: "142b343d-bf5a-428c-a64f-6d1a7566bbe9", imageUrl: "StrayKids"), 19 | ArtistInfo(name: "NCT DREAM", mbid: "dc7c8277-d4b6-4bca-bdfe-50eee42536ac", imageUrl: "NCTDream"), 20 | ArtistInfo(name: "이영지", mbid: "0433e75d-70b6-4118-9ff8-9b6f7e534093", imageUrl: "LeeYoungJi"), 21 | ArtistInfo(name: "윤하", mbid: "5e3bc4c7-adbe-40e0-b56e-57d755908d52", imageUrl: "Yoonha") 22 | ] 23 | 24 | public let foreignArtistModel: [ArtistInfo] = [ 25 | ArtistInfo(name: "Bruno Mars", mbid: "afb680f2-b6eb-4cd7-a70b-a63b25c763d5", imageUrl: "BrunoMars"), 26 | ArtistInfo(name: "The Weekend", mbid: "c8b03190-306c-4120-bb0b-6f2ebfc06ea9", imageUrl: "TheWeekend"), 27 | ArtistInfo(name: "Maroon5", mbid: "0ab49580-c84f-44d4-875f-d83760ea2cfe", imageUrl: "Maroon5"), 28 | ArtistInfo(name: "Post Malone", mbid: "b1e26560-60e5-4236-bbdb-9aa5a8d5ee19", imageUrl: "PostMalone"), 29 | ArtistInfo(name: "Sam Smith", mbid: "5a85c140-dcf9-4dd2-b2c8-aff0471549f3", imageUrl: "SamSmith"), 30 | ArtistInfo(name: "Doja Cat", mbid: "5df62a88-cac9-490a-b62c-c7c88f4020f4", imageUrl: "DojaCat"), 31 | ArtistInfo(name: "Eminem", mbid: "b95ce3ff-3d05-4e87-9e01-c97b66af13d4", imageUrl: "Eminem"), 32 | ArtistInfo(name: "Dua Lipa", mbid: "6f1a58bf-9b1b-49cf-a44a-6cefad7ae04f", imageUrl: "DuaLipa"), 33 | ArtistInfo(name: "John K", mbid: "be465d46-9615-4722-b828-ea531ff97ea3", imageUrl: "JohnK"), 34 | ArtistInfo(name: "Troye Sivan", mbid: "e5712ceb-c37a-4c49-a11c-ccf4e21852d4", imageUrl: "TroyeSivan"), 35 | ArtistInfo(name: "Drake", mbid: "9fff2f8a-21e6-47de-a2b8-7f449929d43f", imageUrl: "Drake") 36 | 37 | ] 38 | -------------------------------------------------------------------------------- /Projects/Core/Sources/Model/SetlistListModel.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SetlistListModel.swift 3 | // Core 4 | // 5 | // Created by 고혜지 on 10/9/23. 6 | // Copyright © 2023 com.creative8. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | // MARK: - SetlistListModel 12 | public struct SetlistListModel: Codable { 13 | public let type: String? 14 | public let itemsPerPage: Int? 15 | public let page: Int? 16 | public let total: Int? 17 | public let setlist: [Setlist]? 18 | } 19 | 20 | // MARK: - Setlist 21 | public struct Setlist: Codable { 22 | public let id: String? 23 | public let versionId: String? 24 | public let eventDate: String? 25 | public let lastUpdated: String? 26 | public let artist: Artist? 27 | public let venue: Venue? 28 | public let sets: Sets? 29 | public let url: String? 30 | public let info: String? 31 | public let tour: Tour? 32 | 33 | public init(id: String?, versionId: String?, eventDate: String?, lastUpdated: String?, artist: Artist?, venue: Venue?, sets: Sets?, url: String?, info: String?, tour: Tour?) { 34 | self.id = id 35 | self.versionId = versionId 36 | self.eventDate = eventDate 37 | self.lastUpdated = lastUpdated 38 | self.artist = artist 39 | self.venue = venue 40 | self.sets = sets 41 | self.url = url 42 | self.info = info 43 | self.tour = tour 44 | } 45 | } 46 | 47 | // MARK: - Artist 48 | public struct Artist: Codable, Hashable, Equatable { 49 | public let mbid: String? 50 | public let name: String? 51 | public let sortName: String? 52 | public let disambiguation: String? 53 | public let url: String? 54 | 55 | public init(mbid: String?, name: String?, sortName: String?, disambiguation: String?, url: String?) { 56 | self.mbid = mbid 57 | self.name = name 58 | self.sortName = sortName 59 | self.disambiguation = disambiguation 60 | self.url = url 61 | } 62 | } 63 | 64 | // MARK: - Sets 65 | public struct Sets: Codable { 66 | public let setsSet: [Session]? 67 | 68 | public init(setsSet: [Session]?) { 69 | self.setsSet = setsSet 70 | } 71 | 72 | enum CodingKeys: String, CodingKey { 73 | case setsSet = "set" 74 | } 75 | } 76 | 77 | // MARK: - Session 78 | public struct Session: Codable, Hashable { 79 | public let song: [Song]? 80 | public let encore: Int? 81 | public let name: String? 82 | 83 | public init(song: [Song]?, encore: Int?, name: String?) { 84 | self.song = song 85 | self.encore = encore 86 | self.name = name 87 | } 88 | } 89 | 90 | extension Session: Equatable { 91 | public static func == (lhs: Session, rhs: Session) -> Bool { 92 | return lhs.song == rhs.song && 93 | lhs.encore == rhs.encore && 94 | lhs.name == rhs.name 95 | } 96 | } 97 | 98 | // MARK: - Song 99 | public struct Song: Codable, Hashable, Equatable { 100 | public static func == (lhs: Song, rhs: Song) -> Bool { 101 | return lhs.name == rhs.name && lhs.info == rhs.info && lhs.cover == rhs.cover && lhs.tape == rhs.tape 102 | } 103 | 104 | public let name: String? 105 | public let info: String? 106 | public let cover: Artist? 107 | public let tape: Bool? 108 | } 109 | 110 | // MARK: - Tour 111 | public struct Tour: Codable { 112 | public let name: String? 113 | } 114 | 115 | // MARK: - Venue 116 | public struct Venue: Codable { 117 | public let id: String? 118 | public let name: String? 119 | public let city: City? 120 | public let url: String? 121 | } 122 | 123 | // MARK: - City 124 | public struct City: Codable { 125 | public let id: String? 126 | public let name: String? 127 | public let state: String? 128 | public let stateCode: String? 129 | public let coords: Coords? 130 | public let country: Country? 131 | } 132 | 133 | // MARK: - Coords 134 | public struct Coords: Codable { 135 | public let lat: Double? 136 | public let long: Double? 137 | } 138 | 139 | // MARK: - Country 140 | public struct Country: Codable { 141 | public let code: String? 142 | public let name: String? 143 | } 144 | -------------------------------------------------------------------------------- /Projects/Core/Sources/Service/APIKeys.swift: -------------------------------------------------------------------------------- 1 | // 2 | // APIKeys.swift 3 | // Core 4 | // 5 | // Created by 고혜지 on 10/12/23. 6 | // Copyright © 2023 com.creative8. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | struct APIKeys { 12 | let setlistFM = "YOUR_API_KEY" 13 | let musicBrainz = "YOUR_API_KEY" 14 | let genius = "YOUR_API_KEY" 15 | } 16 | -------------------------------------------------------------------------------- /Projects/Core/Sources/Service/AppState.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AppState.swift 3 | // Core 4 | // 5 | // Created by 예슬 on 3/7/25. 6 | // Copyright © 2025 com.creative8.seta. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | public class AppState: ObservableObject { 12 | @Published public var isOnboarding: Bool { 13 | didSet { 14 | UserDefaults.standard.set(isOnboarding, forKey: "isOnboarding") 15 | } 16 | } 17 | 18 | public init() { 19 | if UserDefaults.standard.object(forKey: "isOnboarding") == nil { 20 | self.isOnboarding = true 21 | UserDefaults.standard.set(true, forKey: "isOnboarding") 22 | } else { 23 | self.isOnboarding = UserDefaults.standard.bool(forKey: "isOnboarding") 24 | } 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /Projects/Core/Sources/Service/AppleMusicService.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AppleMusicService.swift 3 | // Core 4 | // 5 | // Created by 최효원 on 10/18/23. 6 | // Copyright © 2023 com.creative8.seta. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import MusicKit 11 | import StoreKit 12 | import Combine 13 | 14 | public final class AppleMusicService: MusicPlaylistAddable, Sendable { 15 | public var id: MusicItemID = "" 16 | 17 | public init() { 18 | } 19 | 20 | actor SearchResultsManager { 21 | private var results: [(Int, MusicCatalogSearchResponse?)] = [] 22 | 23 | func append(_ result: (Int, MusicCatalogSearchResponse?)) { 24 | results.append(result) 25 | } 26 | 27 | func getSortedResults() -> [(Int, MusicCatalogSearchResponse?)] { 28 | return results.sorted { $0.0 < $1.0 } 29 | } 30 | } 31 | 32 | public func addPlayList(name: String, musicList: [(String, String?)], venue: String?) { 33 | let searchResultsManager = SearchResultsManager() 34 | 35 | Task { 36 | // musicList를 index가 들어간 튜플로 변환 37 | let indexedMusicList = musicList.enumerated().map { (index, music) in 38 | (index, music) 39 | } 40 | 41 | // 모든 음악을 비동기로 검색 42 | await withTaskGroup(of: Void.self) { group in 43 | for (index, music) in indexedMusicList { 44 | group.addTask { 45 | 46 | do { 47 | let response = try await self.searchMusic(title: music.0, singer: music.1 ?? "") 48 | 49 | // actor를 통해 안전하게 searchResults에 추가 50 | await searchResultsManager.append((index, response)) 51 | } catch { 52 | print("Error searching for '\(music.0)': \(error)") 53 | 54 | // 실패한 경우에도 인덱스를 유지 55 | await searchResultsManager.append((index, nil)) 56 | } 57 | } 58 | } 59 | } 60 | 61 | // 검색 결과를 인덱스 순서대로 정렬 62 | let sortedResults = await searchResultsManager.getSortedResults() 63 | 64 | // 유효한 곡만 필터링하여 배열로 변환 65 | let songsToAdd = sortedResults.compactMap { $0.1?.songs.first } 66 | 67 | // 플레이리스트 생성 및 곡 추가를 더 빠르게 처리 68 | let _ = try await MusicLibrary.shared.createPlaylist( 69 | name: name, 70 | description: "Seta에서 생성된 플레이리스트 입니다.", 71 | authorDisplayName: venue, 72 | items: songsToAdd 73 | ) 74 | } 75 | } 76 | 77 | // MusicCatalogSearchRequest를 비동기로 처리하는 함수 78 | private func searchMusic(title: String, singer: String) async throws -> MusicCatalogSearchResponse { 79 | var request = MusicCatalogSearchRequest(term: "\(singer) \(title)", types: [MusicKit.Song.self]) 80 | request.includeTopResults = true 81 | let response = try await request.response() 82 | return response 83 | } 84 | 85 | public func requestMusicAuthorization() { 86 | SKCloudServiceController.requestAuthorization { [weak self] status in 87 | switch status { 88 | case .authorized: 89 | print("Apple Music authorized") 90 | case .restricted: 91 | print("Apple Music authorization restricted") 92 | case .notDetermined: 93 | print("Apple Music authorization not determined") 94 | case .denied: 95 | print("Apple Music authorization denied") 96 | @unknown default: 97 | print("") 98 | } 99 | } 100 | } 101 | 102 | } 103 | 104 | public final class CheckAppleMusicSubscription: ObservableObject { 105 | @Published var check: Bool = false 106 | public static let shared: CheckAppleMusicSubscription = CheckAppleMusicSubscription() 107 | // 사용자가 애플 뮤직을 구독 중인지 확인 108 | public init() { 109 | } 110 | 111 | public func appleMusicSubscription(completion: @escaping (Bool) -> Void) { 112 | SKCloudServiceController().requestCapabilities { (capability: SKCloudServiceCapability, err: Error?) in 113 | guard err == nil else { return } 114 | if capability.contains(SKCloudServiceCapability.musicCatalogPlayback) { 115 | self.check = true 116 | } else if capability.contains(SKCloudServiceCapability.musicCatalogSubscriptionEligible) { 117 | self.check = false 118 | } 119 | completion(self.check) 120 | } 121 | } 122 | } 123 | -------------------------------------------------------------------------------- /Projects/Core/Sources/Service/ArtistFetchService.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ArtistFetchService.swift 3 | // Core 4 | // 5 | // Created by A_Mcflurry on 11/25/23. 6 | // Copyright © 2023 com.creative8.seta. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | public final class ArtistFetchService: ObservableObject { 12 | public init() { } 13 | 14 | @Published public var allArtist: [OnboardingModel] = [] 15 | 16 | // 주 요청 URL을 새로 받은 URL로 변경 17 | private let urls: [URL] = [ 18 | URL(string: "https://port-0-seta-server-bkcl2bloxy1ug8.sel5.cloudtype.app/api/getArtists")!, 19 | URL(string: "https://seta-server.fly.dev/api/getArtists")! 20 | ] 21 | 22 | public func fetchData(completion: @escaping (Bool) -> Void) { 23 | fetch(at: 0, completion: completion) 24 | } 25 | 26 | private func fetch(at index: Int, completion: @escaping (Bool) -> Void) { 27 | guard index < urls.count else { 28 | completion(false) 29 | return 30 | } 31 | 32 | var request: URLRequest = URLRequest(url: urls[index], timeoutInterval: 5) 33 | 34 | URLSession.shared.dataTask(with: request) { [weak self] data, _, error in 35 | guard let self else { return } 36 | 37 | guard let data, 38 | let artists = try? JSONDecoder().decode([OnboardingModel].self, from: data) else { fetch(at: index + 1, completion: completion); return } 39 | 40 | Task { @MainActor in 41 | self.allArtist = artists 42 | completion(true) 43 | } 44 | }.resume() 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /Projects/Core/Sources/Service/NetworkManager.swift: -------------------------------------------------------------------------------- 1 | // 2 | // NetworkManager.swift 3 | // Core 4 | // 5 | // Created by 장수민 on 2/26/24. 6 | // Copyright © 2024 com.creative8.seta. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import Network 11 | 12 | @Observable 13 | public final class NetworkMonitor: ObservableObject { 14 | public let networkMonitor = NWPathMonitor() 15 | public let queue = DispatchQueue(label: "Monitor") 16 | public var isConnected = false 17 | 18 | public init() { 19 | networkMonitor.pathUpdateHandler = { path in 20 | self.isConnected = path.status == .satisfied 21 | } 22 | networkMonitor.start(queue: queue) 23 | } 24 | 25 | public func startMonitoring() { 26 | networkMonitor.start(queue: queue) 27 | networkMonitor.pathUpdateHandler = { path in 28 | self.isConnected = path.status == .satisfied 29 | 30 | if path.usesInterfaceType(.wifi) { 31 | print("Using wifi") 32 | } else if path.usesInterfaceType(.cellular) { 33 | print("Using cellular") 34 | } 35 | } 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /Projects/Core/Sources/Service/SearchHistoryDataManager/KoreanConverter.swift: -------------------------------------------------------------------------------- 1 | // 2 | // KoreanConverter.swift 3 | // Core 4 | // 5 | // Created by 고혜지 on 10/18/23. 6 | // Copyright © 2023 com.creative8. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | public final class KoreanConverter { 12 | public init() { 13 | 14 | } 15 | 16 | public func isKorean() -> Bool { 17 | guard let languageCode = Locale.current.language.languageCode?.identifier else { return false } 18 | return languageCode == "ko" 19 | } 20 | 21 | public func findKoreanName(artist: MusicBrainzArtist) -> (String, String?) { 22 | var primaryAlias: String? 23 | 24 | guard let aliases = artist.aliases else { return (artist.name!, nil) } 25 | 26 | if self.isKorean() { 27 | for alias in aliases { 28 | if ((alias.locale == "ko" || alias.locale == "ko_KR") && (alias.name?.lowercased() != artist.name?.lowercased())) { 29 | primaryAlias = alias.name 30 | return (artist.name!, primaryAlias) 31 | } 32 | } 33 | } 34 | 35 | for alias in aliases { 36 | if (alias.primary == true) && (alias.name?.lowercased() != artist.name?.lowercased()) { 37 | primaryAlias = alias.name 38 | return (artist.name!, primaryAlias) 39 | } 40 | } 41 | 42 | if let alias = artist.aliases?[0] { 43 | primaryAlias = alias.name 44 | } 45 | 46 | return (artist.name!, primaryAlias) 47 | } 48 | 49 | public func findKoreanTitle(title: String, songList: [Titles]) -> String? { 50 | for song in songList { 51 | if title.lowercased() == song.title.lowercased() { 52 | return title 53 | } else if title.lowercased() == song.subTitle.lowercased() { 54 | return song.title 55 | } 56 | } 57 | 58 | for song in songList { 59 | if compareSongTitles(title.lowercased(), song.subTitle.lowercased()) >= 0.6 { 60 | return song.title 61 | } 62 | } 63 | 64 | return nil 65 | } 66 | 67 | // Levenshtein Distance를 계산하는 함수 68 | private func levenshteinDistance(_ s1: String, _ s2: String) -> Int { 69 | let len1 = s1.count 70 | let len2 = s2.count 71 | var matrix = Array(repeating: Array(repeating: 0, count: len2 + 1), count: len1 + 1) 72 | 73 | for rowIndex in 0...len1 { 74 | matrix[rowIndex][0] = rowIndex 75 | } 76 | 77 | for colIndex in 0...len2 { 78 | matrix[0][colIndex] = colIndex 79 | } 80 | 81 | for (rowIndex, char1) in s1.enumerated() { 82 | for (colIndex, char2) in s2.enumerated() { 83 | let cost = char1 == char2 ? 0 : 1 84 | matrix[rowIndex + 1][colIndex + 1] = min( 85 | matrix[rowIndex][colIndex + 1] + 1, 86 | matrix[rowIndex + 1][colIndex] + 1, 87 | matrix[rowIndex][colIndex] + cost 88 | ) 89 | } 90 | } 91 | 92 | return matrix[len1][len2] 93 | } 94 | 95 | // 노래 제목 유사도 비교 함수 96 | private func compareSongTitles(_ title1: String, _ title2: String) -> Double { 97 | let distance = levenshteinDistance(title1, title2) 98 | let maxLength = max(title1.count, title2.count) 99 | return 1.0 - Double(distance) / Double(maxLength) 100 | } 101 | } 102 | -------------------------------------------------------------------------------- /Projects/Core/Sources/Service/SetlistDataService.swift: -------------------------------------------------------------------------------- 1 | // 2 | // DataService.swift 3 | // Core 4 | // 5 | // Created by 고혜지 on 10/9/23. 6 | // Copyright © 2023 com.creative8. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | public final class SetlistDataService { 12 | public init() { 13 | 14 | } 15 | 16 | private func APIRequest(url: URL, httpMethod: String, headers: [String: String], completion: @escaping (T?) -> Void) { 17 | var request = URLRequest(url: url) 18 | request.httpMethod = httpMethod 19 | 20 | for (headerField, headerValue) in headers { 21 | request.setValue(headerValue, forHTTPHeaderField: headerField) 22 | } 23 | 24 | let session = URLSession(configuration: .default) 25 | 26 | let task = session.dataTask(with: request) { data, _, error in 27 | if let error = error { 28 | print(error) 29 | completion(nil) 30 | } else if let JSONdata = data { 31 | let decoder = JSONDecoder() 32 | do { 33 | let decodedData = try decoder.decode(T.self, from: JSONdata) 34 | completion(decodedData) 35 | } catch let error { 36 | print(error) 37 | completion(nil) 38 | } 39 | } else { 40 | completion(nil) 41 | } 42 | } 43 | task.resume() 44 | } 45 | 46 | public func fetchArtistFromMusicBrainz(artistMbid: String, completion: @escaping (ArtistListModel?) -> Void) { 47 | if let url = URL(string: "https://musicbrainz.org/ws/2/artist/?query=mbid:\(artistMbid)&fmt=json") { 48 | let headers = ["Authorization": APIKeys().musicBrainz] 49 | APIRequest(url: url, httpMethod: "GET", headers: headers, completion: completion) 50 | } else { 51 | completion(nil) 52 | } 53 | } 54 | 55 | public func searchArtistsFromMusicBrainz(artistName: String, completion: @escaping (ArtistListModel?) -> Void) { 56 | if let url = URL(string: "https://musicbrainz.org/ws/2/artist?query=\(artistName)&fmt=json") { 57 | let headers = ["Authorization": APIKeys().musicBrainz] 58 | APIRequest(url: url, httpMethod: "GET", headers: headers, completion: completion) 59 | } else { 60 | completion(nil) 61 | } 62 | } 63 | 64 | public func fetchOneSetlistFromSetlistFM(setlistId: String, completion: @escaping (Setlist?) -> Void) { 65 | if let url = URL(string: "https://api.setlist.fm/rest/1.0/setlist/\(setlistId)") { 66 | let headers = [ 67 | "x-api-key": APIKeys().setlistFM, 68 | "Accept": "application/json", 69 | "Accept-Language": "en" 70 | ] 71 | APIRequest(url: url, httpMethod: "GET", headers: headers, completion: completion) 72 | } else { 73 | completion(nil) 74 | } 75 | } 76 | 77 | public func fetchSetlistsFromSetlistFM(artistMbid: String, page: Int, completion: @escaping (SetlistListModel?) -> Void) { 78 | if let url = URL(string: "https://api.setlist.fm/rest/1.0/search/setlists?artistMbid=\(artistMbid)&p=\(page)") { 79 | let headers = [ 80 | "x-api-key": APIKeys().setlistFM, 81 | "Accept": "application/json", 82 | "Accept-Language": "en" 83 | ] 84 | APIRequest(url: url, httpMethod: "GET", headers: headers, completion: completion) 85 | } else { 86 | completion(nil) 87 | } 88 | } 89 | 90 | public func searchArtistFromGenius(artistName: String, completion: @escaping (GeniusArtistsModel?) -> Void) { 91 | if let url = URL(string: "https://api.genius.com/search?q=\(artistName)") { 92 | print("request url: \(url)") 93 | let headers = ["Authorization": APIKeys().genius] 94 | APIRequest(url: url, httpMethod: "GET", headers: headers, completion: completion) 95 | } else { 96 | completion(nil) 97 | } 98 | } 99 | 100 | public func fetchSongsFromGenius(artistId: Int, page: Int, completion: @escaping (GeniusSongsModel?) -> Void) { 101 | if let url = URL(string: "https://api.genius.com/artists/\(artistId)/songs?page=\(page)&per_page=50") { 102 | let headers = ["Authorization": APIKeys().genius] 103 | APIRequest(url: url, httpMethod: "GET", headers: headers, completion: completion) 104 | } else { 105 | completion(nil) 106 | } 107 | } 108 | 109 | } 110 | -------------------------------------------------------------------------------- /Projects/Core/Sources/Service/UserDefaultsManger.swift: -------------------------------------------------------------------------------- 1 | // 2 | // UserDefaultsManger.swift 3 | // Core 4 | // 5 | // Created by A_Mcflurry on 6/30/24. 6 | // Copyright © 2024 com.creative8.seta. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | public final class UserDefaultsManger { 12 | enum UserDefaultsKey: String { 13 | case accessToken 14 | } 15 | 16 | public var accessToken: String { 17 | get { 18 | UserDefaults.standard.string(forKey: UserDefaultsKey.accessToken.rawValue) ?? "" 19 | } set { 20 | UserDefaults.standard.setValue(newValue, forKey: UserDefaultsKey.accessToken.rawValue) 21 | } 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /Projects/Feature/Feature.xcodeproj/xcuserdata/a_mcflurry.xcuserdatad/xcschemes/xcschememanagement.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | SchemeUserState 6 | 7 | Feature.xcscheme_^#shared#^_ 8 | 9 | isShown 10 | 11 | orderHint 12 | 1 13 | 14 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /Projects/Feature/Feature.xcodeproj/xcuserdata/kohyeji.xcuserdatad/xcschemes/xcschememanagement.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | SchemeUserState 6 | 7 | Feature.xcscheme_^#shared#^_ 8 | 9 | isShown 10 | 11 | orderHint 12 | 1 13 | 14 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /Projects/Feature/Feature.xcodeproj/xcuserdata/yeseul.xcuserdatad/xcschemes/xcschememanagement.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | SchemeUserState 6 | 7 | Feature.xcscheme_^#shared#^_ 8 | 9 | isShown 10 | 11 | orderHint 12 | 1 13 | 14 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /Projects/Feature/Project.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Project.swift 3 | // ProjectDescriptionHelpers 4 | // 5 | // Created by 최효원 on 2023/10/06. 6 | // 7 | 8 | import ProjectDescription 9 | import ProjectDescriptionHelpers 10 | 11 | let project = Project.makeModule( 12 | name: "Feature", 13 | product: .staticFramework, 14 | packages: [ 15 | .Firebase, 16 | .MarqueeText 17 | ], 18 | dependencies: [ 19 | .project(target: "Core", path: .relativeToRoot("Projects/Core")), 20 | .project(target: "UI", path: .relativeToRoot("Projects/UI")), 21 | .SPM.MarqueeText, 22 | .SPM.Firebase 23 | ], 24 | sources: ["Scenes/**"] 25 | ) 26 | -------------------------------------------------------------------------------- /Projects/Feature/Scenes/ArchiveScene/Component/ArchiveArtistCell.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ArchiveArtistCell.swift 3 | // Feature 4 | // 5 | // Created by A_Mcflurry on 10/14/23. 6 | // Copyright © 2023 com.creative8. All rights reserved. 7 | // 8 | 9 | import SwiftUI 10 | import UI 11 | 12 | struct ArchiveArtistCellImage: View { 13 | let artistUrl: URL? 14 | @Environment(\.colorScheme) var colorScheme 15 | var body: some View { 16 | Group { 17 | if let url = artistUrl { 18 | AsyncImage(url: url) { image in 19 | image 20 | .centerCropped() 21 | } placeholder: { 22 | ProgressView() 23 | } 24 | } else { 25 | if colorScheme == .light { 26 | Image(uiImage: UIImage(named: "artistViewTicket", in: Bundle(identifier: "com.creative8.seta.UI"), compatibleWith: nil)!) 27 | .centerCropped() 28 | .overlay( 29 | RoundedRectangle(cornerRadius: 12) 30 | .stroke(Color.gray, lineWidth: 1) // 색상과 선 두께를 원하는 대로 설정 31 | ) 32 | } else { 33 | Image(uiImage: UIImage(named: "darkArtistViewTicket", in: Bundle(identifier: "com.creative8.seta.UI"), compatibleWith: nil)!) 34 | .centerCropped() 35 | .overlay( 36 | RoundedRectangle(cornerRadius: 12) 37 | .stroke(Color.gray, lineWidth: 1) // 색상과 선 두께를 원하는 대로 설정 38 | ) 39 | } 40 | } 41 | } 42 | .aspectRatio(1.0, contentMode: .fit) 43 | .clipShape(RoundedRectangle(cornerRadius: 12)) 44 | .frameForCell() 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /Projects/Feature/Scenes/ArchiveScene/Component/ArtistSetCell.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ArtistSetCell.swift 3 | // Feature 4 | // 5 | // Created by A_Mcflurry on 10/31/23. 6 | // Copyright © 2023 com.creative8.seta. All rights reserved. 7 | // 8 | 9 | import SwiftUI 10 | import UI 11 | 12 | struct ArtistSetCell: View { 13 | let name: String 14 | let artistImgUrl: URL? 15 | let isSelected: Bool 16 | @Environment(\.colorScheme) var colorScheme 17 | 18 | var body: some View { 19 | HStack { 20 | artistImg 21 | Text(name) 22 | .font(.subheadline) 23 | .fontWeight(isSelected ? .semibold : .regular) 24 | .foregroundStyle(isSelected ? Color.mainWhite : Color.mainBlack) 25 | .frame(maxWidth: UIWidth * 0.17) 26 | } 27 | .padding(6) 28 | .background { 29 | let color = isSelected ? Color.mainBlack : Color.mainWhite 30 | color.clipShape(RoundedRectangle(cornerRadius: 12)) 31 | } 32 | .lineLimit(1) 33 | } 34 | 35 | @ViewBuilder 36 | private var artistImg: some View { 37 | Group { 38 | if let url = artistImgUrl { 39 | AsyncImage(url: url) { image in 40 | image 41 | .centerCropped() 42 | // .overlay( 43 | // RoundedRectangle(cornerRadius: 8) 44 | //// .stroke(Color.mainGrey1, lineWidth: 1) 45 | // .foregroundStyle(Color.clear) 46 | // ) 47 | } placeholder: { 48 | ProgressView() 49 | } 50 | } else { 51 | if colorScheme == .light { 52 | Image(uiImage: UIImage(named: "artistViewTicket", in: Bundle(identifier: "com.creative8.seta.UI"), compatibleWith: nil)!) 53 | .centerCropped() 54 | // .overlay( 55 | // RoundedRectangle(cornerRadius: 8) 56 | // .stroke(Color(UIColor.systemGray), lineWidth: 1) // 색상과 선 두께를 원하는 대로 설정 57 | // ) 58 | } else { 59 | Image(uiImage: UIImage(named: "darkArtistViewTicket", in: Bundle(identifier: "com.creative8.seta.UI"), compatibleWith: nil)!) 60 | .centerCropped() 61 | // .overlay( 62 | // RoundedRectangle(cornerRadius: 8) 63 | // .stroke(Color(UIColor.systemGray), lineWidth: 1) // 색상과 선 두께를 원하는 대로 설정 64 | // ) 65 | } 66 | } 67 | } 68 | .aspectRatio(1.0, contentMode: .fit) 69 | .clipShape(RoundedRectangle(cornerRadius: 8)) 70 | } 71 | } 72 | 73 | struct AllArtistsSetCell: View { 74 | let name: LocalizedStringResource 75 | let isSelected: Bool 76 | var body: some View { 77 | Text(name) 78 | .foregroundStyle(isSelected ? Color.mainWhite : Color.mainBlack) 79 | .fontWeight(isSelected ? .bold : .regular) 80 | .padding(10) 81 | .background { 82 | let color = isSelected ? Color.mainBlack : Color.mainWhite 83 | color.clipShape(RoundedRectangle(cornerRadius: 12)) 84 | } 85 | } 86 | } 87 | // 88 | // #Preview { 89 | // ArtistSetCell(name: "방탄소년단", isSelected: false) 90 | // } 91 | -------------------------------------------------------------------------------- /Projects/Feature/Scenes/ArchiveScene/Component/EmptyArtistImage.swift: -------------------------------------------------------------------------------- 1 | // 2 | // EmptyArtistImage.swift 3 | // Feature 4 | // 5 | // Created by 장수민 on 7/24/24. 6 | // Copyright © 2024 com.creative8.seta. All rights reserved. 7 | // 8 | 9 | import SwiftUI 10 | 11 | struct EmptyArtistImage: View { 12 | let foregroundColor: Color 13 | let backgroundColor: Color 14 | var body: some View { 15 | // TODO: 컬러 수정 16 | HStack(spacing: 0) { 17 | Rectangle() 18 | .foregroundStyle(foregroundColor) 19 | 20 | Rectangle() 21 | .foregroundStyle(foregroundColor) 22 | .cornerRadius(40, corners: [.topLeft]) 23 | } 24 | .background(backgroundColor) 25 | } 26 | } 27 | 28 | #Preview { 29 | EmptyArtistImage(foregroundColor: Color.orange, backgroundColor: Color.yellow) 30 | } 31 | -------------------------------------------------------------------------------- /Projects/Feature/Scenes/ArchiveScene/Component/IsEmptyCell.swift: -------------------------------------------------------------------------------- 1 | // 2 | // IsEmptyCell.swift 3 | // Feature 4 | // 5 | // Created by A_Mcflurry on 11/2/23. 6 | // Copyright © 2023 com.creative8.seta. All rights reserved. 7 | // 8 | 9 | import SwiftUI 10 | import UI 11 | 12 | struct IsEmptyCell: View { 13 | enum EmptyType { 14 | case bookmark 15 | case likeArtist 16 | } 17 | let type: EmptyType 18 | var body: some View { 19 | VStack { 20 | Text(type == .bookmark ? "북마크 한 공연이 없어요" : "찜한 아티스트가 없어요") 21 | .font(.headline) 22 | .foregroundStyle(Color.mainBlack) 23 | .padding(.bottom, 12) 24 | Group { 25 | Text(type == .bookmark ? "관심있는 공연을 북마크하고" : "관심있는 아티스트를 찜하고") 26 | Text(type == .bookmark ? "세트리스트를 바로 확인해보세요" : "공연 및 세트리스트 정보를 빠르게 확인해보세요") 27 | } 28 | .font(.footnote) 29 | .foregroundStyle(Color.gray) 30 | } 31 | .frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .center) 32 | .multilineTextAlignment(.center) 33 | } 34 | } 35 | 36 | #Preview { 37 | IsEmptyCell(type: .bookmark) 38 | } 39 | -------------------------------------------------------------------------------- /Projects/Feature/Scenes/ArchiveScene/Component/MenuButton.swift: -------------------------------------------------------------------------------- 1 | // 2 | // MenuButton.swift 3 | // Feature 4 | // 5 | // Created by A_Mcflurry on 11/1/23. 6 | // Copyright © 2023 com.creative8.seta. All rights reserved. 7 | // 8 | 9 | import SwiftUI 10 | import Core 11 | import UI 12 | 13 | struct MenuButton: View { 14 | @Binding var selectedTab: Tab 15 | let item: LikeArtist 16 | @StateObject var dataManager = SwiftDataManager() 17 | @Environment(\.modelContext) var modelContext 18 | var body: some View { 19 | Menu { 20 | NavigationLink(value: NavigationDelivery(artistInfo: SaveArtistInfo(name: item.artistInfo.name, country: "", alias: item.artistInfo.alias, mbid: item.artistInfo.mbid, gid: 0, imageUrl: "", songList: []))) { 21 | Text("아티스트 보기") 22 | } 23 | Button("찜하기 취소") { dataManager.deleteLikeArtist(item) } 24 | } label: { 25 | Image(systemName: "ellipsis") 26 | .foregroundStyle(Color.mainBlack) 27 | .padding() 28 | .background(Color.clear) 29 | } 30 | .onAppear { dataManager.modelContext = modelContext } 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /Projects/Feature/Scenes/ArchiveScene/View/ArchivingArtistView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ArchivingArtistView.swift 3 | // Feature 4 | // 5 | // Created by 최효원 on 7/31/24. 6 | // Copyright © 2024 com.creative8.seta. All rights reserved. 7 | // 8 | 9 | import SwiftUI 10 | import SwiftData 11 | import Core 12 | import UI 13 | import Combine 14 | 15 | struct ArchivingArtistView: View { 16 | @Query(sort: \LikeArtist.orderIndex, order: .reverse) var likeArtists: [LikeArtist] 17 | @Query(sort: \ArchivedConcertInfo.setlist.date, order: .reverse) var concertInfo: [ArchivedConcertInfo] 18 | @StateObject var viewModel = ArchivingViewModel.shared 19 | @Environment(\.modelContext) var modelContext 20 | @Namespace var topID 21 | 22 | var body: some View { 23 | ZStack { 24 | Color.gray6.ignoresSafeArea() 25 | VStack { 26 | HStack { 27 | Text("찜한 아티스트") 28 | .font(.title).bold() 29 | .foregroundStyle(Color.mainBlack) 30 | .padding([.leading, .top], 23) 31 | Spacer() 32 | } 33 | .padding(.bottom, -5) 34 | 35 | if likeArtists.isEmpty { 36 | IsEmptyCell(type: .likeArtist) 37 | } else { 38 | List { 39 | VStack(alignment: .leading) { 40 | Group { 41 | Text("상단 5명의 아티스트만 홈 화면에 표시됩니다\n") 42 | Text("변경을 원하신다면 순서를 옮겨보세요") 43 | } 44 | .font(.footnote) 45 | .foregroundStyle(Color.gray) 46 | .padding(.bottom, 6) 47 | 48 | }.id(topID) 49 | .listRowSeparator(.hidden) 50 | .listRowBackground(Color.gray6) 51 | 52 | archiveArtistListCell 53 | .listRowSeparator(.hidden) 54 | .listRowBackground(Color.gray6) 55 | } 56 | .padding(.horizontal, 5) 57 | .scrollIndicators(.hidden) 58 | .listStyle(.plain) 59 | 60 | } 61 | } 62 | } 63 | } 64 | 65 | private var archiveArtistListCell: some View { 66 | ForEach(Array(likeArtists.enumerated()), id: \.element) { index, item in 67 | HStack(spacing: 16) { 68 | ArchiveArtistCellImage(artistUrl: URL(string: item.artistInfo.imageUrl)) 69 | Text(item.artistInfo.name) 70 | .font(.subheadline) 71 | .foregroundStyle(index < 5 ? Color.mainOrange : Color.mainBlack) 72 | .bold(index < 5) 73 | Spacer() 74 | Image(systemName: "chevron.up.chevron.down") 75 | .foregroundColor(.mainBlack) 76 | } 77 | if index == 4 { 78 | Divider() 79 | .background(Color(UIColor.systemGray3)) 80 | } 81 | } 82 | .onMove { source, destination in 83 | var updatedItems = likeArtists 84 | updatedItems.move(fromOffsets: source, toOffset: destination) 85 | 86 | for (index, item) in updatedItems.enumerated() { 87 | item.orderIndex = likeArtists.count - 1 - index 88 | } 89 | 90 | do { 91 | try modelContext.save() 92 | } catch { 93 | print("Failed to save context: \(error)") 94 | } 95 | } 96 | } 97 | } 98 | -------------------------------------------------------------------------------- /Projects/Feature/Scenes/ArchiveScene/ViewModel/ArchivingViewModel.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ArchivingViewModel.swift 3 | // Feature 4 | // 5 | // Created by A_Mcflurry on 10/31/23. 6 | // Copyright © 2023 com.creative8.seta. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import Core 11 | import Combine 12 | import SwiftUI 13 | 14 | class ArchivingViewModel: ObservableObject { 15 | 16 | // // MARK: Tab View 17 | // @Published var pageStack: [NavigationDelivery] = [] 18 | // 19 | // private var subscription: AnyCancellable? 20 | // 21 | // init(consecutiveTaps: AnyPublisher) { 22 | // subscription = consecutiveTaps 23 | // .sink { [weak self] in 24 | // guard let self else { return } 25 | // 26 | // withAnimation { 27 | // if self.pageStack.isEmpty { 28 | // // Scroll 최상단으로 이동 29 | // } else { 30 | // self.pageStack.removeLast() 31 | // } 32 | // } 33 | // 34 | // } 35 | // } 36 | 37 | func getDateFormatted(date: Date) -> String { 38 | guard let languageCode = Locale.current.language.languageCode?.identifier else { return "" } 39 | 40 | let dateFormatter = DateFormatter() 41 | 42 | dateFormatter.dateFormat = (languageCode == "ko") ? "yyyy년 MM월 dd일" : "MMMM dd, yyyy" 43 | return dateFormatter.string(from: date) 44 | } 45 | 46 | static let shared = ArchivingViewModel() 47 | @Published var selectSegment: SelectEnum = .bookmark 48 | @Published var selectArtist: String = "" 49 | @Published var artistSet: Set = [] 50 | 51 | func insertArtistSet(_ info: [ArchivedConcertInfo]) { 52 | if !artistSet.isEmpty { 53 | artistSet.removeAll() 54 | if !info.contains(where: { $0.artistInfo.name == selectArtist }) { 55 | selectArtist = "" 56 | } 57 | } 58 | 59 | // 임시 딕셔너리를 사용하여 아티스트 MBID별로 가장 최근 정보를 저장 60 | var uniqueArtists: [String: ArchivedConcertInfo] = [:] 61 | 62 | for concert in info { 63 | let artistMBID = concert.artistInfo.mbid 64 | // 이미 존재하는 MBID라면, 더 최신 정보로 업데이트 (또는 다른 기준 적용) 65 | if let existingConcert = uniqueArtists[artistMBID] { 66 | // 여기에 더 최신 정보를 선택하는 로직을 추가할 수 있습니다. 67 | // 예를 들어, 날짜를 비교하거나 다른 기준을 사용할 수 있습니다. 68 | uniqueArtists[artistMBID] = concert 69 | } else { 70 | uniqueArtists[artistMBID] = concert 71 | } 72 | } 73 | 74 | // 중복이 제거된 아티스트 정보를 Set에 삽입 75 | artistSet = Set(uniqueArtists.values) 76 | 77 | // for index in 0.. 1 { 27 | VStack { 28 | ScrollView(.horizontal) { 29 | HStack(spacing: 0) { 30 | ForEach(Array(bookmarkedSetlists.prefix(min(bookmarkedSetlists.count, 3))), id: \.self) { _ in 31 | setlistsLayer 32 | .padding(.horizontal, UIWidth * 0.05) 33 | } 34 | } 35 | .scrollTargetLayout() 36 | } 37 | .scrollTargetBehavior(.viewAligned) 38 | .scrollPosition(id: $scrollPosition) 39 | .scrollIndicators(.hidden) 40 | 41 | HStack { 42 | ForEach(Array(bookmarkedSetlists.prefix(min(bookmarkedSetlists.count, 3))), id: \.self) { element in 43 | if element == scrollPosition { 44 | RoundedRectangle(cornerRadius: 25.0) 45 | .frame(width: 15, height: 8) 46 | .foregroundStyle(Color.mainBlack) 47 | .padding(.vertical) 48 | .padding(.horizontal, 5) 49 | } else { 50 | Circle() 51 | .frame(width: 8) 52 | .foregroundStyle(Color.gray) 53 | .padding(.vertical) 54 | .padding(.horizontal, 5) 55 | } 56 | } 57 | } 58 | } 59 | } else { 60 | setlistsLayer 61 | } 62 | } 63 | .padding(.bottom) 64 | .onAppear { getBookmarkedSetlists() } 65 | } 66 | 67 | private func getBookmarkedSetlists() { 68 | bookmarkedSetlists = [] 69 | for concert in concertInfo { 70 | if concert.artistInfo.mbid == vm.artistInfo.mbid { 71 | bookmarkedSetlists.append(concert) 72 | } 73 | } 74 | if !bookmarkedSetlists.isEmpty { 75 | scrollPosition = bookmarkedSetlists[0] 76 | } 77 | } 78 | 79 | private var emptyLayer: some View { 80 | SummarizedSetlistInfoView( 81 | type: .bookmarkedConcert, 82 | info: nil, 83 | infoButtonAction: nil, 84 | chevronButtonAction: nil 85 | ) 86 | } 87 | 88 | private var setlistsLayer: some View { 89 | SummarizedSetlistInfoView( 90 | type: .bookmarkedConcert, 91 | info: SetlistInfo( 92 | artistInfo: vm.artistInfo, 93 | id: scrollPosition!.setlist.setlistId, 94 | date: vm.getFormattedDate( 95 | date: scrollPosition!.setlist.date, 96 | format: "yyyy년 MM월 dd일" 97 | ), 98 | title: scrollPosition!.setlist.title, 99 | venue: "\(scrollPosition!.setlist.venue)\n\(scrollPosition!.setlist.city), \(scrollPosition!.setlist.country)" 100 | ), 101 | infoButtonAction: nil, 102 | chevronButtonAction: { 103 | vm.archivingViewModel.selectSegment = .bookmark 104 | vm.archivingViewModel.selectArtist = vm.artistInfo.name 105 | if selectedTab == .archiving { 106 | dismiss() 107 | } else { 108 | selectedTab = .archiving 109 | } 110 | } 111 | ) 112 | } 113 | } 114 | 115 | #Preview { 116 | ZStack { 117 | Color.gray.ignoresSafeArea() 118 | BookmarkedSetlistsView(vm: ArtistViewModel(), selectedTab: .constant(.archiving)) 119 | } 120 | } 121 | -------------------------------------------------------------------------------- /Projects/Feature/Scenes/MainScene/Component/ArtistImage.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ArtistImage.swift 3 | // Feature 4 | // 5 | // Created by 장수민 on 11/4/23. 6 | // Copyright © 2023 com.creative8.seta. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import SwiftUI 11 | import SwiftData 12 | import Core 13 | import UI 14 | import Combine 15 | 16 | struct ArtistImage: View { 17 | @Binding var selectedTab: Tab 18 | @Environment(\.colorScheme) var colorScheme 19 | var imageUrl: String 20 | 21 | var body: some View { 22 | VStack { 23 | Group { 24 | if !imageUrl.isEmpty { 25 | AsyncImage(url: URL(string: imageUrl)) { image in 26 | image 27 | .resizable() 28 | .aspectRatio(contentMode: .fill) 29 | .overlay(RoundedRectangle(cornerRadius: 15).stroke(Color(UIColor.systemGray5), lineWidth: 1)) 30 | } placeholder: { 31 | ProgressView() 32 | } 33 | } else { 34 | artistEmptyImage 35 | } 36 | } 37 | .frame(height: UIHeight * 0.38) 38 | .clipShape(RoundedRectangle(cornerRadius: 15)) 39 | .clipped() 40 | .safeAreaPadding(.horizontal, UIWidth * 0.07) 41 | 42 | } 43 | } 44 | 45 | public var artistEmptyImage: some View { 46 | Image("artistViewTicket", bundle: setaBundle) 47 | .resizable() 48 | .aspectRatio(contentMode: .fit) 49 | } 50 | } 51 | 52 | #Preview { 53 | ArtistImage(selectedTab: .constant(.home), imageUrl: "https://images-prod.dazeddigital.com/1428/azure/dazed-prod/1320/4/1324149.jpeg") 54 | } 55 | -------------------------------------------------------------------------------- /Projects/Feature/Scenes/MainScene/Component/ArtistNameView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // HorizontalArtistNameScrollView.swift 3 | // Feature 4 | // 5 | // Created by 장수민 on 11/17/23. 6 | // Copyright © 2023 com.creative8.seta. All rights reserved. 7 | // 8 | 9 | import SwiftUI 10 | import MarqueeText 11 | 12 | struct ArtistNameView: View { 13 | @Binding var isShowToolTip: Bool 14 | @Binding var selectedTab: Tab 15 | @ObservedObject var viewModel: MainViewModel 16 | var index: Int 17 | var name: String 18 | @State private var textWidth: CGFloat = 0 19 | @State private var parentWidth: CGFloat = 0 20 | 21 | var body: some View { 22 | HStack(spacing: 0) { 23 | if textWidth > parentWidth { 24 | MarqueeText( 25 | text: name, 26 | font: .systemFont(ofSize: 32, weight: .semibold), 27 | leftFade: 16, 28 | rightFade: 16, 29 | startDelay: isShowToolTip ? 3 : 0 30 | ) 31 | .frame(width: parentWidth) 32 | } else { 33 | Text(name) 34 | .font(.title) 35 | .fontWeight(.semibold) 36 | .lineLimit(1) 37 | .frame(width: UIWidth * 0.8, alignment: .center) 38 | } 39 | } 40 | .frame(width: UIWidth * 0.8, height: UIHeight * 0.06) 41 | .foregroundColor(viewModel.selectedIndex == index ? Color.mainBlack : Color.gray) 42 | .background( 43 | GeometryReader { geo in 44 | Color.clear 45 | .onAppear { 46 | parentWidth = geo.size.width 47 | measureTextWidth() 48 | } 49 | } 50 | ) 51 | } 52 | 53 | private func measureTextWidth() { 54 | let text = Text(name) 55 | .font(.title) 56 | .fontWeight(.semibold) 57 | 58 | let controller = UIHostingController(rootView: text) 59 | controller.view.frame.size = .zero 60 | controller.view.sizeToFit() 61 | textWidth = controller.view.intrinsicContentSize.width 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /Projects/Feature/Scenes/MainScene/Component/ArtistsContentView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ArtistImageAndConcertListView.swift 3 | // Feature 4 | // 5 | // Created by 장수민 on 11/17/23. 6 | // Copyright © 2023 com.creative8.seta. All rights reserved. 7 | // 8 | import SwiftUI 9 | import Core 10 | import SwiftData 11 | 12 | struct ArtistsContentView: View { 13 | @Binding var selectedTab: Tab 14 | @StateObject var viewModel: MainViewModel 15 | @State var artistInfo: SaveArtistInfo 16 | @StateObject var dataManager = SwiftDataManager() 17 | @Environment(\.modelContext) var modelContext 18 | @Query(sort: \LikeArtist.orderIndex, order: .reverse) var likeArtists: [LikeArtist] 19 | @StateObject var tabViewManager: TabViewManager 20 | 21 | var index: Int 22 | 23 | var body: some View { 24 | let setlists: [Setlist?] = viewModel.setlists[index] ?? [] 25 | 26 | NavigationStack(path: $tabViewManager.pageStack) { 27 | VStack(spacing: 24) { 28 | 29 | if viewModel.isLoading { 30 | loadingView 31 | } else { 32 | if let firstSetlist = setlists.compactMap({ $0 }).first { 33 | VStack(spacing: 12) { 34 | summarizedSetlistView(for: firstSetlist) 35 | ArtistMainSetlistView(viewModel: viewModel, index: index) 36 | } 37 | 38 | } else { 39 | emptySetlistView 40 | } 41 | } 42 | } 43 | .onAppear { 44 | dataManager.modelContext = modelContext 45 | } 46 | } 47 | } 48 | 49 | private var loadingView: some View { 50 | ProgressView() 51 | } 52 | 53 | private var emptySetlistView: some View { 54 | SummarizedSetlistInfoView( 55 | type: .recentConcert, 56 | info: nil, 57 | infoButtonAction: nil, 58 | chevronButtonAction: nil 59 | ) 60 | } 61 | 62 | private func summarizedSetlistView(for setlist: Setlist) -> some View { 63 | let venueName = setlist.venue?.name ?? "" 64 | let city = setlist.venue?.city?.name ?? "" 65 | let countryName = setlist.venue?.city?.country?.name ?? "" 66 | let artistName = setlist.artist?.name ?? "" 67 | 68 | return SummarizedSetlistInfoView( 69 | type: .recentConcert, 70 | info: SetlistInfo( 71 | artistInfo: artistInfo.toArtistInfo(), 72 | id: setlist.id ?? "", 73 | date: viewModel.getDateFormatted(dateString: setlist.eventDate ?? ""), 74 | title: setlist.tour?.name ?? "\(artistName) Setlist", 75 | venue: "\(venueName)\n\(city), \(countryName)" 76 | ), 77 | infoButtonAction: nil, 78 | chevronButtonAction: { 79 | viewModel.navigateToArtistView = true 80 | } 81 | ) 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /Projects/Feature/Scenes/MainScene/Component/EmptyMainView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // EmptyMain.swift 3 | // Feature 4 | // 5 | // Created by 장수민 on 11/4/23. 6 | // Copyright © 2023 com.creative8.seta. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import SwiftUI 11 | import SwiftData 12 | import Core 13 | import UI 14 | 15 | struct EmptyMainView: View { 16 | @Binding var selectedTab: Tab 17 | 18 | var body: some View { 19 | ZStack { 20 | Color.gray6.ignoresSafeArea() 21 | VStack { 22 | Spacer() 23 | Text("찜한 아티스트가 없어요") 24 | .font(.system(size: 16, weight: .semibold)) 25 | .padding(.bottom, 9) 26 | .foregroundStyle(Color.mainBlack) 27 | Group { 28 | VStack { 29 | Text("검색에서 원하는 아티스트를 찾고") 30 | Text("하트버튼을 눌러주세요") 31 | } 32 | .foregroundStyle(Color.gray) 33 | } 34 | .font(.system(size: 13, weight: .regular)) 35 | .multilineTextAlignment(.center) 36 | .padding(.bottom) 37 | Spacer() 38 | } 39 | } 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /Projects/Feature/Scenes/MainScene/Component/MainToolTipView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // MainToolTip.swift 3 | // Feature 4 | // 5 | // Created by 최효원 on 7/31/24. 6 | // Copyright © 2024 com.creative8.seta. All rights reserved. 7 | // 8 | 9 | import SwiftUI 10 | 11 | struct MainTooltipView: View { 12 | var body: some View { 13 | ZStack(alignment: .topTrailing) { 14 | CustomTriangleShape() 15 | .fill(Color.mainOrange) 16 | .offset(x: -50, y: 3) 17 | .frame(width: 20, height: 50) 18 | 19 | CustomRectangleShape(text: "메인 화면 아티스트를 수정할 수 있어요") 20 | .frame(width: 250, height: 40) 21 | 22 | } 23 | } 24 | } 25 | 26 | private struct CustomTriangleShape: Shape { 27 | private var width: CGFloat 28 | private var height: CGFloat 29 | private var radius: CGFloat 30 | 31 | fileprivate init( 32 | width: CGFloat = 20, 33 | height: CGFloat = 20, 34 | radius: CGFloat = 1 35 | ) { 36 | self.width = width 37 | self.height = height 38 | self.radius = radius 39 | } 40 | 41 | fileprivate func path(in rect: CGRect) -> Path { 42 | var path = Path() 43 | 44 | let topCenter = CGPoint(x: rect.minX + width / 2, y: rect.minY) 45 | let bottomLeft = CGPoint(x: rect.minX, y: rect.minY + height) 46 | let bottomRight = CGPoint(x: rect.minX + width, y: rect.minY + height) 47 | 48 | path.move(to: CGPoint(x: topCenter.x - radius, y: rect.minY)) 49 | path.addArc( 50 | center: CGPoint(x: topCenter.x, y: rect.minY + radius), 51 | radius: radius, 52 | startAngle: Angle(degrees: 180), 53 | endAngle: Angle(degrees: 0), 54 | clockwise: false 55 | ) 56 | 57 | path.addLine(to: bottomRight) 58 | path.addLine(to: bottomLeft) 59 | path.closeSubpath() 60 | 61 | return path 62 | } 63 | } 64 | 65 | private struct CustomRectangleShape: View { 66 | private var text: LocalizedStringKey 67 | 68 | fileprivate init(text: LocalizedStringKey) { 69 | self.text = text 70 | } 71 | 72 | fileprivate var body: some View { 73 | VStack { 74 | Spacer() 75 | Text(text) 76 | .font(.footnote) 77 | .foregroundColor(.white) 78 | .padding(.vertical, 10) 79 | .padding(.horizontal, 18) 80 | .background( 81 | RoundedRectangle(cornerRadius: 12) 82 | .fill(Color.mainOrange) 83 | ) 84 | } 85 | } 86 | } 87 | 88 | #Preview { 89 | MainTooltipView() 90 | } 91 | -------------------------------------------------------------------------------- /Projects/Feature/Scenes/NetworkUnavailableView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // NetworkUnavailableView.swift 3 | // Feature 4 | // 5 | // Created by 장수민 on 2/26/24. 6 | // Copyright © 2024 com.creative8.seta. All rights reserved. 7 | // 8 | 9 | import SwiftUI 10 | import Core 11 | import Foundation 12 | 13 | struct NetworkUnavailableView: View { 14 | @Environment(NetworkMonitor.self) private var networkMonitor 15 | 16 | var body: some View { 17 | ContentUnavailableView { 18 | Label("네트워크에 연결되어 있지 않습니다", systemImage: "wifi.exclamationmark") 19 | .foregroundStyle(Color.mainBlack) 20 | } description: { 21 | Text("인터넷 연결을 확인한 뒤 다시 시도해 주세요") 22 | .foregroundStyle(Color.mainBlack) 23 | } actions: { 24 | Button { 25 | networkMonitor.startMonitoring() 26 | } label: { 27 | Text("새로고침") 28 | .bold() 29 | .foregroundStyle(Color.mainWhite) 30 | .font(.system(size: 14)) 31 | .padding(EdgeInsets(top: 17, leading: 22, bottom: 17, trailing: 22)) 32 | .background(RoundedRectangle(cornerRadius: 14) 33 | .foregroundStyle(Color.mainBlack)) 34 | } 35 | } 36 | .background(Color.mainWhite) 37 | } 38 | } 39 | 40 | #Preview { 41 | NetworkUnavailableView() 42 | } 43 | -------------------------------------------------------------------------------- /Projects/Feature/Scenes/OnboardingScene/ViewModel/OnboardingViewModel.swift: -------------------------------------------------------------------------------- 1 | // 2 | // OnboardingViewModel.swift 3 | // Feature 4 | // 5 | // Created by 예슬 on 2023/10/22. 6 | // Copyright © 2023 com.creative8.seta. All rights reserved. 7 | // 8 | 9 | import SwiftUI 10 | import Foundation 11 | import Core 12 | 13 | public final class OnboardingViewModel: ObservableObject { 14 | @Published var selectedArtist: [OnboardingModel] = [] 15 | @Published var selectedGenere: OnboardingFilterType = .all 16 | @Published var isShowToastBar = false 17 | @Published var artistFetchError = false 18 | @StateObject var dataManager = SwiftDataManager() 19 | @AppStorage("isOnboarding") var isOnboarding: Bool? 20 | 21 | init() { 22 | } 23 | 24 | func findFilterTagName(_ tag: OnboardingFilterType) -> String { 25 | switch(tag) { 26 | case .all: return "" 27 | case .kpop: return "K-Pop" 28 | case .pop: return "Pop" 29 | case .hiphop: return "Hip-Hop" 30 | case .rnb: return "R&B" 31 | case .rock: return "Rock" 32 | case .metal: return "Metal" 33 | case .elctronic: return "Electronic" 34 | case .folk: return "Country/Folk" 35 | case .jpop: return "J-Pop" 36 | } 37 | } 38 | 39 | func isKorean() -> Bool { 40 | guard let languageCode = Locale.current.language.languageCode?.identifier else { return false } 41 | return languageCode == "ko" 42 | } 43 | } 44 | 45 | // array nil 분기 처리 46 | extension Array { 47 | subscript (safe index: Int) -> Element? { 48 | return indices ~= index ? self[index] : nil 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /Projects/Feature/Scenes/SearchScene/Component/SearchArtistCell.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SearchArtistCell.swift 3 | // Feature 4 | // 5 | // Created by A_Mcflurry on 10/8/23. 6 | // Copyright © 2023 com.creative8. All rights reserved. 7 | // 8 | 9 | import SwiftUI 10 | import UI 11 | import Core 12 | 13 | struct SearchArtistCell: View { 14 | @Environment(\.colorScheme) var colorScheme 15 | @Binding var selectedTab: Tab 16 | let imageURL: String 17 | let artistName: String 18 | let artistAlias: String 19 | let artistMbid: String 20 | let artistGid: Int 21 | 22 | var body: some View { 23 | VStack(alignment: .leading) { 24 | NavigationLink(value: NavigationDelivery(artistInfo: SaveArtistInfo(name: artistName, country: "", alias: artistAlias, mbid: artistMbid, gid: artistGid, imageUrl: imageURL, songList: []))) { 25 | AsyncImage(url: URL(string: imageURL)) { phase in 26 | switch phase { 27 | case .empty: 28 | RoundedRectangle(cornerRadius: 12) 29 | .overlay( 30 | Group { 31 | if colorScheme == .light { 32 | Image(uiImage: UIImage(named: "artistViewTicket", in: Bundle(identifier: "com.creative8.seta.UI"), compatibleWith: nil)!) 33 | .centerCropped() 34 | .scaledToFill() 35 | .overlay( 36 | RoundedRectangle(cornerRadius: 12) 37 | .stroke(Color.gray, lineWidth: 1) // 색상과 선 두께를 원하는 대로 설정 38 | ) 39 | } else { 40 | Image(uiImage: UIImage(named: "darkArtistViewTicket", in: Bundle(identifier: "com.creative8.seta.UI"), compatibleWith: nil)!) 41 | .centerCropped() 42 | .scaledToFill() 43 | .overlay( 44 | RoundedRectangle(cornerRadius: 12) 45 | .stroke(Color.gray, lineWidth: 1) // 색상과 선 두께를 원하는 대로 설정 46 | ) 47 | } 48 | } 49 | 50 | ) 51 | .foregroundStyle(Color(UIColor.systemGray5)) 52 | case .success(let image): 53 | image 54 | .resizable() 55 | .scaledToFill() 56 | case .failure: 57 | RoundedRectangle(cornerRadius: 12) 58 | .overlay( 59 | Group { 60 | if colorScheme == .light { 61 | Image(uiImage: UIImage(named: "artistViewTicket", in: Bundle(identifier: "com.creative8.seta.UI"), compatibleWith: nil)!) 62 | .centerCropped() 63 | .scaledToFill() 64 | .overlay( 65 | RoundedRectangle(cornerRadius: 12) 66 | .stroke(Color.gray, lineWidth: 1) // 색상과 선 두께를 원하는 대로 설정 67 | ) 68 | } else { 69 | Image(uiImage: UIImage(named: "darkArtistViewTicket", in: Bundle(identifier: "com.creative8.seta.UI"), compatibleWith: nil)!) 70 | .centerCropped() 71 | .scaledToFill() 72 | .overlay( 73 | RoundedRectangle(cornerRadius: 12) 74 | .stroke(Color.gray, lineWidth: 1) // 색상과 선 두께를 원하는 대로 설정 75 | ) 76 | } 77 | } 78 | 79 | ) 80 | .foregroundStyle(Color(UIColor.systemGray5)) 81 | @unknown default: 82 | EmptyView() 83 | } 84 | } 85 | } 86 | .aspectRatio(contentMode: .fit) 87 | .clipShape(RoundedRectangle(cornerRadius: 12)) 88 | .overlay(RoundedRectangle(cornerRadius: 12) 89 | .stroke(Color(UIColor.systemGray4), lineWidth: 1)) 90 | 91 | Text("\(artistName)") 92 | .foregroundStyle(Color.mainBlack) 93 | .font(.footnote) 94 | .lineLimit(1) 95 | } 96 | } 97 | } 98 | -------------------------------------------------------------------------------- /Projects/Feature/Scenes/SearchScene/Component/SearchArtistList.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SearchArtistList.swift 3 | // Feature 4 | // 5 | // Created by 고혜지 on 10/23/23. 6 | // Copyright © 2023 com.creative8.seta. All rights reserved. 7 | // 8 | 9 | import SwiftUI 10 | import SwiftData 11 | import Core 12 | import UI 13 | 14 | struct SearchArtistList: View { 15 | @Binding var selectedTab: Tab 16 | @ObservedObject var viewModel: SearchViewModel 17 | @StateObject var dataManager = SwiftDataManager() 18 | @Environment(\.modelContext) var modelContext 19 | @Query var searchHistory: [SearchHistory] 20 | var body: some View { 21 | if viewModel.isLoading { 22 | VStack { 23 | Spacer() 24 | ProgressView() 25 | .toolbar(.hidden, for: .tabBar) 26 | Spacer() 27 | } 28 | } else { 29 | ForEach(viewModel.artistList, id: \.name) { artist in 30 | let namePair: (String, String?) = viewModel.koreanConverter.findKoreanName(artist: artist) 31 | let info: String = ((namePair.1 != nil) ? namePair.1! + ", " : "") + (artist.area?.name ?? "") 32 | VStack(alignment: .leading) { 33 | NavigationLink { 34 | ArtistView(selectedTab: $selectedTab, artistName: namePair.0, artistAlias: namePair.1, artistMbid: artist.id ?? "") 35 | } label: { 36 | ListRow(namePair: namePair, info: info) 37 | } 38 | .simultaneousGesture(TapGesture().onEnded { 39 | dataManager.findHistoryAndDelete(searchHistory, artist.id ?? "") 40 | dataManager.addSearchHistory(name: namePair.0, country: info, alias: namePair.1 ?? "", mbid: artist.id ?? "", gid: 0, imageUrl: "", songList: []) 41 | }) 42 | 43 | Divider() 44 | .foregroundStyle(Color.gray) 45 | } 46 | } 47 | .onAppear { dataManager.modelContext = modelContext } 48 | .toolbar(.hidden, for: .tabBar) 49 | } 50 | 51 | } 52 | } 53 | 54 | public struct ListRow: View { 55 | let namePair: (String, String?) 56 | let info: String 57 | 58 | public var body: some View { 59 | HStack { 60 | VStack(alignment: .leading) { 61 | Text(namePair.0) 62 | .font(.subheadline) 63 | .foregroundStyle(Color.mainBlack) 64 | .lineLimit(1) 65 | 66 | Group { 67 | if info == "" { 68 | Text(" ") 69 | } else { 70 | Text(info) 71 | } 72 | } 73 | .lineLimit(1) 74 | .font(.footnote) 75 | .foregroundStyle(Color(UIColor.systemGray2)) 76 | 77 | } 78 | Spacer() 79 | } 80 | .padding(.vertical, 5) 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /Projects/Feature/Scenes/SearchScene/Component/SearchHistoryCell.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SearchHistoryCell.swift 3 | // Feature 4 | // 5 | // Created by A_Mcflurry on 10/9/23. 6 | // Copyright © 2023 com.creative8. All rights reserved. 7 | // 8 | 9 | import SwiftUI 10 | import SwiftData 11 | import Core 12 | import UI 13 | 14 | struct SearchHistoryCell: View { 15 | @Binding var searchText: String 16 | @Binding var selectedTab: Tab 17 | let history: SearchHistory 18 | let dataManager: SwiftDataManager 19 | 20 | var body: some View { 21 | VStack { 22 | HStack { 23 | NavigationLink { 24 | ArtistView(selectedTab: $selectedTab, artistName: history.artistInfo.name, artistAlias: history.artistInfo.alias, artistMbid: history.artistInfo.mbid) 25 | } label: { 26 | ListRow(namePair: (history.artistInfo.name, ""), info: history.artistInfo.country) 27 | } 28 | 29 | Spacer() 30 | 31 | Button { 32 | dataManager.deleteSearchHistory(history) 33 | } label: { 34 | Image(systemName: "xmark") 35 | .foregroundStyle(Color(UIColor.systemGray3)) 36 | .padding(.trailing, 12) 37 | } 38 | } 39 | .padding(.top) 40 | 41 | Divider() 42 | .foregroundStyle(Color(UIColor.systemGray3)) 43 | .padding(.top, 15) 44 | } 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /Projects/Feature/Scenes/SearchScene/Component/SearchbarCustom.swift: -------------------------------------------------------------------------------- 1 | // 2 | // UISearchbarCustom.swift 3 | // Feature 4 | // 5 | // Created by A_Mcflurry on 10/8/23. 6 | // Copyright © 2023 com.creative8. All rights reserved. 7 | // 8 | 9 | import SwiftUI 10 | import Core 11 | import UI 12 | 13 | struct SearchBar: View { 14 | @Binding var text: String 15 | @Binding var isEditing: Bool 16 | var placeholder: LocalizedStringResource = "아티스트를 검색하세요" 17 | 18 | init(text: Binding, isEditing: Binding) { 19 | self._text = text 20 | self._isEditing = isEditing 21 | } 22 | 23 | var body: some View { 24 | HStack { 25 | TextField("", text: $text, prompt: Text(placeholder)) 26 | .autocorrectionDisabled(true) 27 | .padding(7) 28 | .padding(.horizontal, 34) 29 | .foregroundColor(Color.gray) 30 | .background(Color.mainWhite) 31 | .cornerRadius(10) 32 | .overlay( 33 | HStack { 34 | Image(systemName: "magnifyingglass") 35 | .foregroundColor(Color.gray) 36 | .frame(minWidth: 0, maxWidth: .infinity, alignment: .leading) 37 | .padding(.leading, 16) 38 | 39 | if isEditing && text.count != 0 { 40 | Button { 41 | self.text = "" 42 | } label: { 43 | Image(systemName: "multiply.circle.fill") 44 | .foregroundColor(Color.gray) 45 | .padding(.trailing, 7.5) 46 | } 47 | } 48 | } 49 | ) 50 | .onTapGesture { 51 | withAnimation { 52 | self.isEditing = true 53 | } 54 | AnalyticsEvent.trackButtonTap(buttonName: AnalyticsEvent.Event.searchBar) 55 | } 56 | 57 | if isEditing { 58 | Button("취소") { 59 | withAnimation { 60 | self.isEditing = false 61 | self.text = "" 62 | } 63 | UIApplication.shared.sendAction(#selector(UIResponder.resignFirstResponder), to: nil, from: nil, for: nil) 64 | } 65 | .foregroundStyle(Color.mainBlack) 66 | .padding(.horizontal, 10) 67 | } 68 | } 69 | .textCase(.none) 70 | .padding(.bottom, 20) 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /Projects/Feature/Scenes/SearchScene/ViewModel/SearchViewModel.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SearchViewModel.swift 3 | // Feature 4 | // 5 | // Created by 최효원 on 10/7/23. 6 | // Copyright © 2023 com.creative8. All rights reserved. 7 | // 8 | 9 | import SwiftUI 10 | import Core 11 | import Combine 12 | 13 | final class SearchViewModel: ObservableObject { 14 | let dataService = SetlistDataService() 15 | let artistDataManager = ArtistDataManager() 16 | let koreanConverter = KoreanConverter() 17 | let artistFetchService = ArtistFetchService() 18 | 19 | @Published var searchText: String = "" 20 | @Published var searchIsPresented: Bool = false 21 | @Published var artistList: [MusicBrainzArtist] = [] 22 | @Published var isLoading: Bool = false 23 | @Published var domesticArtists: [OnboardingModel] = [] 24 | @Published var foreignArtists: [OnboardingModel] = [] 25 | private var cancellables = Set() 26 | 27 | init() { 28 | let searchTextPublisher = $searchText 29 | .debounce(for: .seconds(0.5), scheduler: DispatchQueue.main) 30 | .removeDuplicates() 31 | searchTextPublisher 32 | .sink { [weak self] _ in 33 | self?.getSearchArtistList() 34 | } 35 | .store(in: &cancellables) 36 | 37 | if artistFetchService.allArtist.isEmpty { 38 | artistFetchService.fetchData { [weak self] success in 39 | guard let self else { return } 40 | if success { 41 | getRandomArtistInfos() 42 | } 43 | } 44 | } 45 | } 46 | 47 | func getRandomArtistInfos() { 48 | if searchIsPresented { return } 49 | 50 | var domestic: [OnboardingModel] = [] 51 | var foreign: [OnboardingModel] = [] 52 | 53 | for data in artistFetchService.allArtist { 54 | guard data.url != nil && data.url != "" && data.url != "https://assets.genius.com/images/default_avatar_300.png?1722887932" else { continue } 55 | guard let tags = data.tags else { continue } 56 | 57 | if tags.contains("K-Pop") && data.country == "South Korea" { 58 | domestic.append(data) 59 | } else if data.country != "South Korea" && !data.country.isEmpty { 60 | foreign.append(data) 61 | } 62 | } 63 | domesticArtists = Array(domestic.shuffled().prefix(9)) 64 | foreignArtists = Array(foreign.shuffled().prefix(9)) 65 | } 66 | 67 | func getSearchArtistList() { 68 | self.isLoading = true 69 | Future<[MusicBrainzArtist], Error> { promise in 70 | self.dataService.searchArtistsFromMusicBrainz(artistName: self.searchText) { result in 71 | if let result = result { 72 | promise(.success(result.artists ?? [])) 73 | } else { 74 | promise(.failure(NSError(domain: "YourDomain", code: 1, userInfo: nil))) 75 | } 76 | } 77 | } 78 | .receive(on: DispatchQueue.main) 79 | .sink { completion in 80 | switch completion { 81 | case .finished: 82 | self.isLoading = false 83 | case .failure(let error): 84 | print("Failed to fetch musicbrainz data: \(error)") 85 | } 86 | } receiveValue: { artists in 87 | self.artistList = artists 88 | } 89 | .store(in: &cancellables) 90 | } 91 | } 92 | -------------------------------------------------------------------------------- /Projects/Feature/Scenes/SetlistScene/Component/MusicButtonView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // MusicButtonView.swift 3 | // Feature 4 | // 5 | // Created by A_Mcflurry on 7/14/24. 6 | // Copyright © 2024 com.creative8.seta. All rights reserved. 7 | // 8 | 9 | import SwiftUI 10 | import UI 11 | 12 | struct MusicButtonView: View { 13 | let music: MusicCases 14 | var body: some View { 15 | VStack { 16 | Image(music.rawValue, bundle: setaBundle) 17 | .resizable() 18 | .frame(width: 40, height: 40) 19 | .frame(maxWidth: .infinity) 20 | .padding(.vertical, 21) 21 | .background(Color.gray6) 22 | .clipShape(RoundedRectangle(cornerRadius: 14)) 23 | 24 | Text(music.name) 25 | .font(.system(size: 15)) 26 | .foregroundStyle(Color.mainBlack) 27 | .opacity(0.6) 28 | } 29 | 30 | } 31 | 32 | enum MusicCases: String { 33 | case appleMusic 34 | case spotify 35 | 36 | var name: String { 37 | switch self { 38 | case .appleMusic: 39 | return "Apple Music" 40 | case .spotify: 41 | return "Spotify" 42 | } 43 | } 44 | } 45 | } 46 | 47 | #Preview { 48 | HStack { 49 | MusicButtonView(music: .appleMusic) 50 | MusicButtonView(music: .spotify) 51 | } 52 | .padding() 53 | } 54 | -------------------------------------------------------------------------------- /Projects/Feature/Scenes/SetlistScene/Component/SetlistFMLinkButtonView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SetlistFMLinkButtonView.swift 3 | // Feature 4 | // 5 | // Created by 고혜지 on 11/7/23. 6 | // Copyright © 2023 com.creative8.seta. All rights reserved. 7 | // 8 | 9 | import SwiftUI 10 | import UI 11 | 12 | struct SetlistFMLinkButtonView: View { 13 | var body: some View { 14 | VStack { 15 | if let url = URL(string: "https://www.setlist.fm") { 16 | Link(destination: url) { 17 | Text("세트리스트 추가하기") 18 | .foregroundStyle(Color.mainBlack) 19 | .font(.callout) 20 | .fontWeight(.semibold) 21 | .frame(maxWidth: .infinity) 22 | .padding(.vertical, 20) 23 | .background(Color.gray) 24 | .cornerRadius(14) 25 | } 26 | } 27 | } 28 | } 29 | } 30 | 31 | #Preview { 32 | SetlistFMLinkButtonView() 33 | } 34 | -------------------------------------------------------------------------------- /Projects/Feature/Scenes/SetlistScene/Component/ShareOptionButtonView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ShareOptionButtonView.swift 3 | // Feature 4 | // 5 | // Created by 예슬 on 6/10/24. 6 | // Copyright © 2024 com.creative8.seta. All rights reserved. 7 | // 8 | 9 | import SwiftUI 10 | 11 | struct ShareOptionButtonView: View { 12 | let action: () -> Void 13 | let label: LocalizedStringKey 14 | let systemImageName: String 15 | 16 | var body: some View { 17 | Button(action: { 18 | action() 19 | }, label: { 20 | HStack { 21 | Text(label) 22 | .font(.callout) 23 | .fontWeight(.semibold) 24 | Spacer() 25 | Image(systemName: systemImageName) 26 | } 27 | .foregroundStyle(.black) 28 | }) 29 | .padding(EdgeInsets(top: 19, leading: 20, bottom: 19, trailing: 16)) 30 | } 31 | } 32 | 33 | struct CustomDivider: View { 34 | var body: some View { 35 | Rectangle() 36 | .frame(height: 0.33) 37 | .foregroundColor(.gray) 38 | .padding(.leading, 20) 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /Projects/Feature/Scenes/SetlistScene/Component/ToastMessageView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ToastMessageView.swift 3 | // Feature 4 | // 5 | // Created by 고혜지 on 11/7/23. 6 | // Copyright © 2023 com.creative8.seta. All rights reserved. 7 | // 8 | 9 | import SwiftUI 10 | import UI 11 | 12 | struct ToastMessageView: View { 13 | let message: LocalizedStringResource 14 | let subMessage: LocalizedStringResource? 15 | let icon: String 16 | let color: Color 17 | 18 | var body: some View { 19 | HStack { 20 | VStack(alignment: .leading) { 21 | Text(message) 22 | .foregroundStyle(Color.mainWhite) 23 | .font(.subheadline) 24 | if let subMessage = subMessage { 25 | Text(subMessage) 26 | .foregroundStyle(Color.toast1) 27 | .font(.subheadline) 28 | } 29 | } 30 | Spacer() 31 | Image(systemName: icon) 32 | .renderingMode(.template) 33 | .foregroundStyle(color) 34 | } 35 | .padding(15) 36 | .frame(maxWidth: .infinity) 37 | .background(Blur().cornerRadius(6.0)) 38 | .background(Color.toastBG.cornerRadius(6.0)) 39 | } 40 | } 41 | 42 | open class UIBackdropView: UIView { 43 | 44 | open override class var layerClass: AnyClass { 45 | NSClassFromString("CABackdropLayer") ?? CALayer.self 46 | } 47 | } 48 | 49 | public struct Backdrop: UIViewRepresentable { 50 | 51 | public init() {} 52 | 53 | public func makeUIView(context: Context) -> UIBackdropView { 54 | UIBackdropView() 55 | } 56 | 57 | public func updateUIView(_ uiView: UIBackdropView, context: Context) {} 58 | } 59 | 60 | public struct Blur: View { 61 | 62 | public var radius: CGFloat 63 | public var opaque: Bool 64 | 65 | public init(radius: CGFloat = 3.0, opaque: Bool = false) { 66 | self.radius = radius 67 | self.opaque = opaque 68 | } 69 | 70 | public var body: some View { 71 | Backdrop() 72 | .blur(radius: radius, opaque: opaque) 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /Projects/Feature/Scenes/SetlistScene/View/EmptySetlistView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // EmptySetlistView.swift 3 | // Feature 4 | // 5 | // Created by 고혜지 on 11/7/23. 6 | // Copyright © 2023 com.creative8.seta. All rights reserved. 7 | // 8 | 9 | import SwiftUI 10 | import UI 11 | 12 | struct EmptySetlistView: View { 13 | @StateObject var vm = MainViewModel() 14 | var body: some View { 15 | VStack { 16 | Text("등록된 세트리스트가 없어요") 17 | .font(.headline) 18 | .fontWeight(.semibold) 19 | .padding(.top, UIHeight * 0.1) 20 | .padding(.bottom, 5) 21 | Text("세트리스트를 직접 작성하고 싶으신가요?") 22 | .multilineTextAlignment(.center) 23 | .font(.footnote) 24 | .foregroundStyle(Color.gray) 25 | HStack(spacing: 0) { 26 | if vm.isKorean() { 27 | Link(destination: URL(string: "https://www.setlist.fm")!) { 28 | Text("Setlist.fm") 29 | .underline() 30 | } 31 | Text("에서 추가하세요.") 32 | } else { 33 | Text("setlist? Add it on ") 34 | Link(destination: URL(string: "https://www.setlist.fm")!) { 35 | Text("Setlist.fm.") 36 | .underline() 37 | } 38 | } 39 | } 40 | .foregroundStyle(Color.gray) 41 | .font(.footnote) 42 | } 43 | } 44 | } 45 | 46 | #Preview { 47 | EmptySetlistView() 48 | } 49 | -------------------------------------------------------------------------------- /Projects/Feature/Scenes/SetlistScene/View/ListView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ListView.swift 3 | // Feature 4 | // 5 | // Created by 고혜지 on 11/7/23. 6 | // Copyright © 2023 com.creative8.seta. All rights reserved. 7 | // 8 | 9 | import SwiftUI 10 | import Core 11 | import UI 12 | 13 | struct ListView: View { 14 | let setlist: Setlist? 15 | let artistInfo: ArtistInfo? 16 | @ObservedObject var vm: SetlistViewModel 17 | 18 | var body: some View { 19 | VStack { 20 | ForEach(setlist?.sets?.setsSet ?? [], id: \.self) { session in 21 | VStack(alignment: .leading) { 22 | Text(session.name != nil ? session.name! : session.encore != nil ? "Encore" : "Main") 23 | .font(.headline) 24 | .fontWeight(.bold) 25 | .foregroundStyle(Color(UIColor.systemGray2)) 26 | .padding(.top, 30) 27 | let songs = session.song ?? [] 28 | ForEach(Array(songs.enumerated()), id: \.offset) { index, song in 29 | if let title = song.name { 30 | 31 | Group { 32 | if song.tape != nil && song.tape == true { 33 | ListRowView( 34 | index: nil, 35 | title: vm.koreanConverter.findKoreanTitle(title: title, songList: artistInfo?.songList ?? []) ?? title, 36 | info: song.info 37 | ) 38 | .opacity(0.6) 39 | } else { 40 | ListRowView( 41 | index: index + 1, 42 | title: vm.koreanConverter.findKoreanTitle(title: title, songList: artistInfo?.songList ?? []) ?? title, 43 | info: song.info 44 | ) 45 | } 46 | } 47 | .padding(.vertical, 7) 48 | 49 | if index + 1 < songs.count { 50 | Divider().foregroundStyle(Color(UIColor.systemGray3)) 51 | } 52 | } 53 | } 54 | } 55 | } 56 | } 57 | } 58 | } 59 | 60 | private struct ListRowView: View { 61 | var index: Int? 62 | var title: String 63 | var info: String? 64 | 65 | var body: some View { 66 | HStack(alignment: .top, spacing: 20) { 67 | if let index = index { 68 | Text(String(format: "%02d", index)) 69 | .foregroundStyle(Color.mainBlack) 70 | } else { 71 | Image(systemName: "recordingtape") 72 | .foregroundStyle(Color.mainBlack) 73 | } 74 | 75 | VStack(alignment: .leading, spacing: 5) { 76 | Text(title) 77 | .foregroundStyle(Color.mainBlack) 78 | .lineLimit(1) 79 | 80 | if let info = info { 81 | Text(info) 82 | .fontWeight(.regular) 83 | .foregroundStyle(Color(UIColor.systemGray2)) 84 | } 85 | } 86 | } 87 | .font(.callout) 88 | .fontWeight(.semibold) 89 | .frame(maxWidth: .infinity, alignment: .leading) 90 | } 91 | } 92 | -------------------------------------------------------------------------------- /Projects/Feature/Scenes/SetlistScene/ViewModel/ExportPlaylistViewModel.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ExportPlaylistViewModel.swift 3 | // Feature 4 | // 5 | // Created by 장수민 on 11/21/23. 6 | // Copyright © 2023 com.creative8.seta. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import Photos 11 | import Core 12 | import MusicKit 13 | 14 | final class ExportPlaylistViewModel: ObservableObject { 15 | @Published var playlistTitle: String = "" 16 | @Published var showAppleMusicAlert: Bool = false 17 | @Published var showToastMessageAppleMusic: Bool = false 18 | @Published var showToastMessageSubscription: Bool = false 19 | @Published var showToastMessageCapture = false 20 | @Published var showLibrarySettingsAlert = false 21 | @Published var showMusicSettingsAlert = false 22 | @Published var showSpotifyAlert = false 23 | @Published var showCaptureAlert = false 24 | 25 | // MARK: Photo 26 | func checkPhotoPermission() -> Bool { 27 | var status: PHAuthorizationStatus = .notDetermined 28 | 29 | if #available(iOS 14, *) { 30 | status = PHPhotoLibrary.authorizationStatus() 31 | } else { 32 | status = PHPhotoLibrary.authorizationStatus() 33 | } 34 | return status == .denied || status == .notDetermined 35 | 36 | } 37 | 38 | func requestPhotoLibraryPermission() { 39 | PHPhotoLibrary.requestAuthorization { status in 40 | switch status { 41 | case .authorized: 42 | // 권한이 허용된 경우 43 | print("Photo library access granted") 44 | case .denied, .restricted: 45 | // 권한이 거부되거나 제한된 경우 46 | print("Photo library access denied") 47 | case .notDetermined: 48 | // 권한이 아직 결정되지 않은 경우 49 | print("Photo library access not determined") 50 | case .limited: 51 | print("Photo library access limited") 52 | @unknown default: 53 | print("omg") 54 | } 55 | } 56 | } 57 | 58 | func getPhotoLibraryPermissionStatus() -> PHAuthorizationStatus { 59 | let status = PHPhotoLibrary.authorizationStatus() 60 | return status 61 | } 62 | 63 | func handlePhotoExportButtonAction() { 64 | if self.getPhotoLibraryPermissionStatus() == .notDetermined { 65 | self.requestPhotoLibraryPermission() 66 | } else if self.getPhotoLibraryPermissionStatus() == .denied { 67 | self.showLibrarySettingsAlert = true 68 | } else { 69 | self.showToastMessageCapture = true 70 | DispatchQueue.main.asyncAfter(deadline: .now() + 3.0) { 71 | self.showToastMessageCapture = false 72 | } 73 | } 74 | } 75 | 76 | // MARK: AppleMusic 77 | 78 | var musicKitPermissionStatus: MusicAuthorization.Status { 79 | return MusicAuthorization.currentStatus 80 | } 81 | 82 | func addToAppleMusic(musicList: [(String, String?)], setlist: Setlist?) { 83 | AppleMusicService().addPlayList( 84 | name: self.playlistTitle, 85 | musicList: musicList, 86 | venue: setlist?.venue?.name 87 | ) 88 | self.showAppleMusicAlert = false 89 | self.showToastMessageAppleMusic = true 90 | DispatchQueue.main.asyncAfter(deadline: .now() + 3.0) { 91 | self.showToastMessageAppleMusic = false 92 | } 93 | } 94 | 95 | func handleAppleMusicButtonAction() { 96 | Task { 97 | switch musicKitPermissionStatus { 98 | case .notDetermined: 99 | AppleMusicService().requestMusicAuthorization() 100 | case .denied, .restricted: 101 | self.showMusicSettingsAlert = true 102 | case .authorized: 103 | let subscriptionChecker = CheckAppleMusicSubscription.shared 104 | subscriptionChecker.appleMusicSubscription { [self] isSubscribed in 105 | 106 | if isSubscribed { 107 | self.showAppleMusicAlert.toggle() 108 | playlistTitle = "" 109 | } else { 110 | self.showToastMessageSubscription = true 111 | DispatchQueue.main.asyncAfter(deadline: .now() + 3.0) { 112 | self.showToastMessageSubscription = false 113 | } 114 | } 115 | } 116 | default: break 117 | } 118 | } 119 | } 120 | } 121 | -------------------------------------------------------------------------------- /Projects/Feature/Scenes/SetlistScene/ViewModel/SetlistViewModel.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SetlistViewModel.swift 3 | // Feature 4 | // 5 | // Created by 고혜지 on 10/14/23. 6 | // Copyright © 2023 com.creative8. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import Core 11 | 12 | final class SetlistViewModel: ObservableObject { 13 | @Published var isBookmarked: Bool 14 | @Published var showModal: Bool 15 | @Published var isLoading: Bool 16 | 17 | let koreanConverter = KoreanConverter() 18 | let dataService = SetlistDataService() 19 | let artistDataManager = ArtistDataManager() 20 | let dataManager = SwiftDataManager() 21 | 22 | // [(songTitle, artistName)] 23 | var setlistSongName: [(String, String?)] = [] 24 | var setlistSongKoreanName: [(String, String?)] = [] 25 | 26 | init() { 27 | self.isBookmarked = false 28 | self.showModal = false 29 | self.isLoading = false 30 | } 31 | 32 | func isKorean() -> Bool { 33 | guard let languageCode = Locale.current.language.languageCode?.identifier else { return false } 34 | return languageCode == "ko" 35 | } 36 | 37 | func isEmptySetlist(_ setlist: Setlist) -> Bool { 38 | return setlist.sets?.setsSet?.isEmpty == true 39 | } 40 | 41 | func getFormattedDateFromString(date: String, format: String) -> String? { 42 | let inputDateFormatter: DateFormatter = { 43 | let formatter = DateFormatter() 44 | formatter.dateFormat = "dd-MM-yyyy" 45 | return formatter 46 | }() 47 | 48 | let outputDateFormatter: DateFormatter = { 49 | let formatter = DateFormatter() 50 | formatter.dateFormat = format 51 | formatter.locale = Locale(identifier: "en_US") 52 | return formatter 53 | }() 54 | 55 | if let inputDate = inputDateFormatter.date(from: date) { 56 | return outputDateFormatter.string(from: inputDate) 57 | } else { 58 | return nil 59 | } 60 | } 61 | 62 | func getDateFormatted(date: Date?) -> String { 63 | guard let date = date else { return "-" } 64 | guard let languageCode = Locale.current.language.languageCode?.identifier else { return "" } 65 | 66 | let dateFormatter = DateFormatter() 67 | 68 | dateFormatter.dateFormat = (languageCode == "ko") ? "yyyy년 MM월 dd일" : "MMMM dd, yyyy" 69 | return dateFormatter.string(from: date) 70 | } 71 | 72 | func allDateFormatter(inputDate: String) -> String? { 73 | guard let languageCode = Locale.current.language.languageCode?.identifier else { return "" } 74 | 75 | let dateFormatter = DateFormatter() 76 | dateFormatter.dateFormat = "dd-MM-yyyy" 77 | 78 | // 입력된 날짜 문자열을 "dd-MM-yyyy" 형식으로 변환 79 | guard let convertedDate = dateFormatter.date(from: inputDate) else { 80 | return "" 81 | } 82 | // 날짜 형식 어떻게 보여줄지? 정하기 83 | dateFormatter.dateFormat = (languageCode == "ko") ? "yyyy년 MM월 dd일" : "MMMM dd, yyyy" 84 | 85 | // 변환된 날짜를 설정한 형식으로 문자열로 반환 86 | return dateFormatter.string(from: convertedDate) 87 | } 88 | 89 | func convertDateStringToDate(_ dateString: String, format: String = "dd-MM-yyyy") -> Date? { 90 | let dateFormatter = DateFormatter() 91 | dateFormatter.dateFormat = format 92 | dateFormatter.locale = Locale(identifier: "en_US_POSIX") // Optional: Specify the locale 93 | 94 | if let date = dateFormatter.date(from: dateString) { 95 | return date 96 | } else { 97 | return nil // 날짜 형식이 맞지 않을 경우 nil 반환 98 | } 99 | } 100 | 101 | func createArrayForExportPlaylist(setlist: Setlist?, songList: [Titles], artistName: String?) { 102 | 103 | setlistSongName = [] 104 | setlistSongKoreanName = [] 105 | 106 | for session in setlist?.sets?.setsSet ?? [] { 107 | for song in session.song ?? [] { 108 | if let title = song.name { 109 | var name: String? 110 | if let cover = song.cover?.name { // 커버곡이면 111 | name = cover 112 | } else { // 커버곡이 아니면 113 | name = artistName 114 | } 115 | 116 | // 영문 배열에 추가 117 | self.setlistSongName.append((title, name)) 118 | 119 | // 한글 배열에 추가 120 | let tmp = self.koreanConverter.findKoreanTitle(title: title, songList: songList) ?? title 121 | self.setlistSongKoreanName.append((tmp, name)) 122 | 123 | } 124 | 125 | } 126 | } 127 | 128 | } 129 | } 130 | -------------------------------------------------------------------------------- /Projects/Feature/Scenes/SettingScene/View/AskView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AskView.swift 3 | // Feature 4 | // 5 | // Created by 예슬 on 2023/10/15. 6 | // Copyright © 2023 com.creative8. All rights reserved. 7 | // 8 | 9 | import SwiftUI 10 | import MessageUI 11 | import UI 12 | 13 | struct AskView: View { 14 | @State var isMailErrorAlertPresented: Bool = false 15 | @StateObject var coordinator = Coordinator() 16 | 17 | var body: some View { 18 | Button(action: { 19 | commentsButtonTapped() 20 | }, label: { 21 | LinkLabelView(linkLabel: "문의하기") 22 | }) 23 | .alert(isPresented: $isMailErrorAlertPresented) { 24 | Alert( 25 | title: Text("메일 전송 실패"), 26 | message: Text("메일을 보내려면 'Mail' 앱이 필요합니다. App Store에서 해당 앱을 복원하거나 이메일 설정을 확인하고 다시 시도해주세요."), 27 | primaryButton: .default(Text("App Store로 이동하기")) { 28 | if let url = URL(string: "https://apps.apple.com/kr/app/mail/id1108187098"), UIApplication.shared.canOpenURL(url) { 29 | if #available(iOS 10.0, *) { 30 | UIApplication.shared.open(url, options: [:], completionHandler: nil) 31 | } else { 32 | UIApplication.shared.openURL(url) 33 | } 34 | } 35 | }, 36 | secondaryButton: .destructive(Text("취소")) 37 | ) 38 | } 39 | } 40 | 41 | func commentsButtonTapped() { 42 | guard let windowScene = UIApplication.shared.connectedScenes.first as? UIWindowScene, 43 | let rootViewController = windowScene.windows.first?.rootViewController else { 44 | return 45 | } 46 | 47 | if MFMailComposeViewController.canSendMail() { 48 | let composeViewController = MFMailComposeViewController() 49 | composeViewController.mailComposeDelegate = coordinator 50 | 51 | let bodyString = 52 | """ 53 | 안녕하세요. Seta 입니다. 아래 내용을 보내주시면 문의 확인에 도움이 됩니다. 54 | Hello, this is Seta. Providing the following information will help us investigate your inquiry 55 | 56 | - iOS 버전(iOS version) : 57 | - 기기 모델명(Device model) : 58 | - 문제발생일시(Date and time of issue) : 59 | - 문의 내용(Details of your inquiry) : 60 | 61 | 62 | """ 63 | 64 | composeViewController.setToRecipients(["thecreative8team@gmail.com"]) 65 | composeViewController.setSubject(" 문의 및 의견") 66 | composeViewController.setMessageBody(bodyString, isHTML: false) 67 | composeViewController.view.tintColor = .systemBlue 68 | 69 | if let presented = rootViewController.presentedViewController { 70 | presented.dismiss(animated: false) { 71 | rootViewController.present(composeViewController, animated: true, completion: nil) 72 | } 73 | } else { 74 | rootViewController.present(composeViewController, animated: true, completion: nil) 75 | } 76 | } else { 77 | isMailErrorAlertPresented.toggle() 78 | } 79 | } 80 | 81 | class Coordinator: NSObject, MFMailComposeViewControllerDelegate, ObservableObject { 82 | func mailComposeController( 83 | _ controller: MFMailComposeViewController, didFinishWith 84 | result: MFMailComposeResult, 85 | error: Error?) { 86 | controller.dismiss(animated: true, completion: nil) 87 | } 88 | } 89 | } 90 | 91 | #Preview { 92 | AskView() 93 | } 94 | -------------------------------------------------------------------------------- /Projects/UI/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DeveloperAcademy-POSTECH/2023-MacC-Team12-Creative8/4b68cf6d2cee5683693269560ffbd23162034ec0/Projects/UI/.DS_Store -------------------------------------------------------------------------------- /Projects/UI/Project.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Projects.swift 3 | // ProjectDescriptionHelpers 4 | // 5 | // Created by 최효원 on 2023/10/06. 6 | // 7 | 8 | import ProjectDescription 9 | import ProjectDescriptionHelpers 10 | 11 | let project = Project.makeModule( 12 | name: "UI", 13 | product: .framework, 14 | dependencies: [ 15 | 16 | ], 17 | sources: ["Sources/**"], 18 | resources: ["Resources/**"] 19 | ) 20 | -------------------------------------------------------------------------------- /Projects/UI/Resources/Colors.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "author" : "xcode", 4 | "version" : 1 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /Projects/UI/Resources/Colors.xcassets/ellipsis.colorset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "colors" : [ 3 | { 4 | "color" : { 5 | "color-space" : "srgb", 6 | "components" : { 7 | "alpha" : "1.000", 8 | "blue" : "0xFF", 9 | "green" : "0xFF", 10 | "red" : "0xFF" 11 | } 12 | }, 13 | "idiom" : "universal" 14 | }, 15 | { 16 | "appearances" : [ 17 | { 18 | "appearance" : "luminosity", 19 | "value" : "dark" 20 | } 21 | ], 22 | "color" : { 23 | "color-space" : "srgb", 24 | "components" : { 25 | "alpha" : "1.000", 26 | "blue" : "0xEE", 27 | "green" : "0xEA", 28 | "red" : "0xE9" 29 | } 30 | }, 31 | "idiom" : "universal" 32 | } 33 | ], 34 | "info" : { 35 | "author" : "xcode", 36 | "version" : 1 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /Projects/UI/Resources/Colors.xcassets/gray6.colorset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "colors" : [ 3 | { 4 | "color" : { 5 | "color-space" : "srgb", 6 | "components" : { 7 | "alpha" : "1.000", 8 | "blue" : "0xF6", 9 | "green" : "0xF2", 10 | "red" : "0xF2" 11 | } 12 | }, 13 | "idiom" : "universal" 14 | }, 15 | { 16 | "appearances" : [ 17 | { 18 | "appearance" : "luminosity", 19 | "value" : "dark" 20 | } 21 | ], 22 | "color" : { 23 | "color-space" : "srgb", 24 | "components" : { 25 | "alpha" : "1.000", 26 | "blue" : "0x17", 27 | "green" : "0x17", 28 | "red" : "0x17" 29 | } 30 | }, 31 | "idiom" : "universal" 32 | } 33 | ], 34 | "info" : { 35 | "author" : "xcode", 36 | "version" : 1 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /Projects/UI/Resources/Colors.xcassets/gray600.colorset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "colors" : [ 3 | { 4 | "color" : { 5 | "color-space" : "srgb", 6 | "components" : { 7 | "alpha" : "0.600", 8 | "blue" : "0xE9", 9 | "green" : "0xE5", 10 | "red" : "0xE5" 11 | } 12 | }, 13 | "idiom" : "universal" 14 | }, 15 | { 16 | "appearances" : [ 17 | { 18 | "appearance" : "luminosity", 19 | "value" : "dark" 20 | } 21 | ], 22 | "color" : { 23 | "color-space" : "srgb", 24 | "components" : { 25 | "alpha" : "0.600", 26 | "blue" : "0x2E", 27 | "green" : "0x2C", 28 | "red" : "0x2C" 29 | } 30 | }, 31 | "idiom" : "universal" 32 | } 33 | ], 34 | "info" : { 35 | "author" : "xcode", 36 | "version" : 1 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /Projects/UI/Resources/Colors.xcassets/mainBlack.colorset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "colors" : [ 3 | { 4 | "color" : { 5 | "color-space" : "srgb", 6 | "components" : { 7 | "alpha" : "1.000", 8 | "blue" : "0x00", 9 | "green" : "0x00", 10 | "red" : "0x00" 11 | } 12 | }, 13 | "idiom" : "universal" 14 | }, 15 | { 16 | "appearances" : [ 17 | { 18 | "appearance" : "luminosity", 19 | "value" : "dark" 20 | } 21 | ], 22 | "color" : { 23 | "color-space" : "srgb", 24 | "components" : { 25 | "alpha" : "1.000", 26 | "blue" : "0xEE", 27 | "green" : "0xEA", 28 | "red" : "0xE9" 29 | } 30 | }, 31 | "idiom" : "universal" 32 | } 33 | ], 34 | "info" : { 35 | "author" : "xcode", 36 | "version" : 1 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /Projects/UI/Resources/Colors.xcassets/mainOrange.colorset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "colors" : [ 3 | { 4 | "color" : { 5 | "color-space" : "srgb", 6 | "components" : { 7 | "alpha" : "1.000", 8 | "blue" : "0x32", 9 | "green" : "0x64", 10 | "red" : "0xFA" 11 | } 12 | }, 13 | "idiom" : "universal" 14 | }, 15 | { 16 | "appearances" : [ 17 | { 18 | "appearance" : "luminosity", 19 | "value" : "dark" 20 | } 21 | ], 22 | "color" : { 23 | "color-space" : "srgb", 24 | "components" : { 25 | "alpha" : "1.000", 26 | "blue" : "0x30", 27 | "green" : "0x5A", 28 | "red" : "0xD7" 29 | } 30 | }, 31 | "idiom" : "universal" 32 | } 33 | ], 34 | "info" : { 35 | "author" : "xcode", 36 | "version" : 1 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /Projects/UI/Resources/Colors.xcassets/mainWhite.colorset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "colors" : [ 3 | { 4 | "color" : { 5 | "color-space" : "srgb", 6 | "components" : { 7 | "alpha" : "1.000", 8 | "blue" : "0xFF", 9 | "green" : "0xFF", 10 | "red" : "0xFF" 11 | } 12 | }, 13 | "idiom" : "universal" 14 | }, 15 | { 16 | "appearances" : [ 17 | { 18 | "appearance" : "luminosity", 19 | "value" : "dark" 20 | } 21 | ], 22 | "color" : { 23 | "color-space" : "srgb", 24 | "components" : { 25 | "alpha" : "1.000", 26 | "blue" : "0x1E", 27 | "green" : "0x1C", 28 | "red" : "0x1C" 29 | } 30 | }, 31 | "idiom" : "universal" 32 | } 33 | ], 34 | "info" : { 35 | "author" : "xcode", 36 | "version" : 1 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /Projects/UI/Resources/Colors.xcassets/orange100.colorset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "colors" : [ 3 | { 4 | "color" : { 5 | "color-space" : "srgb", 6 | "components" : { 7 | "alpha" : "0.100", 8 | "blue" : "0x32", 9 | "green" : "0x64", 10 | "red" : "0xFA" 11 | } 12 | }, 13 | "idiom" : "universal" 14 | }, 15 | { 16 | "appearances" : [ 17 | { 18 | "appearance" : "luminosity", 19 | "value" : "dark" 20 | } 21 | ], 22 | "color" : { 23 | "color-space" : "srgb", 24 | "components" : { 25 | "alpha" : "0.100", 26 | "blue" : "0x30", 27 | "green" : "0x5A", 28 | "red" : "0xD7" 29 | } 30 | }, 31 | "idiom" : "universal" 32 | } 33 | ], 34 | "info" : { 35 | "author" : "xcode", 36 | "version" : 1 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /Projects/UI/Resources/Colors.xcassets/shareBG.colorset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "colors" : [ 3 | { 4 | "color" : { 5 | "color-space" : "srgb", 6 | "components" : { 7 | "alpha" : "1.000", 8 | "blue" : "0x17", 9 | "green" : "0x17", 10 | "red" : "0x17" 11 | } 12 | }, 13 | "idiom" : "universal" 14 | }, 15 | { 16 | "appearances" : [ 17 | { 18 | "appearance" : "luminosity", 19 | "value" : "dark" 20 | } 21 | ], 22 | "color" : { 23 | "color-space" : "srgb", 24 | "components" : { 25 | "alpha" : "1.000", 26 | "blue" : "0x17", 27 | "green" : "0x17", 28 | "red" : "0x17" 29 | } 30 | }, 31 | "idiom" : "universal" 32 | } 33 | ], 34 | "info" : { 35 | "author" : "xcode", 36 | "version" : 1 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /Projects/UI/Resources/Colors.xcassets/toast1.colorset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "colors" : [ 3 | { 4 | "color" : { 5 | "color-space" : "srgb", 6 | "components" : { 7 | "alpha" : "1.000", 8 | "blue" : "0x6F", 9 | "green" : "0xFF", 10 | "red" : "0x9D" 11 | } 12 | }, 13 | "idiom" : "universal" 14 | }, 15 | { 16 | "appearances" : [ 17 | { 18 | "appearance" : "luminosity", 19 | "value" : "dark" 20 | } 21 | ], 22 | "color" : { 23 | "color-space" : "srgb", 24 | "components" : { 25 | "alpha" : "1.000", 26 | "blue" : "0x41", 27 | "green" : "0xDE", 28 | "red" : "0x73" 29 | } 30 | }, 31 | "idiom" : "universal" 32 | } 33 | ], 34 | "info" : { 35 | "author" : "xcode", 36 | "version" : 1 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /Projects/UI/Resources/Colors.xcassets/toast2.colorset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "colors" : [ 3 | { 4 | "color" : { 5 | "color-space" : "srgb", 6 | "components" : { 7 | "alpha" : "1.000", 8 | "blue" : "0x32", 9 | "green" : "0x32", 10 | "red" : "0xFF" 11 | } 12 | }, 13 | "idiom" : "universal" 14 | }, 15 | { 16 | "appearances" : [ 17 | { 18 | "appearance" : "luminosity", 19 | "value" : "dark" 20 | } 21 | ], 22 | "color" : { 23 | "color-space" : "srgb", 24 | "components" : { 25 | "alpha" : "1.000", 26 | "blue" : "0x25", 27 | "green" : "0x25", 28 | "red" : "0xDC" 29 | } 30 | }, 31 | "idiom" : "universal" 32 | } 33 | ], 34 | "info" : { 35 | "author" : "xcode", 36 | "version" : 1 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /Projects/UI/Resources/Colors.xcassets/toastBG.colorset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "colors" : [ 3 | { 4 | "color" : { 5 | "color-space" : "srgb", 6 | "components" : { 7 | "alpha" : "0.900", 8 | "blue" : "0x60", 9 | "green" : "0x60", 10 | "red" : "0x60" 11 | } 12 | }, 13 | "idiom" : "universal" 14 | }, 15 | { 16 | "appearances" : [ 17 | { 18 | "appearance" : "luminosity", 19 | "value" : "dark" 20 | } 21 | ], 22 | "color" : { 23 | "color-space" : "srgb", 24 | "components" : { 25 | "alpha" : "0.900", 26 | "blue" : "0xDB", 27 | "green" : "0xDB", 28 | "red" : "0xDB" 29 | } 30 | }, 31 | "idiom" : "universal" 32 | } 33 | ], 34 | "info" : { 35 | "author" : "xcode", 36 | "version" : 1 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /Projects/UI/Resources/Images.xcassets/AppImages/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "author" : "xcode", 4 | "version" : 1 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /Projects/UI/Resources/Images.xcassets/AppImages/appleMusic.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "appleMusic.png", 5 | "idiom" : "universal", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "author" : "xcode", 19 | "version" : 1 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /Projects/UI/Resources/Images.xcassets/AppImages/appleMusic.imageset/appleMusic.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DeveloperAcademy-POSTECH/2023-MacC-Team12-Creative8/4b68cf6d2cee5683693269560ffbd23162034ec0/Projects/UI/Resources/Images.xcassets/AppImages/appleMusic.imageset/appleMusic.png -------------------------------------------------------------------------------- /Projects/UI/Resources/Images.xcassets/AppImages/spotify.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "spotify.png", 5 | "idiom" : "universal", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "author" : "xcode", 19 | "version" : 1 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /Projects/UI/Resources/Images.xcassets/AppImages/spotify.imageset/spotify.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DeveloperAcademy-POSTECH/2023-MacC-Team12-Creative8/4b68cf6d2cee5683693269560ffbd23162034ec0/Projects/UI/Resources/Images.xcassets/AppImages/spotify.imageset/spotify.png -------------------------------------------------------------------------------- /Projects/UI/Resources/Images.xcassets/AppImages/youtubeMusic.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "youtubeMusic.png", 5 | "idiom" : "universal", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "author" : "xcode", 19 | "version" : 1 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /Projects/UI/Resources/Images.xcassets/AppImages/youtubeMusic.imageset/youtubeMusic.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DeveloperAcademy-POSTECH/2023-MacC-Team12-Creative8/4b68cf6d2cee5683693269560ffbd23162034ec0/Projects/UI/Resources/Images.xcassets/AppImages/youtubeMusic.imageset/youtubeMusic.png -------------------------------------------------------------------------------- /Projects/UI/Resources/Images.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "author" : "xcode", 4 | "version" : 1 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /Projects/UI/Resources/Images.xcassets/ForeignArtist/AJR.imageset/AJR.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DeveloperAcademy-POSTECH/2023-MacC-Team12-Creative8/4b68cf6d2cee5683693269560ffbd23162034ec0/Projects/UI/Resources/Images.xcassets/ForeignArtist/AJR.imageset/AJR.jpg -------------------------------------------------------------------------------- /Projects/UI/Resources/Images.xcassets/ForeignArtist/AJR.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "AJR.jpg", 5 | "idiom" : "universal", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "author" : "xcode", 19 | "version" : 1 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /Projects/UI/Resources/Images.xcassets/ForeignArtist/BrunoMars.imageset/BrunoMars.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DeveloperAcademy-POSTECH/2023-MacC-Team12-Creative8/4b68cf6d2cee5683693269560ffbd23162034ec0/Projects/UI/Resources/Images.xcassets/ForeignArtist/BrunoMars.imageset/BrunoMars.jpg -------------------------------------------------------------------------------- /Projects/UI/Resources/Images.xcassets/ForeignArtist/BrunoMars.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "BrunoMars.jpg", 5 | "idiom" : "universal", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "author" : "xcode", 19 | "version" : 1 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /Projects/UI/Resources/Images.xcassets/ForeignArtist/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "author" : "xcode", 4 | "version" : 1 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /Projects/UI/Resources/Images.xcassets/ForeignArtist/DojaCat.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "DojaCat.jpg", 5 | "idiom" : "universal", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "author" : "xcode", 19 | "version" : 1 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /Projects/UI/Resources/Images.xcassets/ForeignArtist/DojaCat.imageset/DojaCat.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DeveloperAcademy-POSTECH/2023-MacC-Team12-Creative8/4b68cf6d2cee5683693269560ffbd23162034ec0/Projects/UI/Resources/Images.xcassets/ForeignArtist/DojaCat.imageset/DojaCat.jpg -------------------------------------------------------------------------------- /Projects/UI/Resources/Images.xcassets/ForeignArtist/Drake.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "Drake.png", 5 | "idiom" : "universal", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "author" : "xcode", 19 | "version" : 1 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /Projects/UI/Resources/Images.xcassets/ForeignArtist/Drake.imageset/Drake.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DeveloperAcademy-POSTECH/2023-MacC-Team12-Creative8/4b68cf6d2cee5683693269560ffbd23162034ec0/Projects/UI/Resources/Images.xcassets/ForeignArtist/Drake.imageset/Drake.png -------------------------------------------------------------------------------- /Projects/UI/Resources/Images.xcassets/ForeignArtist/DuaLipa.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "DuaLipa.jpg", 5 | "idiom" : "universal", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "author" : "xcode", 19 | "version" : 1 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /Projects/UI/Resources/Images.xcassets/ForeignArtist/DuaLipa.imageset/DuaLipa.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DeveloperAcademy-POSTECH/2023-MacC-Team12-Creative8/4b68cf6d2cee5683693269560ffbd23162034ec0/Projects/UI/Resources/Images.xcassets/ForeignArtist/DuaLipa.imageset/DuaLipa.jpg -------------------------------------------------------------------------------- /Projects/UI/Resources/Images.xcassets/ForeignArtist/Eminem.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "Eminem.jpg", 5 | "idiom" : "universal", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "author" : "xcode", 19 | "version" : 1 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /Projects/UI/Resources/Images.xcassets/ForeignArtist/Eminem.imageset/Eminem.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DeveloperAcademy-POSTECH/2023-MacC-Team12-Creative8/4b68cf6d2cee5683693269560ffbd23162034ec0/Projects/UI/Resources/Images.xcassets/ForeignArtist/Eminem.imageset/Eminem.jpg -------------------------------------------------------------------------------- /Projects/UI/Resources/Images.xcassets/ForeignArtist/JohnK.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "JohnK.jpg", 5 | "idiom" : "universal", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "author" : "xcode", 19 | "version" : 1 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /Projects/UI/Resources/Images.xcassets/ForeignArtist/JohnK.imageset/JohnK.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DeveloperAcademy-POSTECH/2023-MacC-Team12-Creative8/4b68cf6d2cee5683693269560ffbd23162034ec0/Projects/UI/Resources/Images.xcassets/ForeignArtist/JohnK.imageset/JohnK.jpg -------------------------------------------------------------------------------- /Projects/UI/Resources/Images.xcassets/ForeignArtist/Maroon5.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "Maroon5.jpg", 5 | "idiom" : "universal", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "author" : "xcode", 19 | "version" : 1 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /Projects/UI/Resources/Images.xcassets/ForeignArtist/Maroon5.imageset/Maroon5.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DeveloperAcademy-POSTECH/2023-MacC-Team12-Creative8/4b68cf6d2cee5683693269560ffbd23162034ec0/Projects/UI/Resources/Images.xcassets/ForeignArtist/Maroon5.imageset/Maroon5.jpg -------------------------------------------------------------------------------- /Projects/UI/Resources/Images.xcassets/ForeignArtist/PostMalone.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "PostMalone.jpg", 5 | "idiom" : "universal", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "author" : "xcode", 19 | "version" : 1 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /Projects/UI/Resources/Images.xcassets/ForeignArtist/PostMalone.imageset/PostMalone.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DeveloperAcademy-POSTECH/2023-MacC-Team12-Creative8/4b68cf6d2cee5683693269560ffbd23162034ec0/Projects/UI/Resources/Images.xcassets/ForeignArtist/PostMalone.imageset/PostMalone.jpg -------------------------------------------------------------------------------- /Projects/UI/Resources/Images.xcassets/ForeignArtist/SamSmith.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "SamSmith.jpg", 5 | "idiom" : "universal", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "author" : "xcode", 19 | "version" : 1 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /Projects/UI/Resources/Images.xcassets/ForeignArtist/SamSmith.imageset/SamSmith.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DeveloperAcademy-POSTECH/2023-MacC-Team12-Creative8/4b68cf6d2cee5683693269560ffbd23162034ec0/Projects/UI/Resources/Images.xcassets/ForeignArtist/SamSmith.imageset/SamSmith.jpg -------------------------------------------------------------------------------- /Projects/UI/Resources/Images.xcassets/ForeignArtist/TheWeekend.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "TheWeekend.jpg", 5 | "idiom" : "universal", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "author" : "xcode", 19 | "version" : 1 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /Projects/UI/Resources/Images.xcassets/ForeignArtist/TheWeekend.imageset/TheWeekend.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DeveloperAcademy-POSTECH/2023-MacC-Team12-Creative8/4b68cf6d2cee5683693269560ffbd23162034ec0/Projects/UI/Resources/Images.xcassets/ForeignArtist/TheWeekend.imageset/TheWeekend.jpg -------------------------------------------------------------------------------- /Projects/UI/Resources/Images.xcassets/ForeignArtist/TroyeSivan.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "TroyeSivan.jpg", 5 | "idiom" : "universal", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "author" : "xcode", 19 | "version" : 1 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /Projects/UI/Resources/Images.xcassets/ForeignArtist/TroyeSivan.imageset/TroyeSivan.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DeveloperAcademy-POSTECH/2023-MacC-Team12-Creative8/4b68cf6d2cee5683693269560ffbd23162034ec0/Projects/UI/Resources/Images.xcassets/ForeignArtist/TroyeSivan.imageset/TroyeSivan.jpg -------------------------------------------------------------------------------- /Projects/UI/Resources/Images.xcassets/KoreanArtist/BTS.imageset/BTS.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DeveloperAcademy-POSTECH/2023-MacC-Team12-Creative8/4b68cf6d2cee5683693269560ffbd23162034ec0/Projects/UI/Resources/Images.xcassets/KoreanArtist/BTS.imageset/BTS.jpg -------------------------------------------------------------------------------- /Projects/UI/Resources/Images.xcassets/KoreanArtist/BTS.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "BTS.jpg", 5 | "idiom" : "universal", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "author" : "xcode", 19 | "version" : 1 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /Projects/UI/Resources/Images.xcassets/KoreanArtist/CarTheGarden.imageset/CarTheGarden.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DeveloperAcademy-POSTECH/2023-MacC-Team12-Creative8/4b68cf6d2cee5683693269560ffbd23162034ec0/Projects/UI/Resources/Images.xcassets/KoreanArtist/CarTheGarden.imageset/CarTheGarden.jpg -------------------------------------------------------------------------------- /Projects/UI/Resources/Images.xcassets/KoreanArtist/CarTheGarden.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "CarTheGarden.jpg", 5 | "idiom" : "universal", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "author" : "xcode", 19 | "version" : 1 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /Projects/UI/Resources/Images.xcassets/KoreanArtist/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "author" : "xcode", 4 | "version" : 1 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /Projects/UI/Resources/Images.xcassets/KoreanArtist/DAY6.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "DAY6.jpg", 5 | "idiom" : "universal", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "author" : "xcode", 19 | "version" : 1 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /Projects/UI/Resources/Images.xcassets/KoreanArtist/DAY6.imageset/DAY6.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DeveloperAcademy-POSTECH/2023-MacC-Team12-Creative8/4b68cf6d2cee5683693269560ffbd23162034ec0/Projects/UI/Resources/Images.xcassets/KoreanArtist/DAY6.imageset/DAY6.jpg -------------------------------------------------------------------------------- /Projects/UI/Resources/Images.xcassets/KoreanArtist/JayPark.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "JayPark.jpg", 5 | "idiom" : "universal", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "author" : "xcode", 19 | "version" : 1 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /Projects/UI/Resources/Images.xcassets/KoreanArtist/JayPark.imageset/JayPark.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DeveloperAcademy-POSTECH/2023-MacC-Team12-Creative8/4b68cf6d2cee5683693269560ffbd23162034ec0/Projects/UI/Resources/Images.xcassets/KoreanArtist/JayPark.imageset/JayPark.jpg -------------------------------------------------------------------------------- /Projects/UI/Resources/Images.xcassets/KoreanArtist/LeeYoungJi.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "LeeYoungJi.jpg", 5 | "idiom" : "universal", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "author" : "xcode", 19 | "version" : 1 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /Projects/UI/Resources/Images.xcassets/KoreanArtist/LeeYoungJi.imageset/LeeYoungJi.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DeveloperAcademy-POSTECH/2023-MacC-Team12-Creative8/4b68cf6d2cee5683693269560ffbd23162034ec0/Projects/UI/Resources/Images.xcassets/KoreanArtist/LeeYoungJi.imageset/LeeYoungJi.jpg -------------------------------------------------------------------------------- /Projects/UI/Resources/Images.xcassets/KoreanArtist/NCTDream.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "NCTDream.jpg", 5 | "idiom" : "universal", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "author" : "xcode", 19 | "version" : 1 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /Projects/UI/Resources/Images.xcassets/KoreanArtist/NCTDream.imageset/NCTDream.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DeveloperAcademy-POSTECH/2023-MacC-Team12-Creative8/4b68cf6d2cee5683693269560ffbd23162034ec0/Projects/UI/Resources/Images.xcassets/KoreanArtist/NCTDream.imageset/NCTDream.jpg -------------------------------------------------------------------------------- /Projects/UI/Resources/Images.xcassets/KoreanArtist/NewJeans.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "NewJeans.jpg", 5 | "idiom" : "universal", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "author" : "xcode", 19 | "version" : 1 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /Projects/UI/Resources/Images.xcassets/KoreanArtist/NewJeans.imageset/NewJeans.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DeveloperAcademy-POSTECH/2023-MacC-Team12-Creative8/4b68cf6d2cee5683693269560ffbd23162034ec0/Projects/UI/Resources/Images.xcassets/KoreanArtist/NewJeans.imageset/NewJeans.jpg -------------------------------------------------------------------------------- /Projects/UI/Resources/Images.xcassets/KoreanArtist/PaulKim.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "PaulKim.jpg", 5 | "idiom" : "universal", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "author" : "xcode", 19 | "version" : 1 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /Projects/UI/Resources/Images.xcassets/KoreanArtist/PaulKim.imageset/PaulKim.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DeveloperAcademy-POSTECH/2023-MacC-Team12-Creative8/4b68cf6d2cee5683693269560ffbd23162034ec0/Projects/UI/Resources/Images.xcassets/KoreanArtist/PaulKim.imageset/PaulKim.jpg -------------------------------------------------------------------------------- /Projects/UI/Resources/Images.xcassets/KoreanArtist/StrayKids.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "StrayKids.jpg", 5 | "idiom" : "universal", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "author" : "xcode", 19 | "version" : 1 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /Projects/UI/Resources/Images.xcassets/KoreanArtist/StrayKids.imageset/StrayKids.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DeveloperAcademy-POSTECH/2023-MacC-Team12-Creative8/4b68cf6d2cee5683693269560ffbd23162034ec0/Projects/UI/Resources/Images.xcassets/KoreanArtist/StrayKids.imageset/StrayKids.jpg -------------------------------------------------------------------------------- /Projects/UI/Resources/Images.xcassets/KoreanArtist/Woodz.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "Woodz.jpg", 5 | "idiom" : "universal", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "author" : "xcode", 19 | "version" : 1 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /Projects/UI/Resources/Images.xcassets/KoreanArtist/Woodz.imageset/Woodz.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DeveloperAcademy-POSTECH/2023-MacC-Team12-Creative8/4b68cf6d2cee5683693269560ffbd23162034ec0/Projects/UI/Resources/Images.xcassets/KoreanArtist/Woodz.imageset/Woodz.jpg -------------------------------------------------------------------------------- /Projects/UI/Resources/Images.xcassets/KoreanArtist/Yoonha.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "Yoonha.jpg", 5 | "idiom" : "universal", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "author" : "xcode", 19 | "version" : 1 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /Projects/UI/Resources/Images.xcassets/KoreanArtist/Yoonha.imageset/Yoonha.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DeveloperAcademy-POSTECH/2023-MacC-Team12-Creative8/4b68cf6d2cee5683693269560ffbd23162034ec0/Projects/UI/Resources/Images.xcassets/KoreanArtist/Yoonha.imageset/Yoonha.jpg -------------------------------------------------------------------------------- /Projects/UI/Resources/Images.xcassets/archiveIcon.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "archiveIcon.svg", 5 | "idiom" : "universal" 6 | } 7 | ], 8 | "info" : { 9 | "author" : "xcode", 10 | "version" : 1 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /Projects/UI/Resources/Images.xcassets/archiveIcon.imageset/archiveIcon.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /Projects/UI/Resources/Images.xcassets/artistViewTicket.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x" 6 | }, 7 | { 8 | "appearances" : [ 9 | { 10 | "appearance" : "luminosity", 11 | "value" : "dark" 12 | } 13 | ], 14 | "idiom" : "universal", 15 | "scale" : "1x" 16 | }, 17 | { 18 | "idiom" : "universal", 19 | "scale" : "2x" 20 | }, 21 | { 22 | "appearances" : [ 23 | { 24 | "appearance" : "luminosity", 25 | "value" : "dark" 26 | } 27 | ], 28 | "idiom" : "universal", 29 | "scale" : "2x" 30 | }, 31 | { 32 | "filename" : "Group 1000002051.png", 33 | "idiom" : "universal", 34 | "scale" : "3x" 35 | }, 36 | { 37 | "appearances" : [ 38 | { 39 | "appearance" : "luminosity", 40 | "value" : "dark" 41 | } 42 | ], 43 | "filename" : "darkArtistViewTicket.png", 44 | "idiom" : "universal", 45 | "scale" : "3x" 46 | } 47 | ], 48 | "info" : { 49 | "author" : "xcode", 50 | "version" : 1 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /Projects/UI/Resources/Images.xcassets/artistViewTicket.imageset/Group 1000002051.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DeveloperAcademy-POSTECH/2023-MacC-Team12-Creative8/4b68cf6d2cee5683693269560ffbd23162034ec0/Projects/UI/Resources/Images.xcassets/artistViewTicket.imageset/Group 1000002051.png -------------------------------------------------------------------------------- /Projects/UI/Resources/Images.xcassets/artistViewTicket.imageset/darkArtistViewTicket.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DeveloperAcademy-POSTECH/2023-MacC-Team12-Creative8/4b68cf6d2cee5683693269560ffbd23162034ec0/Projects/UI/Resources/Images.xcassets/artistViewTicket.imageset/darkArtistViewTicket.png -------------------------------------------------------------------------------- /Projects/UI/Resources/Images.xcassets/darkArtistViewTicket.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "darkArtistViewTicket.png", 5 | "idiom" : "universal", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "author" : "xcode", 19 | "version" : 1 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /Projects/UI/Resources/Images.xcassets/darkArtistViewTicket.imageset/darkArtistViewTicket.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DeveloperAcademy-POSTECH/2023-MacC-Team12-Creative8/4b68cf6d2cee5683693269560ffbd23162034ec0/Projects/UI/Resources/Images.xcassets/darkArtistViewTicket.imageset/darkArtistViewTicket.png -------------------------------------------------------------------------------- /Projects/UI/Resources/Images.xcassets/darkTicket.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "darkTicket.png", 5 | "idiom" : "universal", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "author" : "xcode", 19 | "version" : 1 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /Projects/UI/Resources/Images.xcassets/darkTicket.imageset/darkTicket.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DeveloperAcademy-POSTECH/2023-MacC-Team12-Creative8/4b68cf6d2cee5683693269560ffbd23162034ec0/Projects/UI/Resources/Images.xcassets/darkTicket.imageset/darkTicket.png -------------------------------------------------------------------------------- /Projects/UI/Resources/Images.xcassets/homeIcon.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "homeIcon.svg", 5 | "idiom" : "universal" 6 | } 7 | ], 8 | "info" : { 9 | "author" : "xcode", 10 | "version" : 1 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /Projects/UI/Resources/Images.xcassets/homeIcon.imageset/homeIcon.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /Projects/UI/Resources/Images.xcassets/mainViewTicket.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "Ticket.svg", 5 | "idiom" : "universal" 6 | } 7 | ], 8 | "info" : { 9 | "author" : "xcode", 10 | "version" : 1 11 | }, 12 | "properties" : { 13 | "template-rendering-intent" : "template" 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /Projects/UI/Resources/Images.xcassets/mainViewTicket.imageset/Ticket.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /Projects/UI/Resources/Images.xcassets/modalCloseButton.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "modal_close_button.png", 5 | "idiom" : "universal", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "author" : "xcode", 19 | "version" : 1 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /Projects/UI/Resources/Images.xcassets/modalCloseButton.imageset/modal_close_button.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DeveloperAcademy-POSTECH/2023-MacC-Team12-Creative8/4b68cf6d2cee5683693269560ffbd23162034ec0/Projects/UI/Resources/Images.xcassets/modalCloseButton.imageset/modal_close_button.png -------------------------------------------------------------------------------- /Projects/UI/Resources/Images.xcassets/screenshotforOCR.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "screenshotforOCR.png", 5 | "idiom" : "universal", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "author" : "xcode", 19 | "version" : 1 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /Projects/UI/Resources/Images.xcassets/screenshotforOCR.imageset/screenshotforOCR.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DeveloperAcademy-POSTECH/2023-MacC-Team12-Creative8/4b68cf6d2cee5683693269560ffbd23162034ec0/Projects/UI/Resources/Images.xcassets/screenshotforOCR.imageset/screenshotforOCR.png -------------------------------------------------------------------------------- /Projects/UI/Resources/Images.xcassets/searchIcon.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "searchIcon.svg", 5 | "idiom" : "universal" 6 | } 7 | ], 8 | "info" : { 9 | "author" : "xcode", 10 | "version" : 1 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /Projects/UI/Resources/Images.xcassets/searchIcon.imageset/searchIcon.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /Projects/UI/Resources/Images.xcassets/serviceForConcertgoers.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "serviceForConcertgoers.png", 5 | "idiom" : "universal", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "author" : "xcode", 19 | "version" : 1 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /Projects/UI/Resources/Images.xcassets/serviceForConcertgoers.imageset/serviceForConcertgoers.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DeveloperAcademy-POSTECH/2023-MacC-Team12-Creative8/4b68cf6d2cee5683693269560ffbd23162034ec0/Projects/UI/Resources/Images.xcassets/serviceForConcertgoers.imageset/serviceForConcertgoers.png -------------------------------------------------------------------------------- /Projects/UI/Resources/Images.xcassets/seta.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x" 6 | }, 7 | { 8 | "idiom" : "universal", 9 | "scale" : "2x" 10 | }, 11 | { 12 | "filename" : "Seta.svg", 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "author" : "xcode", 19 | "version" : 1 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /Projects/UI/Resources/Images.xcassets/seta.imageset/Seta.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /Projects/UI/Resources/Images.xcassets/ticket.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "ticket.png", 5 | "idiom" : "universal", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "author" : "xcode", 19 | "version" : 1 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /Projects/UI/Resources/Images.xcassets/ticket.imageset/ticket.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DeveloperAcademy-POSTECH/2023-MacC-Team12-Creative8/4b68cf6d2cee5683693269560ffbd23162034ec0/Projects/UI/Resources/Images.xcassets/ticket.imageset/ticket.png -------------------------------------------------------------------------------- /Projects/UI/Resources/Images.xcassets/whiteTicket.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "whiteTicket.png", 5 | "idiom" : "universal", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "author" : "xcode", 19 | "version" : 1 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /Projects/UI/Resources/Images.xcassets/whiteTicket.imageset/whiteTicket.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DeveloperAcademy-POSTECH/2023-MacC-Team12-Creative8/4b68cf6d2cee5683693269560ffbd23162034ec0/Projects/UI/Resources/Images.xcassets/whiteTicket.imageset/whiteTicket.png -------------------------------------------------------------------------------- /Projects/UI/Sources/Extensions/ArchiveExtension.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ArchiveExtension.swift 3 | // UI 4 | // 5 | // Created by A_Mcflurry on 10/22/23. 6 | // Copyright © 2023 com.creative8.seta. All rights reserved. 7 | // 8 | 9 | import SwiftUI 10 | -------------------------------------------------------------------------------- /Projects/UI/Sources/Extensions/CellFrameExtension.swift: -------------------------------------------------------------------------------- 1 | // 2 | // CellFrameExtension.swift 3 | // UI 4 | // 5 | // Created by A_Mcflurry on 10/17/23. 6 | // Copyright © 2023 com.creative8. All rights reserved. 7 | // 8 | 9 | import SwiftUI 10 | 11 | public extension View { 12 | func frameForCell() -> some View { 13 | self.frame(width: getCellFrame(), height: getCellFrame()) 14 | } 15 | private func getCellFrame() -> CGFloat { 16 | return 56 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /Projects/UI/Sources/Extensions/ColorExtension.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Color+.swift 3 | // UI 4 | // 5 | // Created by 고혜지 on 10/15/23. 6 | // Copyright © 2023 com.creative8. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import SwiftUI 11 | 12 | public extension Color { 13 | init(hex: Int, opacity: Double = 1.0) { 14 | let red = Double((hex >> 16) & 0xff) / 255 15 | let green = Double((hex >> 8) & 0xff) / 255 16 | let blue = Double((hex >> 0) & 0xff) / 255 17 | 18 | self.init(.sRGB, red: red, green: green, blue: blue, opacity: opacity) 19 | } 20 | } 21 | 22 | public let setaBundle = Bundle(identifier: "com.creative8.seta.UI") 23 | public extension Color { 24 | static var mainBlack: Color { 25 | Color("mainBlack", bundle: setaBundle) 26 | } 27 | 28 | static var mainOrange: Color { 29 | Color("mainOrange", bundle: setaBundle) 30 | } 31 | 32 | static var mainWhite: Color { 33 | Color("mainWhite", bundle: setaBundle) 34 | } 35 | 36 | static var gray6: Color { 37 | Color("gray6", bundle: setaBundle) 38 | } 39 | 40 | static var orange100: Color { 41 | Color("orange100", bundle: setaBundle) 42 | } 43 | 44 | static var toast1: Color { 45 | Color("toast1", bundle: setaBundle) 46 | } 47 | 48 | static var toast2: Color { 49 | Color("toast2", bundle: setaBundle) 50 | } 51 | 52 | static var ellipsis: Color { 53 | Color("ellipsis", bundle: setaBundle) 54 | } 55 | 56 | static var gray600: Color { 57 | Color("gray600", bundle: setaBundle) 58 | } 59 | 60 | static var toastBG: Color { 61 | Color("toastBG", bundle: setaBundle) 62 | } 63 | 64 | static var shareBG: Color { 65 | Color("shareBG", bundle: setaBundle) 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /Projects/UI/Sources/Extensions/DateFormatterExtension.swift: -------------------------------------------------------------------------------- 1 | // 2 | // DateFormaterExtension.swift 3 | // UI 4 | // 5 | // Created by A_Mcflurry on 10/9/23. 6 | // Copyright © 2023 com.creative8. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | public extension DateFormatter { 12 | static func dateMonthFormatter() -> DateFormatter { 13 | let formatter = DateFormatter() 14 | formatter.dateFormat = "MM.dd" 15 | return formatter 16 | } 17 | 18 | static func yearFormatter() -> DateFormatter { 19 | let formatter = DateFormatter() 20 | formatter.locale = Locale(identifier: "ko_KR") 21 | formatter.dateFormat = "YYYY" 22 | return formatter 23 | } 24 | 25 | static func dateFormatter() -> DateFormatter { 26 | let formatter = DateFormatter() 27 | formatter.locale = Locale(identifier: "ko_KR") 28 | formatter.dateFormat = "YYYY년 MM월 dd일" 29 | return formatter 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /Projects/UI/Sources/Extensions/ImageCropExtension.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ImageCropExtension.swift 3 | // UI 4 | // 5 | // Created by A_Mcflurry on 10/17/23. 6 | // Copyright © 2023 com.creative8. All rights reserved. 7 | // 8 | 9 | import SwiftUI 10 | 11 | public extension Image { 12 | func centerCropped() -> some View { 13 | GeometryReader { geo in 14 | self 15 | .resizable() 16 | .scaledToFill() 17 | .frame(width: geo.size.width, height: geo.size.height) 18 | .clipped() 19 | } 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /Projects/UI/Sources/Extensions/UIScreenExtension.swift: -------------------------------------------------------------------------------- 1 | // 2 | // UIScreenExtension.swift 3 | // UI 4 | // 5 | // Created by A_Mcflurry on 10/14/23. 6 | // Copyright © 2023 com.creative8. All rights reserved. 7 | // 8 | 9 | import SwiftUI 10 | 11 | public extension View { 12 | var UIWidth: CGFloat { 13 | return UIScreen.main.bounds.width 14 | } 15 | 16 | var UIHeight: CGFloat { 17 | return UIScreen.main.bounds.height 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /Projects/UI/UI.xcodeproj/xcuserdata/a_mcflurry.xcuserdatad/xcschemes/xcschememanagement.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | SchemeUserState 6 | 7 | UI.xcscheme_^#shared#^_ 8 | 9 | isShown 10 | 11 | orderHint 12 | 4 13 | 14 | UITests.xcscheme_^#shared#^_ 15 | 16 | isShown 17 | 18 | orderHint 19 | 5 20 | 21 | 22 | 23 | 24 | -------------------------------------------------------------------------------- /Projects/UI/UI.xcodeproj/xcuserdata/choihyowon.xcuserdatad/xcschemes/xcschememanagement.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | SchemeUserState 6 | 7 | UI.xcscheme_^#shared#^_ 8 | 9 | isShown 10 | 11 | orderHint 12 | 4 13 | 14 | UITests.xcscheme_^#shared#^_ 15 | 16 | isShown 17 | 18 | orderHint 19 | 5 20 | 21 | 22 | 23 | 24 | -------------------------------------------------------------------------------- /Projects/UI/UI.xcodeproj/xcuserdata/kohyeji.xcuserdatad/xcschemes/xcschememanagement.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | SchemeUserState 6 | 7 | UI.xcscheme_^#shared#^_ 8 | 9 | isShown 10 | 11 | orderHint 12 | 4 13 | 14 | UITests.xcscheme_^#shared#^_ 15 | 16 | isShown 17 | 18 | orderHint 19 | 5 20 | 21 | 22 | 23 | 24 | -------------------------------------------------------------------------------- /Projects/UI/UI.xcodeproj/xcuserdata/yeseul.xcuserdatad/xcschemes/xcschememanagement.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | SchemeUserState 6 | 7 | UI.xcscheme_^#shared#^_ 8 | 9 | isShown 10 | 11 | orderHint 12 | 4 13 | 14 | UITests.xcscheme_^#shared#^_ 15 | 16 | isShown 17 | 18 | orderHint 19 | 5 20 | 21 | 22 | 23 | 24 | -------------------------------------------------------------------------------- /Setlist.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 7 | 9 | 10 | 12 | 13 | 15 | 16 | 18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /Setlist.xcworkspace/xcuserdata/a_mcflurry.xcuserdatad/UserInterfaceState.xcuserstate: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DeveloperAcademy-POSTECH/2023-MacC-Team12-Creative8/4b68cf6d2cee5683693269560ffbd23162034ec0/Setlist.xcworkspace/xcuserdata/a_mcflurry.xcuserdatad/UserInterfaceState.xcuserstate -------------------------------------------------------------------------------- /Setlist.xcworkspace/xcuserdata/a_mcflurry.xcuserdatad/xcschemes/xcschememanagement.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | SchemeUserState 6 | 7 | Promises (Playground) 1.xcscheme 8 | 9 | isShown 10 | 11 | orderHint 12 | 7 13 | 14 | Promises (Playground) 2.xcscheme 15 | 16 | isShown 17 | 18 | orderHint 19 | 8 20 | 21 | Promises (Playground).xcscheme 22 | 23 | isShown 24 | 25 | orderHint 26 | 6 27 | 28 | Setlist-Workspace.xcscheme_^#shared#^_ 29 | 30 | isShown 31 | 32 | orderHint 33 | 3 34 | 35 | 36 | 37 | 38 | -------------------------------------------------------------------------------- /Setlist.xcworkspace/xcuserdata/choihyowon.xcuserdatad/UserInterfaceState.xcuserstate: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DeveloperAcademy-POSTECH/2023-MacC-Team12-Creative8/4b68cf6d2cee5683693269560ffbd23162034ec0/Setlist.xcworkspace/xcuserdata/choihyowon.xcuserdatad/UserInterfaceState.xcuserstate -------------------------------------------------------------------------------- /Setlist.xcworkspace/xcuserdata/kohyeji.xcuserdatad/UserInterfaceState.xcuserstate: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DeveloperAcademy-POSTECH/2023-MacC-Team12-Creative8/4b68cf6d2cee5683693269560ffbd23162034ec0/Setlist.xcworkspace/xcuserdata/kohyeji.xcuserdatad/UserInterfaceState.xcuserstate -------------------------------------------------------------------------------- /Setlist.xcworkspace/xcuserdata/kohyeji.xcuserdatad/xcdebugger/Breakpoints_v2.xcbkptlist: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 9 | 16 | 17 | 18 | 19 | 20 | 22 | 34 | 35 | 36 | 37 | 38 | -------------------------------------------------------------------------------- /Setlist.xcworkspace/xcuserdata/kohyeji.xcuserdatad/xcschemes/xcschememanagement.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | SchemeUserState 6 | 7 | Promises (Playground) 1.xcscheme 8 | 9 | isShown 10 | 11 | orderHint 12 | 10 13 | 14 | Promises (Playground) 2.xcscheme 15 | 16 | isShown 17 | 18 | orderHint 19 | 11 20 | 21 | Promises (Playground).xcscheme 22 | 23 | isShown 24 | 25 | orderHint 26 | 9 27 | 28 | Setlist-Workspace.xcscheme_^#shared#^_ 29 | 30 | isShown 31 | 32 | orderHint 33 | 3 34 | 35 | 36 | 37 | 38 | -------------------------------------------------------------------------------- /Setlist.xcworkspace/xcuserdata/yeseul.xcuserdatad/UserInterfaceState.xcuserstate: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DeveloperAcademy-POSTECH/2023-MacC-Team12-Creative8/4b68cf6d2cee5683693269560ffbd23162034ec0/Setlist.xcworkspace/xcuserdata/yeseul.xcuserdatad/UserInterfaceState.xcuserstate -------------------------------------------------------------------------------- /Setlist.xcworkspace/xcuserdata/yeseul.xcuserdatad/xcdebugger/Breakpoints_v2.xcbkptlist: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 9 | 21 | 22 | 23 | 24 | 25 | -------------------------------------------------------------------------------- /Setlist.xcworkspace/xcuserdata/yeseul.xcuserdatad/xcschemes/xcschememanagement.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | SchemeUserState 6 | 7 | Promises (Playground) 1.xcscheme 8 | 9 | isShown 10 | 11 | orderHint 12 | 11 13 | 14 | Promises (Playground) 2.xcscheme 15 | 16 | isShown 17 | 18 | orderHint 19 | 12 20 | 21 | Promises (Playground).xcscheme 22 | 23 | isShown 24 | 25 | orderHint 26 | 10 27 | 28 | Setlist-Workspace.xcscheme_^#shared#^_ 29 | 30 | isShown 31 | 32 | orderHint 33 | 3 34 | 35 | 36 | 37 | 38 | -------------------------------------------------------------------------------- /Tuist/Config.swift: -------------------------------------------------------------------------------- 1 | import ProjectDescription 2 | 3 | let config = Config( 4 | 5 | ) 6 | -------------------------------------------------------------------------------- /Tuist/ProjectDescriptionHelpers/Dependency+Project.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Dependency+Project.swift 3 | // ProjectDescriptionHelpers 4 | // 5 | // Created by 최효원 on 2023/10/06. 6 | // 7 | 8 | import ProjectDescription 9 | 10 | extension TargetDependency { 11 | public enum Projcet {} 12 | } 13 | 14 | public extension TargetDependency.Projcet { 15 | static let Feature = TargetDependency.project(target: "Feature", path: .relativeToRoot("Projects/Feature")) 16 | static let Core = TargetDependency.project(target: "Core", path: .relativeToRoot("Projects/Core")) 17 | static let UI = TargetDependency.project(target: "UI", path: .relativeToRoot("Projects/UI")) 18 | } 19 | -------------------------------------------------------------------------------- /Tuist/ProjectDescriptionHelpers/Dependency+Spm.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Dependency+Spm.swift 3 | // ProjectDescriptionHelpers 4 | // 5 | // Created by 최효원 on 2023/10/06. 6 | // 7 | 8 | import ProjectDescription 9 | 10 | public extension TargetDependency { 11 | enum SPM {} 12 | } 13 | 14 | public extension Package { 15 | static let MarqueeText = Package.remote(url: "https://github.com/joekndy/MarqueeText.git", requirement: .branch("master")) 16 | static let SpotifyAPI = Package.remote(url: "https://github.com/Peter-Schorn/SpotifyAPI.git", requirement: .upToNextMajor(from: "3.0.0")) 17 | static let KeychainAccess = Package.remote(url: "https://github.com/kishikawakatsumi/KeychainAccess.git", requirement: .upToNextMajor(from: "4.2.2")) 18 | static let Firebase = Package.remote(url: "https://github.com/firebase/firebase-ios-sdk.git", requirement: .upToNextMajor(from: "10.17.0")) 19 | } 20 | 21 | public extension TargetDependency.SPM { 22 | static let Firebase = TargetDependency.package(product: "FirebaseAnalytics") 23 | static let MarqueeText = TargetDependency.package(product: "MarqueeText") 24 | static let SpotifyAPI = TargetDependency.package(product: "SpotifyAPI") 25 | static let KeychainAccess = TargetDependency.package(product: "KeychainAccess") 26 | } 27 | -------------------------------------------------------------------------------- /Tuist/ProjectDescriptionHelpers/Project+Templates.swift: -------------------------------------------------------------------------------- 1 | import ProjectDescription 2 | 3 | public extension Project { 4 | static func makeModule( 5 | name: String, 6 | platform: Platform = .iOS, 7 | product: Product, 8 | organizationName: String = "com.creative8.seta", 9 | packages: [Package] = [], 10 | deploymentTarget: DeploymentTarget? = .iOS(targetVersion: "17.0", devices: [.iphone]), 11 | dependencies: [TargetDependency] = [ 12 | ], 13 | sources: SourceFilesList? = nil, 14 | resources: ResourceFileElements? = nil, 15 | infoPlist: InfoPlist = .default 16 | ) -> Project { 17 | let settings: Settings = .settings( 18 | base: [:], 19 | configurations: [ 20 | .debug(name: .debug), 21 | .release(name: .release) 22 | ], defaultSettings: .recommended) 23 | 24 | let appTarget = Target( 25 | name: name, 26 | platform: platform, 27 | product: product, 28 | bundleId: "\(organizationName).\(name)", 29 | deploymentTarget: deploymentTarget, 30 | infoPlist: infoPlist, 31 | sources: sources, 32 | resources: resources, 33 | dependencies: dependencies 34 | ) 35 | 36 | let testTarget = Target( 37 | name: "\(name)Tests", 38 | platform: platform, 39 | product: .unitTests, 40 | bundleId: "\(organizationName).\(name)Tests", 41 | deploymentTarget: deploymentTarget, 42 | infoPlist: .default, 43 | dependencies: [.target(name: name)] 44 | ) 45 | 46 | let schemes: [Scheme] = [.makeScheme(target: .debug, name: name)] 47 | 48 | let targets: [Target] = [appTarget, testTarget] 49 | 50 | return Project( 51 | name: name, 52 | organizationName: organizationName, 53 | packages: packages, 54 | settings: settings, 55 | targets: targets, 56 | schemes: schemes 57 | ) 58 | } 59 | } 60 | 61 | extension Scheme { 62 | static func makeScheme(target: ConfigurationName, name: String) -> Scheme { 63 | return Scheme( 64 | name: name, 65 | shared: true, 66 | buildAction: .buildAction(targets: ["\(name)"]), 67 | testAction: .targets( 68 | ["\(name)Tests"], 69 | configuration: target, 70 | options: .options(coverage: true, codeCoverageTargets: ["\(name)"]) 71 | ), 72 | runAction: .runAction(configuration: target), 73 | archiveAction: .archiveAction(configuration: target), 74 | profileAction: .profileAction(configuration: target), 75 | analyzeAction: .analyzeAction(configuration: target) 76 | ) 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /Workspace.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Workspace.swift 3 | // ProjectDescriptionHelpers 4 | // 5 | // Created by 최효원 on 2023/10/06. 6 | // 7 | 8 | import ProjectDescription 9 | 10 | let workspace = Workspace( 11 | name: "Setlist", 12 | projects: [ 13 | "Projects/App" 14 | ] 15 | ) 16 | --------------------------------------------------------------------------------