├── .github
└── pull_request_template.md
├── .gitignore
├── README.md
├── app
├── .gitignore
├── build.gradle.kts
├── proguard-rules.pro
└── src
│ ├── androidTest
│ └── java
│ │ └── com
│ │ └── squirtles
│ │ └── musicroad
│ │ └── ExampleInstrumentedTest.kt
│ ├── main
│ ├── AndroidManifest.xml
│ ├── ic_musicroad_playstore.png
│ ├── java
│ │ └── com
│ │ │ └── squirtles
│ │ │ └── musicroad
│ │ │ └── MusicRoadApplication.kt
│ └── res
│ │ ├── mipmap-anydpi-v26
│ │ ├── ic_musicroad.xml
│ │ └── ic_musicroad_round.xml
│ │ ├── mipmap-hdpi
│ │ ├── ic_musicroad.webp
│ │ └── ic_musicroad_round.webp
│ │ ├── mipmap-mdpi
│ │ ├── ic_musicroad.webp
│ │ └── ic_musicroad_round.webp
│ │ ├── mipmap-xhdpi
│ │ ├── ic_musicroad.webp
│ │ └── ic_musicroad_round.webp
│ │ ├── mipmap-xxhdpi
│ │ ├── ic_musicroad.webp
│ │ └── ic_musicroad_round.webp
│ │ ├── mipmap-xxxhdpi
│ │ ├── ic_musicroad.webp
│ │ └── ic_musicroad_round.webp
│ │ └── xml
│ │ ├── backup_rules.xml
│ │ └── data_extraction_rules.xml
│ └── test
│ └── java
│ └── com
│ └── squirtles
│ └── musicroad
│ └── ExampleUnitTest.kt
├── build-logic
├── convention
│ ├── .gitignore
│ ├── build.gradle.kts
│ └── src
│ │ └── main
│ │ └── java
│ │ └── com
│ │ └── squirtles
│ │ └── convention
│ │ ├── AndroidApplicationPlugin.kt
│ │ ├── AndroidComposePlugin.kt
│ │ ├── AndroidLibraryPlugin.kt
│ │ ├── HiltPlugin.kt
│ │ ├── JavaLibraryPlugin.kt
│ │ ├── MusicRoadDataPlugin.kt
│ │ ├── MusicRoadFeaturePlugin.kt
│ │ └── extensions
│ │ ├── ComposeAndroid.kt
│ │ ├── DependencyHandlerScopeExtension.kt
│ │ ├── KotlinAndroid.kt
│ │ ├── KotlinCoroutine.kt
│ │ ├── ProjectExtension.kt
│ │ └── VersionCatalogExtension.kt
├── gradle.properties
└── settings.gradle.kts
├── build.gradle.kts
├── core
├── account
│ ├── .gitignore
│ ├── build.gradle.kts
│ ├── proguard-rules.pro
│ └── src
│ │ └── main
│ │ ├── java
│ │ └── com
│ │ │ └── squirtles
│ │ │ └── core
│ │ │ └── account
│ │ │ ├── AccountViewModel.kt
│ │ │ └── GoogleId.kt
│ │ └── res
│ │ └── values
│ │ └── strings.xml
├── buildconfig
│ ├── .gitignore
│ ├── build.gradle.kts
│ └── src
│ │ └── main
│ │ └── java
│ │ └── com
│ │ └── squirtles
│ │ └── core
│ │ └── buildconfig
│ │ └── LocalPropertyProvider.kt
├── common
│ ├── .gitignore
│ ├── build.gradle.kts
│ ├── proguard-rules.pro
│ └── src
│ │ └── main
│ │ ├── java
│ │ └── com
│ │ │ └── squirtles
│ │ │ └── core
│ │ │ └── common
│ │ │ └── ui
│ │ │ ├── AlbumImage.kt
│ │ │ ├── Constants.kt
│ │ │ ├── CreatedByPickText.kt
│ │ │ ├── DefaultTopAppBar.kt
│ │ │ ├── DoubleBackPressToExit.kt
│ │ │ ├── MessageAlertDialog.kt
│ │ │ ├── MusicRoadPermissions.kt
│ │ │ ├── PickInfoText.kt
│ │ │ ├── SignInAlertDialog.kt
│ │ │ ├── Spacer.kt
│ │ │ └── theme
│ │ │ ├── Color.kt
│ │ │ ├── Theme.kt
│ │ │ └── Type.kt
│ │ └── res
│ │ ├── drawable
│ │ ├── ic_favorite.xml
│ │ └── img_google_logo.png
│ │ └── values
│ │ └── strings.xml
├── mediaservice
│ ├── .gitignore
│ ├── build.gradle.kts
│ ├── proguard-rules.pro
│ └── src
│ │ ├── androidTest
│ │ └── java
│ │ │ └── com
│ │ │ └── squirtles
│ │ │ └── core
│ │ │ └── mediaservice
│ │ │ └── ExampleInstrumentedTest.kt
│ │ ├── main
│ │ ├── java
│ │ │ └── com
│ │ │ │ └── squirtles
│ │ │ │ └── core
│ │ │ │ └── mediaservice
│ │ │ │ ├── CustomMediaSessionCallback.kt
│ │ │ │ ├── MediaControllerProvider.kt
│ │ │ │ ├── MediaNotificationProvider.kt
│ │ │ │ ├── MediaPlayerService.kt
│ │ │ │ ├── PlayerCommands.kt
│ │ │ │ └── di
│ │ │ │ └── MediaDiModule.kt
│ │ └── res
│ │ │ └── drawable
│ │ │ └── ic_musicroad_foreground.xml
│ │ └── test
│ │ └── java
│ │ └── com
│ │ └── squirtles
│ │ └── core
│ │ └── mediaservice
│ │ └── ExampleUnitTest.kt
├── model
│ ├── .gitignore
│ ├── build.gradle.kts
│ ├── proguard-rules.pro
│ └── src
│ │ └── main
│ │ └── java
│ │ └── com
│ │ └── squirtles
│ │ └── core
│ │ └── model
│ │ ├── MusicVideo.kt
│ │ ├── Order.kt
│ │ ├── Pick.kt
│ │ ├── PlayerState.kt
│ │ ├── Song.kt
│ │ └── User.kt
├── musicplayer
│ ├── .gitignore
│ ├── build.gradle.kts
│ ├── proguard-rules.pro
│ └── src
│ │ └── main
│ │ └── java
│ │ └── com
│ │ └── squirtles
│ │ └── core
│ │ └── musicplayer
│ │ ├── PlayerServiceViewModel.kt
│ │ ├── PlayerUiState.kt
│ │ └── PlayerViewModel.kt
├── navigation
│ ├── .gitignore
│ ├── build.gradle.kts
│ ├── proguard-rules.pro
│ └── src
│ │ └── main
│ │ └── java
│ │ └── com
│ │ └── squirtles
│ │ └── core
│ │ └── navigation
│ │ ├── MainRoute.kt
│ │ ├── MapRoute.kt
│ │ ├── Route.kt
│ │ ├── SearchRoute.kt
│ │ └── UserInfoRoute.kt
├── picklist
│ ├── .gitignore
│ ├── build.gradle.kts
│ ├── proguard-rules.pro
│ └── src
│ │ ├── androidTest
│ │ └── java
│ │ │ └── com
│ │ │ └── squirtles
│ │ │ └── core
│ │ │ └── picklist
│ │ │ └── ExampleInstrumentedTest.kt
│ │ ├── main
│ │ ├── java
│ │ │ └── com
│ │ │ │ └── squirtles
│ │ │ │ └── core
│ │ │ │ └── picklist
│ │ │ │ ├── PickListScreenContents.kt
│ │ │ │ ├── PickListType.kt
│ │ │ │ ├── PickListUiState.kt
│ │ │ │ ├── PickListViewModel.kt
│ │ │ │ └── components
│ │ │ │ ├── DeleteSelectedPickDialog.kt
│ │ │ │ ├── EditModeAction.kt
│ │ │ │ ├── EditModeBottomButton.kt
│ │ │ │ ├── OrderBottomSheet.kt
│ │ │ │ └── PickItem.kt
│ │ └── res
│ │ │ └── values
│ │ │ └── strings.xml
│ │ └── test
│ │ └── java
│ │ └── com
│ │ └── squirtles
│ │ └── core
│ │ └── picklist
│ │ └── ExampleUnitTest.kt
├── preference
│ ├── .gitignore
│ ├── build.gradle.kts
│ ├── consumer-rules.pro
│ ├── proguard-rules.pro
│ └── src
│ │ ├── androidTest
│ │ └── java
│ │ │ └── com
│ │ │ └── squirtles
│ │ │ └── core
│ │ │ └── preference
│ │ │ └── ExampleInstrumentedTest.kt
│ │ ├── main
│ │ └── java
│ │ │ └── com
│ │ │ └── squirtles
│ │ │ └── core
│ │ │ └── preference
│ │ │ └── PreferenceViewModel.kt
│ │ └── test
│ │ └── java
│ │ └── com
│ │ └── squirtles
│ │ └── core
│ │ └── preference
│ │ └── ExampleUnitTest.kt
└── util
│ ├── .gitignore
│ ├── build.gradle.kts
│ ├── proguard-rules.pro
│ └── src
│ └── main
│ └── java
│ └── com
│ └── squirtles
│ └── core
│ └── util
│ ├── SerializableType.kt
│ └── ThrottleFirst.kt
├── data
├── applemusic
│ ├── .gitignore
│ ├── build.gradle.kts
│ ├── proguard-rules.pro
│ └── src
│ │ └── main
│ │ └── java
│ │ └── com
│ │ └── squirtles
│ │ └── data
│ │ └── applemusic
│ │ ├── AppleMusicDataSource.kt
│ │ ├── AppleMusicDataSourceImpl.kt
│ │ ├── AppleMusicRepositoryImpl.kt
│ │ ├── SearchSongsPagingSource.kt
│ │ ├── api
│ │ ├── AppleMusicApi.kt
│ │ └── NetworkModule.kt
│ │ ├── di
│ │ └── AppleMusicDiModule.kt
│ │ └── model
│ │ ├── AppleMusicMapper.kt
│ │ ├── Artwork.kt
│ │ ├── Attributes.kt
│ │ ├── Data.kt
│ │ ├── MusicVideoResponse.kt
│ │ ├── Preview.kt
│ │ ├── Results.kt
│ │ ├── SearchResponse.kt
│ │ └── Songs.kt
├── favorite
│ ├── .gitignore
│ ├── build.gradle.kts
│ ├── proguard-rules.pro
│ └── src
│ │ └── main
│ │ └── java
│ │ └── com
│ │ └── squirtles
│ │ └── data
│ │ └── favorite
│ │ ├── CloudFunctionHelper.kt
│ │ ├── FirebaseFavoriteDataSource.kt
│ │ ├── FirebaseFavoriteDataSourceImpl.kt
│ │ ├── FirebaseFavoriteRepositoryImpl.kt
│ │ └── di
│ │ └── FavoriteDiModule.kt
├── firebase
│ ├── .gitignore
│ ├── build.gradle.kts
│ ├── proguard-rules.pro
│ └── src
│ │ └── main
│ │ └── java
│ │ └── com
│ │ └── squirtles
│ │ └── data
│ │ └── firebase
│ │ ├── BaseFirebaseDataSource.kt
│ │ ├── FirebaseDataSourceConstants.kt
│ │ ├── FirebaseModule.kt
│ │ ├── FirebaseRepositoryUtils.kt
│ │ └── model
│ │ ├── FirebaseFavorite.kt
│ │ ├── FirebasePick.kt
│ │ ├── FirebaseUser.kt
│ │ └── Mapper.kt
├── location
│ ├── .gitignore
│ ├── build.gradle.kts
│ ├── proguard-rules.pro
│ └── src
│ │ └── main
│ │ └── java
│ │ └── com
│ │ └── squirtles
│ │ └── data
│ │ └── location
│ │ ├── LocalLocationRepositoryImpl.kt
│ │ └── di
│ │ └── LocationDiModule.kt
├── order
│ ├── .gitignore
│ ├── build.gradle.kts
│ ├── proguard-rules.pro
│ └── src
│ │ └── main
│ │ └── java
│ │ └── com
│ │ └── squirtles
│ │ └── data
│ │ └── order
│ │ ├── LocalPickListOrderRepositoryImpl.kt
│ │ └── di
│ │ └── OrderDiModule.kt
├── pick
│ ├── .gitignore
│ ├── build.gradle.kts
│ ├── proguard-rules.pro
│ └── src
│ │ └── main
│ │ └── java
│ │ └── com
│ │ └── squirtles
│ │ └── data
│ │ └── pick
│ │ ├── FirebasePickDataSource.kt
│ │ ├── FirebasePickDataSourceImpl.kt
│ │ ├── FirebasePickRepositoryImpl.kt
│ │ └── di
│ │ └── PickDiModule.kt
├── preference
│ ├── .gitignore
│ ├── build.gradle.kts
│ ├── consumer-rules.pro
│ ├── proguard-rules.pro
│ └── src
│ │ └── main
│ │ └── java
│ │ └── com
│ │ └── squirtles
│ │ └── data
│ │ └── preference
│ │ ├── PreferenceKeys.kt
│ │ ├── PreferenceRepositoryImpl.kt
│ │ └── di
│ │ └── PreferenceDiModule.kt
└── user
│ ├── .gitignore
│ ├── build.gradle.kts
│ ├── proguard-rules.pro
│ └── src
│ └── main
│ └── java
│ └── com
│ └── squirtles
│ └── data
│ └── user
│ ├── FirebaseUserDataSource.kt
│ ├── FirebaseUserDataSourceImpl.kt
│ ├── FirebaseUserRepositoryImpl.kt
│ ├── LocalUserDataSource.kt
│ ├── LocalUserDataSourceImpl.kt
│ ├── LocalUserRepositoryImpl.kt
│ └── di
│ └── UserDiModule.kt
├── domain
├── applemusic
│ ├── .gitignore
│ ├── build.gradle.kts
│ ├── proguard-rules.pro
│ └── src
│ │ └── main
│ │ └── java
│ │ └── com
│ │ └── squirtles
│ │ └── domain
│ │ └── applemusic
│ │ ├── AppleMusicException.kt
│ │ ├── AppleMusicRepository.kt
│ │ └── usecase
│ │ ├── FetchMusicVideoUseCase.kt
│ │ └── FetchSongsUseCase.kt
├── favorite
│ ├── .gitignore
│ ├── build.gradle.kts
│ ├── proguard-rules.pro
│ └── src
│ │ └── main
│ │ └── java
│ │ └── com
│ │ └── squirtles
│ │ └── domain
│ │ └── favorite
│ │ ├── FirebaseFavoriteRepository.kt
│ │ └── usecase
│ │ ├── CreateFavoriteUseCase.kt
│ │ ├── DeleteFavoriteUseCase.kt
│ │ └── FetchIsFavoriteUseCase.kt
├── firebase
│ ├── .gitignore
│ ├── build.gradle.kts
│ └── src
│ │ └── main
│ │ └── java
│ │ └── com
│ │ └── squirtles
│ │ └── domain
│ │ └── firebase
│ │ └── FirebaseException.kt
├── location
│ ├── .gitignore
│ ├── build.gradle.kts
│ ├── proguard-rules.pro
│ └── src
│ │ └── main
│ │ └── java
│ │ └── com
│ │ └── squirtles
│ │ └── domain
│ │ └── location
│ │ ├── LocalLocationRepository.kt
│ │ └── usecase
│ │ ├── GetLastLocationUseCase.kt
│ │ └── SaveLastLocationUseCase.kt
├── order
│ ├── .gitignore
│ ├── build.gradle.kts
│ ├── proguard-rules.pro
│ └── src
│ │ └── main
│ │ └── java
│ │ └── com
│ │ └── squirtles
│ │ └── domain
│ │ └── order
│ │ ├── LocalPickListOrderRepository.kt
│ │ └── usecase
│ │ ├── GetFavoriteListOrderUseCase.kt
│ │ ├── GetMyPickListOrderUseCase.kt
│ │ ├── SaveFavoriteListOrderUseCase.kt
│ │ └── SaveMyPickListOrderUseCase.kt
├── pick
│ ├── .gitignore
│ ├── build.gradle.kts
│ ├── proguard-rules.pro
│ └── src
│ │ └── main
│ │ └── java
│ │ └── com
│ │ └── squirtles
│ │ └── domain
│ │ └── pick
│ │ ├── FirebasePickRepository.kt
│ │ └── usecase
│ │ ├── CreatePickUseCase.kt
│ │ ├── DeletePickUseCase.kt
│ │ ├── FetchFavoritePicksUseCase.kt
│ │ ├── FetchMyPicksUseCase.kt
│ │ └── FetchPickUseCase.kt
├── picklist
│ ├── .gitignore
│ ├── build.gradle.kts
│ ├── proguard-rules.pro
│ └── src
│ │ └── main
│ │ └── java
│ │ └── com
│ │ └── squirtles
│ │ └── domain
│ │ └── picklist
│ │ ├── FetchPickListUseCaseInterface.kt
│ │ ├── GetPickListOrderUseCaseInterface.kt
│ │ ├── RemovePickUseCaseInterface.kt
│ │ └── SavePickListOrderUseCaseInterface.kt
├── player
│ ├── .gitignore
│ ├── build.gradle.kts
│ ├── proguard-rules.pro
│ └── src
│ │ └── main
│ │ └── java
│ │ └── com
│ │ └── squirtles
│ │ └── domain
│ │ └── player
│ │ ├── MediaPlayerListenerUseCase.kt
│ │ └── MediaPlayerUseCase.kt
├── preference
│ ├── .gitignore
│ ├── build.gradle.kts
│ └── src
│ │ └── main
│ │ └── java
│ │ └── com
│ │ └── squirtles
│ │ └── domain
│ │ └── preference
│ │ ├── PlayerPreference.kt
│ │ ├── PreferenceRepository.kt
│ │ └── usecase
│ │ ├── LoadPlayerPreferenceUseCase.kt
│ │ └── SavePlayerPreferenceUseCase.kt
└── user
│ ├── .gitignore
│ ├── build.gradle.kts
│ ├── proguard-rules.pro
│ └── src
│ └── main
│ └── java
│ └── com
│ └── squirtles
│ └── domain
│ └── user
│ ├── FirebaseUserRepository.kt
│ ├── LocalUserRepository.kt
│ └── usecase
│ ├── CreateGoogleIdUserUseCase.kt
│ ├── DeleteAccountUseCase.kt
│ ├── FetchUserByIdUseCase.kt
│ ├── GetCurrentUidUseCase.kt
│ ├── SignOutUseCase.kt
│ └── UpdateUserNameUseCase.kt
├── feature
├── create
│ ├── .gitignore
│ ├── build.gradle.kts
│ ├── proguard-rules.pro
│ └── src
│ │ ├── androidTest
│ │ └── java
│ │ │ └── com
│ │ │ └── squirtles
│ │ │ └── feature
│ │ │ └── create
│ │ │ └── ExampleInstrumentedTest.kt
│ │ ├── main
│ │ ├── java
│ │ │ └── com
│ │ │ │ └── squirtles
│ │ │ │ └── feature
│ │ │ │ └── create
│ │ │ │ ├── CreatePickScreen.kt
│ │ │ │ ├── CreatePickUiState.kt
│ │ │ │ ├── CreatePickViewModel.kt
│ │ │ │ └── navigation
│ │ │ │ └── CreateNavigation.kt
│ │ └── res
│ │ │ └── values
│ │ │ └── strings.xml
│ │ └── test
│ │ └── java
│ │ └── com
│ │ └── squirtles
│ │ └── feature
│ │ └── create
│ │ └── ExampleUnitTest.kt
├── detail
│ ├── .gitignore
│ ├── build.gradle.kts
│ ├── proguard-rules.pro
│ └── src
│ │ ├── androidTest
│ │ └── java
│ │ │ └── com
│ │ │ └── squirtles
│ │ │ └── feature
│ │ │ └── detail
│ │ │ └── ExampleInstrumentedTest.kt
│ │ ├── main
│ │ ├── java
│ │ │ └── com
│ │ │ │ └── squirtles
│ │ │ │ └── feature
│ │ │ │ └── detail
│ │ │ │ ├── DetailViewModel.kt
│ │ │ │ ├── FavoriteAction.kt
│ │ │ │ ├── PickDetailScreen.kt
│ │ │ │ ├── PickDetailUiState.kt
│ │ │ │ ├── components
│ │ │ │ ├── CircleAlbumCover.kt
│ │ │ │ ├── DetailPickTopAppBar.kt
│ │ │ │ ├── MusicVideoKnob.kt
│ │ │ │ ├── PickCommentText.kt
│ │ │ │ ├── PickInformation.kt
│ │ │ │ ├── PlayCircularProgressIndicator.kt
│ │ │ │ ├── SongInfo.kt
│ │ │ │ ├── SwipeUpIcon.kt
│ │ │ │ └── music
│ │ │ │ │ ├── MusicPlayer.kt
│ │ │ │ │ ├── PlayBar.kt
│ │ │ │ │ ├── PlayProgressIndicator.kt
│ │ │ │ │ └── PlayerControls.kt
│ │ │ │ ├── navigation
│ │ │ │ └── PickDetailNavigation.kt
│ │ │ │ └── videoplayer
│ │ │ │ ├── MusicVideoPlayer.kt
│ │ │ │ ├── MusicVideoScreen.kt
│ │ │ │ ├── VideoPlayerOverlay.kt
│ │ │ │ ├── VideoPlayerState.kt
│ │ │ │ └── VideoPlayerViewModel.kt
│ │ └── res
│ │ │ ├── drawable
│ │ │ ├── ic_delete.xml
│ │ │ ├── ic_favorite.xml
│ │ │ ├── ic_favorite_false.xml
│ │ │ ├── ic_favorite_true.xml
│ │ │ └── ic_swipe.xml
│ │ │ └── values
│ │ │ └── strings.xml
│ │ └── test
│ │ └── java
│ │ └── com
│ │ └── squirtles
│ │ └── feature
│ │ └── detail
│ │ └── ExampleUnitTest.kt
├── favorite
│ ├── .gitignore
│ ├── build.gradle.kts
│ ├── proguard-rules.pro
│ └── src
│ │ ├── androidTest
│ │ └── java
│ │ │ └── com
│ │ │ └── squirtles
│ │ │ └── feature
│ │ │ └── favorite
│ │ │ └── ExampleInstrumentedTest.kt
│ │ ├── main
│ │ └── java
│ │ │ └── com
│ │ │ └── squirtles
│ │ │ └── feature
│ │ │ └── favorite
│ │ │ ├── FavoriteListViewModel.kt
│ │ │ ├── FavoriteScreen.kt
│ │ │ └── navigation
│ │ │ └── FavoriteNavigation.kt
│ │ └── test
│ │ └── java
│ │ └── com
│ │ └── squirtles
│ │ └── feature
│ │ └── favorite
│ │ └── ExampleUnitTest.kt
├── main
│ ├── .gitignore
│ ├── build.gradle.kts
│ ├── proguard-rules.pro
│ └── src
│ │ ├── androidTest
│ │ └── java
│ │ │ └── com
│ │ │ └── squirtles
│ │ │ └── feature
│ │ │ └── main
│ │ │ └── ExampleInstrumentedTest.kt
│ │ ├── main
│ │ ├── AndroidManifest.xml
│ │ ├── java
│ │ │ └── com
│ │ │ │ └── squirtles
│ │ │ │ └── feature
│ │ │ │ └── main
│ │ │ │ ├── LoadingState.kt
│ │ │ │ ├── MainActivity.kt
│ │ │ │ ├── MainViewModel.kt
│ │ │ │ └── navigation
│ │ │ │ ├── MainNavHost.kt
│ │ │ │ └── MainNavigator.kt
│ │ └── res
│ │ │ └── values
│ │ │ ├── colors.xml
│ │ │ ├── strings.xml
│ │ │ └── themes.xml
│ │ └── test
│ │ └── java
│ │ └── com
│ │ └── squirtles
│ │ └── feature
│ │ └── main
│ │ └── ExampleUnitTest.kt
├── map
│ ├── .gitignore
│ ├── build.gradle.kts
│ ├── proguard-rules.pro
│ └── src
│ │ ├── androidTest
│ │ └── java
│ │ │ └── com
│ │ │ └── squirtles
│ │ │ └── feature
│ │ │ └── map
│ │ │ └── ExampleInstrumentedTest.kt
│ │ ├── main
│ │ ├── AndroidManifest.xml
│ │ ├── java
│ │ │ └── com
│ │ │ │ └── squirtles
│ │ │ │ └── feature
│ │ │ │ └── map
│ │ │ │ ├── Constants.kt
│ │ │ │ ├── MapScreen.kt
│ │ │ │ ├── MapViewModel.kt
│ │ │ │ ├── NaverMap.kt
│ │ │ │ ├── components
│ │ │ │ ├── ClusterBottomSheet.kt
│ │ │ │ ├── InfoWindowCard.kt
│ │ │ │ ├── LoadingDialog.kt
│ │ │ │ ├── MapBottomNavBar.kt
│ │ │ │ └── PickNotificationBanner.kt
│ │ │ │ ├── marker
│ │ │ │ ├── ClusterMarkerIconView.kt
│ │ │ │ ├── Clusterer.kt
│ │ │ │ ├── DensityType.kt
│ │ │ │ ├── LeafMarkerIconView.kt
│ │ │ │ └── MarkerKey.kt
│ │ │ │ └── navigation
│ │ │ │ ├── MapNavigation.kt
│ │ │ │ └── NavTab.kt
│ │ └── res
│ │ │ ├── drawable
│ │ │ ├── ic_location.xml
│ │ │ └── ic_musical_note_64.png
│ │ │ └── values
│ │ │ └── strings.xml
│ │ └── test
│ │ └── java
│ │ └── com
│ │ └── squirtles
│ │ └── feature
│ │ └── map
│ │ └── ExampleUnitTest.kt
├── mypick
│ ├── .gitignore
│ ├── build.gradle.kts
│ ├── proguard-rules.pro
│ └── src
│ │ ├── androidTest
│ │ └── java
│ │ │ └── com
│ │ │ └── squirtles
│ │ │ └── feature
│ │ │ └── mypick
│ │ │ └── ExampleInstrumentedTest.kt
│ │ ├── main
│ │ └── java
│ │ │ └── com
│ │ │ └── squirtles
│ │ │ └── feature
│ │ │ └── mypick
│ │ │ ├── MyPickListViewModel.kt
│ │ │ ├── MyPickScreen.kt
│ │ │ └── navigation
│ │ │ └── MyPickNavigation.kt
│ │ └── test
│ │ └── java
│ │ └── com
│ │ └── squirtles
│ │ └── feature
│ │ └── mypick
│ │ └── ExampleUnitTest.kt
├── permission
│ ├── .gitignore
│ ├── build.gradle.kts
│ ├── proguard-rules.pro
│ └── src
│ │ ├── androidTest
│ │ └── java
│ │ │ └── com
│ │ │ └── squirtles
│ │ │ └── feature
│ │ │ └── permission
│ │ │ └── ExampleInstrumentedTest.kt
│ │ ├── main
│ │ ├── java
│ │ │ └── com
│ │ │ │ └── squirtles
│ │ │ │ └── feature
│ │ │ │ └── permission
│ │ │ │ ├── PermissionBar.kt
│ │ │ │ ├── PermissionData.kt
│ │ │ │ ├── PermissionDataFactory.kt
│ │ │ │ └── PermissionScreen.kt
│ │ └── res
│ │ │ └── values
│ │ │ └── strings.xml
│ │ └── test
│ │ └── java
│ │ └── com
│ │ └── squirtles
│ │ └── feature
│ │ └── permission
│ │ └── ExampleUnitTest.kt
├── search
│ ├── .gitignore
│ ├── build.gradle.kts
│ ├── proguard-rules.pro
│ └── src
│ │ ├── androidTest
│ │ └── java
│ │ │ └── com
│ │ │ └── squirtles
│ │ │ └── feature
│ │ │ └── search
│ │ │ └── ExampleInstrumentedTest.kt
│ │ ├── main
│ │ ├── java
│ │ │ └── com
│ │ │ │ └── squirtles
│ │ │ │ └── feature
│ │ │ │ └── search
│ │ │ │ ├── SearchMusicScreen.kt
│ │ │ │ ├── SearchUiConstants.kt
│ │ │ │ ├── SearchUiState.kt
│ │ │ │ ├── SearchViewModel.kt
│ │ │ │ └── navigation
│ │ │ │ └── SearchNavigation.kt
│ │ └── res
│ │ │ └── values
│ │ │ └── strings.xml
│ │ └── test
│ │ └── java
│ │ └── com
│ │ └── squirtles
│ │ └── feature
│ │ └── search
│ │ └── ExampleUnitTest.kt
└── userinfo
│ ├── .gitignore
│ ├── build.gradle.kts
│ ├── proguard-rules.pro
│ └── src
│ ├── androidTest
│ └── java
│ │ └── com
│ │ └── squirtles
│ │ └── feature
│ │ └── userinfo
│ │ └── ExampleInstrumentedTest.kt
│ ├── main
│ ├── java
│ │ └── com
│ │ │ └── squirtles
│ │ │ └── feature
│ │ │ └── userinfo
│ │ │ ├── UserInfoConstants.kt
│ │ │ ├── UserInfoViewModel.kt
│ │ │ ├── components
│ │ │ ├── MenuItem.kt
│ │ │ └── UserInfoMenus.kt
│ │ │ ├── navigation
│ │ │ └── UserInfoNavigation.kt
│ │ │ └── screen
│ │ │ ├── EditNotificationSettingScreen.kt
│ │ │ ├── EditPlayerEffectScreen.kt
│ │ │ ├── EditProfileScreen.kt
│ │ │ └── UserInfoScreen.kt
│ └── res
│ │ ├── drawable
│ │ ├── img_user_default_profile.jpg
│ │ ├── soundeffectbar.xml
│ │ ├── soundeffectfill.xml
│ │ ├── soundeffectnone.xml
│ │ └── soundeffectstroke.xml
│ │ └── values
│ │ └── strings.xml
│ └── test
│ └── java
│ └── com
│ └── squirtles
│ └── feature
│ └── userinfo
│ └── ExampleUnitTest.kt
├── gradle.properties
├── gradle
├── libs.versions.toml
└── wrapper
│ ├── gradle-wrapper.jar
│ └── gradle-wrapper.properties
├── gradlew
├── gradlew.bat
└── settings.gradle.kts
/.github/pull_request_template.md:
--------------------------------------------------------------------------------
1 | ## #️⃣연관된 이슈
2 |
3 | > ex) #이슈번호
4 |
5 | ## 📝작업 내용 및 코드
6 |
7 | > 이번 PR에서 작업한 내용을 간략히 설명해주세요(이미지 첨부 가능)
8 |
9 | ## 💬리뷰 요구사항(선택)
10 |
11 | > 리뷰어가 특별히 봐주었으면 하는 부분이 있다면 작성해주세요
12 | >
13 | > ex) 메서드 XXX의 이름을 더 잘 짓고 싶은데 혹시 좋은 명칭이 있을까요?
--------------------------------------------------------------------------------
/app/.gitignore:
--------------------------------------------------------------------------------
1 | /build
--------------------------------------------------------------------------------
/app/proguard-rules.pro:
--------------------------------------------------------------------------------
1 | # Add project specific ProGuard rules here.
2 | # You can control the set of applied configuration files using the
3 | # proguardFiles setting in build.gradle.
4 | #
5 | # For more details, see
6 | # http://developer.android.com/guide/developing/tools/proguard.html
7 |
8 | # If your project uses WebView with JS, uncomment the following
9 | # and specify the fully qualified class name to the JavaScript interface
10 | # class:
11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview {
12 | # public *;
13 | #}
14 |
15 | # Uncomment this to preserve the line number information for
16 | # debugging stack traces.
17 | #-keepattributes SourceFile,LineNumberTable
18 |
19 | # If you keep the line number information, uncomment this to
20 | # hide the original source file name.
21 | #-renamesourcefileattribute SourceFile
--------------------------------------------------------------------------------
/app/src/androidTest/java/com/squirtles/musicroad/ExampleInstrumentedTest.kt:
--------------------------------------------------------------------------------
1 | package com.squirtles.musicroad
2 |
3 | import androidx.test.platform.app.InstrumentationRegistry
4 | import androidx.test.ext.junit.runners.AndroidJUnit4
5 |
6 | import org.junit.Test
7 | import org.junit.runner.RunWith
8 |
9 | import org.junit.Assert.*
10 |
11 | /**
12 | * Instrumented test, which will execute on an Android device.
13 | *
14 | * See [testing documentation](http://d.android.com/tools/testing).
15 | */
16 | @RunWith(AndroidJUnit4::class)
17 | class ExampleInstrumentedTest {
18 | @Test
19 | fun useAppContext() {
20 | // Context of the app under test.
21 | val appContext = InstrumentationRegistry.getInstrumentation().targetContext
22 | assertEquals("com.squirtles.musicroad", appContext.packageName)
23 | }
24 | }
--------------------------------------------------------------------------------
/app/src/main/ic_musicroad_playstore.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/boostcampwm-2024/and06-musicroad/d893dff01a193094f208e5a4c877d4e8ea93423b/app/src/main/ic_musicroad_playstore.png
--------------------------------------------------------------------------------
/app/src/main/java/com/squirtles/musicroad/MusicRoadApplication.kt:
--------------------------------------------------------------------------------
1 | package com.squirtles.musicroad
2 |
3 | import android.app.Application
4 | import dagger.hilt.android.HiltAndroidApp
5 |
6 | @HiltAndroidApp
7 | class MusicRoadApplication : Application()
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-anydpi-v26/ic_musicroad.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-anydpi-v26/ic_musicroad_round.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-hdpi/ic_musicroad.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/boostcampwm-2024/and06-musicroad/d893dff01a193094f208e5a4c877d4e8ea93423b/app/src/main/res/mipmap-hdpi/ic_musicroad.webp
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-hdpi/ic_musicroad_round.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/boostcampwm-2024/and06-musicroad/d893dff01a193094f208e5a4c877d4e8ea93423b/app/src/main/res/mipmap-hdpi/ic_musicroad_round.webp
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-mdpi/ic_musicroad.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/boostcampwm-2024/and06-musicroad/d893dff01a193094f208e5a4c877d4e8ea93423b/app/src/main/res/mipmap-mdpi/ic_musicroad.webp
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-mdpi/ic_musicroad_round.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/boostcampwm-2024/and06-musicroad/d893dff01a193094f208e5a4c877d4e8ea93423b/app/src/main/res/mipmap-mdpi/ic_musicroad_round.webp
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xhdpi/ic_musicroad.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/boostcampwm-2024/and06-musicroad/d893dff01a193094f208e5a4c877d4e8ea93423b/app/src/main/res/mipmap-xhdpi/ic_musicroad.webp
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xhdpi/ic_musicroad_round.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/boostcampwm-2024/and06-musicroad/d893dff01a193094f208e5a4c877d4e8ea93423b/app/src/main/res/mipmap-xhdpi/ic_musicroad_round.webp
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxhdpi/ic_musicroad.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/boostcampwm-2024/and06-musicroad/d893dff01a193094f208e5a4c877d4e8ea93423b/app/src/main/res/mipmap-xxhdpi/ic_musicroad.webp
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxhdpi/ic_musicroad_round.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/boostcampwm-2024/and06-musicroad/d893dff01a193094f208e5a4c877d4e8ea93423b/app/src/main/res/mipmap-xxhdpi/ic_musicroad_round.webp
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxxhdpi/ic_musicroad.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/boostcampwm-2024/and06-musicroad/d893dff01a193094f208e5a4c877d4e8ea93423b/app/src/main/res/mipmap-xxxhdpi/ic_musicroad.webp
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxxhdpi/ic_musicroad_round.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/boostcampwm-2024/and06-musicroad/d893dff01a193094f208e5a4c877d4e8ea93423b/app/src/main/res/mipmap-xxxhdpi/ic_musicroad_round.webp
--------------------------------------------------------------------------------
/app/src/main/res/xml/backup_rules.xml:
--------------------------------------------------------------------------------
1 |
8 |
9 |
13 |
--------------------------------------------------------------------------------
/app/src/main/res/xml/data_extraction_rules.xml:
--------------------------------------------------------------------------------
1 |
6 |
7 |
8 |
12 |
13 |
19 |
--------------------------------------------------------------------------------
/app/src/test/java/com/squirtles/musicroad/ExampleUnitTest.kt:
--------------------------------------------------------------------------------
1 | package com.squirtles.musicroad
2 |
3 | import org.junit.Test
4 |
5 | import org.junit.Assert.*
6 |
7 | /**
8 | * Example local unit test, which will execute on the development machine (host).
9 | *
10 | * See [testing documentation](http://d.android.com/tools/testing).
11 | */
12 | class ExampleUnitTest {
13 | @Test
14 | fun addition_isCorrect() {
15 | assertEquals(4, 2 + 2)
16 | }
17 | }
--------------------------------------------------------------------------------
/build-logic/convention/.gitignore:
--------------------------------------------------------------------------------
1 | /build
--------------------------------------------------------------------------------
/build-logic/convention/src/main/java/com/squirtles/convention/AndroidApplicationPlugin.kt:
--------------------------------------------------------------------------------
1 | package com.squirtles.convention
2 |
3 | import com.android.build.api.dsl.ApplicationExtension
4 | import com.squirtles.convention.extensions.configureKotlinAndroid
5 | import com.squirtles.convention.extensions.libs
6 | import org.gradle.api.Plugin
7 | import org.gradle.api.Project
8 | import org.gradle.kotlin.dsl.configure
9 |
10 | // app module
11 | class AndroidApplicationPlugin: Plugin {
12 | override fun apply(target: Project) {
13 | with(target) {
14 | pluginManager.run {
15 | apply("com.android.application")
16 | apply("org.jetbrains.kotlin.android")
17 | }
18 |
19 | extensions.configure {
20 | defaultConfig {
21 | applicationId = "com.squirtles.musicroad"
22 |
23 | targetSdk = libs.findVersion("targetSdk").get().toString().toInt()
24 | versionCode = libs.findVersion("versionCode").get().toString().toInt()
25 | versionName = libs.findVersion("versionName").get().toString()
26 |
27 | testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
28 | }
29 |
30 | configureKotlinAndroid(this)
31 | }
32 | }
33 | }
34 | }
35 |
--------------------------------------------------------------------------------
/build-logic/convention/src/main/java/com/squirtles/convention/AndroidComposePlugin.kt:
--------------------------------------------------------------------------------
1 | package com.squirtles.convention
2 |
3 | import com.android.build.gradle.LibraryExtension
4 | import com.squirtles.convention.extensions.configureComposeAndroid
5 | import com.squirtles.convention.extensions.getLibrary
6 | import com.squirtles.convention.extensions.implementation
7 | import com.squirtles.convention.extensions.libs
8 | import org.gradle.api.Plugin
9 | import org.gradle.api.Project
10 | import org.gradle.kotlin.dsl.configure
11 | import org.gradle.kotlin.dsl.dependencies
12 |
13 | class AndroidComposePlugin : Plugin {
14 | override fun apply(target: Project) {
15 | with(target) {
16 | pluginManager.apply("musicroad.android.library")
17 |
18 | extensions.configure {
19 | configureComposeAndroid(this)
20 | }
21 |
22 | dependencies {
23 | implementation(libs.getLibrary("kotlinx.immutable"))
24 | }
25 | }
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/build-logic/convention/src/main/java/com/squirtles/convention/AndroidLibraryPlugin.kt:
--------------------------------------------------------------------------------
1 | package com.squirtles.convention
2 |
3 | import com.android.build.gradle.LibraryExtension
4 | import com.squirtles.convention.extensions.configureKotlinAndroid
5 | import com.squirtles.convention.extensions.configureKotlinCoroutine
6 | import com.squirtles.convention.extensions.getBundle
7 | import com.squirtles.convention.extensions.implementation
8 | import com.squirtles.convention.extensions.libs
9 | import org.gradle.api.Plugin
10 | import org.gradle.api.Project
11 | import org.gradle.kotlin.dsl.configure
12 | import org.gradle.kotlin.dsl.dependencies
13 |
14 | class AndroidLibraryPlugin : Plugin {
15 | override fun apply(target: Project) {
16 | with(target) {
17 | pluginManager.apply("com.android.library")
18 |
19 | extensions.configure {
20 | configureKotlinAndroid(this)
21 | configureKotlinCoroutine(this)
22 | }
23 |
24 | dependencies {
25 | implementation(libs.getBundle("androidx-core"))
26 | }
27 | }
28 | }
29 | }
30 |
--------------------------------------------------------------------------------
/build-logic/convention/src/main/java/com/squirtles/convention/HiltPlugin.kt:
--------------------------------------------------------------------------------
1 | package com.squirtles.convention
2 |
3 | import com.squirtles.convention.extensions.androidTestImplementation
4 | import com.squirtles.convention.extensions.getLibrary
5 | import com.squirtles.convention.extensions.implementation
6 | import com.squirtles.convention.extensions.ksp
7 | import com.squirtles.convention.extensions.kspTest
8 | import com.squirtles.convention.extensions.libs
9 | import org.gradle.api.Plugin
10 | import org.gradle.api.Project
11 | import org.gradle.kotlin.dsl.dependencies
12 |
13 | class HiltPlugin : Plugin {
14 | override fun apply(target: Project) {
15 | with(target) {
16 | pluginManager.run {
17 | apply("dagger.hilt.android.plugin")
18 | apply("com.google.devtools.ksp")
19 | }
20 |
21 | dependencies {
22 | ksp(libs.getLibrary("hilt.android.compiler"))
23 | kspTest(libs.getLibrary("hilt.android.compiler"))
24 | implementation(libs.getLibrary("hilt.android"))
25 | androidTestImplementation(libs.getLibrary("hilt.android.testing"))
26 | }
27 | }
28 | }
29 | }
30 |
--------------------------------------------------------------------------------
/build-logic/convention/src/main/java/com/squirtles/convention/JavaLibraryPlugin.kt:
--------------------------------------------------------------------------------
1 | package com.squirtles.convention
2 |
3 | import com.squirtles.convention.extensions.getLibrary
4 | import com.squirtles.convention.extensions.getVersion
5 | import com.squirtles.convention.extensions.implementation
6 | import com.squirtles.convention.extensions.libs
7 | import org.gradle.api.JavaVersion
8 | import org.gradle.api.Plugin
9 | import org.gradle.api.Project
10 | import org.gradle.api.plugins.JavaPluginExtension
11 | import org.gradle.kotlin.dsl.configure
12 | import org.gradle.kotlin.dsl.dependencies
13 | import org.jetbrains.kotlin.gradle.dsl.KotlinProjectExtension
14 |
15 | class JavaLibraryPlugin : Plugin {
16 | override fun apply(target: Project) {
17 | with(target) {
18 | pluginManager.apply {
19 | apply("org.jetbrains.kotlin.jvm")
20 | apply("java-library")
21 | }
22 |
23 | extensions.configure {
24 | sourceCompatibility = JavaVersion.VERSION_17
25 | targetCompatibility = JavaVersion.VERSION_17
26 | }
27 |
28 | extensions.configure {
29 | jvmToolchain(libs.getVersion("jdkVersion").requiredVersion.toInt())
30 | }
31 |
32 | dependencies {
33 | implementation(libs.getLibrary("inject"))
34 | }
35 | }
36 | }
37 | }
38 |
--------------------------------------------------------------------------------
/build-logic/convention/src/main/java/com/squirtles/convention/MusicRoadDataPlugin.kt:
--------------------------------------------------------------------------------
1 | package com.squirtles.convention
2 |
3 | import com.squirtles.convention.extensions.implementation
4 | import org.gradle.api.Plugin
5 | import org.gradle.api.Project
6 | import org.gradle.kotlin.dsl.dependencies
7 |
8 | class MusicRoadDataPlugin : Plugin {
9 | override fun apply(target: Project) {
10 | with(target) {
11 | pluginManager.apply {
12 | apply("musicroad.android.library")
13 | apply("musicroad.hilt")
14 | }
15 |
16 | dependencies {
17 | implementation(project(":core:model"))
18 | implementation(project(":core:buildconfig"))
19 | }
20 | }
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/build-logic/convention/src/main/java/com/squirtles/convention/MusicRoadFeaturePlugin.kt:
--------------------------------------------------------------------------------
1 | package com.squirtles.convention
2 |
3 | import com.squirtles.convention.extensions.getBundle
4 | import com.squirtles.convention.extensions.implementation
5 | import com.squirtles.convention.extensions.libs
6 | import org.gradle.api.Plugin
7 | import org.gradle.api.Project
8 | import org.gradle.kotlin.dsl.dependencies
9 |
10 |
11 | class MusicRoadFeaturePlugin : Plugin {
12 | override fun apply(target: Project) {
13 | with(target) {
14 | pluginManager.run {
15 | apply("musicroad.compose.library")
16 | apply("musicroad.hilt")
17 | }
18 |
19 | dependencies {
20 | implementation(project(":core:model"))
21 | implementation(project(":core:util"))
22 | implementation(project(":core:common"))
23 | implementation(project(":core:navigation"))
24 |
25 | implementation(libs.getBundle("navigation"))
26 | }
27 | }
28 | }
29 | }
30 |
--------------------------------------------------------------------------------
/build-logic/convention/src/main/java/com/squirtles/convention/extensions/ComposeAndroid.kt:
--------------------------------------------------------------------------------
1 | package com.squirtles.convention.extensions
2 |
3 | import com.android.build.api.dsl.CommonExtension
4 | import org.gradle.api.Project
5 | import org.gradle.kotlin.dsl.dependencies
6 |
7 | internal fun Project.configureComposeAndroid(commonExtension: CommonExtension<*, *, *, *, *, *>) {
8 | commonExtension.apply {
9 | buildFeatures {
10 | compose = true
11 | }
12 |
13 | composeOptions {
14 | kotlinCompilerExtensionVersion = libs.getVersion("compose-compiler").requiredVersion
15 | }
16 |
17 | dependencies {
18 | val composeBom = libs.getLibrary("compose.bom")
19 | implementation(platform(composeBom))
20 | androidTestImplementation(platform(composeBom))
21 | implementation(libs.getBundle("compose"))
22 | implementation(libs.getBundle("material"))
23 | debugImplementation(libs.getBundle("compose-debug"))
24 | androidTestImplementation(libs.getBundle("compose-debug"))
25 | }
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/build-logic/convention/src/main/java/com/squirtles/convention/extensions/KotlinCoroutine.kt:
--------------------------------------------------------------------------------
1 | package com.squirtles.convention.extensions
2 |
3 | import com.android.build.api.dsl.CommonExtension
4 | import org.gradle.api.Project
5 | import org.gradle.kotlin.dsl.dependencies
6 |
7 | internal fun Project.configureKotlinCoroutine(commonExtension: CommonExtension<*, *, *, *, *, *>) {
8 | commonExtension.apply {
9 | dependencies {
10 | implementation(libs.getBundle("coroutines"))
11 | }
12 | }
13 | }
14 |
--------------------------------------------------------------------------------
/build-logic/convention/src/main/java/com/squirtles/convention/extensions/ProjectExtension.kt:
--------------------------------------------------------------------------------
1 | package com.squirtles.convention.extensions
2 |
3 | import org.gradle.api.Project
4 | import org.gradle.api.artifacts.VersionCatalog
5 | import org.gradle.api.artifacts.VersionCatalogsExtension
6 | import org.gradle.kotlin.dsl.getByType
7 |
8 | val Project.libs: VersionCatalog
9 | get() = extensions.getByType().named("libs")
10 |
--------------------------------------------------------------------------------
/build-logic/convention/src/main/java/com/squirtles/convention/extensions/VersionCatalogExtension.kt:
--------------------------------------------------------------------------------
1 | package com.squirtles.convention.extensions
2 |
3 | import org.gradle.api.artifacts.ExternalModuleDependencyBundle
4 | import org.gradle.api.artifacts.MinimalExternalModuleDependency
5 | import org.gradle.api.artifacts.VersionCatalog
6 | import org.gradle.api.artifacts.VersionConstraint
7 | import org.gradle.api.provider.Provider
8 |
9 | fun VersionCatalog.getBundle(bundleName: String): Provider =
10 | findBundle(bundleName).orElseThrow {
11 | NoSuchElementException("Bundle with name $bundleName not found in the catalog")
12 | }
13 |
14 | fun VersionCatalog.getLibrary(libraryName: String): Provider =
15 | findLibrary(libraryName).orElseThrow {
16 | NoSuchElementException("Library with name $libraryName not found in the catalog")
17 | }
18 |
19 | fun VersionCatalog.getVersion(versionName: String): VersionConstraint =
20 | findVersion(versionName).orElseThrow {
21 | NoSuchElementException("Version with name $versionName not found in the catalog")
22 | }
23 |
--------------------------------------------------------------------------------
/build-logic/gradle.properties:
--------------------------------------------------------------------------------
1 | org.gradle.parallel=true
2 | org.gradle.caching=true
3 | org.gradle.configureondemand=true
4 |
--------------------------------------------------------------------------------
/build-logic/settings.gradle.kts:
--------------------------------------------------------------------------------
1 | dependencyResolutionManagement {
2 | repositories {
3 | google()
4 | mavenCentral()
5 | }
6 |
7 | versionCatalogs {
8 | create("libs") {
9 | from(files("../gradle/libs.versions.toml"))
10 | }
11 | }
12 | }
13 |
14 | rootProject.name = "build-logic"
15 | include(":convention")
16 |
--------------------------------------------------------------------------------
/build.gradle.kts:
--------------------------------------------------------------------------------
1 | // Top-level build file where you can add configuration options common to all sub-projects/modules.
2 | plugins {
3 | alias(libs.plugins.android.application) apply false
4 | alias(libs.plugins.jetbrains.kotlin.android) apply false
5 | alias(libs.plugins.android.library) apply false
6 | alias(libs.plugins.ksp) apply false
7 | alias(libs.plugins.hilt) apply false
8 | alias(libs.plugins.kotlin.serialization) apply false
9 | alias(libs.plugins.google.services) apply false
10 | alias(libs.plugins.firebase.crashlytics) apply false
11 | alias(libs.plugins.jetbrains.kotlin.jvm) apply false
12 | }
13 |
14 | buildscript {
15 | repositories {
16 | google()
17 | mavenCentral()
18 | maven {
19 | url = uri("https://repository.map.naver.com/archive/maven")
20 | }
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/core/account/.gitignore:
--------------------------------------------------------------------------------
1 | /build
--------------------------------------------------------------------------------
/core/account/build.gradle.kts:
--------------------------------------------------------------------------------
1 | plugins {
2 | alias(libs.plugins.musicroad.android.library)
3 | alias(libs.plugins.musicroad.hilt)
4 | }
5 |
6 | android {
7 | namespace = "com.squirtles.core.account"
8 | }
9 |
10 | dependencies {
11 | implementation(projects.domain.user)
12 | implementation(projects.core.model)
13 | implementation(projects.core.buildconfig)
14 |
15 | implementation(libs.bundles.auth)
16 | implementation(libs.firebase.auth.ktx)
17 |
18 | testImplementation(libs.junit)
19 | androidTestImplementation(libs.bundles.test)
20 |
21 | // Credentials
22 | implementation(libs.bundles.auth)
23 | }
24 |
--------------------------------------------------------------------------------
/core/account/proguard-rules.pro:
--------------------------------------------------------------------------------
1 | # Add project specific ProGuard rules here.
2 | # You can control the set of applied configuration files using the
3 | # proguardFiles setting in build.gradle.
4 | #
5 | # For more details, see
6 | # http://developer.android.com/guide/developing/tools/proguard.html
7 |
8 | # If your project uses WebView with JS, uncomment the following
9 | # and specify the fully qualified class name to the JavaScript interface
10 | # class:
11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview {
12 | # public *;
13 | #}
14 |
15 | # Uncomment this to preserve the line number information for
16 | # debugging stack traces.
17 | #-keepattributes SourceFile,LineNumberTable
18 |
19 | # If you keep the line number information, uncomment this to
20 | # hide the original source file name.
21 | #-renamesourcefileattribute SourceFile
--------------------------------------------------------------------------------
/core/account/src/main/res/values/strings.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | 기기에 로그인된 구글 계정이 없습니다
5 | Google 로그인을 사용할 수 없는 기기입니다
6 | 로그인에 실패했습니다
7 |
8 |
--------------------------------------------------------------------------------
/core/buildconfig/.gitignore:
--------------------------------------------------------------------------------
1 | /build
--------------------------------------------------------------------------------
/core/buildconfig/src/main/java/com/squirtles/core/buildconfig/LocalPropertyProvider.kt:
--------------------------------------------------------------------------------
1 | package com.squirtles.core.buildconfig
2 |
3 | object LocalPropertyProvider {
4 | const val googleClientId: String = BuildConfig.GOOGLE_CLIENT_ID
5 |
6 | const val appleMusicApiToken: String = BuildConfig.APPLE_MUSIC_API_TOKEN
7 |
8 | const val firestoreDbId: String = BuildConfig.FIRESTORE_DB_ID
9 |
10 | const val httpsCallable: String = BuildConfig.HTTPS_CALLABLE
11 | }
12 |
--------------------------------------------------------------------------------
/core/common/.gitignore:
--------------------------------------------------------------------------------
1 | /build
--------------------------------------------------------------------------------
/core/common/build.gradle.kts:
--------------------------------------------------------------------------------
1 | plugins {
2 | alias(libs.plugins.musicroad.compose.library)
3 | }
4 |
5 | android {
6 | namespace = "com.squirtles.core.common"
7 | }
8 |
9 | dependencies {
10 |
11 | // Compose
12 | // implementation(platform(libs.compose.bom))
13 | // implementation(libs.compose.runtime.android)
14 | // implementation(libs.compose.ui.tooling.preview)
15 | // implementation(platform(libs.compose.bom))
16 | // implementation(libs.bundles.compose)
17 | // implementation(libs.bundles.compose.debug)
18 | // implementation(libs.bundles.material)
19 |
20 | // Coil
21 | implementation(libs.bundles.coil)
22 | }
23 |
--------------------------------------------------------------------------------
/core/common/proguard-rules.pro:
--------------------------------------------------------------------------------
1 | # Add project specific ProGuard rules here.
2 | # You can control the set of applied configuration files using the
3 | # proguardFiles setting in build.gradle.
4 | #
5 | # For more details, see
6 | # http://developer.android.com/guide/developing/tools/proguard.html
7 |
8 | # If your project uses WebView with JS, uncomment the following
9 | # and specify the fully qualified class name to the JavaScript interface
10 | # class:
11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview {
12 | # public *;
13 | #}
14 |
15 | # Uncomment this to preserve the line number information for
16 | # debugging stack traces.
17 | #-keepattributes SourceFile,LineNumberTable
18 |
19 | # If you keep the line number information, uncomment this to
20 | # hide the original source file name.
21 | #-renamesourcefileattribute SourceFile
--------------------------------------------------------------------------------
/core/common/src/main/java/com/squirtles/core/common/ui/AlbumImage.kt:
--------------------------------------------------------------------------------
1 | package com.squirtles.core.common.ui
2 |
3 | import android.util.Size
4 | import androidx.compose.runtime.Composable
5 | import androidx.compose.ui.Modifier
6 | import androidx.compose.ui.graphics.painter.ColorPainter
7 | import androidx.compose.ui.layout.ContentScale
8 | import androidx.compose.ui.platform.LocalContext
9 | import androidx.compose.ui.res.stringResource
10 | import coil3.compose.AsyncImage
11 | import coil3.request.ImageRequest
12 | import coil3.request.crossfade
13 | import com.squirtles.core.common.R
14 | import com.squirtles.core.common.ui.theme.Gray
15 |
16 | @Composable
17 | fun AlbumImage(
18 | imageUrl: String?,
19 | modifier: Modifier = Modifier,
20 | contentDescription: String = stringResource(R.string.map_album_image_description),
21 | ) {
22 | AsyncImage(
23 | model = ImageRequest.Builder(LocalContext.current)
24 | .data(imageUrl)
25 | .crossfade(true)
26 | .build(),
27 | contentDescription = contentDescription,
28 | modifier = modifier,
29 | placeholder = ColorPainter(Gray),
30 | error = ColorPainter(Gray),
31 | contentScale = ContentScale.Crop,
32 | )
33 | }
34 |
35 | fun String.toImageUrlWithSize(size: Size): String? {
36 | return if (isEmpty()) null
37 | else replace("{w}", size.width.toString())
38 | .replace("{h}", size.height.toString())
39 | }
40 |
--------------------------------------------------------------------------------
/core/common/src/main/java/com/squirtles/core/common/ui/Constants.kt:
--------------------------------------------------------------------------------
1 | package com.squirtles.core.common.ui
2 |
3 | import android.util.Size
4 | import androidx.compose.ui.unit.dp
5 | import com.squirtles.core.common.ui.theme.Black
6 | import com.squirtles.core.common.ui.theme.Primary
7 |
8 | object Constants {
9 | val DEFAULT_PADDING = 16.dp
10 |
11 | val REQUEST_IMAGE_SIZE_DEFAULT = Size(300, 300)
12 |
13 | val COLOR_STOPS = arrayOf(
14 | 0.0f to Primary,
15 | 0.25f to Black
16 | )
17 | }
18 |
--------------------------------------------------------------------------------
/core/common/src/main/java/com/squirtles/core/common/ui/DoubleBackPressToExit.kt:
--------------------------------------------------------------------------------
1 | package com.squirtles.core.common.ui
2 |
3 | import android.widget.Toast
4 | import androidx.activity.compose.BackHandler
5 | import androidx.compose.runtime.Composable
6 | import androidx.compose.runtime.getValue
7 | import androidx.compose.runtime.mutableLongStateOf
8 | import androidx.compose.runtime.mutableStateOf
9 | import androidx.compose.runtime.remember
10 | import androidx.compose.runtime.setValue
11 | import androidx.compose.ui.platform.LocalContext
12 | import androidx.compose.ui.res.stringResource
13 | import com.squirtles.core.common.R
14 |
15 | @Composable
16 | fun DoubleBackPressToExit(enabled: Boolean = true, onBackClick: () -> Unit) {
17 | var backPressedTime by remember { mutableLongStateOf(0L) }
18 | val context = LocalContext.current
19 | val backExitString = stringResource(R.string.back_to_exit)
20 |
21 | BackHandler(enabled = enabled) {
22 | val currentTime = System.currentTimeMillis()
23 | if (currentTime - backPressedTime < 2000) {
24 | onBackClick()
25 | } else {
26 | backPressedTime = currentTime
27 | Toast.makeText(context, backExitString, Toast.LENGTH_SHORT).show()
28 | }
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/core/common/src/main/java/com/squirtles/core/common/ui/MusicRoadPermissions.kt:
--------------------------------------------------------------------------------
1 | package com.squirtles.core.common.ui
2 |
3 | import android.Manifest
4 | import android.content.Context
5 | import androidx.core.content.PermissionChecker
6 |
7 | object MusicRoadPermissions {
8 | // 선택적 권한
9 | val OPTIONAL_PERMISSIONS = listOf(
10 | Manifest.permission.ACCESS_FINE_LOCATION,
11 | Manifest.permission.ACCESS_COARSE_LOCATION,
12 | )
13 |
14 | // 필수 권한
15 | val CORE_PERMISSIONS = listOf(
16 | Manifest.permission.RECORD_AUDIO,
17 | )
18 |
19 | val ALL_PERMISSIONS = CORE_PERMISSIONS + OPTIONAL_PERMISSIONS
20 |
21 | fun checkLocationPermission(context: Context): Boolean {
22 | return OPTIONAL_PERMISSIONS.all {
23 | PermissionChecker.checkSelfPermission(context, it) == PermissionChecker.PERMISSION_GRANTED
24 | }
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/core/common/src/main/java/com/squirtles/core/common/ui/Spacer.kt:
--------------------------------------------------------------------------------
1 | package com.squirtles.core.common.ui
2 |
3 | import androidx.compose.foundation.layout.Spacer
4 | import androidx.compose.foundation.layout.height
5 | import androidx.compose.foundation.layout.width
6 | import androidx.compose.runtime.Composable
7 | import androidx.compose.ui.Modifier
8 | import androidx.compose.ui.unit.dp
9 |
10 | @Composable
11 | fun VerticalSpacer(height: Int) = Spacer(Modifier.height(height.dp))
12 |
13 | @Composable
14 | fun HorizontalSpacer(width: Int) = Spacer(Modifier.width(width.dp))
15 |
--------------------------------------------------------------------------------
/core/common/src/main/java/com/squirtles/core/common/ui/theme/Color.kt:
--------------------------------------------------------------------------------
1 | package com.squirtles.core.common.ui.theme
2 |
3 | import androidx.compose.ui.graphics.Color
4 |
5 | val Primary = Color(0xFFFF5F61)
6 | val Primary80 = Color(0xFFFFB3B0)
7 | val Primary50 = Color(0xFFAD625F)
8 | val Primary20 = Color(0xFF571D1E)
9 | val Blue = Color(0xFF6B84FF)
10 |
11 | val Purple = Color(0xFFBB8280)
12 | val Purple15 = Color(0x26BB8280)
13 | val PurpleGrey = Color(0xFFB4AEAE)
14 |
15 | val Black = Color(0xFF000000)
16 | val Dark = Color(0xFF151515)
17 | val DarkGray = Color(0xFF646464)
18 | val Gray = Color(0xFFAAAAAA)
19 | val White = Color(0xFFFFFFFF)
20 |
21 | val PlayerBackground = Color(0xFF353535)
22 |
23 | val SignInButtonDarkBackground = Color(0xFF131314)
24 | val SignInButtonLightStroke = Color(0xFF747775)
25 | val SignInButtonDarkStroke = Color(0xFF8E918F)
26 |
--------------------------------------------------------------------------------
/core/common/src/main/java/com/squirtles/core/common/ui/theme/Theme.kt:
--------------------------------------------------------------------------------
1 | package com.squirtles.core.common.ui.theme
2 |
3 | import androidx.compose.foundation.isSystemInDarkTheme
4 | import androidx.compose.material3.MaterialTheme
5 | import androidx.compose.material3.darkColorScheme
6 | import androidx.compose.material3.lightColorScheme
7 | import androidx.compose.runtime.Composable
8 |
9 | private val DarkColorScheme = darkColorScheme(
10 | primary = Primary,
11 | onPrimary = Black,
12 | primaryContainer = White,
13 | onPrimaryContainer = Black,
14 | secondary = Blue,
15 | tertiary = Purple,
16 | surface = Black,
17 | onSurface = White,
18 | onSurfaceVariant = DarkGray,
19 | onSecondary = Gray
20 | )
21 |
22 | private val LightColorScheme = lightColorScheme(
23 | primary = Primary,
24 | onPrimary = White,
25 | primaryContainer = Dark,
26 | onPrimaryContainer = White,
27 | secondary = Blue,
28 | tertiary = Purple,
29 | surface = White,
30 | onSurface = Black,
31 | onSurfaceVariant = Gray,
32 | onSecondary = DarkGray
33 | )
34 |
35 | @Composable
36 | fun MusicRoadTheme(
37 | darkTheme: Boolean = isSystemInDarkTheme(),
38 | content: @Composable () -> Unit
39 | ) {
40 | MaterialTheme(
41 | colorScheme = if (darkTheme) DarkColorScheme else LightColorScheme,
42 | typography = Typography,
43 | content = content
44 | )
45 | }
46 |
--------------------------------------------------------------------------------
/core/common/src/main/java/com/squirtles/core/common/ui/theme/Type.kt:
--------------------------------------------------------------------------------
1 | package com.squirtles.core.common.ui.theme
2 |
3 | import androidx.compose.material3.Typography
4 | import androidx.compose.ui.text.TextStyle
5 | import androidx.compose.ui.text.font.FontFamily
6 | import androidx.compose.ui.text.font.FontWeight
7 | import androidx.compose.ui.unit.sp
8 |
9 | // Set of Material typography styles to start with
10 | val Typography = Typography(
11 | bodyLarge = TextStyle(
12 | fontFamily = FontFamily.Default,
13 | fontWeight = FontWeight.Normal,
14 | fontSize = 16.sp,
15 | lineHeight = 24.sp,
16 | letterSpacing = 0.5.sp
17 | )
18 | /* Other default text styles to override
19 | titleLarge = TextStyle(
20 | fontFamily = FontFamily.Default,
21 | fontWeight = FontWeight.Normal,
22 | fontSize = 22.sp,
23 | lineHeight = 28.sp,
24 | letterSpacing = 0.sp
25 | ),
26 | labelSmall = TextStyle(
27 | fontFamily = FontFamily.Default,
28 | fontWeight = FontWeight.Medium,
29 | fontSize = 11.sp,
30 | lineHeight = 16.sp,
31 | letterSpacing = 0.5.sp
32 | )
33 | */
34 | )
35 |
--------------------------------------------------------------------------------
/core/common/src/main/res/drawable/ic_favorite.xml:
--------------------------------------------------------------------------------
1 |
2 |
7 |
10 |
--------------------------------------------------------------------------------
/core/common/src/main/res/drawable/img_google_logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/boostcampwm-2024/and06-musicroad/d893dff01a193094f208e5a4c877d4e8ea93423b/core/common/src/main/res/drawable/img_google_logo.png
--------------------------------------------------------------------------------
/core/common/src/main/res/values/strings.xml:
--------------------------------------------------------------------------------
1 |
2 | 앨범 이미지
3 |
4 | 내가
5 | 등록한 픽
6 | 님의 픽
7 |
8 | 상단 바 뒤로 가기 버튼
9 |
10 | 삭제하시겠습니까?
11 | 등록하신 픽이 삭제됩니다.
12 |
13 | 픽을 담은 개수
14 | 전체
15 |
16 |
17 | 로그인
18 | Sign in with Google
19 | Google Logo
20 |
21 |
22 | 더 많은 기능을 이용하기 위해\n로그인이 필요합니다
23 | 담은 픽을 확인하기 위해\n로그인이 필요합니다
24 | 픽을 등록하기 위해\n로그인이 필요합니다
25 | 픽을 담기 위해\n로그인이 필요합니다
26 | 픽을 담기 위해\n로그인이 필요합니다
27 | 로그인이 필요합니다
28 | 취소
29 | 기기에 로그인된 구글 계정이 없습니다
30 |
31 |
32 | 한 번 더 누르면 종료됩니다.
33 |
34 |
35 |
--------------------------------------------------------------------------------
/core/mediaservice/.gitignore:
--------------------------------------------------------------------------------
1 | /build
--------------------------------------------------------------------------------
/core/mediaservice/build.gradle.kts:
--------------------------------------------------------------------------------
1 | plugins {
2 | alias(libs.plugins.musicroad.android.library)
3 | alias(libs.plugins.musicroad.hilt)
4 | }
5 |
6 | android {
7 | namespace = "com.squirtles.core.mediaservice"
8 | }
9 |
10 | dependencies {
11 | testImplementation(libs.junit)
12 | androidTestImplementation(libs.bundles.test)
13 | // ExoPlayer
14 | implementation(libs.bundles.exoplayer)
15 | implementation(libs.androidx.media3.session)
16 | }
17 |
--------------------------------------------------------------------------------
/core/mediaservice/proguard-rules.pro:
--------------------------------------------------------------------------------
1 | # Add project specific ProGuard rules here.
2 | # You can control the set of applied configuration files using the
3 | # proguardFiles setting in build.gradle.
4 | #
5 | # For more details, see
6 | # http://developer.android.com/guide/developing/tools/proguard.html
7 |
8 | # If your project uses WebView with JS, uncomment the following
9 | # and specify the fully qualified class name to the JavaScript interface
10 | # class:
11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview {
12 | # public *;
13 | #}
14 |
15 | # Uncomment this to preserve the line number information for
16 | # debugging stack traces.
17 | #-keepattributes SourceFile,LineNumberTable
18 |
19 | # If you keep the line number information, uncomment this to
20 | # hide the original source file name.
21 | #-renamesourcefileattribute SourceFile
--------------------------------------------------------------------------------
/core/mediaservice/src/androidTest/java/com/squirtles/core/mediaservice/ExampleInstrumentedTest.kt:
--------------------------------------------------------------------------------
1 | package com.squirtles.mediaservice
2 |
3 | import androidx.test.platform.app.InstrumentationRegistry
4 | import androidx.test.ext.junit.runners.AndroidJUnit4
5 |
6 | import org.junit.Test
7 | import org.junit.runner.RunWith
8 |
9 | import org.junit.Assert.*
10 |
11 | /**
12 | * Instrumented test, which will execute on an Android device.
13 | *
14 | * See [testing documentation](http://d.android.com/tools/testing).
15 | */
16 | @RunWith(AndroidJUnit4::class)
17 | class ExampleInstrumentedTest {
18 | @Test
19 | fun useAppContext() {
20 | // Context of the app under test.
21 | val appContext = InstrumentationRegistry.getInstrumentation().targetContext
22 | assertEquals("com.squirtles.mediaservice.test", appContext.packageName)
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/core/mediaservice/src/main/java/com/squirtles/core/mediaservice/PlayerCommands.kt:
--------------------------------------------------------------------------------
1 | package com.squirtles.core.mediaservice
2 |
3 | import android.os.Bundle
4 | import androidx.media3.session.SessionCommand
5 |
6 | private const val ACTION_SEEK_FORWARD = "action_seek_forward"
7 | private const val ACTION_SEEK_REWIND = "action_seek_rewind"
8 | private const val ACTION_PLAY_AND_PAUSE = "action_play_and_pause"
9 |
10 | enum class PlayerCommands(
11 | val customAction: String,
12 | val displayName: String,
13 | val iconResId: (Boolean) -> Int,
14 | val sessionCommand: SessionCommand,
15 | ) {
16 | SEEK_REWIND(
17 | customAction = ACTION_SEEK_REWIND,
18 | displayName = "SeekRewind",
19 | iconResId = { androidx.media3.session.R.drawable.media3_icon_skip_back_5 },
20 | sessionCommand = SessionCommand(ACTION_SEEK_REWIND, Bundle.EMPTY)
21 | ),
22 | PLAY_AND_PAUSE(
23 | customAction = ACTION_PLAY_AND_PAUSE,
24 | displayName = "PlayPause",
25 | iconResId = { isPlaying ->
26 | if (isPlaying) {
27 | androidx.media3.session.R.drawable.media3_icon_pause
28 | } else {
29 | androidx.media3.session.R.drawable.media3_icon_play
30 | }
31 | },
32 | sessionCommand = SessionCommand(ACTION_PLAY_AND_PAUSE, Bundle.EMPTY)
33 | ),
34 | SEEK_FORWARD(
35 | customAction = ACTION_SEEK_FORWARD,
36 | displayName = "SeekForward",
37 | iconResId = { androidx.media3.session.R.drawable.media3_icon_skip_forward_5 },
38 | sessionCommand = SessionCommand(ACTION_SEEK_FORWARD, Bundle.EMPTY)
39 | ),
40 | }
41 |
--------------------------------------------------------------------------------
/core/mediaservice/src/test/java/com/squirtles/core/mediaservice/ExampleUnitTest.kt:
--------------------------------------------------------------------------------
1 | package com.squirtles.mediaservice
2 |
3 | import org.junit.Test
4 |
5 | import org.junit.Assert.*
6 |
7 | /**
8 | * Example local unit test, which will execute on the development machine (host).
9 | *
10 | * See [testing documentation](http://d.android.com/tools/testing).
11 | */
12 | class ExampleUnitTest {
13 | @Test
14 | fun addition_isCorrect() {
15 | assertEquals(4, 2 + 2)
16 | }
17 | }
18 |
--------------------------------------------------------------------------------
/core/model/.gitignore:
--------------------------------------------------------------------------------
1 | /build
--------------------------------------------------------------------------------
/core/model/build.gradle.kts:
--------------------------------------------------------------------------------
1 | plugins {
2 | alias(libs.plugins.musicroad.java.library)
3 | alias(libs.plugins.kotlin.serialization)
4 | }
5 |
6 | dependencies {
7 | // Serialization
8 | implementation(libs.kotlinx.serialization.json)
9 | }
10 |
--------------------------------------------------------------------------------
/core/model/proguard-rules.pro:
--------------------------------------------------------------------------------
1 | # Add project specific ProGuard rules here.
2 | # You can control the set of applied configuration files using the
3 | # proguardFiles setting in build.gradle.
4 | #
5 | # For more details, see
6 | # http://developer.android.com/guide/developing/tools/proguard.html
7 |
8 | # If your project uses WebView with JS, uncomment the following
9 | # and specify the fully qualified class name to the JavaScript interface
10 | # class:
11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview {
12 | # public *;
13 | #}
14 |
15 | # Uncomment this to preserve the line number information for
16 | # debugging stack traces.
17 | #-keepattributes SourceFile,LineNumberTable
18 |
19 | # If you keep the line number information, uncomment this to
20 | # hide the original source file name.
21 | #-renamesourcefileattribute SourceFile
--------------------------------------------------------------------------------
/core/model/src/main/java/com/squirtles/core/model/MusicVideo.kt:
--------------------------------------------------------------------------------
1 | package com.squirtles.core.model
2 |
3 | import java.time.LocalDate
4 |
5 | data class MusicVideo(
6 | val id: String,
7 | val songName: String,
8 | val artistName: String,
9 | val albumName: String,
10 | val releaseDate: LocalDate,
11 | val previewUrl: String,
12 | val thumbnailUrl: String
13 | )
14 |
--------------------------------------------------------------------------------
/core/model/src/main/java/com/squirtles/core/model/Order.kt:
--------------------------------------------------------------------------------
1 | package com.squirtles.core.model
2 |
3 | enum class Order {
4 | LATEST,
5 | OLDEST,
6 | FAVORITE_DESC,
7 | }
8 |
--------------------------------------------------------------------------------
/core/model/src/main/java/com/squirtles/core/model/Pick.kt:
--------------------------------------------------------------------------------
1 | package com.squirtles.core.model
2 |
3 | /**
4 | * 앱에서 사용하기 위한 Pick 정보 데이터클래스
5 | */
6 | data class Pick(
7 | val id: String,
8 | val song: Song,
9 | val comment: String,
10 | val favoriteCount: Int = 0,
11 | val createdBy: Creator,
12 | val createdAt: String,
13 | val location: LocationPoint,
14 | val musicVideoUrl: String = "",
15 | val musicVideoThumbnailUrl: String = ""
16 | )
17 |
18 | data class LocationPoint(
19 | val latitude: Double,
20 | val longitude: Double
21 | ) {
22 | /* TODO: Location 변환 함수 */
23 | }
24 |
25 | data class Creator(
26 | val uid: String,
27 | val userName: String,
28 | )
29 |
--------------------------------------------------------------------------------
/core/model/src/main/java/com/squirtles/core/model/PlayerState.kt:
--------------------------------------------------------------------------------
1 | package com.squirtles.core.model
2 |
3 | data class PlayerState(
4 | val id: String = "",
5 | val isLoading: Boolean = false,
6 | val isPlaying: Boolean = false,
7 | val hasNext: Boolean = false,
8 | val currentPosition: Long = 0L,
9 | val duration: Long = 30_000L,
10 | val bufferPercentage: Int = 0,
11 | )
12 |
--------------------------------------------------------------------------------
/core/model/src/main/java/com/squirtles/core/model/Song.kt:
--------------------------------------------------------------------------------
1 | package com.squirtles.core.model
2 |
3 | import kotlinx.serialization.Serializable
4 | import java.net.URLEncoder
5 | import java.nio.charset.StandardCharsets
6 |
7 | /**
8 | * 애플뮤직에서 불러온 노래 정보를 비즈니스 로직에서 사용하기 위해 변환한 클래스
9 | */
10 | @Serializable
11 | data class Song(
12 | val id: String,
13 | val songName: String,
14 | val artistName: String,
15 | val albumName: String,
16 | val imageUrl: String,
17 | val genreNames: List,
18 | val bgColor: Int,
19 | val externalUrl: String,
20 | val previewUrl: String,
21 | ) {
22 | fun getImageUrlWithSize(width: Int, height: Int): String? {
23 | return if (imageUrl.isEmpty()) null
24 | else imageUrl.replace("{w}", width.toString())
25 | .replace("{h}", height.toString())
26 | }
27 |
28 | fun encoded(): Song = this.copy(
29 | songName = URLEncoder.encode(songName, StandardCharsets.UTF_8.toString()).replace("+", "%20"),
30 | artistName = URLEncoder.encode(artistName, StandardCharsets.UTF_8.toString()).replace("+", "%20"),
31 | albumName = URLEncoder.encode(albumName, StandardCharsets.UTF_8.toString()).replace("+", "%20"),
32 | imageUrl = URLEncoder.encode(imageUrl, StandardCharsets.UTF_8.toString()).replace("+", "%20"),
33 | previewUrl = URLEncoder.encode(previewUrl, StandardCharsets.UTF_8.toString()).replace("+", "%20"),
34 | externalUrl = URLEncoder.encode(externalUrl, StandardCharsets.UTF_8.toString()).replace("+", "%20"),
35 | genreNames = genreNames.map {
36 | URLEncoder.encode(it, StandardCharsets.UTF_8.toString()).replace("+", "%20")
37 | },
38 | )
39 | }
40 |
--------------------------------------------------------------------------------
/core/model/src/main/java/com/squirtles/core/model/User.kt:
--------------------------------------------------------------------------------
1 | package com.squirtles.core.model
2 |
3 | data class User(
4 | val uid: String,
5 | val email: String,
6 | val userName: String,
7 | val userProfileImage: String?,
8 | val myPicks: List,
9 | )
10 |
--------------------------------------------------------------------------------
/core/musicplayer/.gitignore:
--------------------------------------------------------------------------------
1 | /build
--------------------------------------------------------------------------------
/core/musicplayer/build.gradle.kts:
--------------------------------------------------------------------------------
1 | plugins {
2 | alias(libs.plugins.musicroad.android.library)
3 | alias(libs.plugins.musicroad.hilt)
4 | }
5 |
6 | android {
7 | namespace = "com.squirtles.core.musicplayer"
8 | }
9 |
10 | dependencies {
11 | implementation(projects.domain.player)
12 | implementation(projects.core.model)
13 | implementation(libs.material)
14 |
15 | // ExoPlayer
16 | implementation(libs.bundles.exoplayer)
17 | implementation(libs.androidx.media3.session)
18 | }
19 |
--------------------------------------------------------------------------------
/core/musicplayer/proguard-rules.pro:
--------------------------------------------------------------------------------
1 | # Add project specific ProGuard rules here.
2 | # You can control the set of applied configuration files using the
3 | # proguardFiles setting in build.gradle.
4 | #
5 | # For more details, see
6 | # http://developer.android.com/guide/developing/tools/proguard.html
7 |
8 | # If your project uses WebView with JS, uncomment the following
9 | # and specify the fully qualified class name to the JavaScript interface
10 | # class:
11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview {
12 | # public *;
13 | #}
14 |
15 | # Uncomment this to preserve the line number information for
16 | # debugging stack traces.
17 | #-keepattributes SourceFile,LineNumberTable
18 |
19 | # If you keep the line number information, uncomment this to
20 | # hide the original source file name.
21 | #-renamesourcefileattribute SourceFile
--------------------------------------------------------------------------------
/core/musicplayer/src/main/java/com/squirtles/core/musicplayer/PlayerUiState.kt:
--------------------------------------------------------------------------------
1 | package com.squirtles.core.musicplayer
2 |
3 | data class PlayerUiState(
4 | val isReady: Boolean = true,
5 | val isPlaying: Boolean = false,
6 | val currentPosition: Long = 0L,
7 | ) {
8 | companion object {
9 | val PLAYER_STATE_INITIAL = PlayerUiState(isReady = false)
10 | val PLAYER_STATE_STOP = PlayerUiState(isReady = true, isPlaying = false)
11 | }
12 | }
13 |
--------------------------------------------------------------------------------
/core/navigation/.gitignore:
--------------------------------------------------------------------------------
1 | /build
--------------------------------------------------------------------------------
/core/navigation/build.gradle.kts:
--------------------------------------------------------------------------------
1 | plugins {
2 | alias(libs.plugins.musicroad.java.library)
3 | alias(libs.plugins.kotlin.serialization)
4 | }
5 |
6 | dependencies {
7 | implementation(projects.core.model)
8 |
9 | // Serialization
10 | implementation(libs.kotlinx.serialization.json)
11 | }
12 |
--------------------------------------------------------------------------------
/core/navigation/proguard-rules.pro:
--------------------------------------------------------------------------------
1 | # Add project specific ProGuard rules here.
2 | # You can control the set of applied configuration files using the
3 | # proguardFiles setting in build.gradle.
4 | #
5 | # For more details, see
6 | # http://developer.android.com/guide/developing/tools/proguard.html
7 |
8 | # If your project uses WebView with JS, uncomment the following
9 | # and specify the fully qualified class name to the JavaScript interface
10 | # class:
11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview {
12 | # public *;
13 | #}
14 |
15 | # Uncomment this to preserve the line number information for
16 | # debugging stack traces.
17 | #-keepattributes SourceFile,LineNumberTable
18 |
19 | # If you keep the line number information, uncomment this to
20 | # hide the original source file name.
21 | #-renamesourcefileattribute SourceFile
--------------------------------------------------------------------------------
/core/navigation/src/main/java/com/squirtles/core/navigation/MainRoute.kt:
--------------------------------------------------------------------------------
1 | package com.squirtles.core.navigation
2 |
3 | import kotlinx.serialization.Serializable
4 |
5 | @Serializable
6 | sealed interface MainRoute : Route {
7 | @Serializable
8 | data object Search : MainRoute
9 |
10 | @Serializable
11 | data class Favorite(val uid: String) : MainRoute
12 |
13 | @Serializable
14 | data class UserInfo(val uid: String) : MainRoute
15 | }
16 |
--------------------------------------------------------------------------------
/core/navigation/src/main/java/com/squirtles/core/navigation/MapRoute.kt:
--------------------------------------------------------------------------------
1 | package com.squirtles.core.navigation
2 |
3 | import kotlinx.serialization.Serializable
4 |
5 | @Serializable
6 | sealed interface MapRoute : Route {
7 | @Serializable
8 | data class PickDetail(val pickId: String) : MapRoute
9 | }
10 |
--------------------------------------------------------------------------------
/core/navigation/src/main/java/com/squirtles/core/navigation/Route.kt:
--------------------------------------------------------------------------------
1 | package com.squirtles.core.navigation
2 |
3 | import kotlinx.serialization.Serializable
4 |
5 | @Serializable
6 | sealed interface Route {
7 | @Serializable
8 | data object Map : Route
9 | }
10 |
--------------------------------------------------------------------------------
/core/navigation/src/main/java/com/squirtles/core/navigation/SearchRoute.kt:
--------------------------------------------------------------------------------
1 | package com.squirtles.core.navigation
2 |
3 | import com.squirtles.core.model.Song
4 | import kotlinx.serialization.Serializable
5 |
6 | @Serializable
7 | sealed interface SearchRoute : Route {
8 | @Serializable
9 | data class Create(val song: Song) : SearchRoute
10 | }
11 |
--------------------------------------------------------------------------------
/core/navigation/src/main/java/com/squirtles/core/navigation/UserInfoRoute.kt:
--------------------------------------------------------------------------------
1 | package com.squirtles.core.navigation
2 |
3 | import kotlinx.serialization.Serializable
4 |
5 | @Serializable
6 | sealed interface UserInfoRoute : Route {
7 | @Serializable
8 | data class MyPicks(val uid: String) : UserInfoRoute
9 |
10 | @Serializable
11 | data class EditProfile(val userName: String) : UserInfoRoute
12 |
13 | @Serializable
14 | data object EditNotification : UserInfoRoute
15 |
16 | @Serializable
17 | data object EditPlayer : UserInfoRoute
18 | }
19 |
20 |
--------------------------------------------------------------------------------
/core/picklist/.gitignore:
--------------------------------------------------------------------------------
1 | /build
--------------------------------------------------------------------------------
/core/picklist/build.gradle.kts:
--------------------------------------------------------------------------------
1 | plugins {
2 | alias(libs.plugins.musicroad.compose.library)
3 | }
4 |
5 | android {
6 | namespace = "com.squirtles.core.picklist"
7 | }
8 |
9 | dependencies {
10 | implementation(projects.core.model)
11 | implementation(projects.core.common)
12 | implementation(projects.domain.picklist)
13 | implementation(projects.domain.user)
14 |
15 | implementation(libs.inject)
16 |
17 | testImplementation(libs.junit)
18 | androidTestImplementation(libs.bundles.test)
19 |
20 | // Coil
21 | implementation(libs.bundles.coil)
22 | }
23 |
--------------------------------------------------------------------------------
/core/picklist/proguard-rules.pro:
--------------------------------------------------------------------------------
1 | # Add project specific ProGuard rules here.
2 | # You can control the set of applied configuration files using the
3 | # proguardFiles setting in build.gradle.
4 | #
5 | # For more details, see
6 | # http://developer.android.com/guide/developing/tools/proguard.html
7 |
8 | # If your project uses WebView with JS, uncomment the following
9 | # and specify the fully qualified class name to the JavaScript interface
10 | # class:
11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview {
12 | # public *;
13 | #}
14 |
15 | # Uncomment this to preserve the line number information for
16 | # debugging stack traces.
17 | #-keepattributes SourceFile,LineNumberTable
18 |
19 | # If you keep the line number information, uncomment this to
20 | # hide the original source file name.
21 | #-renamesourcefileattribute SourceFile
--------------------------------------------------------------------------------
/core/picklist/src/androidTest/java/com/squirtles/core/picklist/ExampleInstrumentedTest.kt:
--------------------------------------------------------------------------------
1 | package com.squirtles.picklist
2 |
3 | import androidx.test.platform.app.InstrumentationRegistry
4 | import androidx.test.ext.junit.runners.AndroidJUnit4
5 |
6 | import org.junit.Test
7 | import org.junit.runner.RunWith
8 |
9 | import org.junit.Assert.*
10 |
11 | /**
12 | * Instrumented test, which will execute on an Android device.
13 | *
14 | * See [testing documentation](http://d.android.com/tools/testing).
15 | */
16 | @RunWith(AndroidJUnit4::class)
17 | class ExampleInstrumentedTest {
18 | @Test
19 | fun useAppContext() {
20 | // Context of the app under test.
21 | val appContext = InstrumentationRegistry.getInstrumentation().targetContext
22 | assertEquals("com.squirtles.picklist.test", appContext.packageName)
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/core/picklist/src/main/java/com/squirtles/core/picklist/PickListType.kt:
--------------------------------------------------------------------------------
1 | package com.squirtles.core.picklist
2 |
3 | enum class PickListType {
4 | FAVORITE, CREATED
5 | }
6 |
--------------------------------------------------------------------------------
/core/picklist/src/main/java/com/squirtles/core/picklist/PickListUiState.kt:
--------------------------------------------------------------------------------
1 | package com.squirtles.core.picklist
2 |
3 | import com.squirtles.core.model.Order
4 | import com.squirtles.core.model.Pick
5 |
6 | sealed class PickListUiState {
7 | data object Loading : PickListUiState()
8 | data class Success(val pickList: List, val order: Order) : PickListUiState()
9 | data object Error : PickListUiState()
10 | }
11 |
--------------------------------------------------------------------------------
/core/picklist/src/test/java/com/squirtles/core/picklist/ExampleUnitTest.kt:
--------------------------------------------------------------------------------
1 | package com.squirtles.picklist
2 |
3 | import org.junit.Test
4 |
5 | import org.junit.Assert.*
6 |
7 | /**
8 | * Example local unit test, which will execute on the development machine (host).
9 | *
10 | * See [testing documentation](http://d.android.com/tools/testing).
11 | */
12 | class ExampleUnitTest {
13 | @Test
14 | fun addition_isCorrect() {
15 | assertEquals(4, 2 + 2)
16 | }
17 | }
18 |
--------------------------------------------------------------------------------
/core/preference/.gitignore:
--------------------------------------------------------------------------------
1 | /build
--------------------------------------------------------------------------------
/core/preference/build.gradle.kts:
--------------------------------------------------------------------------------
1 | plugins {
2 | alias(libs.plugins.musicroad.android.library)
3 | alias(libs.plugins.musicroad.hilt)
4 | }
5 |
6 | android {
7 | namespace = "com.squirtles.core.preference"
8 | }
9 |
10 | dependencies {
11 | implementation(projects.domain.preference)
12 |
13 | testImplementation(libs.junit)
14 | androidTestImplementation(libs.bundles.test)
15 | }
16 |
--------------------------------------------------------------------------------
/core/preference/consumer-rules.pro:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/boostcampwm-2024/and06-musicroad/d893dff01a193094f208e5a4c877d4e8ea93423b/core/preference/consumer-rules.pro
--------------------------------------------------------------------------------
/core/preference/proguard-rules.pro:
--------------------------------------------------------------------------------
1 | # Add project specific ProGuard rules here.
2 | # You can control the set of applied configuration files using the
3 | # proguardFiles setting in build.gradle.
4 | #
5 | # For more details, see
6 | # http://developer.android.com/guide/developing/tools/proguard.html
7 |
8 | # If your project uses WebView with JS, uncomment the following
9 | # and specify the fully qualified class name to the JavaScript interface
10 | # class:
11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview {
12 | # public *;
13 | #}
14 |
15 | # Uncomment this to preserve the line number information for
16 | # debugging stack traces.
17 | #-keepattributes SourceFile,LineNumberTable
18 |
19 | # If you keep the line number information, uncomment this to
20 | # hide the original source file name.
21 | #-renamesourcefileattribute SourceFile
--------------------------------------------------------------------------------
/core/preference/src/androidTest/java/com/squirtles/core/preference/ExampleInstrumentedTest.kt:
--------------------------------------------------------------------------------
1 | package com.squirtles.core.preference
2 |
3 | import androidx.test.platform.app.InstrumentationRegistry
4 | import androidx.test.ext.junit.runners.AndroidJUnit4
5 |
6 | import org.junit.Test
7 | import org.junit.runner.RunWith
8 |
9 | import org.junit.Assert.*
10 |
11 | /**
12 | * Instrumented test, which will execute on an Android device.
13 | *
14 | * See [testing documentation](http://d.android.com/tools/testing).
15 | */
16 | @RunWith(AndroidJUnit4::class)
17 | class ExampleInstrumentedTest {
18 | @Test
19 | fun useAppContext() {
20 | // Context of the app under test.
21 | val appContext = InstrumentationRegistry.getInstrumentation().targetContext
22 | assertEquals("com.squirtles.core.preference.test", appContext.packageName)
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/core/preference/src/test/java/com/squirtles/core/preference/ExampleUnitTest.kt:
--------------------------------------------------------------------------------
1 | package com.squirtles.core.preference
2 |
3 | import org.junit.Test
4 |
5 | import org.junit.Assert.*
6 |
7 | /**
8 | * Example local unit test, which will execute on the development machine (host).
9 | *
10 | * See [testing documentation](http://d.android.com/tools/testing).
11 | */
12 | class ExampleUnitTest {
13 | @Test
14 | fun addition_isCorrect() {
15 | assertEquals(4, 2 + 2)
16 | }
17 | }
18 |
--------------------------------------------------------------------------------
/core/util/.gitignore:
--------------------------------------------------------------------------------
1 | /build
--------------------------------------------------------------------------------
/core/util/build.gradle.kts:
--------------------------------------------------------------------------------
1 | plugins {
2 | alias(libs.plugins.musicroad.android.library)
3 | alias(libs.plugins.kotlin.serialization)
4 | }
5 |
6 | android {
7 | namespace = "com.squirtles.core.util"
8 | }
9 |
10 | dependencies {
11 | implementation(libs.androidx.navigation.common.ktx)
12 |
13 | // Serialization
14 | implementation(libs.kotlinx.serialization.json)
15 | }
16 |
--------------------------------------------------------------------------------
/core/util/proguard-rules.pro:
--------------------------------------------------------------------------------
1 | # Add project specific ProGuard rules here.
2 | # You can control the set of applied configuration files using the
3 | # proguardFiles setting in build.gradle.
4 | #
5 | # For more details, see
6 | # http://developer.android.com/guide/developing/tools/proguard.html
7 |
8 | # If your project uses WebView with JS, uncomment the following
9 | # and specify the fully qualified class name to the JavaScript interface
10 | # class:
11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview {
12 | # public *;
13 | #}
14 |
15 | # Uncomment this to preserve the line number information for
16 | # debugging stack traces.
17 | #-keepattributes SourceFile,LineNumberTable
18 |
19 | # If you keep the line number information, uncomment this to
20 | # hide the original source file name.
21 | #-renamesourcefileattribute SourceFile
--------------------------------------------------------------------------------
/core/util/src/main/java/com/squirtles/core/util/SerializableType.kt:
--------------------------------------------------------------------------------
1 | package com.squirtles.core.util
2 |
3 | import android.os.Bundle
4 | import androidx.navigation.NavType
5 | import kotlinx.serialization.encodeToString
6 | import kotlinx.serialization.json.Json
7 |
8 | inline fun serializableType(
9 | isNullableAllowed: Boolean = false,
10 | json: Json = Json,
11 | ) = object : NavType(isNullableAllowed = isNullableAllowed) {
12 | override fun get(bundle: Bundle, key: String) =
13 | bundle.getString(key)?.let(json::decodeFromString)
14 |
15 | override fun parseValue(value: String): T = json.decodeFromString(value)
16 |
17 | override fun serializeAsValue(value: T): String = json.encodeToString(value)
18 |
19 | override fun put(bundle: Bundle, key: String, value: T) {
20 | bundle.putString(key, json.encodeToString(value))
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/core/util/src/main/java/com/squirtles/core/util/ThrottleFirst.kt:
--------------------------------------------------------------------------------
1 | package com.squirtles.core.util
2 |
3 | import kotlinx.coroutines.flow.Flow
4 | import kotlinx.coroutines.flow.flow
5 |
6 | fun Flow.throttleFirst(periodMillis: Long): Flow {
7 | require(periodMillis > 0) { "period should be positive" }
8 | return flow {
9 | var lastTime = 0L
10 | collect { value ->
11 | val currentTime = System.currentTimeMillis()
12 | if (currentTime - lastTime >= periodMillis) {
13 | lastTime = currentTime
14 | emit(value)
15 | }
16 | }
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/data/applemusic/.gitignore:
--------------------------------------------------------------------------------
1 | /build
--------------------------------------------------------------------------------
/data/applemusic/build.gradle.kts:
--------------------------------------------------------------------------------
1 | plugins {
2 | id(libs.plugins.musicroad.data.get().pluginId)
3 | alias(libs.plugins.kotlin.serialization)
4 | }
5 |
6 | android {
7 | namespace = "com.squirtles.data.applemusic"
8 | }
9 |
10 | dependencies {
11 | implementation(projects.domain.applemusic)
12 |
13 | testImplementation(libs.junit)
14 | androidTestImplementation(libs.bundles.test)
15 |
16 | implementation(libs.androidx.paging.runtime)
17 |
18 | // Kotlinx Serialization
19 | implementation(libs.kotlinx.serialization.json)
20 |
21 | // OkHttp
22 | implementation(libs.bundles.network)
23 | }
24 |
--------------------------------------------------------------------------------
/data/applemusic/proguard-rules.pro:
--------------------------------------------------------------------------------
1 | # Add project specific ProGuard rules here.
2 | # You can control the set of applied configuration files using the
3 | # proguardFiles setting in build.gradle.
4 | #
5 | # For more details, see
6 | # http://developer.android.com/guide/developing/tools/proguard.html
7 |
8 | # If your project uses WebView with JS, uncomment the following
9 | # and specify the fully qualified class name to the JavaScript interface
10 | # class:
11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview {
12 | # public *;
13 | #}
14 |
15 | # Uncomment this to preserve the line number information for
16 | # debugging stack traces.
17 | #-keepattributes SourceFile,LineNumberTable
18 |
19 | # If you keep the line number information, uncomment this to
20 | # hide the original source file name.
21 | #-renamesourcefileattribute SourceFile
--------------------------------------------------------------------------------
/data/applemusic/src/main/java/com/squirtles/data/applemusic/AppleMusicDataSource.kt:
--------------------------------------------------------------------------------
1 | package com.squirtles.data.applemusic
2 |
3 | import androidx.paging.PagingData
4 | import com.squirtles.core.model.MusicVideo
5 | import com.squirtles.core.model.Song
6 | import kotlinx.coroutines.flow.Flow
7 |
8 | interface AppleMusicDataSource {
9 | fun searchSongs(searchText: String): Flow>
10 | suspend fun searchMusicVideos(searchText: String): List
11 | }
12 |
--------------------------------------------------------------------------------
/data/applemusic/src/main/java/com/squirtles/data/applemusic/AppleMusicRepositoryImpl.kt:
--------------------------------------------------------------------------------
1 | package com.squirtles.data.applemusic
2 |
3 | import androidx.paging.PagingData
4 | import com.squirtles.domain.applemusic.AppleMusicException
5 | import com.squirtles.domain.applemusic.AppleMusicRepository
6 | import com.squirtles.core.model.MusicVideo
7 | import com.squirtles.core.model.Song
8 | import kotlinx.coroutines.flow.Flow
9 | import javax.inject.Inject
10 |
11 | class AppleMusicRepositoryImpl @Inject constructor(
12 | private val appleMusicDataSource: AppleMusicDataSource
13 | ) : AppleMusicRepository {
14 |
15 | override fun searchSongs(searchText: String): Flow> =
16 | appleMusicDataSource.searchSongs(searchText)
17 |
18 | override suspend fun searchMusicVideos(searchText: String): Result> {
19 | return handleResult(AppleMusicException.NotFoundException()) {
20 | appleMusicDataSource.searchMusicVideos(searchText).ifEmpty { null }
21 | }
22 | }
23 |
24 | private suspend fun handleResult(
25 | appleMusicException: AppleMusicException,
26 | call: suspend () -> T?
27 | ): Result {
28 | return runCatching {
29 | call() ?: throw appleMusicException
30 | }
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/data/applemusic/src/main/java/com/squirtles/data/applemusic/api/AppleMusicApi.kt:
--------------------------------------------------------------------------------
1 | package com.squirtles.data.applemusic.api
2 |
3 | import com.squirtles.data.applemusic.model.SearchResponse
4 | import retrofit2.Response
5 | import retrofit2.http.GET
6 | import retrofit2.http.Path
7 | import retrofit2.http.Query
8 |
9 | interface AppleMusicApi {
10 | @GET("v1/catalog/{storefront}/search")
11 | suspend fun searchSongs(
12 | @Path("storefront") storefront: String,
13 | @Query("types") types: String,
14 | @Query("term") term: String,
15 | @Query("limit") limit: Int,
16 | @Query("offset") offset: String
17 | ): Response
18 | }
19 |
--------------------------------------------------------------------------------
/data/applemusic/src/main/java/com/squirtles/data/applemusic/di/AppleMusicDiModule.kt:
--------------------------------------------------------------------------------
1 | package com.squirtles.data.applemusic.di
2 |
3 | import com.squirtles.data.applemusic.AppleMusicDataSource
4 | import com.squirtles.data.applemusic.AppleMusicDataSourceImpl
5 | import com.squirtles.domain.applemusic.AppleMusicRepository
6 | import com.squirtles.data.applemusic.AppleMusicRepositoryImpl
7 | import com.squirtles.data.applemusic.api.AppleMusicApi
8 | import dagger.Module
9 | import dagger.Provides
10 | import dagger.hilt.InstallIn
11 | import dagger.hilt.components.SingletonComponent
12 | import okhttp3.OkHttpClient
13 | import retrofit2.Converter
14 | import retrofit2.Retrofit
15 | import javax.inject.Singleton
16 |
17 | @Module
18 | @InstallIn(SingletonComponent::class)
19 | object AppleMusicDiModule{
20 |
21 | private const val BASE_APPLE_MUSIC_URL = "https://api.music.apple.com/"
22 |
23 | @Provides
24 | @Singleton
25 | fun provideAppleMusicApi(
26 | appleOkHttpClient: OkHttpClient,
27 | converterFactory: Converter.Factory,
28 | ): AppleMusicApi {
29 | return Retrofit.Builder()
30 | .baseUrl(BASE_APPLE_MUSIC_URL)
31 | .addConverterFactory(converterFactory)
32 | .client(appleOkHttpClient)
33 | .build()
34 | .create(AppleMusicApi::class.java)
35 | }
36 |
37 | @Provides
38 | @Singleton
39 | fun provideAppleMusicRepository(appleMusicDataSource: AppleMusicDataSource): AppleMusicRepository =
40 | AppleMusicRepositoryImpl(appleMusicDataSource)
41 |
42 | @Provides
43 | @Singleton
44 | fun provideAppleMusicDataSource(api: AppleMusicApi): AppleMusicDataSource =
45 | AppleMusicDataSourceImpl(api)
46 | }
47 |
--------------------------------------------------------------------------------
/data/applemusic/src/main/java/com/squirtles/data/applemusic/model/AppleMusicMapper.kt:
--------------------------------------------------------------------------------
1 | package com.squirtles.data.applemusic.model
2 |
3 | import androidx.core.graphics.toColorInt
4 | import com.squirtles.core.model.MusicVideo
5 | import com.squirtles.core.model.Song
6 | import java.time.LocalDate
7 | import java.time.format.DateTimeFormatter
8 |
9 | internal fun Data.toSong(): Song = Song(
10 | id = id,
11 | songName = this.attributes.songName,
12 | artistName = this.attributes.artistName,
13 | albumName = this.attributes.albumName.toString(),
14 | imageUrl = this.attributes.artwork.url,
15 | genreNames = this.attributes.genreNames,
16 | bgColor = "#${this.attributes.artwork.bgColor}".toColorInt(),
17 | externalUrl = this.attributes.externalUrl,
18 | previewUrl = this.attributes.previews[0].url.toString(),
19 | )
20 |
21 | internal fun Data.toMusicVideo(): MusicVideo = MusicVideo(
22 | id = id,
23 | songName = this.attributes.songName,
24 | artistName = this.attributes.artistName,
25 | albumName = this.attributes.albumName.toString(),
26 | releaseDate = this.attributes.releaseDate?.toLocalDate() ?: DEFAULT_DATE,
27 | previewUrl = this.attributes.previews[0].url.toString(),
28 | thumbnailUrl = this.attributes.previews[0].artwork?.url.toString()
29 | )
30 |
31 | private fun String.toLocalDate(): LocalDate {
32 | val formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd")
33 | return LocalDate.parse(this, formatter)
34 | }
35 |
36 | private val DEFAULT_DATE = LocalDate.of(2000, 1, 1)
37 |
--------------------------------------------------------------------------------
/data/applemusic/src/main/java/com/squirtles/data/applemusic/model/Artwork.kt:
--------------------------------------------------------------------------------
1 | package com.squirtles.data.applemusic.model
2 |
3 | import kotlinx.serialization.Serializable
4 |
5 | @Serializable
6 | data class Artwork(
7 | val width: Int,
8 | val height: Int,
9 | val url: String,
10 | val bgColor: String? = null
11 | )
12 |
--------------------------------------------------------------------------------
/data/applemusic/src/main/java/com/squirtles/data/applemusic/model/Attributes.kt:
--------------------------------------------------------------------------------
1 | package com.squirtles.data.applemusic.model
2 |
3 | import kotlinx.serialization.SerialName
4 | import kotlinx.serialization.Serializable
5 |
6 | @Serializable
7 | data class Attributes(
8 | @SerialName("name") val songName: String,
9 | @SerialName("artistName") val artistName: String,
10 | @SerialName("albumName") val albumName: String? = null,
11 | @SerialName("releaseDate") val releaseDate: String? = null,
12 | @SerialName("genreNames") val genreNames: List,
13 | @SerialName("artwork") val artwork: Artwork,
14 | @SerialName("url") val externalUrl: String,
15 | @SerialName("previews") val previews: List
16 | )
17 |
--------------------------------------------------------------------------------
/data/applemusic/src/main/java/com/squirtles/data/applemusic/model/Data.kt:
--------------------------------------------------------------------------------
1 | package com.squirtles.data.applemusic.model
2 |
3 | import kotlinx.serialization.Serializable
4 |
5 | @Serializable
6 | data class Data(
7 | val id: String,
8 | val attributes: Attributes,
9 | )
10 |
--------------------------------------------------------------------------------
/data/applemusic/src/main/java/com/squirtles/data/applemusic/model/MusicVideoResponse.kt:
--------------------------------------------------------------------------------
1 | package com.squirtles.data.applemusic.model
2 |
3 | import kotlinx.serialization.Serializable
4 |
5 | @Serializable
6 | data class MusicVideoResponse(
7 | val data: List,
8 | )
9 |
10 |
--------------------------------------------------------------------------------
/data/applemusic/src/main/java/com/squirtles/data/applemusic/model/Preview.kt:
--------------------------------------------------------------------------------
1 | package com.squirtles.data.applemusic.model
2 |
3 | import kotlinx.serialization.Serializable
4 |
5 | @Serializable
6 | data class Preview(
7 | val url: String? = null,
8 | val hlsUrl: String? = null,
9 | val artwork: Artwork? = null,
10 | )
11 |
--------------------------------------------------------------------------------
/data/applemusic/src/main/java/com/squirtles/data/applemusic/model/Results.kt:
--------------------------------------------------------------------------------
1 | package com.squirtles.data.applemusic.model
2 |
3 | import kotlinx.serialization.SerialName
4 | import kotlinx.serialization.Serializable
5 |
6 | @Serializable
7 | data class Results(
8 | @SerialName("songs") val songs: Songs? = null,
9 | @SerialName("music-videos") val musicVideos: MusicVideoResponse? = null
10 | )
11 |
--------------------------------------------------------------------------------
/data/applemusic/src/main/java/com/squirtles/data/applemusic/model/SearchResponse.kt:
--------------------------------------------------------------------------------
1 | package com.squirtles.data.applemusic.model
2 |
3 | import kotlinx.serialization.Serializable
4 |
5 | @Serializable
6 | data class SearchResponse(
7 | val results: Results,
8 | )
9 |
--------------------------------------------------------------------------------
/data/applemusic/src/main/java/com/squirtles/data/applemusic/model/Songs.kt:
--------------------------------------------------------------------------------
1 | package com.squirtles.data.applemusic.model
2 |
3 | import kotlinx.serialization.Serializable
4 |
5 | @Serializable
6 | data class Songs(
7 | val next: String? = null,
8 | val data: List,
9 | )
10 |
--------------------------------------------------------------------------------
/data/favorite/.gitignore:
--------------------------------------------------------------------------------
1 | /build
--------------------------------------------------------------------------------
/data/favorite/build.gradle.kts:
--------------------------------------------------------------------------------
1 | plugins {
2 | id(libs.plugins.musicroad.data.get().pluginId)
3 | alias(libs.plugins.kotlin.serialization)
4 | }
5 |
6 | android {
7 | namespace = "com.squirtles.data.favorite"
8 | }
9 |
10 | dependencies {
11 | implementation(projects.data.firebase)
12 | implementation(projects.domain.firebase)
13 | implementation(projects.domain.favorite)
14 |
15 | testImplementation(libs.junit)
16 | androidTestImplementation(libs.bundles.test)
17 |
18 | // Kotlinx Serialization
19 | implementation(libs.kotlinx.serialization.json)
20 |
21 | // Firebase
22 | implementation(libs.bundles.firebase)
23 | }
24 |
--------------------------------------------------------------------------------
/data/favorite/proguard-rules.pro:
--------------------------------------------------------------------------------
1 | # Add project specific ProGuard rules here.
2 | # You can control the set of applied configuration files using the
3 | # proguardFiles setting in build.gradle.
4 | #
5 | # For more details, see
6 | # http://developer.android.com/guide/developing/tools/proguard.html
7 |
8 | # If your project uses WebView with JS, uncomment the following
9 | # and specify the fully qualified class name to the JavaScript interface
10 | # class:
11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview {
12 | # public *;
13 | #}
14 |
15 | # Uncomment this to preserve the line number information for
16 | # debugging stack traces.
17 | #-keepattributes SourceFile,LineNumberTable
18 |
19 | # If you keep the line number information, uncomment this to
20 | # hide the original source file name.
21 | #-renamesourcefileattribute SourceFile
--------------------------------------------------------------------------------
/data/favorite/src/main/java/com/squirtles/data/favorite/CloudFunctionHelper.kt:
--------------------------------------------------------------------------------
1 | package com.squirtles.data.favorite
2 |
3 | import android.util.Log
4 | import com.google.firebase.functions.FirebaseFunctions
5 | import com.google.firebase.functions.ktx.functions
6 | import com.google.firebase.ktx.Firebase
7 | import com.squirtles.domain.firebase.FirebaseException
8 | import com.squirtles.core.buildconfig.LocalPropertyProvider
9 | import kotlinx.coroutines.tasks.await
10 | import javax.inject.Singleton
11 |
12 | @Singleton
13 | class CloudFunctionHelper {
14 | private val functions: FirebaseFunctions = Firebase.functions
15 |
16 | suspend fun updateFavoriteCount(pickId: String): Result {
17 | return runCatching {
18 | val data = hashMapOf("pickId" to pickId)
19 | val result = functions
20 | .getHttpsCallable(LocalPropertyProvider.httpsCallable)
21 | .call(data)
22 | .await()
23 |
24 | // 성공 메시지 반환
25 | val message = result.getData()?.let {
26 | (it as? Map<*, *>)?.get("message") as? String ?: "Function executed successfully"
27 | } ?: "No message in response"
28 | message
29 | }.onFailure {
30 | Log.d("CloudFunctionHelper", "Error updating favorite count: ${it.message}")
31 | throw FirebaseException.CloudFunctionFailedException(exceptionMessage = it.message.toString())
32 | }
33 | }
34 | }
35 |
--------------------------------------------------------------------------------
/data/favorite/src/main/java/com/squirtles/data/favorite/FirebaseFavoriteDataSource.kt:
--------------------------------------------------------------------------------
1 | package com.squirtles.data.favorite
2 |
3 | interface FirebaseFavoriteDataSource {
4 | suspend fun fetchIsFavorite(pickId: String, userId: String): Result
5 | suspend fun createFavorite(pickId: String, userId: String): Result
6 | suspend fun deleteFavorite(pickId: String, userId: String): Result
7 | }
8 |
--------------------------------------------------------------------------------
/data/favorite/src/main/java/com/squirtles/data/favorite/FirebaseFavoriteRepositoryImpl.kt:
--------------------------------------------------------------------------------
1 | package com.squirtles.data.favorite
2 |
3 | import com.squirtles.domain.favorite.FirebaseFavoriteRepository
4 | import javax.inject.Inject
5 | import javax.inject.Singleton
6 |
7 | @Singleton
8 | class FirebaseFavoriteRepositoryImpl @Inject constructor(
9 | private val favoriteDataSource: FirebaseFavoriteDataSource
10 | ) : FirebaseFavoriteRepository {
11 |
12 | override suspend fun fetchIsFavorite(pickId: String, uid: String): Result {
13 | return favoriteDataSource.fetchIsFavorite(pickId, uid)
14 | }
15 |
16 | override suspend fun createFavorite(pickId: String, uid: String): Result {
17 | return favoriteDataSource.createFavorite(pickId, uid)
18 | }
19 |
20 | override suspend fun deleteFavorite(pickId: String, uid: String): Result {
21 | return favoriteDataSource.deleteFavorite(pickId, uid)
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/data/favorite/src/main/java/com/squirtles/data/favorite/di/FavoriteDiModule.kt:
--------------------------------------------------------------------------------
1 | package com.squirtles.data.favorite.di
2 |
3 | import com.squirtles.data.favorite.CloudFunctionHelper
4 | import com.squirtles.data.favorite.FirebaseFavoriteDataSource
5 | import com.squirtles.data.favorite.FirebaseFavoriteDataSourceImpl
6 | import com.squirtles.domain.favorite.FirebaseFavoriteRepository
7 | import com.squirtles.data.favorite.FirebaseFavoriteRepositoryImpl
8 | import com.google.firebase.firestore.FirebaseFirestore
9 | import dagger.Module
10 | import dagger.Provides
11 | import dagger.hilt.InstallIn
12 | import dagger.hilt.components.SingletonComponent
13 | import javax.inject.Singleton
14 |
15 | @Module
16 | @InstallIn(SingletonComponent::class)
17 | object FavoriteDiModule {
18 |
19 | @Provides
20 | @Singleton
21 | fun provideFirebaseFavoriteRepository(firebaseFavoriteDataSource: FirebaseFavoriteDataSource): FirebaseFavoriteRepository =
22 | FirebaseFavoriteRepositoryImpl(firebaseFavoriteDataSource)
23 |
24 | @Provides
25 | @Singleton
26 | fun provideFirebaseFavoriteDataSource(
27 | db: FirebaseFirestore,
28 | cloudFunctionHelper: CloudFunctionHelper
29 | ): FirebaseFavoriteDataSource =
30 | FirebaseFavoriteDataSourceImpl(db, cloudFunctionHelper)
31 |
32 | @Provides
33 | @Singleton
34 | fun provideCloudFunctionHelper(): CloudFunctionHelper = CloudFunctionHelper()
35 | }
36 |
--------------------------------------------------------------------------------
/data/firebase/.gitignore:
--------------------------------------------------------------------------------
1 | /build
--------------------------------------------------------------------------------
/data/firebase/build.gradle.kts:
--------------------------------------------------------------------------------
1 | plugins {
2 | id(libs.plugins.musicroad.data.get().pluginId)
3 | }
4 |
5 | android {
6 | namespace = "com.squirtles.data.firebase"
7 | }
8 |
9 | dependencies {
10 | implementation(projects.domain.firebase)
11 |
12 | testImplementation(libs.junit)
13 | androidTestImplementation(libs.bundles.test)
14 |
15 | // Firebase
16 | implementation(libs.firebase.firestore.ktx)
17 | implementation(libs.geofire.android.common)
18 | }
19 |
--------------------------------------------------------------------------------
/data/firebase/proguard-rules.pro:
--------------------------------------------------------------------------------
1 | # Add project specific ProGuard rules here.
2 | # You can control the set of applied configuration files using the
3 | # proguardFiles setting in build.gradle.
4 | #
5 | # For more details, see
6 | # http://developer.android.com/guide/developing/tools/proguard.html
7 |
8 | # If your project uses WebView with JS, uncomment the following
9 | # and specify the fully qualified class name to the JavaScript interface
10 | # class:
11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview {
12 | # public *;
13 | #}
14 |
15 | # Uncomment this to preserve the line number information for
16 | # debugging stack traces.
17 | #-keepattributes SourceFile,LineNumberTable
18 |
19 | # If you keep the line number information, uncomment this to
20 | # hide the original source file name.
21 | #-renamesourcefileattribute SourceFile
--------------------------------------------------------------------------------
/data/firebase/src/main/java/com/squirtles/data/firebase/FirebaseDataSourceConstants.kt:
--------------------------------------------------------------------------------
1 | package com.squirtles.data.firebase
2 |
3 | sealed class FirebaseCollections(val name: String) {
4 | data object Favorites: FirebaseCollections("favorites")
5 | data object Picks: FirebaseCollections("picks")
6 | data object Users: FirebaseCollections("users")
7 | }
8 |
9 | sealed class FirebaseDocumentFields(val name: String) {
10 | data object AddedAt: FirebaseDocumentFields("addedAt")
11 | data object PickId: FirebaseDocumentFields("pickId")
12 | data object Uid: FirebaseDocumentFields("uid")
13 | data object MyPicks: FirebaseDocumentFields("myPicks")
14 | data object Name: FirebaseDocumentFields("name")
15 | data object Location: FirebaseDocumentFields("location")
16 | data object GeoHash: FirebaseDocumentFields("geoHash")
17 | data object CreatedUserName: FirebaseDocumentFields("createdBy.userName")
18 | }
19 |
--------------------------------------------------------------------------------
/data/firebase/src/main/java/com/squirtles/data/firebase/FirebaseModule.kt:
--------------------------------------------------------------------------------
1 | package com.squirtles.data.firebase
2 |
3 | import com.google.firebase.firestore.FirebaseFirestore
4 | import com.squirtles.core.buildconfig.LocalPropertyProvider
5 | import dagger.Module
6 | import dagger.Provides
7 | import dagger.hilt.InstallIn
8 | import dagger.hilt.components.SingletonComponent
9 | import javax.inject.Singleton
10 |
11 | @Module
12 | @InstallIn(SingletonComponent::class) // SingletonComponent에 등록
13 | object FirebaseModule {
14 |
15 | @Provides
16 | @Singleton
17 | fun provideFirebaseFirestore(): FirebaseFirestore {
18 | return FirebaseFirestore.getInstance(LocalPropertyProvider.firestoreDbId)
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/data/firebase/src/main/java/com/squirtles/data/firebase/FirebaseRepositoryUtils.kt:
--------------------------------------------------------------------------------
1 | package com.squirtles.data.firebase
2 |
3 | import com.squirtles.domain.firebase.FirebaseException
4 |
5 | suspend fun handleResult(
6 | firebaseRepositoryException: FirebaseException,
7 | call: suspend () -> T?
8 | ): Result {
9 | return runCatching {
10 | call() ?: throw firebaseRepositoryException
11 | }
12 | }
13 |
14 | suspend fun handleResult(
15 | call: suspend () -> T
16 | ): Result {
17 | return runCatching {
18 | call()
19 | }
20 | }
21 |
22 |
--------------------------------------------------------------------------------
/data/firebase/src/main/java/com/squirtles/data/firebase/model/FirebaseFavorite.kt:
--------------------------------------------------------------------------------
1 | package com.squirtles.data.firebase.model
2 |
3 | import com.google.firebase.Timestamp
4 | import com.google.firebase.firestore.ServerTimestamp
5 |
6 | data class FirebaseFavorite(
7 | val pickId: String? = null,
8 | val uid: String? = null,
9 | @ServerTimestamp val addedAt: Timestamp? = null,
10 | )
11 |
--------------------------------------------------------------------------------
/data/firebase/src/main/java/com/squirtles/data/firebase/model/FirebasePick.kt:
--------------------------------------------------------------------------------
1 | package com.squirtles.data.firebase.model
2 |
3 | import com.google.firebase.Timestamp
4 | import com.google.firebase.firestore.GeoPoint
5 | import com.google.firebase.firestore.ServerTimestamp
6 |
7 | /**
8 | * Firestore에 저장된 pick document를 불러와 변환하기위한 데이터 클래스
9 | */
10 | data class FirebasePick(
11 | val id: String? = null,
12 | val albumName: String? = null,
13 | val artistName: String? = null,
14 | val artwork: Map? = null,
15 | val comment: String? = null,
16 | @ServerTimestamp val createdAt: Timestamp? = null, // 등록 시 자동으로 서버 시간으로 설정되도록 합니다
17 | val createdBy: Map? = null,
18 | val externalUrl: String? = null,
19 | val favoriteCount: Int = 0,
20 | val genreNames: List = emptyList(),
21 | val geoHash: String? = null,
22 | val location: GeoPoint? = null,
23 | val previewUrl: String? = null,
24 | val musicVideoUrl: String? = null,
25 | val musicVideoThumbnail: String? = null,
26 | val songId: String? = null,
27 | val songName: String? = null,
28 | )
29 |
--------------------------------------------------------------------------------
/data/firebase/src/main/java/com/squirtles/data/firebase/model/FirebaseUser.kt:
--------------------------------------------------------------------------------
1 | package com.squirtles.data.firebase.model
2 |
3 | data class FirebaseUser(
4 | val email: String? = null,
5 | val name: String? = null,
6 | val profileImage: String? = null,
7 | val myPicks: List = emptyList()
8 | )
9 |
--------------------------------------------------------------------------------
/data/location/.gitignore:
--------------------------------------------------------------------------------
1 | /build
--------------------------------------------------------------------------------
/data/location/build.gradle.kts:
--------------------------------------------------------------------------------
1 | plugins {
2 | alias(libs.plugins.musicroad.android.library)
3 | alias(libs.plugins.musicroad.hilt)
4 | }
5 |
6 | android {
7 | namespace = "com.squirtles.data.location"
8 | }
9 |
10 | dependencies {
11 | implementation(projects.domain.location)
12 | implementation(projects.core.model)
13 |
14 | // Datastore
15 | implementation(libs.androidx.datastore.preferences)
16 |
17 | testImplementation(libs.junit)
18 | androidTestImplementation(libs.bundles.test)
19 | }
20 |
--------------------------------------------------------------------------------
/data/location/proguard-rules.pro:
--------------------------------------------------------------------------------
1 | # Add project specific ProGuard rules here.
2 | # You can control the set of applied configuration files using the
3 | # proguardFiles setting in build.gradle.
4 | #
5 | # For more details, see
6 | # http://developer.android.com/guide/developing/tools/proguard.html
7 |
8 | # If your project uses WebView with JS, uncomment the following
9 | # and specify the fully qualified class name to the JavaScript interface
10 | # class:
11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview {
12 | # public *;
13 | #}
14 |
15 | # Uncomment this to preserve the line number information for
16 | # debugging stack traces.
17 | #-keepattributes SourceFile,LineNumberTable
18 |
19 | # If you keep the line number information, uncomment this to
20 | # hide the original source file name.
21 | #-renamesourcefileattribute SourceFile
--------------------------------------------------------------------------------
/data/location/src/main/java/com/squirtles/data/location/di/LocationDiModule.kt:
--------------------------------------------------------------------------------
1 | package com.squirtles.data.location.di
2 |
3 | import android.content.Context
4 | import com.squirtles.domain.location.LocalLocationRepository
5 | import com.squirtles.data.location.LocalLocationRepositoryImpl
6 | import dagger.Module
7 | import dagger.Provides
8 | import dagger.hilt.InstallIn
9 | import dagger.hilt.android.qualifiers.ApplicationContext
10 | import dagger.hilt.components.SingletonComponent
11 | import javax.inject.Singleton
12 |
13 | @Module
14 | @InstallIn(SingletonComponent::class)
15 | object LocationDiModule {
16 | @Provides
17 | @Singleton
18 | fun provideLocalLocationRepository(@ApplicationContext context: Context): LocalLocationRepository =
19 | LocalLocationRepositoryImpl(context)
20 | }
21 |
--------------------------------------------------------------------------------
/data/order/.gitignore:
--------------------------------------------------------------------------------
1 | /build
--------------------------------------------------------------------------------
/data/order/build.gradle.kts:
--------------------------------------------------------------------------------
1 | plugins {
2 | id(libs.plugins.musicroad.data.get().pluginId)
3 | }
4 |
5 | android {
6 | namespace = "com.squirtles.data.order"
7 | }
8 |
9 | dependencies {
10 | implementation(projects.domain.order)
11 |
12 | testImplementation(libs.junit)
13 | androidTestImplementation(libs.bundles.test)
14 | }
15 |
--------------------------------------------------------------------------------
/data/order/proguard-rules.pro:
--------------------------------------------------------------------------------
1 | # Add project specific ProGuard rules here.
2 | # You can control the set of applied configuration files using the
3 | # proguardFiles setting in build.gradle.
4 | #
5 | # For more details, see
6 | # http://developer.android.com/guide/developing/tools/proguard.html
7 |
8 | # If your project uses WebView with JS, uncomment the following
9 | # and specify the fully qualified class name to the JavaScript interface
10 | # class:
11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview {
12 | # public *;
13 | #}
14 |
15 | # Uncomment this to preserve the line number information for
16 | # debugging stack traces.
17 | #-keepattributes SourceFile,LineNumberTable
18 |
19 | # If you keep the line number information, uncomment this to
20 | # hide the original source file name.
21 | #-renamesourcefileattribute SourceFile
--------------------------------------------------------------------------------
/data/order/src/main/java/com/squirtles/data/order/LocalPickListOrderRepositoryImpl.kt:
--------------------------------------------------------------------------------
1 | package com.squirtles.data.order
2 |
3 | import com.squirtles.core.model.Order
4 | import com.squirtles.domain.order.LocalPickListOrderRepository
5 | import javax.inject.Singleton
6 |
7 | @Singleton
8 | class LocalPickListOrderRepositoryImpl : LocalPickListOrderRepository {
9 | private var _favoriteListOrder = Order.LATEST
10 | override val favoriteListOrder get() = _favoriteListOrder
11 |
12 | private var _myListOrder = Order.LATEST
13 | override val myListOrder get() = _myListOrder
14 |
15 | override suspend fun saveFavoriteListOrder(order: Order) {
16 | _favoriteListOrder = order
17 | }
18 |
19 | override suspend fun saveMyListOrder(order: Order) {
20 | _myListOrder = order
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/data/order/src/main/java/com/squirtles/data/order/di/OrderDiModule.kt:
--------------------------------------------------------------------------------
1 | package com.squirtles.data.order.di
2 |
3 | import com.squirtles.domain.order.LocalPickListOrderRepository
4 | import com.squirtles.data.order.LocalPickListOrderRepositoryImpl
5 | import dagger.Module
6 | import dagger.Provides
7 | import dagger.hilt.InstallIn
8 | import dagger.hilt.components.SingletonComponent
9 | import javax.inject.Singleton
10 |
11 | @Module
12 | @InstallIn(SingletonComponent::class)
13 | object OrderDiModule {
14 | @Provides
15 | @Singleton
16 | fun provideLocalPickListOrderRepository(): LocalPickListOrderRepository =
17 | LocalPickListOrderRepositoryImpl()
18 | }
19 |
--------------------------------------------------------------------------------
/data/pick/.gitignore:
--------------------------------------------------------------------------------
1 | /build
--------------------------------------------------------------------------------
/data/pick/build.gradle.kts:
--------------------------------------------------------------------------------
1 | plugins {
2 | id(libs.plugins.musicroad.data.get().pluginId)
3 | }
4 |
5 | android {
6 | namespace = "com.squirtles.data.pick"
7 | }
8 |
9 | dependencies {
10 | implementation(projects.domain.pick)
11 | implementation(projects.data.firebase)
12 |
13 | testImplementation(libs.junit)
14 | androidTestImplementation(libs.bundles.test)
15 |
16 | // Firebase
17 | implementation(libs.firebase.firestore.ktx)
18 | implementation(libs.geofire.android.common)
19 | }
20 |
--------------------------------------------------------------------------------
/data/pick/proguard-rules.pro:
--------------------------------------------------------------------------------
1 | # Add project specific ProGuard rules here.
2 | # You can control the set of applied configuration files using the
3 | # proguardFiles setting in build.gradle.
4 | #
5 | # For more details, see
6 | # http://developer.android.com/guide/developing/tools/proguard.html
7 |
8 | # If your project uses WebView with JS, uncomment the following
9 | # and specify the fully qualified class name to the JavaScript interface
10 | # class:
11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview {
12 | # public *;
13 | #}
14 |
15 | # Uncomment this to preserve the line number information for
16 | # debugging stack traces.
17 | #-keepattributes SourceFile,LineNumberTable
18 |
19 | # If you keep the line number information, uncomment this to
20 | # hide the original source file name.
21 | #-renamesourcefileattribute SourceFile
--------------------------------------------------------------------------------
/data/pick/src/main/java/com/squirtles/data/pick/FirebasePickDataSource.kt:
--------------------------------------------------------------------------------
1 | package com.squirtles.data.pick
2 |
3 | import com.squirtles.core.model.Pick
4 | import com.squirtles.data.firebase.model.FirebasePick
5 | import kotlinx.coroutines.flow.Flow
6 |
7 | data class PickWithType(
8 | val type: PickType,
9 | val pick: Pick
10 | )
11 |
12 | enum class PickType {
13 | UPDATED, REMOVED
14 | }
15 |
16 | interface FirebasePickDataSource {
17 | suspend fun fetchPick(pickId: String): Result
18 | suspend fun fetchPicksInArea(lat: Double, lng: Double, radiusInM: Double): Flow>
19 | suspend fun createPick(firebasePick: FirebasePick, userId: String): Result
20 | suspend fun deletePick(pickId: String, userId: String): Result
21 | suspend fun fetchMyPicks(userId: String): Result>
22 | suspend fun fetchFavoritePicks(userId: String): Result>
23 | }
24 |
--------------------------------------------------------------------------------
/data/pick/src/main/java/com/squirtles/data/pick/di/PickDiModule.kt:
--------------------------------------------------------------------------------
1 | package com.squirtles.data.pick.di
2 |
3 | import com.squirtles.data.pick.FirebasePickDataSource
4 | import com.squirtles.data.pick.FirebasePickDataSourceImpl
5 | import com.squirtles.domain.pick.FirebasePickRepository
6 | import com.squirtles.data.pick.FirebasePickRepositoryImpl
7 | import com.google.firebase.firestore.FirebaseFirestore
8 | import dagger.Module
9 | import dagger.Provides
10 | import dagger.hilt.InstallIn
11 | import dagger.hilt.components.SingletonComponent
12 | import javax.inject.Singleton
13 |
14 | @Module
15 | @InstallIn(SingletonComponent::class)
16 | object PickDiModule {
17 |
18 | @Provides
19 | @Singleton
20 | fun provideFirebasePickRepository(firebasePickDataSource: FirebasePickDataSource): FirebasePickRepository =
21 | FirebasePickRepositoryImpl(firebasePickDataSource)
22 |
23 | @Provides
24 | @Singleton
25 | fun provideFirebasePickDataSource(db: FirebaseFirestore): FirebasePickDataSource =
26 | FirebasePickDataSourceImpl(db)
27 | }
28 |
--------------------------------------------------------------------------------
/data/preference/.gitignore:
--------------------------------------------------------------------------------
1 | /build
--------------------------------------------------------------------------------
/data/preference/build.gradle.kts:
--------------------------------------------------------------------------------
1 | plugins {
2 | alias(libs.plugins.musicroad.data)
3 | }
4 |
5 | android {
6 | namespace = "com.squirtles.data.preference"
7 | }
8 |
9 | dependencies {
10 | implementation(projects.domain.preference)
11 |
12 | // Datastore
13 | implementation(libs.androidx.datastore.preferences)
14 | }
15 |
--------------------------------------------------------------------------------
/data/preference/consumer-rules.pro:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/boostcampwm-2024/and06-musicroad/d893dff01a193094f208e5a4c877d4e8ea93423b/data/preference/consumer-rules.pro
--------------------------------------------------------------------------------
/data/preference/proguard-rules.pro:
--------------------------------------------------------------------------------
1 | # Add project specific ProGuard rules here.
2 | # You can control the set of applied configuration files using the
3 | # proguardFiles setting in build.gradle.
4 | #
5 | # For more details, see
6 | # http://developer.android.com/guide/developing/tools/proguard.html
7 |
8 | # If your project uses WebView with JS, uncomment the following
9 | # and specify the fully qualified class name to the JavaScript interface
10 | # class:
11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview {
12 | # public *;
13 | #}
14 |
15 | # Uncomment this to preserve the line number information for
16 | # debugging stack traces.
17 | #-keepattributes SourceFile,LineNumberTable
18 |
19 | # If you keep the line number information, uncomment this to
20 | # hide the original source file name.
21 | #-renamesourcefileattribute SourceFile
--------------------------------------------------------------------------------
/data/preference/src/main/java/com/squirtles/data/preference/PreferenceKeys.kt:
--------------------------------------------------------------------------------
1 | package com.squirtles.data.preference
2 |
3 | import androidx.datastore.preferences.core.stringPreferencesKey
4 |
5 | object PreferenceKeys {
6 | val PLAYER_EFFECT = stringPreferencesKey("player_effect")
7 | }
8 |
--------------------------------------------------------------------------------
/data/preference/src/main/java/com/squirtles/data/preference/PreferenceRepositoryImpl.kt:
--------------------------------------------------------------------------------
1 | package com.squirtles.data.preference
2 |
3 | import android.content.Context
4 | import androidx.datastore.preferences.core.edit
5 | import androidx.datastore.preferences.preferencesDataStore
6 | import com.squirtles.data.preference.PreferenceKeys.PLAYER_EFFECT
7 | import com.squirtles.domain.preference.PlayerPreference
8 | import com.squirtles.domain.preference.PreferenceRepository
9 | import kotlinx.coroutines.flow.Flow
10 | import kotlinx.coroutines.flow.map
11 | import javax.inject.Inject
12 | import javax.inject.Singleton
13 |
14 | @Singleton
15 | class PreferenceRepositoryImpl @Inject constructor(
16 | context: Context
17 | ) : PreferenceRepository {
18 | private val Context.dataStore by preferencesDataStore(name = PLAYER_PREFERENCE_NAME)
19 | private val dataStore = context.dataStore
20 |
21 | override suspend fun savePlayerPreference(preference: PlayerPreference): Result {
22 | return runCatching {
23 | dataStore.edit { pref ->
24 | pref[PLAYER_EFFECT] = preference.name
25 | }
26 | true
27 | }
28 | }
29 |
30 | override fun loadPlayerPreference(): Flow {
31 | return dataStore.data.map { pref ->
32 | PlayerPreference.valueOf(pref[PLAYER_EFFECT] ?: "BAR")
33 | }
34 | }
35 |
36 | companion object {
37 | private const val PLAYER_PREFERENCE_NAME = "player_preference"
38 | }
39 | }
40 |
--------------------------------------------------------------------------------
/data/preference/src/main/java/com/squirtles/data/preference/di/PreferenceDiModule.kt:
--------------------------------------------------------------------------------
1 | package com.squirtles.data.preference.di
2 |
3 | import android.content.Context
4 | import com.squirtles.data.preference.PreferenceRepositoryImpl
5 | import com.squirtles.domain.preference.PreferenceRepository
6 | import dagger.Module
7 | import dagger.Provides
8 | import dagger.hilt.InstallIn
9 | import dagger.hilt.android.qualifiers.ApplicationContext
10 | import dagger.hilt.components.SingletonComponent
11 | import javax.inject.Singleton
12 |
13 | @Module
14 | @InstallIn(SingletonComponent::class)
15 | object PreferenceDiModule {
16 | @Provides
17 | @Singleton
18 | fun providePreferenceRepository(@ApplicationContext context: Context): PreferenceRepository =
19 | PreferenceRepositoryImpl(context)
20 | }
21 |
--------------------------------------------------------------------------------
/data/user/.gitignore:
--------------------------------------------------------------------------------
1 | /build
--------------------------------------------------------------------------------
/data/user/build.gradle.kts:
--------------------------------------------------------------------------------
1 | plugins {
2 | id(libs.plugins.musicroad.data.get().pluginId)
3 | }
4 |
5 | android {
6 | namespace = "com.squirtles.data.user"
7 | }
8 |
9 | dependencies {
10 | implementation(projects.domain.user)
11 | implementation(projects.data.firebase)
12 |
13 | testImplementation(libs.junit)
14 | androidTestImplementation(libs.bundles.test)
15 |
16 | // Datastore
17 | implementation(libs.androidx.datastore.preferences)
18 |
19 | // firebase
20 | implementation(libs.firebase.firestore.ktx)
21 | implementation(libs.firebase.auth.ktx)
22 | }
23 |
--------------------------------------------------------------------------------
/data/user/proguard-rules.pro:
--------------------------------------------------------------------------------
1 | # Add project specific ProGuard rules here.
2 | # You can control the set of applied configuration files using the
3 | # proguardFiles setting in build.gradle.
4 | #
5 | # For more details, see
6 | # http://developer.android.com/guide/developing/tools/proguard.html
7 |
8 | # If your project uses WebView with JS, uncomment the following
9 | # and specify the fully qualified class name to the JavaScript interface
10 | # class:
11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview {
12 | # public *;
13 | #}
14 |
15 | # Uncomment this to preserve the line number information for
16 | # debugging stack traces.
17 | #-keepattributes SourceFile,LineNumberTable
18 |
19 | # If you keep the line number information, uncomment this to
20 | # hide the original source file name.
21 | #-renamesourcefileattribute SourceFile
--------------------------------------------------------------------------------
/data/user/src/main/java/com/squirtles/data/user/FirebaseUserDataSource.kt:
--------------------------------------------------------------------------------
1 | package com.squirtles.data.user
2 |
3 | import com.squirtles.data.firebase.model.FirebaseUser
4 |
5 | interface FirebaseUserDataSource {
6 | suspend fun fetchUser(uid: String): Result
7 | suspend fun createGoogleIdUser(uid: String, newUser: FirebaseUser): Result
8 | suspend fun updateUserName(uid: String, newUserName: String): Result
9 | suspend fun deleteUser(uid: String): Result
10 | }
11 |
--------------------------------------------------------------------------------
/data/user/src/main/java/com/squirtles/data/user/LocalUserDataSource.kt:
--------------------------------------------------------------------------------
1 | package com.squirtles.data.user
2 |
3 | import com.squirtles.core.model.User
4 | import kotlinx.coroutines.flow.Flow
5 |
6 | interface LocalUserDataSource {
7 | val currentUser: User?
8 |
9 | fun readUserIdDataStore(): Flow
10 | suspend fun saveUserIdDataStore(userId: String)
11 | suspend fun saveCurrentUser(user: User)
12 | suspend fun clearUser()
13 | }
14 |
--------------------------------------------------------------------------------
/data/user/src/main/java/com/squirtles/data/user/LocalUserRepositoryImpl.kt:
--------------------------------------------------------------------------------
1 | package com.squirtles.data.user
2 |
3 | import com.squirtles.core.model.User
4 | import com.squirtles.domain.user.LocalUserRepository
5 | import kotlinx.coroutines.flow.Flow
6 | import javax.inject.Inject
7 | import javax.inject.Singleton
8 |
9 | @Singleton
10 | class LocalUserRepositoryImpl @Inject constructor(
11 | private val userDataSource: LocalUserDataSource
12 | ) : LocalUserRepository {
13 | override val currentUser get() = userDataSource.currentUser
14 |
15 | override fun readUserIdDataStore(): Flow {
16 | return userDataSource.readUserIdDataStore()
17 | }
18 |
19 | override suspend fun saveUserIdDataStore(userId: String) {
20 | userDataSource.saveUserIdDataStore(userId)
21 | }
22 |
23 | override suspend fun saveCurrentUser(user: User) {
24 | userDataSource.saveCurrentUser(user)
25 | }
26 |
27 | override suspend fun clearUser(): Result {
28 | return try {
29 | userDataSource.clearUser()
30 | Result.success(Unit)
31 | } catch (e: Exception) {
32 | Result.failure(e)
33 | }
34 | }
35 | }
36 |
--------------------------------------------------------------------------------
/data/user/src/main/java/com/squirtles/data/user/di/UserDiModule.kt:
--------------------------------------------------------------------------------
1 | package com.squirtles.data.user.di
2 |
3 | import android.content.Context
4 | import com.google.firebase.firestore.FirebaseFirestore
5 | import com.squirtles.data.user.FirebaseUserDataSource
6 | import com.squirtles.data.user.FirebaseUserDataSourceImpl
7 | import com.squirtles.domain.user.FirebaseUserRepository
8 | import com.squirtles.data.user.FirebaseUserRepositoryImpl
9 | import com.squirtles.data.user.LocalUserDataSource
10 | import com.squirtles.data.user.LocalUserDataSourceImpl
11 | import com.squirtles.domain.user.LocalUserRepository
12 | import com.squirtles.data.user.LocalUserRepositoryImpl
13 | import dagger.Module
14 | import dagger.Provides
15 | import dagger.hilt.InstallIn
16 | import dagger.hilt.android.qualifiers.ApplicationContext
17 | import dagger.hilt.components.SingletonComponent
18 | import javax.inject.Singleton
19 |
20 | @Module
21 | @InstallIn(SingletonComponent::class)
22 | object UserDiModule {
23 | @Provides
24 | @Singleton
25 | fun provideLocalUserRepository(localUserDataSource: LocalUserDataSource): LocalUserRepository =
26 | LocalUserRepositoryImpl(localUserDataSource)
27 |
28 | @Provides
29 | @Singleton
30 | fun provideLocalUserDataSource(@ApplicationContext context: Context): LocalUserDataSource =
31 | LocalUserDataSourceImpl(context)
32 |
33 | @Provides
34 | @Singleton
35 | fun provideFirebaseUserRepository(firebaseUserDataSource: FirebaseUserDataSource): FirebaseUserRepository =
36 | FirebaseUserRepositoryImpl(firebaseUserDataSource)
37 |
38 | @Provides
39 | @Singleton
40 | fun provideFirebaseUserDataSource(db: FirebaseFirestore): FirebaseUserDataSource =
41 | FirebaseUserDataSourceImpl(db)
42 | }
43 |
--------------------------------------------------------------------------------
/domain/applemusic/.gitignore:
--------------------------------------------------------------------------------
1 | /build
--------------------------------------------------------------------------------
/domain/applemusic/build.gradle.kts:
--------------------------------------------------------------------------------
1 | plugins {
2 | alias(libs.plugins.musicroad.android.library)
3 | }
4 |
5 | android {
6 | namespace = "com.squirtles.domain.applemusic"
7 | }
8 |
9 | dependencies {
10 | implementation(projects.core.model)
11 | implementation(libs.androidx.paging.runtime)
12 | implementation(libs.inject)
13 |
14 | testImplementation(libs.junit)
15 | androidTestImplementation(libs.bundles.test)
16 | }
17 |
--------------------------------------------------------------------------------
/domain/applemusic/proguard-rules.pro:
--------------------------------------------------------------------------------
1 | # Add project specific ProGuard rules here.
2 | # You can control the set of applied configuration files using the
3 | # proguardFiles setting in build.gradle.
4 | #
5 | # For more details, see
6 | # http://developer.android.com/guide/developing/tools/proguard.html
7 |
8 | # If your project uses WebView with JS, uncomment the following
9 | # and specify the fully qualified class name to the JavaScript interface
10 | # class:
11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview {
12 | # public *;
13 | #}
14 |
15 | # Uncomment this to preserve the line number information for
16 | # debugging stack traces.
17 | #-keepattributes SourceFile,LineNumberTable
18 |
19 | # If you keep the line number information, uncomment this to
20 | # hide the original source file name.
21 | #-renamesourcefileattribute SourceFile
--------------------------------------------------------------------------------
/domain/applemusic/src/main/java/com/squirtles/domain/applemusic/AppleMusicException.kt:
--------------------------------------------------------------------------------
1 | package com.squirtles.domain.applemusic
2 |
3 | /**
4 | * 400 에러가 여러 종류가 있는데 이를 구분할 용도로 만든 예외 클래스
5 | */
6 | sealed class AppleMusicException(override val message: String) : Exception() {
7 | data class InvalidParameterException(override val message: String) :
8 | AppleMusicException(message)
9 |
10 | data class NotFoundException(override val message: String = "No such resource") :
11 | AppleMusicException(message)
12 | }
13 |
--------------------------------------------------------------------------------
/domain/applemusic/src/main/java/com/squirtles/domain/applemusic/AppleMusicRepository.kt:
--------------------------------------------------------------------------------
1 | package com.squirtles.domain.applemusic
2 |
3 | import androidx.paging.PagingData
4 | import com.squirtles.core.model.MusicVideo
5 | import com.squirtles.core.model.Song
6 | import kotlinx.coroutines.flow.Flow
7 |
8 | interface AppleMusicRepository {
9 | fun searchSongs(searchText: String): Flow>
10 | suspend fun searchMusicVideos(searchText: String): Result>
11 | }
12 |
--------------------------------------------------------------------------------
/domain/applemusic/src/main/java/com/squirtles/domain/applemusic/usecase/FetchMusicVideoUseCase.kt:
--------------------------------------------------------------------------------
1 | package com.squirtles.domain.applemusic.usecase
2 |
3 | import com.squirtles.domain.applemusic.AppleMusicRepository
4 | import com.squirtles.core.model.MusicVideo
5 | import com.squirtles.core.model.Song
6 | import javax.inject.Inject
7 |
8 | class FetchMusicVideoUseCase @Inject constructor(
9 | private val appleMusicRepository: AppleMusicRepository
10 | ) {
11 | suspend operator fun invoke(song: Song): MusicVideo? {
12 | val keyword = "${song.songName}-${song.artistName}"
13 | appleMusicRepository.searchMusicVideos(keyword).onSuccess { musicVideos ->
14 | return musicVideos.find {
15 | it.artistName == song.artistName && song.songName in it.songName
16 | }
17 | }
18 | return null
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/domain/applemusic/src/main/java/com/squirtles/domain/applemusic/usecase/FetchSongsUseCase.kt:
--------------------------------------------------------------------------------
1 | package com.squirtles.domain.applemusic.usecase
2 |
3 | import com.squirtles.domain.applemusic.AppleMusicRepository
4 | import javax.inject.Inject
5 |
6 | class FetchSongsUseCase @Inject constructor(
7 | private val appleMusicRepository: AppleMusicRepository
8 | ) {
9 | operator fun invoke(searchText: String) = appleMusicRepository.searchSongs(searchText)
10 | }
11 |
--------------------------------------------------------------------------------
/domain/favorite/.gitignore:
--------------------------------------------------------------------------------
1 | /build
--------------------------------------------------------------------------------
/domain/favorite/build.gradle.kts:
--------------------------------------------------------------------------------
1 | plugins {
2 | id(libs.plugins.musicroad.java.library.get().pluginId)
3 | }
4 |
5 | dependencies {
6 | implementation(projects.core.model)
7 | implementation(projects.domain.picklist)
8 |
9 | testImplementation(libs.junit)
10 | }
11 |
--------------------------------------------------------------------------------
/domain/favorite/proguard-rules.pro:
--------------------------------------------------------------------------------
1 | # Add project specific ProGuard rules here.
2 | # You can control the set of applied configuration files using the
3 | # proguardFiles setting in build.gradle.
4 | #
5 | # For more details, see
6 | # http://developer.android.com/guide/developing/tools/proguard.html
7 |
8 | # If your project uses WebView with JS, uncomment the following
9 | # and specify the fully qualified class name to the JavaScript interface
10 | # class:
11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview {
12 | # public *;
13 | #}
14 |
15 | # Uncomment this to preserve the line number information for
16 | # debugging stack traces.
17 | #-keepattributes SourceFile,LineNumberTable
18 |
19 | # If you keep the line number information, uncomment this to
20 | # hide the original source file name.
21 | #-renamesourcefileattribute SourceFile
--------------------------------------------------------------------------------
/domain/favorite/src/main/java/com/squirtles/domain/favorite/FirebaseFavoriteRepository.kt:
--------------------------------------------------------------------------------
1 | package com.squirtles.domain.favorite
2 |
3 | interface FirebaseFavoriteRepository {
4 | suspend fun fetchIsFavorite(pickId: String, uid: String): Result
5 | suspend fun createFavorite(pickId: String, uid: String): Result
6 | suspend fun deleteFavorite(pickId: String, uid: String): Result
7 | }
8 |
--------------------------------------------------------------------------------
/domain/favorite/src/main/java/com/squirtles/domain/favorite/usecase/CreateFavoriteUseCase.kt:
--------------------------------------------------------------------------------
1 | package com.squirtles.domain.favorite.usecase
2 |
3 | import com.squirtles.domain.favorite.FirebaseFavoriteRepository
4 | import javax.inject.Inject
5 |
6 | class CreateFavoriteUseCase @Inject constructor(
7 | private val favoriteRepository: FirebaseFavoriteRepository
8 | ) {
9 | suspend operator fun invoke(pickId: String, uid: String) =
10 | favoriteRepository.createFavorite(pickId, uid)
11 | }
12 |
--------------------------------------------------------------------------------
/domain/favorite/src/main/java/com/squirtles/domain/favorite/usecase/DeleteFavoriteUseCase.kt:
--------------------------------------------------------------------------------
1 | package com.squirtles.domain.favorite.usecase
2 |
3 | import com.squirtles.domain.favorite.FirebaseFavoriteRepository
4 | import com.squirtles.domain.picklist.RemovePickUseCaseInterface
5 | import javax.inject.Inject
6 |
7 | class DeleteFavoriteUseCase @Inject constructor(
8 | private val favoriteRepository: FirebaseFavoriteRepository
9 | ) : RemovePickUseCaseInterface {
10 | override suspend operator fun invoke(pickId: String, uid: String): Result =
11 | favoriteRepository.deleteFavorite(pickId, uid)
12 | }
13 |
--------------------------------------------------------------------------------
/domain/favorite/src/main/java/com/squirtles/domain/favorite/usecase/FetchIsFavoriteUseCase.kt:
--------------------------------------------------------------------------------
1 | package com.squirtles.domain.favorite.usecase
2 |
3 | import com.squirtles.domain.favorite.FirebaseFavoriteRepository
4 | import javax.inject.Inject
5 |
6 | class FetchIsFavoriteUseCase @Inject constructor(
7 | private val favoriteRepository: FirebaseFavoriteRepository
8 | ) {
9 | suspend operator fun invoke(pickId: String, userId: String) =
10 | favoriteRepository.fetchIsFavorite(pickId, userId)
11 | }
12 |
--------------------------------------------------------------------------------
/domain/firebase/.gitignore:
--------------------------------------------------------------------------------
1 | /build
--------------------------------------------------------------------------------
/domain/firebase/build.gradle.kts:
--------------------------------------------------------------------------------
1 | plugins {
2 | id(libs.plugins.musicroad.java.library.get().pluginId)
3 | }
4 |
5 | dependencies {
6 | implementation(projects.core.model)
7 |
8 | testImplementation(libs.junit)
9 | }
10 |
--------------------------------------------------------------------------------
/domain/location/.gitignore:
--------------------------------------------------------------------------------
1 | /build
--------------------------------------------------------------------------------
/domain/location/build.gradle.kts:
--------------------------------------------------------------------------------
1 | plugins {
2 | id(libs.plugins.musicroad.java.library.get().pluginId)
3 | }
4 |
5 | dependencies {
6 | implementation(projects.core.model)
7 | implementation(libs.kotlinx.coroutines.core)
8 |
9 | testImplementation(libs.junit)
10 | }
11 |
--------------------------------------------------------------------------------
/domain/location/proguard-rules.pro:
--------------------------------------------------------------------------------
1 | # Add project specific ProGuard rules here.
2 | # You can control the set of applied configuration files using the
3 | # proguardFiles setting in build.gradle.
4 | #
5 | # For more details, see
6 | # http://developer.android.com/guide/developing/tools/proguard.html
7 |
8 | # If your project uses WebView with JS, uncomment the following
9 | # and specify the fully qualified class name to the JavaScript interface
10 | # class:
11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview {
12 | # public *;
13 | #}
14 |
15 | # Uncomment this to preserve the line number information for
16 | # debugging stack traces.
17 | #-keepattributes SourceFile,LineNumberTable
18 |
19 | # If you keep the line number information, uncomment this to
20 | # hide the original source file name.
21 | #-renamesourcefileattribute SourceFile
--------------------------------------------------------------------------------
/domain/location/src/main/java/com/squirtles/domain/location/LocalLocationRepository.kt:
--------------------------------------------------------------------------------
1 | package com.squirtles.domain.location
2 |
3 | import com.squirtles.core.model.LocationPoint
4 | import kotlinx.coroutines.flow.Flow
5 |
6 | interface LocalLocationRepository {
7 |
8 | fun readLastLocation(): Flow
9 | suspend fun saveLastLocation(location: LocationPoint)
10 | }
11 |
--------------------------------------------------------------------------------
/domain/location/src/main/java/com/squirtles/domain/location/usecase/GetLastLocationUseCase.kt:
--------------------------------------------------------------------------------
1 | package com.squirtles.domain.location.usecase
2 |
3 | import com.squirtles.domain.location.LocalLocationRepository
4 | import javax.inject.Inject
5 |
6 | class GetLastLocationUseCase @Inject constructor(
7 | private val localLocationRepository: LocalLocationRepository
8 | ) {
9 | operator fun invoke() = localLocationRepository.readLastLocation()
10 | }
11 |
--------------------------------------------------------------------------------
/domain/location/src/main/java/com/squirtles/domain/location/usecase/SaveLastLocationUseCase.kt:
--------------------------------------------------------------------------------
1 | package com.squirtles.domain.location.usecase
2 |
3 | import com.squirtles.core.model.LocationPoint
4 | import com.squirtles.domain.location.LocalLocationRepository
5 | import javax.inject.Inject
6 |
7 | class SaveLastLocationUseCase @Inject constructor(
8 | private val localLocationRepository: LocalLocationRepository
9 | ) {
10 | suspend operator fun invoke(location: LocationPoint) = localLocationRepository.saveLastLocation(location)
11 | }
12 |
--------------------------------------------------------------------------------
/domain/order/.gitignore:
--------------------------------------------------------------------------------
1 | /build
--------------------------------------------------------------------------------
/domain/order/build.gradle.kts:
--------------------------------------------------------------------------------
1 | plugins {
2 | id(libs.plugins.musicroad.java.library.get().pluginId)
3 | }
4 |
5 | dependencies {
6 | implementation(projects.core.model)
7 | implementation(projects.domain.picklist)
8 | }
9 |
--------------------------------------------------------------------------------
/domain/order/proguard-rules.pro:
--------------------------------------------------------------------------------
1 | # Add project specific ProGuard rules here.
2 | # You can control the set of applied configuration files using the
3 | # proguardFiles setting in build.gradle.
4 | #
5 | # For more details, see
6 | # http://developer.android.com/guide/developing/tools/proguard.html
7 |
8 | # If your project uses WebView with JS, uncomment the following
9 | # and specify the fully qualified class name to the JavaScript interface
10 | # class:
11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview {
12 | # public *;
13 | #}
14 |
15 | # Uncomment this to preserve the line number information for
16 | # debugging stack traces.
17 | #-keepattributes SourceFile,LineNumberTable
18 |
19 | # If you keep the line number information, uncomment this to
20 | # hide the original source file name.
21 | #-renamesourcefileattribute SourceFile
--------------------------------------------------------------------------------
/domain/order/src/main/java/com/squirtles/domain/order/LocalPickListOrderRepository.kt:
--------------------------------------------------------------------------------
1 | package com.squirtles.domain.order
2 |
3 | import com.squirtles.core.model.Order
4 |
5 | interface LocalPickListOrderRepository {
6 | val favoriteListOrder: Order // 픽 보관함 정렬 순서
7 | val myListOrder: Order // 등록한 픽 정렬 순서
8 |
9 | suspend fun saveFavoriteListOrder(order: Order)
10 | suspend fun saveMyListOrder(order: Order)
11 | }
12 |
--------------------------------------------------------------------------------
/domain/order/src/main/java/com/squirtles/domain/order/usecase/GetFavoriteListOrderUseCase.kt:
--------------------------------------------------------------------------------
1 | package com.squirtles.domain.order.usecase
2 |
3 | import com.squirtles.domain.order.LocalPickListOrderRepository
4 | import com.squirtles.domain.picklist.GetPickListOrderUseCaseInterface
5 | import javax.inject.Inject
6 |
7 | class GetFavoriteListOrderUseCase @Inject constructor(
8 | private val localPickListOrderRepository: LocalPickListOrderRepository
9 | ) : GetPickListOrderUseCaseInterface {
10 | override suspend operator fun invoke() = localPickListOrderRepository.favoriteListOrder
11 | }
12 |
--------------------------------------------------------------------------------
/domain/order/src/main/java/com/squirtles/domain/order/usecase/GetMyPickListOrderUseCase.kt:
--------------------------------------------------------------------------------
1 | package com.squirtles.domain.order.usecase
2 |
3 | import com.squirtles.domain.order.LocalPickListOrderRepository
4 | import com.squirtles.domain.picklist.GetPickListOrderUseCaseInterface
5 | import javax.inject.Inject
6 |
7 | class GetMyPickListOrderUseCase @Inject constructor(
8 | private val localPickListOrderRepository: LocalPickListOrderRepository
9 | ) : GetPickListOrderUseCaseInterface {
10 | override suspend operator fun invoke() = localPickListOrderRepository.myListOrder
11 | }
12 |
--------------------------------------------------------------------------------
/domain/order/src/main/java/com/squirtles/domain/order/usecase/SaveFavoriteListOrderUseCase.kt:
--------------------------------------------------------------------------------
1 | package com.squirtles.domain.order.usecase
2 |
3 | import com.squirtles.core.model.Order
4 | import com.squirtles.domain.order.LocalPickListOrderRepository
5 | import com.squirtles.domain.picklist.SavePickListOrderUseCaseInterface
6 | import javax.inject.Inject
7 |
8 | class SaveFavoriteListOrderUseCase @Inject constructor(
9 | private val localPickListOrderRepository: LocalPickListOrderRepository
10 | ) : SavePickListOrderUseCaseInterface {
11 | override suspend operator fun invoke(order: Order) = localPickListOrderRepository.saveFavoriteListOrder(order)
12 | }
13 |
--------------------------------------------------------------------------------
/domain/order/src/main/java/com/squirtles/domain/order/usecase/SaveMyPickListOrderUseCase.kt:
--------------------------------------------------------------------------------
1 | package com.squirtles.domain.order.usecase
2 |
3 | import com.squirtles.core.model.Order
4 | import com.squirtles.domain.order.LocalPickListOrderRepository
5 | import com.squirtles.domain.picklist.SavePickListOrderUseCaseInterface
6 | import javax.inject.Inject
7 |
8 | class SaveMyPickListOrderUseCase @Inject constructor(
9 | private val localPickListOrderRepository: LocalPickListOrderRepository
10 | ) : SavePickListOrderUseCaseInterface {
11 | override suspend operator fun invoke(order: Order) = localPickListOrderRepository.saveMyListOrder(order)
12 | }
13 |
--------------------------------------------------------------------------------
/domain/pick/.gitignore:
--------------------------------------------------------------------------------
1 | /build
--------------------------------------------------------------------------------
/domain/pick/build.gradle.kts:
--------------------------------------------------------------------------------
1 | plugins {
2 | id(libs.plugins.musicroad.java.library.get().pluginId)
3 | }
4 |
5 | dependencies {
6 | implementation(projects.core.model)
7 | implementation(projects.domain.picklist)
8 |
9 | implementation(libs.kotlinx.coroutines.core)
10 | }
11 |
--------------------------------------------------------------------------------
/domain/pick/proguard-rules.pro:
--------------------------------------------------------------------------------
1 | # Add project specific ProGuard rules here.
2 | # You can control the set of applied configuration files using the
3 | # proguardFiles setting in build.gradle.
4 | #
5 | # For more details, see
6 | # http://developer.android.com/guide/developing/tools/proguard.html
7 |
8 | # If your project uses WebView with JS, uncomment the following
9 | # and specify the fully qualified class name to the JavaScript interface
10 | # class:
11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview {
12 | # public *;
13 | #}
14 |
15 | # Uncomment this to preserve the line number information for
16 | # debugging stack traces.
17 | #-keepattributes SourceFile,LineNumberTable
18 |
19 | # If you keep the line number information, uncomment this to
20 | # hide the original source file name.
21 | #-renamesourcefileattribute SourceFile
--------------------------------------------------------------------------------
/domain/pick/src/main/java/com/squirtles/domain/pick/FirebasePickRepository.kt:
--------------------------------------------------------------------------------
1 | package com.squirtles.domain.pick
2 |
3 | import com.squirtles.core.model.Pick
4 | import kotlinx.coroutines.flow.Flow
5 |
6 | interface FirebasePickRepository {
7 | suspend fun createPick(pick: Pick): Result
8 | suspend fun deletePick(pickId: String, userId: String): Result
9 | suspend fun fetchPick(pickId: String): Result
10 | suspend fun fetchPicksInArea(lat: Double, lng: Double, radiusInM: Double): Flow>
11 | suspend fun fetchMyPicks(userId: String): Result>
12 | suspend fun fetchFavoritePicks(userId: String): Result>
13 | }
14 |
--------------------------------------------------------------------------------
/domain/pick/src/main/java/com/squirtles/domain/pick/usecase/CreatePickUseCase.kt:
--------------------------------------------------------------------------------
1 | package com.squirtles.domain.pick.usecase
2 |
3 | import com.squirtles.core.model.Pick
4 | import com.squirtles.domain.pick.FirebasePickRepository
5 | import javax.inject.Inject
6 |
7 | class CreatePickUseCase @Inject constructor(
8 | private val pickRepository: FirebasePickRepository
9 | ) {
10 | suspend operator fun invoke(pick: Pick): Result = pickRepository.createPick(pick)
11 | }
12 |
--------------------------------------------------------------------------------
/domain/pick/src/main/java/com/squirtles/domain/pick/usecase/DeletePickUseCase.kt:
--------------------------------------------------------------------------------
1 | package com.squirtles.domain.pick.usecase
2 |
3 | import com.squirtles.domain.pick.FirebasePickRepository
4 | import com.squirtles.domain.picklist.RemovePickUseCaseInterface
5 | import javax.inject.Inject
6 |
7 | class DeletePickUseCase @Inject constructor(
8 | private val pickRepository: FirebasePickRepository
9 | ) : RemovePickUseCaseInterface {
10 | override suspend operator fun invoke(pickId: String, uid: String): Result =
11 | pickRepository.deletePick(pickId, uid)
12 | }
13 |
--------------------------------------------------------------------------------
/domain/pick/src/main/java/com/squirtles/domain/pick/usecase/FetchFavoritePicksUseCase.kt:
--------------------------------------------------------------------------------
1 | package com.squirtles.domain.pick.usecase
2 |
3 | import com.squirtles.core.model.Pick
4 | import com.squirtles.domain.pick.FirebasePickRepository
5 | import com.squirtles.domain.picklist.FetchPickListUseCaseInterface
6 | import javax.inject.Inject
7 |
8 | class FetchFavoritePicksUseCase @Inject constructor(
9 | private val pickRepository: FirebasePickRepository
10 | ) : FetchPickListUseCaseInterface {
11 | override suspend operator fun invoke(userId: String): Result> =
12 | pickRepository.fetchFavoritePicks(userId)
13 | }
14 |
--------------------------------------------------------------------------------
/domain/pick/src/main/java/com/squirtles/domain/pick/usecase/FetchMyPicksUseCase.kt:
--------------------------------------------------------------------------------
1 | package com.squirtles.domain.pick.usecase
2 |
3 | import com.squirtles.core.model.Pick
4 | import com.squirtles.domain.pick.FirebasePickRepository
5 | import com.squirtles.domain.picklist.FetchPickListUseCaseInterface
6 | import javax.inject.Inject
7 |
8 | class FetchMyPicksUseCase @Inject constructor(
9 | private val pickRepository: FirebasePickRepository
10 | ) : FetchPickListUseCaseInterface {
11 | override suspend operator fun invoke(userId: String) : Result> =
12 | pickRepository.fetchMyPicks(userId)
13 | }
14 |
--------------------------------------------------------------------------------
/domain/pick/src/main/java/com/squirtles/domain/pick/usecase/FetchPickUseCase.kt:
--------------------------------------------------------------------------------
1 | package com.squirtles.domain.pick.usecase
2 |
3 | import com.squirtles.core.model.Pick
4 | import com.squirtles.domain.pick.FirebasePickRepository
5 | import kotlinx.coroutines.flow.Flow
6 | import javax.inject.Inject
7 |
8 | class FetchPickUseCase @Inject constructor(
9 | private val pickRepository: FirebasePickRepository
10 | ) {
11 | suspend operator fun invoke(pickId: String): Result =
12 | pickRepository.fetchPick(pickId)
13 |
14 | suspend operator fun invoke(lat: Double, lng: Double, radiusInM: Double): Flow> =
15 | pickRepository.fetchPicksInArea(lat, lng, radiusInM)
16 | }
17 |
--------------------------------------------------------------------------------
/domain/picklist/.gitignore:
--------------------------------------------------------------------------------
1 | /build
--------------------------------------------------------------------------------
/domain/picklist/build.gradle.kts:
--------------------------------------------------------------------------------
1 | plugins {
2 | id(libs.plugins.musicroad.java.library.get().pluginId)
3 | }
4 |
5 | dependencies {
6 | implementation(projects.core.model)
7 | }
8 |
--------------------------------------------------------------------------------
/domain/picklist/proguard-rules.pro:
--------------------------------------------------------------------------------
1 | # Add project specific ProGuard rules here.
2 | # You can control the set of applied configuration files using the
3 | # proguardFiles setting in build.gradle.
4 | #
5 | # For more details, see
6 | # http://developer.android.com/guide/developing/tools/proguard.html
7 |
8 | # If your project uses WebView with JS, uncomment the following
9 | # and specify the fully qualified class name to the JavaScript interface
10 | # class:
11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview {
12 | # public *;
13 | #}
14 |
15 | # Uncomment this to preserve the line number information for
16 | # debugging stack traces.
17 | #-keepattributes SourceFile,LineNumberTable
18 |
19 | # If you keep the line number information, uncomment this to
20 | # hide the original source file name.
21 | #-renamesourcefileattribute SourceFile
--------------------------------------------------------------------------------
/domain/picklist/src/main/java/com/squirtles/domain/picklist/FetchPickListUseCaseInterface.kt:
--------------------------------------------------------------------------------
1 | package com.squirtles.domain.picklist
2 |
3 | import com.squirtles.core.model.Pick
4 |
5 | interface FetchPickListUseCaseInterface {
6 | suspend operator fun invoke(userId: String): Result>
7 | }
8 |
--------------------------------------------------------------------------------
/domain/picklist/src/main/java/com/squirtles/domain/picklist/GetPickListOrderUseCaseInterface.kt:
--------------------------------------------------------------------------------
1 | package com.squirtles.domain.picklist
2 |
3 | import com.squirtles.core.model.Order
4 |
5 | interface GetPickListOrderUseCaseInterface {
6 | suspend operator fun invoke(): Order
7 | }
8 |
--------------------------------------------------------------------------------
/domain/picklist/src/main/java/com/squirtles/domain/picklist/RemovePickUseCaseInterface.kt:
--------------------------------------------------------------------------------
1 | package com.squirtles.domain.picklist
2 |
3 | interface RemovePickUseCaseInterface {
4 | suspend operator fun invoke(pickId: String, uid: String): Result
5 | }
6 |
--------------------------------------------------------------------------------
/domain/picklist/src/main/java/com/squirtles/domain/picklist/SavePickListOrderUseCaseInterface.kt:
--------------------------------------------------------------------------------
1 | package com.squirtles.domain.picklist
2 |
3 | import com.squirtles.core.model.Order
4 |
5 | interface SavePickListOrderUseCaseInterface {
6 | suspend operator fun invoke(order: Order)
7 | }
8 |
--------------------------------------------------------------------------------
/domain/player/.gitignore:
--------------------------------------------------------------------------------
1 | /build
--------------------------------------------------------------------------------
/domain/player/build.gradle.kts:
--------------------------------------------------------------------------------
1 | plugins {
2 | alias(libs.plugins.musicroad.android.library)
3 | }
4 |
5 | android {
6 | namespace = "com.squirtles.domain.player"
7 | }
8 |
9 | dependencies {
10 | implementation(projects.core.model)
11 | implementation(projects.core.mediaservice)
12 |
13 | testImplementation(libs.junit)
14 | androidTestImplementation(libs.bundles.test)
15 |
16 | implementation(libs.inject)
17 | implementation(libs.bundles.media3)
18 | }
19 |
--------------------------------------------------------------------------------
/domain/player/proguard-rules.pro:
--------------------------------------------------------------------------------
1 | # Add project specific ProGuard rules here.
2 | # You can control the set of applied configuration files using the
3 | # proguardFiles setting in build.gradle.
4 | #
5 | # For more details, see
6 | # http://developer.android.com/guide/developing/tools/proguard.html
7 |
8 | # If your project uses WebView with JS, uncomment the following
9 | # and specify the fully qualified class name to the JavaScript interface
10 | # class:
11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview {
12 | # public *;
13 | #}
14 |
15 | # Uncomment this to preserve the line number information for
16 | # debugging stack traces.
17 | #-keepattributes SourceFile,LineNumberTable
18 |
19 | # If you keep the line number information, uncomment this to
20 | # hide the original source file name.
21 | #-renamesourcefileattribute SourceFile
--------------------------------------------------------------------------------
/domain/preference/.gitignore:
--------------------------------------------------------------------------------
1 | /build
--------------------------------------------------------------------------------
/domain/preference/build.gradle.kts:
--------------------------------------------------------------------------------
1 | plugins {
2 | alias(libs.plugins.musicroad.java.library)
3 | }
4 |
5 | dependencies {
6 | implementation(libs.kotlinx.coroutines.core)
7 | }
8 |
--------------------------------------------------------------------------------
/domain/preference/src/main/java/com/squirtles/domain/preference/PlayerPreference.kt:
--------------------------------------------------------------------------------
1 | package com.squirtles.domain.preference
2 |
3 | enum class PlayerPreference {
4 | NONE, BAR, FILL, STROKE
5 | }
6 |
--------------------------------------------------------------------------------
/domain/preference/src/main/java/com/squirtles/domain/preference/PreferenceRepository.kt:
--------------------------------------------------------------------------------
1 | package com.squirtles.domain.preference
2 |
3 | import kotlinx.coroutines.flow.Flow
4 |
5 | interface PreferenceRepository {
6 | suspend fun savePlayerPreference(preference: PlayerPreference): Result
7 | fun loadPlayerPreference(): Flow
8 | }
9 |
--------------------------------------------------------------------------------
/domain/preference/src/main/java/com/squirtles/domain/preference/usecase/LoadPlayerPreferenceUseCase.kt:
--------------------------------------------------------------------------------
1 | package com.squirtles.domain.preference.usecase
2 |
3 | import com.squirtles.domain.preference.PreferenceRepository
4 | import javax.inject.Inject
5 |
6 | class LoadPlayerPreferenceUseCase @Inject constructor(
7 | private val preferenceRepository: PreferenceRepository
8 | ) {
9 | operator fun invoke() = preferenceRepository.loadPlayerPreference()
10 | }
11 |
--------------------------------------------------------------------------------
/domain/preference/src/main/java/com/squirtles/domain/preference/usecase/SavePlayerPreferenceUseCase.kt:
--------------------------------------------------------------------------------
1 | package com.squirtles.domain.preference.usecase
2 |
3 | import com.squirtles.domain.preference.PlayerPreference
4 | import com.squirtles.domain.preference.PreferenceRepository
5 | import javax.inject.Inject
6 |
7 | class SavePlayerPreferenceUseCase @Inject constructor(
8 | private val preferenceRepository: PreferenceRepository
9 | ) {
10 | suspend operator fun invoke(preference: PlayerPreference) = preferenceRepository.savePlayerPreference(preference)
11 | }
12 |
--------------------------------------------------------------------------------
/domain/user/.gitignore:
--------------------------------------------------------------------------------
1 | /build
--------------------------------------------------------------------------------
/domain/user/build.gradle.kts:
--------------------------------------------------------------------------------
1 | plugins {
2 | id(libs.plugins.musicroad.java.library.get().pluginId)
3 | }
4 |
5 | dependencies {
6 | implementation(projects.core.model)
7 | implementation(projects.domain.picklist)
8 | implementation(projects.domain.pick)
9 | implementation(projects.domain.favorite)
10 |
11 | implementation(libs.kotlinx.coroutines.core)
12 | }
13 |
--------------------------------------------------------------------------------
/domain/user/proguard-rules.pro:
--------------------------------------------------------------------------------
1 | # Add project specific ProGuard rules here.
2 | # You can control the set of applied configuration files using the
3 | # proguardFiles setting in build.gradle.
4 | #
5 | # For more details, see
6 | # http://developer.android.com/guide/developing/tools/proguard.html
7 |
8 | # If your project uses WebView with JS, uncomment the following
9 | # and specify the fully qualified class name to the JavaScript interface
10 | # class:
11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview {
12 | # public *;
13 | #}
14 |
15 | # Uncomment this to preserve the line number information for
16 | # debugging stack traces.
17 | #-keepattributes SourceFile,LineNumberTable
18 |
19 | # If you keep the line number information, uncomment this to
20 | # hide the original source file name.
21 | #-renamesourcefileattribute SourceFile
--------------------------------------------------------------------------------
/domain/user/src/main/java/com/squirtles/domain/user/FirebaseUserRepository.kt:
--------------------------------------------------------------------------------
1 | package com.squirtles.domain.user
2 |
3 | import com.squirtles.core.model.User
4 |
5 | interface FirebaseUserRepository {
6 | val currentUser: String?
7 | // user
8 | fun signOut()
9 | suspend fun createGoogleIdUser(uid: String, email: String, userName: String?, userProfileImage: String?): Result
10 | suspend fun fetchUser(userId: String): Result
11 | suspend fun updateUserName(userId: String, newUserName: String): Result
12 | suspend fun deleteUser(uid: String): Result
13 | }
14 |
--------------------------------------------------------------------------------
/domain/user/src/main/java/com/squirtles/domain/user/LocalUserRepository.kt:
--------------------------------------------------------------------------------
1 | package com.squirtles.domain.user
2 |
3 | import com.squirtles.core.model.User
4 | import kotlinx.coroutines.flow.Flow
5 |
6 | interface LocalUserRepository {
7 | val currentUser: User?
8 |
9 | fun readUserIdDataStore(): Flow
10 | suspend fun saveUserIdDataStore(userId: String)
11 | suspend fun saveCurrentUser(user: User)
12 | suspend fun clearUser(): Result
13 | }
14 |
--------------------------------------------------------------------------------
/domain/user/src/main/java/com/squirtles/domain/user/usecase/CreateGoogleIdUserUseCase.kt:
--------------------------------------------------------------------------------
1 | package com.squirtles.domain.user.usecase
2 |
3 | import com.squirtles.core.model.User
4 | import com.squirtles.domain.user.FirebaseUserRepository
5 | import javax.inject.Inject
6 |
7 | class CreateGoogleIdUserUseCase @Inject constructor(
8 | private val firebaseUserRepository: FirebaseUserRepository
9 | ) {
10 | suspend operator fun invoke(
11 | uid: String,
12 | email: String,
13 | userName: String? = null,
14 | userProfileImage: String? = null
15 | ): Result = firebaseUserRepository.createGoogleIdUser(
16 | uid,
17 | email,
18 | userName,
19 | userProfileImage
20 | )
21 | }
22 |
--------------------------------------------------------------------------------
/domain/user/src/main/java/com/squirtles/domain/user/usecase/FetchUserByIdUseCase.kt:
--------------------------------------------------------------------------------
1 | package com.squirtles.domain.user.usecase
2 |
3 | import com.squirtles.domain.user.FirebaseUserRepository
4 | import javax.inject.Inject
5 |
6 | class FetchUserByIdUseCase @Inject constructor(
7 | private val firebaseUserRepository: FirebaseUserRepository
8 | ) {
9 | suspend operator fun invoke(userId: String) =
10 | firebaseUserRepository.fetchUser(userId)
11 | }
12 |
--------------------------------------------------------------------------------
/domain/user/src/main/java/com/squirtles/domain/user/usecase/GetCurrentUidUseCase.kt:
--------------------------------------------------------------------------------
1 | package com.squirtles.domain.user.usecase
2 |
3 | import com.squirtles.domain.user.FirebaseUserRepository
4 | import javax.inject.Inject
5 |
6 | class GetCurrentUidUseCase @Inject constructor(
7 | private val firebaseUserRepository: FirebaseUserRepository
8 | ) {
9 | operator fun invoke() = firebaseUserRepository.currentUser
10 | }
11 |
--------------------------------------------------------------------------------
/domain/user/src/main/java/com/squirtles/domain/user/usecase/SignOutUseCase.kt:
--------------------------------------------------------------------------------
1 | package com.squirtles.domain.user.usecase
2 |
3 | import com.squirtles.domain.user.FirebaseUserRepository
4 | import javax.inject.Inject
5 |
6 | class SignOutUseCase @Inject constructor(
7 | private val firebaseUserRepository: FirebaseUserRepository
8 | ) {
9 | operator fun invoke() = firebaseUserRepository.signOut()
10 | }
11 |
--------------------------------------------------------------------------------
/domain/user/src/main/java/com/squirtles/domain/user/usecase/UpdateUserNameUseCase.kt:
--------------------------------------------------------------------------------
1 | package com.squirtles.domain.user.usecase
2 |
3 | import com.squirtles.domain.user.FirebaseUserRepository
4 | import javax.inject.Inject
5 |
6 | class UpdateUserNameUseCase @Inject constructor(
7 | private val firebaseUserRepository: FirebaseUserRepository
8 | ) {
9 | suspend operator fun invoke(userId: String, newUserName: String) =
10 | firebaseUserRepository.updateUserName(userId, newUserName)
11 | }
12 |
--------------------------------------------------------------------------------
/feature/create/.gitignore:
--------------------------------------------------------------------------------
1 | /build
--------------------------------------------------------------------------------
/feature/create/build.gradle.kts:
--------------------------------------------------------------------------------
1 | plugins {
2 | alias(libs.plugins.musicroad.feature)
3 | alias(libs.plugins.kotlin.serialization)
4 | }
5 |
6 | android {
7 | namespace = "com.squirtles.feature.create"
8 | }
9 |
10 | dependencies {
11 | implementation(projects.core.common)
12 |
13 | implementation(projects.domain.pick)
14 | implementation(projects.domain.user)
15 | implementation(projects.domain.applemusic)
16 | implementation(projects.domain.location)
17 |
18 | testImplementation(libs.junit)
19 | androidTestImplementation(libs.bundles.test)
20 |
21 | // Serialization
22 | implementation(libs.kotlinx.serialization.json)
23 | }
24 |
--------------------------------------------------------------------------------
/feature/create/proguard-rules.pro:
--------------------------------------------------------------------------------
1 | # Add project specific ProGuard rules here.
2 | # You can control the set of applied configuration files using the
3 | # proguardFiles setting in build.gradle.
4 | #
5 | # For more details, see
6 | # http://developer.android.com/guide/developing/tools/proguard.html
7 |
8 | # If your project uses WebView with JS, uncomment the following
9 | # and specify the fully qualified class name to the JavaScript interface
10 | # class:
11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview {
12 | # public *;
13 | #}
14 |
15 | # Uncomment this to preserve the line number information for
16 | # debugging stack traces.
17 | #-keepattributes SourceFile,LineNumberTable
18 |
19 | # If you keep the line number information, uncomment this to
20 | # hide the original source file name.
21 | #-renamesourcefileattribute SourceFile
--------------------------------------------------------------------------------
/feature/create/src/androidTest/java/com/squirtles/feature/create/ExampleInstrumentedTest.kt:
--------------------------------------------------------------------------------
1 | package com.squirtles.create
2 |
3 | import androidx.test.platform.app.InstrumentationRegistry
4 | import androidx.test.ext.junit.runners.AndroidJUnit4
5 |
6 | import org.junit.Test
7 | import org.junit.runner.RunWith
8 |
9 | import org.junit.Assert.*
10 |
11 | /**
12 | * Instrumented test, which will execute on an Android device.
13 | *
14 | * See [testing documentation](http://d.android.com/tools/testing).
15 | */
16 | @RunWith(AndroidJUnit4::class)
17 | class ExampleInstrumentedTest {
18 | @Test
19 | fun useAppContext() {
20 | // Context of the app under test.
21 | val appContext = InstrumentationRegistry.getInstrumentation().targetContext
22 | assertEquals("com.squirtles.create.test", appContext.packageName)
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/feature/create/src/main/java/com/squirtles/feature/create/CreatePickUiState.kt:
--------------------------------------------------------------------------------
1 | package com.squirtles.feature.create
2 |
3 | sealed class CreateUiState {
4 | data object Default : CreateUiState()
5 | data class Success(val data: T) : CreateUiState()
6 | data object Error : CreateUiState()
7 | }
8 |
--------------------------------------------------------------------------------
/feature/create/src/main/java/com/squirtles/feature/create/navigation/CreateNavigation.kt:
--------------------------------------------------------------------------------
1 | package com.squirtles.feature.create.navigation
2 |
3 | import androidx.navigation.NavController
4 | import androidx.navigation.NavGraphBuilder
5 | import androidx.navigation.NavOptions
6 | import androidx.navigation.compose.composable
7 | import androidx.navigation.toRoute
8 | import com.squirtles.core.model.Song
9 | import com.squirtles.core.navigation.SearchRoute
10 | import com.squirtles.core.util.serializableType
11 | import com.squirtles.feature.create.CreatePickScreen
12 | import kotlin.reflect.typeOf
13 |
14 | fun NavController.navigateCreate(song: Song, navOptions: NavOptions? = null) {
15 | val encodedSong = song.encoded()
16 | navigate(SearchRoute.Create(encodedSong), navOptions)
17 | }
18 |
19 | fun NavGraphBuilder.createNavGraph(
20 | onBackClick: () -> Unit,
21 | onCreateClick: (String) -> Unit
22 | ) {
23 | composable(
24 | typeMap = mapOf(typeOf() to serializableType())
25 | ) { backStackEntry ->
26 | val song = backStackEntry.toRoute().song
27 |
28 | CreatePickScreen(
29 | song = song,
30 | onBackClick = onBackClick,
31 | onCreateClick = onCreateClick,
32 | )
33 | }
34 | }
35 |
--------------------------------------------------------------------------------
/feature/create/src/main/res/values/strings.xml:
--------------------------------------------------------------------------------
1 |
2 | MusicRoad
3 |
4 | 상단 바 뒤로 가기 버튼
5 | 앨범 이미지
6 | 거리에 남길 한마디를 입력하세요.
7 | 픽 등록
8 | 등록하기
9 |
10 |
--------------------------------------------------------------------------------
/feature/create/src/test/java/com/squirtles/feature/create/ExampleUnitTest.kt:
--------------------------------------------------------------------------------
1 | package com.squirtles.create
2 |
3 | import org.junit.Test
4 |
5 | import org.junit.Assert.*
6 |
7 | /**
8 | * Example local unit test, which will execute on the development machine (host).
9 | *
10 | * See [testing documentation](http://d.android.com/tools/testing).
11 | */
12 | class ExampleUnitTest {
13 | @Test
14 | fun addition_isCorrect() {
15 | assertEquals(4, 2 + 2)
16 | }
17 | }
18 |
--------------------------------------------------------------------------------
/feature/detail/.gitignore:
--------------------------------------------------------------------------------
1 | /build
--------------------------------------------------------------------------------
/feature/detail/build.gradle.kts:
--------------------------------------------------------------------------------
1 | plugins {
2 | alias(libs.plugins.musicroad.feature)
3 | alias(libs.plugins.kotlin.serialization)
4 | }
5 |
6 | android {
7 | namespace = "com.squirtles.feature.detail"
8 | }
9 |
10 | dependencies {
11 | implementation(projects.core.account)
12 | implementation(projects.core.musicplayer)
13 | implementation(projects.core.preference)
14 | implementation(projects.domain.pick)
15 | implementation(projects.domain.picklist)
16 | implementation(projects.domain.user)
17 | implementation(projects.domain.preference)
18 | implementation(projects.domain.favorite)
19 |
20 | implementation(libs.audio.visualizer)
21 | implementation(libs.coil.compose)
22 | implementation(libs.androidx.media3.exoplayer)
23 | implementation(libs.googleid)
24 |
25 | testImplementation(libs.junit)
26 | androidTestImplementation(libs.bundles.test)
27 |
28 | // Serialization
29 | implementation(libs.kotlinx.serialization.json)
30 | }
31 |
--------------------------------------------------------------------------------
/feature/detail/proguard-rules.pro:
--------------------------------------------------------------------------------
1 | # Add project specific ProGuard rules here.
2 | # You can control the set of applied configuration files using the
3 | # proguardFiles setting in build.gradle.
4 | #
5 | # For more details, see
6 | # http://developer.android.com/guide/developing/tools/proguard.html
7 |
8 | # If your project uses WebView with JS, uncomment the following
9 | # and specify the fully qualified class name to the JavaScript interface
10 | # class:
11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview {
12 | # public *;
13 | #}
14 |
15 | # Uncomment this to preserve the line number information for
16 | # debugging stack traces.
17 | #-keepattributes SourceFile,LineNumberTable
18 |
19 | # If you keep the line number information, uncomment this to
20 | # hide the original source file name.
21 | #-renamesourcefileattribute SourceFile
--------------------------------------------------------------------------------
/feature/detail/src/androidTest/java/com/squirtles/feature/detail/ExampleInstrumentedTest.kt:
--------------------------------------------------------------------------------
1 | package com.squirtles.detail
2 |
3 | import androidx.test.platform.app.InstrumentationRegistry
4 | import androidx.test.ext.junit.runners.AndroidJUnit4
5 |
6 | import org.junit.Test
7 | import org.junit.runner.RunWith
8 |
9 | import org.junit.Assert.*
10 |
11 | /**
12 | * Instrumented test, which will execute on an Android device.
13 | *
14 | * See [testing documentation](http://d.android.com/tools/testing).
15 | */
16 | @RunWith(AndroidJUnit4::class)
17 | class ExampleInstrumentedTest {
18 | @Test
19 | fun useAppContext() {
20 | // Context of the app under test.
21 | val appContext = InstrumentationRegistry.getInstrumentation().targetContext
22 | assertEquals("com.squirtles.detail.test", appContext.packageName)
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/feature/detail/src/main/java/com/squirtles/feature/detail/FavoriteAction.kt:
--------------------------------------------------------------------------------
1 | package com.squirtles.feature.detail
2 |
3 | enum class FavoriteAction {
4 | ADDED, DELETED
5 | }
6 |
--------------------------------------------------------------------------------
/feature/detail/src/main/java/com/squirtles/feature/detail/PickDetailUiState.kt:
--------------------------------------------------------------------------------
1 | package com.squirtles.feature.detail
2 |
3 | import com.squirtles.core.model.Pick
4 |
5 | sealed class PickDetailUiState {
6 | data object Loading : PickDetailUiState()
7 | data class Success(val pick: Pick, val isFavorite: Boolean) : PickDetailUiState()
8 | data object Deleted : PickDetailUiState()
9 | data object Error : PickDetailUiState()
10 | }
11 |
--------------------------------------------------------------------------------
/feature/detail/src/main/java/com/squirtles/feature/detail/components/SongInfo.kt:
--------------------------------------------------------------------------------
1 | package com.squirtles.feature.detail.components
2 |
3 | import androidx.compose.foundation.layout.Column
4 | import androidx.compose.foundation.layout.fillMaxWidth
5 | import androidx.compose.material3.MaterialTheme
6 | import androidx.compose.material3.Text
7 | import androidx.compose.runtime.Composable
8 | import androidx.compose.ui.Alignment
9 | import androidx.compose.ui.Modifier
10 | import androidx.compose.ui.graphics.Color
11 | import androidx.compose.ui.text.font.FontWeight
12 | import androidx.compose.ui.text.style.TextAlign
13 | import com.squirtles.core.model.Song
14 |
15 | @Composable
16 | internal fun SongInfo(
17 | song: Song,
18 | dynamicOnBackgroundColor: Color,
19 | modifier: Modifier,
20 | ) {
21 | Column(
22 | modifier = modifier.fillMaxWidth(),
23 | horizontalAlignment = Alignment.CenterHorizontally
24 | ) {
25 | Text(
26 | text = song.songName,
27 | color = dynamicOnBackgroundColor,
28 | textAlign = TextAlign.Center,
29 | style = MaterialTheme.typography.titleLarge.copy(fontWeight = FontWeight.Bold)
30 | )
31 |
32 | Text(
33 | text = song.artistName,
34 | color = dynamicOnBackgroundColor,
35 | textAlign = TextAlign.Center,
36 | style = MaterialTheme.typography.bodyLarge
37 | )
38 | }
39 | }
40 |
--------------------------------------------------------------------------------
/feature/detail/src/main/java/com/squirtles/feature/detail/components/SwipeUpIcon.kt:
--------------------------------------------------------------------------------
1 | package com.squirtles.feature.detail.components
2 |
3 | import androidx.compose.foundation.layout.Box
4 | import androidx.compose.foundation.layout.fillMaxWidth
5 | import androidx.compose.foundation.layout.heightIn
6 | import androidx.compose.material3.Icon
7 | import androidx.compose.runtime.Composable
8 | import androidx.compose.ui.Alignment
9 | import androidx.compose.ui.Modifier
10 | import androidx.compose.ui.res.painterResource
11 | import androidx.compose.ui.res.stringResource
12 | import androidx.compose.ui.unit.dp
13 | import com.squirtles.core.common.ui.theme.White
14 | import com.squirtles.feature.detail.R
15 |
16 | @Composable
17 | internal fun SwipeUpIcon(
18 | swipeableModifier: Modifier
19 | ) {
20 | Box(
21 | modifier = swipeableModifier
22 | .fillMaxWidth()
23 | .heightIn(min = 100.dp)
24 | ) {
25 | Icon(
26 | painter = painterResource(id = R.drawable.ic_swipe),
27 | contentDescription = stringResource(id = R.string.pick_swipe_icon_description),
28 | modifier = Modifier.align(Alignment.Center),
29 | tint = White
30 | )
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/feature/detail/src/main/java/com/squirtles/feature/detail/navigation/PickDetailNavigation.kt:
--------------------------------------------------------------------------------
1 | package com.squirtles.feature.detail.navigation
2 |
3 | import android.content.Context
4 | import androidx.navigation.NavController
5 | import androidx.navigation.NavGraphBuilder
6 | import androidx.navigation.NavOptions
7 | import androidx.navigation.compose.composable
8 | import androidx.navigation.toRoute
9 | import com.squirtles.feature.detail.PickDetailScreen
10 | import com.squirtles.core.musicplayer.PlayerServiceViewModel
11 | import com.squirtles.core.navigation.MapRoute
12 |
13 | fun NavController.navigatePickDetail(pickId: String, navOptions: NavOptions? = null) {
14 | navigate(MapRoute.PickDetail(pickId), navOptions)
15 | }
16 |
17 | fun NavGraphBuilder.detailNavGraph(
18 | playerServiceViewModel: PlayerServiceViewModel,
19 | onUserInfoClick: (String) -> Unit,
20 | onBackClick: () -> Unit,
21 | onDeleted: (Context) -> Unit,
22 | ) {
23 | composable { backStackEntry ->
24 | val pickId = backStackEntry.toRoute().pickId
25 |
26 | PickDetailScreen(
27 | pickId = pickId,
28 | playerServiceViewModel = playerServiceViewModel,
29 | onUserInfoClick = onUserInfoClick,
30 | onBackClick = onBackClick,
31 | onDeleted = onDeleted,
32 | )
33 | }
34 | }
35 |
--------------------------------------------------------------------------------
/feature/detail/src/main/java/com/squirtles/feature/detail/videoplayer/MusicVideoScreen.kt:
--------------------------------------------------------------------------------
1 | package com.squirtles.feature.detail.videoplayer
2 |
3 | import androidx.activity.compose.BackHandler
4 | import androidx.annotation.OptIn
5 | import androidx.compose.foundation.layout.Box
6 | import androidx.compose.foundation.layout.fillMaxSize
7 | import androidx.compose.material3.CircularProgressIndicator
8 | import androidx.compose.runtime.Composable
9 | import androidx.compose.runtime.getValue
10 | import androidx.compose.ui.Alignment
11 | import androidx.compose.ui.Modifier
12 | import androidx.hilt.navigation.compose.hiltViewModel
13 | import androidx.lifecycle.compose.collectAsStateWithLifecycle
14 | import com.squirtles.core.model.Pick
15 | import dagger.hilt.android.UnstableApi
16 |
17 | @OptIn(UnstableApi::class)
18 | @Composable
19 | fun MusicVideoScreen(
20 | pick: Pick,
21 | modifier: Modifier,
22 | onBackClick: () -> Unit,
23 | videoPlayerViewModel: VideoPlayerViewModel = hiltViewModel()
24 | ) {
25 | val isLoading by videoPlayerViewModel.isLoading.collectAsStateWithLifecycle()
26 |
27 | BackHandler { onBackClick() }
28 |
29 | Box(modifier = modifier.fillMaxSize()) {
30 | MusicVideoPlayer(pick.musicVideoUrl)
31 | VideoPlayerOverlay(pick, onBackClick)
32 |
33 | if (isLoading) {
34 | CircularProgressIndicator(Modifier.align(Alignment.Center))
35 | }
36 | }
37 | }
38 |
--------------------------------------------------------------------------------
/feature/detail/src/main/java/com/squirtles/feature/detail/videoplayer/VideoPlayerState.kt:
--------------------------------------------------------------------------------
1 | package com.squirtles.feature.detail.videoplayer
2 |
3 | enum class VideoPlayerState {
4 | Playing, Pause, Replay
5 | }
6 |
--------------------------------------------------------------------------------
/feature/detail/src/main/res/drawable/ic_delete.xml:
--------------------------------------------------------------------------------
1 |
2 |
7 |
10 |
11 |
--------------------------------------------------------------------------------
/feature/detail/src/main/res/drawable/ic_favorite.xml:
--------------------------------------------------------------------------------
1 |
2 |
7 |
10 |
--------------------------------------------------------------------------------
/feature/detail/src/main/res/drawable/ic_favorite_false.xml:
--------------------------------------------------------------------------------
1 |
2 |
7 |
10 |
11 |
--------------------------------------------------------------------------------
/feature/detail/src/main/res/drawable/ic_favorite_true.xml:
--------------------------------------------------------------------------------
1 |
2 |
7 |
10 |
11 |
--------------------------------------------------------------------------------
/feature/detail/src/main/res/drawable/ic_swipe.xml:
--------------------------------------------------------------------------------
1 |
2 |
7 |
13 |
14 |
--------------------------------------------------------------------------------
/feature/detail/src/test/java/com/squirtles/feature/detail/ExampleUnitTest.kt:
--------------------------------------------------------------------------------
1 | package com.squirtles.detail
2 |
3 | import org.junit.Test
4 |
5 | import org.junit.Assert.*
6 |
7 | /**
8 | * Example local unit test, which will execute on the development machine (host).
9 | *
10 | * See [testing documentation](http://d.android.com/tools/testing).
11 | */
12 | class ExampleUnitTest {
13 | @Test
14 | fun addition_isCorrect() {
15 | assertEquals(4, 2 + 2)
16 | }
17 | }
18 |
--------------------------------------------------------------------------------
/feature/favorite/.gitignore:
--------------------------------------------------------------------------------
1 | /build
--------------------------------------------------------------------------------
/feature/favorite/build.gradle.kts:
--------------------------------------------------------------------------------
1 | plugins {
2 | alias(libs.plugins.musicroad.feature)
3 | }
4 |
5 | android {
6 | namespace = "com.squirtles.feature.favorite"
7 | }
8 |
9 | dependencies {
10 | implementation(projects.core.picklist)
11 | implementation(projects.domain.picklist)
12 | implementation(projects.domain.favorite)
13 | implementation(projects.domain.pick)
14 | implementation(projects.domain.order)
15 | implementation(projects.domain.user)
16 |
17 | testImplementation(libs.junit)
18 | androidTestImplementation(libs.bundles.test)
19 | }
20 |
--------------------------------------------------------------------------------
/feature/favorite/proguard-rules.pro:
--------------------------------------------------------------------------------
1 | # Add project specific ProGuard rules here.
2 | # You can control the set of applied configuration files using the
3 | # proguardFiles setting in build.gradle.
4 | #
5 | # For more details, see
6 | # http://developer.android.com/guide/developing/tools/proguard.html
7 |
8 | # If your project uses WebView with JS, uncomment the following
9 | # and specify the fully qualified class name to the JavaScript interface
10 | # class:
11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview {
12 | # public *;
13 | #}
14 |
15 | # Uncomment this to preserve the line number information for
16 | # debugging stack traces.
17 | #-keepattributes SourceFile,LineNumberTable
18 |
19 | # If you keep the line number information, uncomment this to
20 | # hide the original source file name.
21 | #-renamesourcefileattribute SourceFile
--------------------------------------------------------------------------------
/feature/favorite/src/androidTest/java/com/squirtles/feature/favorite/ExampleInstrumentedTest.kt:
--------------------------------------------------------------------------------
1 | package com.squirtles.feature.favorite
2 |
3 | import androidx.test.platform.app.InstrumentationRegistry
4 | import androidx.test.ext.junit.runners.AndroidJUnit4
5 |
6 | import org.junit.Test
7 | import org.junit.runner.RunWith
8 |
9 | import org.junit.Assert.*
10 |
11 | /**
12 | * Instrumented test, which will execute on an Android device.
13 | *
14 | * See [testing documentation](http://d.android.com/tools/testing).
15 | */
16 | @RunWith(AndroidJUnit4::class)
17 | class ExampleInstrumentedTest {
18 | @Test
19 | fun useAppContext() {
20 | // Context of the app under test.
21 | val appContext = InstrumentationRegistry.getInstrumentation().targetContext
22 | assertEquals("com.squirtles.favorite.test", appContext.packageName)
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/feature/favorite/src/main/java/com/squirtles/feature/favorite/FavoriteListViewModel.kt:
--------------------------------------------------------------------------------
1 | package com.squirtles.feature.favorite
2 |
3 | import com.squirtles.domain.favorite.usecase.DeleteFavoriteUseCase
4 | import com.squirtles.domain.order.usecase.GetFavoriteListOrderUseCase
5 | import com.squirtles.domain.order.usecase.SaveFavoriteListOrderUseCase
6 | import com.squirtles.domain.pick.usecase.FetchFavoritePicksUseCase
7 | import com.squirtles.core.picklist.PickListViewModel
8 | import com.squirtles.domain.user.usecase.GetCurrentUidUseCase
9 | import dagger.hilt.android.lifecycle.HiltViewModel
10 | import javax.inject.Inject
11 |
12 | @HiltViewModel
13 | class FavoriteListViewModel @Inject constructor(
14 | fetchFavoritePicksUseCase: FetchFavoritePicksUseCase,
15 | getFavoriteListOrderUseCase: GetFavoriteListOrderUseCase,
16 | saveFavoriteListOrderUseCase: SaveFavoriteListOrderUseCase,
17 | deleteFavoriteUseCase: DeleteFavoriteUseCase,
18 | getCurrentUidUseCase: GetCurrentUidUseCase
19 | ) : PickListViewModel(
20 | fetchPickListUseCase = fetchFavoritePicksUseCase,
21 | getPickListOrderUseCase = getFavoriteListOrderUseCase,
22 | savePickListOrderUseCase = saveFavoriteListOrderUseCase,
23 | removePickUseCase = deleteFavoriteUseCase,
24 | getCurrentUidUseCase = getCurrentUidUseCase
25 | )
26 |
--------------------------------------------------------------------------------
/feature/favorite/src/main/java/com/squirtles/feature/favorite/navigation/FavoriteNavigation.kt:
--------------------------------------------------------------------------------
1 | package com.squirtles.feature.favorite.navigation
2 |
3 | import androidx.navigation.NavController
4 | import androidx.navigation.NavGraphBuilder
5 | import androidx.navigation.NavOptions
6 | import androidx.navigation.compose.composable
7 | import androidx.navigation.toRoute
8 | import com.squirtles.feature.favorite.FavoriteScreen
9 | import com.squirtles.core.navigation.MainRoute
10 |
11 | fun NavController.navigateFavorite(uid: String, navOptions: NavOptions? = null) {
12 | navigate(MainRoute.Favorite(uid), navOptions)
13 | }
14 |
15 | fun NavGraphBuilder.favoriteNavGraph(
16 | onBackClick: () -> Unit,
17 | onItemClick: (String) -> Unit,
18 | ) {
19 | composable { backStackEntry ->
20 | val uid = backStackEntry.toRoute().uid
21 |
22 | FavoriteScreen(
23 | uid = uid,
24 | onBackClick = onBackClick,
25 | onItemClick = onItemClick,
26 | )
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/feature/favorite/src/test/java/com/squirtles/feature/favorite/ExampleUnitTest.kt:
--------------------------------------------------------------------------------
1 | package com.squirtles.favorite
2 |
3 | import org.junit.Test
4 |
5 | import org.junit.Assert.*
6 |
7 | /**
8 | * Example local unit test, which will execute on the development machine (host).
9 | *
10 | * See [testing documentation](http://d.android.com/tools/testing).
11 | */
12 | class ExampleUnitTest {
13 | @Test
14 | fun addition_isCorrect() {
15 | assertEquals(4, 2 + 2)
16 | }
17 | }
18 |
--------------------------------------------------------------------------------
/feature/main/.gitignore:
--------------------------------------------------------------------------------
1 | /build
--------------------------------------------------------------------------------
/feature/main/build.gradle.kts:
--------------------------------------------------------------------------------
1 | plugins {
2 | alias(libs.plugins.musicroad.feature)
3 |
4 | }
5 |
6 | android {
7 | namespace = "com.squirtles.feature.main"
8 | }
9 |
10 | dependencies {
11 | implementation(projects.feature.map)
12 | implementation(projects.feature.permission)
13 | implementation(projects.feature.create)
14 | implementation(projects.feature.detail)
15 | implementation(projects.feature.mypick)
16 | implementation(projects.feature.favorite)
17 | implementation(projects.feature.search)
18 | implementation(projects.feature.userinfo)
19 | implementation(projects.core.musicplayer)
20 | implementation(projects.domain.user)
21 | implementation(projects.domain.firebase)
22 |
23 | implementation(libs.androidx.core.ktx)
24 | implementation(libs.androidx.appcompat)
25 | implementation(libs.material)
26 | implementation(libs.androidx.core.splashscreen)
27 | implementation(libs.firebase.auth.ktx)
28 |
29 | testImplementation(libs.junit)
30 | androidTestImplementation(libs.bundles.test)
31 | }
32 |
--------------------------------------------------------------------------------
/feature/main/proguard-rules.pro:
--------------------------------------------------------------------------------
1 | # Add project specific ProGuard rules here.
2 | # You can control the set of applied configuration files using the
3 | # proguardFiles setting in build.gradle.
4 | #
5 | # For more details, see
6 | # http://developer.android.com/guide/developing/tools/proguard.html
7 |
8 | # If your project uses WebView with JS, uncomment the following
9 | # and specify the fully qualified class name to the JavaScript interface
10 | # class:
11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview {
12 | # public *;
13 | #}
14 |
15 | # Uncomment this to preserve the line number information for
16 | # debugging stack traces.
17 | #-keepattributes SourceFile,LineNumberTable
18 |
19 | # If you keep the line number information, uncomment this to
20 | # hide the original source file name.
21 | #-renamesourcefileattribute SourceFile
--------------------------------------------------------------------------------
/feature/main/src/androidTest/java/com/squirtles/feature/main/ExampleInstrumentedTest.kt:
--------------------------------------------------------------------------------
1 | package com.squirtles.main
2 |
3 | import androidx.test.platform.app.InstrumentationRegistry
4 | import androidx.test.ext.junit.runners.AndroidJUnit4
5 |
6 | import org.junit.Test
7 | import org.junit.runner.RunWith
8 |
9 | import org.junit.Assert.*
10 |
11 | /**
12 | * Instrumented test, which will execute on an Android device.
13 | *
14 | * See [testing documentation](http://d.android.com/tools/testing).
15 | */
16 | @RunWith(AndroidJUnit4::class)
17 | class ExampleInstrumentedTest {
18 | @Test
19 | fun useAppContext() {
20 | // Context of the app under test.
21 | val appContext = InstrumentationRegistry.getInstrumentation().targetContext
22 | assertEquals("com.squirtles.main.test", appContext.packageName)
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/feature/main/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
--------------------------------------------------------------------------------
/feature/main/src/main/java/com/squirtles/feature/main/LoadingState.kt:
--------------------------------------------------------------------------------
1 | package com.squirtles.feature.main
2 |
3 | sealed class LoadingState {
4 | data object Loading : LoadingState()
5 | data class Success(val uid: String?) : LoadingState()
6 | data class NetworkError(val error: String) : LoadingState()
7 | data class CreatedUserError(val error: String) : LoadingState()
8 | data class UserNotFoundError(val error: String) : LoadingState()
9 | }
10 |
--------------------------------------------------------------------------------
/feature/main/src/main/res/values/colors.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | #FFFF5F61
4 | #FFBB86FC
5 | #FF6200EE
6 | #FF3700B3
7 | #FF03DAC5
8 | #FF018786
9 | #FF000000
10 | #FFFFFFFF
11 |
--------------------------------------------------------------------------------
/feature/main/src/main/res/values/strings.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | 확인
4 | 권한 요청
5 | 앱 실행을 위해\n위치와 마이크 권한이 필요합니다.
6 |
7 |
8 | 설정(앱 정보)에서 권한을 허용해주세요.
9 | 설정으로 이동
10 |
11 |
12 | 네트워크 연결이 원활하지 않습니다.
13 | 유저 정보가 존재하지 않습니다. 앱을 재설치 해주세요.
14 | 유저 등록 실패. 문의해주세요
15 |
16 |
--------------------------------------------------------------------------------
/feature/main/src/main/res/values/themes.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
11 |
--------------------------------------------------------------------------------
/feature/main/src/test/java/com/squirtles/feature/main/ExampleUnitTest.kt:
--------------------------------------------------------------------------------
1 | package com.squirtles.main
2 |
3 | import org.junit.Test
4 |
5 | import org.junit.Assert.*
6 |
7 | /**
8 | * Example local unit test, which will execute on the development machine (host).
9 | *
10 | * See [testing documentation](http://d.android.com/tools/testing).
11 | */
12 | class ExampleUnitTest {
13 | @Test
14 | fun addition_isCorrect() {
15 | assertEquals(4, 2 + 2)
16 | }
17 | }
18 |
--------------------------------------------------------------------------------
/feature/map/.gitignore:
--------------------------------------------------------------------------------
1 | /build
--------------------------------------------------------------------------------
/feature/map/build.gradle.kts:
--------------------------------------------------------------------------------
1 | plugins {
2 | alias(libs.plugins.musicroad.feature)
3 | }
4 |
5 | android {
6 | namespace = "com.squirtles.feature.map"
7 | }
8 |
9 | dependencies {
10 | implementation(projects.core.account)
11 | implementation(projects.core.musicplayer)
12 | implementation(projects.domain.pick)
13 | implementation(projects.domain.location)
14 | implementation(projects.domain.user)
15 |
16 | testImplementation(libs.junit)
17 | androidTestImplementation(libs.bundles.test)
18 |
19 | // Map
20 | implementation(libs.map.sdk)
21 | implementation(libs.play.services.location)
22 | implementation(libs.bundles.coil)
23 | implementation(libs.bundles.auth)
24 | }
25 |
--------------------------------------------------------------------------------
/feature/map/proguard-rules.pro:
--------------------------------------------------------------------------------
1 | # Add project specific ProGuard rules here.
2 | # You can control the set of applied configuration files using the
3 | # proguardFiles setting in build.gradle.
4 | #
5 | # For more details, see
6 | # http://developer.android.com/guide/developing/tools/proguard.html
7 |
8 | # If your project uses WebView with JS, uncomment the following
9 | # and specify the fully qualified class name to the JavaScript interface
10 | # class:
11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview {
12 | # public *;
13 | #}
14 |
15 | # Uncomment this to preserve the line number information for
16 | # debugging stack traces.
17 | #-keepattributes SourceFile,LineNumberTable
18 |
19 | # If you keep the line number information, uncomment this to
20 | # hide the original source file name.
21 | #-renamesourcefileattribute SourceFile
--------------------------------------------------------------------------------
/feature/map/src/androidTest/java/com/squirtles/feature/map/ExampleInstrumentedTest.kt:
--------------------------------------------------------------------------------
1 | package com.squirtles.map
2 |
3 | import androidx.test.platform.app.InstrumentationRegistry
4 | import androidx.test.ext.junit.runners.AndroidJUnit4
5 |
6 | import org.junit.Test
7 | import org.junit.runner.RunWith
8 |
9 | import org.junit.Assert.*
10 |
11 | /**
12 | * Instrumented test, which will execute on an Android device.
13 | *
14 | * See [testing documentation](http://d.android.com/tools/testing).
15 | */
16 | @RunWith(AndroidJUnit4::class)
17 | class ExampleInstrumentedTest {
18 | @Test
19 | fun useAppContext() {
20 | // Context of the app under test.
21 | val appContext = InstrumentationRegistry.getInstrumentation().targetContext
22 | assertEquals("com.squirtles.map.test", appContext.packageName)
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/feature/map/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
--------------------------------------------------------------------------------
/feature/map/src/main/java/com/squirtles/feature/map/Constants.kt:
--------------------------------------------------------------------------------
1 | package com.squirtles.feature.map
2 |
3 | internal enum class BottomNavigationSize(
4 | val size: Int
5 | ) {
6 | WIDTH(245),
7 | HEIGHT(50),
8 | HORIZONTAL_PADDING(32)
9 | }
10 |
11 | internal enum class BottomNavigationIconSize(
12 | val size: Int,
13 | ) {
14 | CENTER(82),
15 | CENTER_ICON(34)
16 | }
17 |
--------------------------------------------------------------------------------
/feature/map/src/main/java/com/squirtles/feature/map/marker/DensityType.kt:
--------------------------------------------------------------------------------
1 | package com.squirtles.feature.map.marker
2 |
3 | import androidx.compose.ui.graphics.toArgb
4 | import com.squirtles.core.common.ui.theme.Primary20
5 | import com.squirtles.core.common.ui.theme.Primary50
6 | import com.squirtles.core.common.ui.theme.Primary80
7 |
8 | enum class DensityType(val offset: Int, val color: Int) {
9 | LOW(4, Primary80.toArgb()),
10 | MEDIUM(2, Primary50.toArgb()),
11 | HIGH(0, Primary20.toArgb())
12 | }
13 |
--------------------------------------------------------------------------------
/feature/map/src/main/java/com/squirtles/feature/map/marker/MarkerKey.kt:
--------------------------------------------------------------------------------
1 | package com.squirtles.feature.map.marker
2 |
3 | import com.naver.maps.geometry.LatLng
4 | import com.naver.maps.map.clustering.ClusteringKey
5 | import com.squirtles.core.model.Pick
6 |
7 | data class MarkerKey(val pick: Pick) : ClusteringKey {
8 | override fun getPosition() = LatLng(pick.location.latitude, pick.location.longitude)
9 | }
10 |
--------------------------------------------------------------------------------
/feature/map/src/main/java/com/squirtles/feature/map/navigation/MapNavigation.kt:
--------------------------------------------------------------------------------
1 | package com.squirtles.feature.map.navigation
2 |
3 | import androidx.navigation.NavController
4 | import androidx.navigation.NavGraphBuilder
5 | import androidx.navigation.NavOptions
6 | import androidx.navigation.compose.composable
7 | import com.squirtles.feature.map.MapScreen
8 | import com.squirtles.feature.map.MapViewModel
9 | import com.squirtles.core.musicplayer.PlayerServiceViewModel
10 | import com.squirtles.core.navigation.Route
11 |
12 | fun NavController.navigateMap(navOptions: NavOptions? = null) {
13 | navigate(Route.Map, navOptions)
14 | }
15 |
16 | fun NavGraphBuilder.mapNavGraph(
17 | mapViewModel: MapViewModel,
18 | playerServiceViewModel: PlayerServiceViewModel,
19 | onFavoriteClick: (String) -> Unit,
20 | onCenterClick: () -> Unit,
21 | onUserInfoClick: (String) -> Unit,
22 | onPickSummaryClick: (String) -> Unit,
23 | onLoadingDialogCloseClick: () -> Unit
24 | ) {
25 | composable {
26 | MapScreen(
27 | mapViewModel = mapViewModel,
28 | playerServiceViewModel = playerServiceViewModel,
29 | onFavoriteClick = onFavoriteClick,
30 | onCenterClick = onCenterClick,
31 | onUserInfoClick = onUserInfoClick,
32 | onPickSummaryClick = onPickSummaryClick,
33 | finishActivity = onLoadingDialogCloseClick
34 | )
35 | }
36 | }
37 |
--------------------------------------------------------------------------------
/feature/map/src/main/java/com/squirtles/feature/map/navigation/NavTab.kt:
--------------------------------------------------------------------------------
1 | package com.squirtles.feature.map.navigation
2 |
3 | import androidx.annotation.StringRes
4 | import androidx.compose.material.icons.Icons
5 | import androidx.compose.material.icons.filled.FavoriteBorder
6 | import androidx.compose.material.icons.outlined.AccountCircle
7 | import androidx.compose.material.icons.outlined.MusicNote
8 | import androidx.compose.ui.graphics.vector.ImageVector
9 | import com.squirtles.feature.map.BottomNavigationIconSize
10 | import com.squirtles.feature.map.R
11 |
12 | internal enum class NavTab(
13 | @StringRes val contentDescription: Int,
14 | val icon: ImageVector,
15 | val iconSize: Int?,
16 | ) {
17 | FAVORITE(
18 | contentDescription = R.string.map_navigation_favorite_icon_description,
19 | icon = Icons.Default.FavoriteBorder,
20 | iconSize = null,
21 | ),
22 |
23 | MYPAGE(
24 | contentDescription = R.string.map_navigation_setting_icon_description,
25 | icon = Icons.Outlined.AccountCircle,
26 | iconSize = null,
27 | ),
28 |
29 | SEARCH(
30 | contentDescription = R.string.map_navigation_center_icon_description,
31 | icon = Icons.Outlined.MusicNote,
32 | iconSize = BottomNavigationIconSize.CENTER_ICON.size,
33 | ),
34 | }
35 |
--------------------------------------------------------------------------------
/feature/map/src/main/res/drawable/ic_location.xml:
--------------------------------------------------------------------------------
1 |
2 |
7 |
12 |
--------------------------------------------------------------------------------
/feature/map/src/main/res/drawable/ic_musical_note_64.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/boostcampwm-2024/and06-musicroad/d893dff01a193094f208e5a4c877d4e8ea93423b/feature/map/src/main/res/drawable/ic_musical_note_64.png
--------------------------------------------------------------------------------
/feature/map/src/main/res/values/strings.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | 현재 위치 로딩 중...
4 | 종료
5 | 데이터를 불러오는 데 일시적인 오류가 발생했습니다.
6 |
7 |
8 | 픽 보관함 이동 버튼 아이콘
9 | 설정 이동 버튼 아이콘
10 | 내비게이션 중앙 버튼 아이콘
11 | 🎧 주변에 %d개의 픽이 있습니다!
12 | 🔇 주변에 %d개의 픽이 있습니다!
13 | 앨범 이미지
14 | 님의 픽
15 | 픽을 담은 개수
16 | 내가
17 | 등록한 픽
18 |
19 |
20 | 담은 픽을 확인하기 위해\n로그인이 필요합니다
21 | 픽을 등록하기 위해\n로그인이 필요합니다
22 | 로그인이 필요합니다
23 |
24 |
--------------------------------------------------------------------------------
/feature/map/src/test/java/com/squirtles/feature/map/ExampleUnitTest.kt:
--------------------------------------------------------------------------------
1 | package com.squirtles.map
2 |
3 | import org.junit.Test
4 |
5 | import org.junit.Assert.*
6 |
7 | /**
8 | * Example local unit test, which will execute on the development machine (host).
9 | *
10 | * See [testing documentation](http://d.android.com/tools/testing).
11 | */
12 | class ExampleUnitTest {
13 | @Test
14 | fun addition_isCorrect() {
15 | assertEquals(4, 2 + 2)
16 | }
17 | }
18 |
--------------------------------------------------------------------------------
/feature/mypick/.gitignore:
--------------------------------------------------------------------------------
1 | /build
--------------------------------------------------------------------------------
/feature/mypick/build.gradle.kts:
--------------------------------------------------------------------------------
1 | plugins {
2 | alias(libs.plugins.musicroad.feature)
3 | }
4 |
5 | android {
6 | namespace = "com.squirtles.feature.mypick"
7 | }
8 |
9 | dependencies {
10 | implementation(projects.core.picklist)
11 | implementation(projects.domain.picklist)
12 | implementation(projects.domain.pick)
13 | implementation(projects.domain.order)
14 | implementation(projects.domain.user)
15 |
16 | testImplementation(libs.junit)
17 | androidTestImplementation(libs.bundles.test)
18 | }
19 |
--------------------------------------------------------------------------------
/feature/mypick/proguard-rules.pro:
--------------------------------------------------------------------------------
1 | # Add project specific ProGuard rules here.
2 | # You can control the set of applied configuration files using the
3 | # proguardFiles setting in build.gradle.
4 | #
5 | # For more details, see
6 | # http://developer.android.com/guide/developing/tools/proguard.html
7 |
8 | # If your project uses WebView with JS, uncomment the following
9 | # and specify the fully qualified class name to the JavaScript interface
10 | # class:
11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview {
12 | # public *;
13 | #}
14 |
15 | # Uncomment this to preserve the line number information for
16 | # debugging stack traces.
17 | #-keepattributes SourceFile,LineNumberTable
18 |
19 | # If you keep the line number information, uncomment this to
20 | # hide the original source file name.
21 | #-renamesourcefileattribute SourceFile
--------------------------------------------------------------------------------
/feature/mypick/src/androidTest/java/com/squirtles/feature/mypick/ExampleInstrumentedTest.kt:
--------------------------------------------------------------------------------
1 | package com.example.mypick
2 |
3 | import androidx.test.platform.app.InstrumentationRegistry
4 | import androidx.test.ext.junit.runners.AndroidJUnit4
5 |
6 | import org.junit.Test
7 | import org.junit.runner.RunWith
8 |
9 | import org.junit.Assert.*
10 |
11 | /**
12 | * Instrumented test, which will execute on an Android device.
13 | *
14 | * See [testing documentation](http://d.android.com/tools/testing).
15 | */
16 | @RunWith(AndroidJUnit4::class)
17 | class ExampleInstrumentedTest {
18 | @Test
19 | fun useAppContext() {
20 | // Context of the app under test.
21 | val appContext = InstrumentationRegistry.getInstrumentation().targetContext
22 | assertEquals("com.example.mypick.test", appContext.packageName)
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/feature/mypick/src/main/java/com/squirtles/feature/mypick/MyPickListViewModel.kt:
--------------------------------------------------------------------------------
1 | package com.squirtles.feature.mypick
2 |
3 | import com.squirtles.domain.order.usecase.GetMyPickListOrderUseCase
4 | import com.squirtles.domain.order.usecase.SaveMyPickListOrderUseCase
5 | import com.squirtles.domain.pick.usecase.DeletePickUseCase
6 | import com.squirtles.domain.pick.usecase.FetchMyPicksUseCase
7 | import com.squirtles.core.picklist.PickListViewModel
8 | import com.squirtles.domain.user.usecase.GetCurrentUidUseCase
9 | import dagger.hilt.android.lifecycle.HiltViewModel
10 | import javax.inject.Inject
11 |
12 | @HiltViewModel
13 | class MyPickListViewModel @Inject constructor(
14 | fetchMyPicksUseCase: FetchMyPicksUseCase,
15 | getMyPickListOrderUseCase: GetMyPickListOrderUseCase,
16 | saveMyPickListOrderUseCase: SaveMyPickListOrderUseCase,
17 | deletePickUseCase: DeletePickUseCase,
18 | getCurrentUidUseCase: GetCurrentUidUseCase
19 | ) : PickListViewModel(
20 | fetchPickListUseCase = fetchMyPicksUseCase,
21 | getPickListOrderUseCase = getMyPickListOrderUseCase,
22 | savePickListOrderUseCase = saveMyPickListOrderUseCase,
23 | removePickUseCase = deletePickUseCase,
24 | getCurrentUidUseCase = getCurrentUidUseCase
25 | )
26 |
--------------------------------------------------------------------------------
/feature/mypick/src/main/java/com/squirtles/feature/mypick/navigation/MyPickNavigation.kt:
--------------------------------------------------------------------------------
1 | package com.squirtles.feature.mypick.navigation
2 |
3 | import androidx.navigation.NavController
4 | import androidx.navigation.NavGraphBuilder
5 | import androidx.navigation.NavOptions
6 | import androidx.navigation.compose.composable
7 | import androidx.navigation.toRoute
8 | import com.squirtles.feature.mypick.MyPickScreen
9 | import com.squirtles.core.navigation.UserInfoRoute
10 |
11 | fun NavController.navigateMyPicks(uid: String, navOptions: NavOptions) {
12 | navigate(UserInfoRoute.MyPicks(uid), navOptions)
13 | }
14 |
15 | fun NavGraphBuilder.myPickNavGraph(
16 | onBackClick: () -> Unit,
17 | onItemClick: (String) -> Unit,
18 | ) {
19 | composable { backStackEntry ->
20 | val uid = backStackEntry.toRoute().uid
21 |
22 | MyPickScreen(
23 | uid = uid,
24 | onBackClick = onBackClick,
25 | onItemClick = onItemClick
26 | )
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/feature/mypick/src/test/java/com/squirtles/feature/mypick/ExampleUnitTest.kt:
--------------------------------------------------------------------------------
1 | package com.example.mypick
2 |
3 | import org.junit.Test
4 |
5 | import org.junit.Assert.*
6 |
7 | /**
8 | * Example local unit test, which will execute on the development machine (host).
9 | *
10 | * See [testing documentation](http://d.android.com/tools/testing).
11 | */
12 | class ExampleUnitTest {
13 | @Test
14 | fun addition_isCorrect() {
15 | assertEquals(4, 2 + 2)
16 | }
17 | }
18 |
--------------------------------------------------------------------------------
/feature/permission/.gitignore:
--------------------------------------------------------------------------------
1 | /build
--------------------------------------------------------------------------------
/feature/permission/build.gradle.kts:
--------------------------------------------------------------------------------
1 | plugins {
2 | alias(libs.plugins.musicroad.feature)
3 | }
4 |
5 | android {
6 | namespace = "com.squirtles.feature.permission"
7 | }
8 |
9 | dependencies {
10 | implementation(libs.accompanist.permissions)
11 | testImplementation(libs.junit)
12 | androidTestImplementation(libs.bundles.test)
13 | }
14 |
--------------------------------------------------------------------------------
/feature/permission/proguard-rules.pro:
--------------------------------------------------------------------------------
1 | # Add project specific ProGuard rules here.
2 | # You can control the set of applied configuration files using the
3 | # proguardFiles setting in build.gradle.
4 | #
5 | # For more details, see
6 | # http://developer.android.com/guide/developing/tools/proguard.html
7 |
8 | # If your project uses WebView with JS, uncomment the following
9 | # and specify the fully qualified class name to the JavaScript interface
10 | # class:
11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview {
12 | # public *;
13 | #}
14 |
15 | # Uncomment this to preserve the line number information for
16 | # debugging stack traces.
17 | #-keepattributes SourceFile,LineNumberTable
18 |
19 | # If you keep the line number information, uncomment this to
20 | # hide the original source file name.
21 | #-renamesourcefileattribute SourceFile
--------------------------------------------------------------------------------
/feature/permission/src/androidTest/java/com/squirtles/feature/permission/ExampleInstrumentedTest.kt:
--------------------------------------------------------------------------------
1 | package com.squirtles.permission
2 |
3 | import androidx.test.platform.app.InstrumentationRegistry
4 | import androidx.test.ext.junit.runners.AndroidJUnit4
5 |
6 | import org.junit.Test
7 | import org.junit.runner.RunWith
8 |
9 | import org.junit.Assert.*
10 |
11 | /**
12 | * Instrumented test, which will execute on an Android device.
13 | *
14 | * See [testing documentation](http://d.android.com/tools/testing).
15 | */
16 | @RunWith(AndroidJUnit4::class)
17 | class ExampleInstrumentedTest {
18 | @Test
19 | fun useAppContext() {
20 | // Context of the app under test.
21 | val appContext = InstrumentationRegistry.getInstrumentation().targetContext
22 | assertEquals("com.squirtles.permission.test", appContext.packageName)
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/feature/permission/src/main/java/com/squirtles/feature/permission/PermissionData.kt:
--------------------------------------------------------------------------------
1 | package com.squirtles.feature.permission
2 |
3 | import androidx.compose.ui.graphics.vector.ImageVector
4 | import com.squirtles.core.common.ui.MusicRoadPermissions
5 |
6 | internal data class PermissionData(
7 | val permission: String,
8 | val imageVector: ImageVector,
9 | val contentDescription: String,
10 | val permissionTitle: String,
11 | val permissionDescription: String,
12 | ) {
13 | val isOptional: Boolean
14 | get() = MusicRoadPermissions.OPTIONAL_PERMISSIONS.contains(permission)
15 | }
16 |
--------------------------------------------------------------------------------
/feature/permission/src/main/java/com/squirtles/feature/permission/PermissionDataFactory.kt:
--------------------------------------------------------------------------------
1 | package com.squirtles.feature.permission
2 |
3 | import android.Manifest
4 | import android.content.Context
5 | import androidx.compose.material.icons.Icons
6 | import androidx.compose.material.icons.filled.Mic
7 | import androidx.compose.material.icons.filled.MyLocation
8 |
9 | internal object PermissionDataFactory {
10 | fun from(permission: String, context: Context): PermissionData? {
11 | val resources = context.resources
12 |
13 | return when (permission) {
14 | Manifest.permission.RECORD_AUDIO -> PermissionData(
15 | permission = permission,
16 | imageVector = Icons.Default.Mic,
17 | contentDescription = resources.getString(R.string.permission_mic_content_desc),
18 | permissionTitle = resources.getString(R.string.permission_mic),
19 | permissionDescription = resources.getString(R.string.permission_mic_desc)
20 | )
21 |
22 | Manifest.permission.ACCESS_FINE_LOCATION -> null
23 |
24 | Manifest.permission.ACCESS_COARSE_LOCATION -> PermissionData(
25 | permission = permission,
26 | imageVector = Icons.Default.MyLocation,
27 | contentDescription = resources.getString(R.string.permission_location_content_desc),
28 | permissionTitle = resources.getString(R.string.permission_location),
29 | permissionDescription = resources.getString(R.string.permission_location_desc)
30 | )
31 |
32 | else -> throw IllegalArgumentException("Unsupported permission: $permission")
33 | }
34 | }
35 | }
36 |
--------------------------------------------------------------------------------
/feature/permission/src/main/res/values/strings.xml:
--------------------------------------------------------------------------------
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 |
--------------------------------------------------------------------------------
/feature/permission/src/test/java/com/squirtles/feature/permission/ExampleUnitTest.kt:
--------------------------------------------------------------------------------
1 | package com.squirtles.permission
2 |
3 | import org.junit.Test
4 |
5 | import org.junit.Assert.*
6 |
7 | /**
8 | * Example local unit test, which will execute on the development machine (host).
9 | *
10 | * See [testing documentation](http://d.android.com/tools/testing).
11 | */
12 | class ExampleUnitTest {
13 | @Test
14 | fun addition_isCorrect() {
15 | assertEquals(4, 2 + 2)
16 | }
17 | }
18 |
--------------------------------------------------------------------------------
/feature/search/.gitignore:
--------------------------------------------------------------------------------
1 | /build
--------------------------------------------------------------------------------
/feature/search/build.gradle.kts:
--------------------------------------------------------------------------------
1 | plugins {
2 | alias(libs.plugins.musicroad.feature)
3 | }
4 |
5 | android {
6 | namespace = "com.squirtles.feature.search"
7 | }
8 |
9 | dependencies {
10 | implementation(projects.domain.applemusic)
11 |
12 | implementation(libs.androidx.paging.compose)
13 |
14 | testImplementation(libs.junit)
15 | androidTestImplementation(libs.bundles.test)
16 | }
17 |
--------------------------------------------------------------------------------
/feature/search/proguard-rules.pro:
--------------------------------------------------------------------------------
1 | # Add project specific ProGuard rules here.
2 | # You can control the set of applied configuration files using the
3 | # proguardFiles setting in build.gradle.
4 | #
5 | # For more details, see
6 | # http://developer.android.com/guide/developing/tools/proguard.html
7 |
8 | # If your project uses WebView with JS, uncomment the following
9 | # and specify the fully qualified class name to the JavaScript interface
10 | # class:
11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview {
12 | # public *;
13 | #}
14 |
15 | # Uncomment this to preserve the line number information for
16 | # debugging stack traces.
17 | #-keepattributes SourceFile,LineNumberTable
18 |
19 | # If you keep the line number information, uncomment this to
20 | # hide the original source file name.
21 | #-renamesourcefileattribute SourceFile
--------------------------------------------------------------------------------
/feature/search/src/androidTest/java/com/squirtles/feature/search/ExampleInstrumentedTest.kt:
--------------------------------------------------------------------------------
1 | package com.example.search
2 |
3 | import androidx.test.platform.app.InstrumentationRegistry
4 | import androidx.test.ext.junit.runners.AndroidJUnit4
5 |
6 | import org.junit.Test
7 | import org.junit.runner.RunWith
8 |
9 | import org.junit.Assert.*
10 |
11 | /**
12 | * Instrumented test, which will execute on an Android device.
13 | *
14 | * See [testing documentation](http://d.android.com/tools/testing).
15 | */
16 | @RunWith(AndroidJUnit4::class)
17 | class ExampleInstrumentedTest {
18 | @Test
19 | fun useAppContext() {
20 | // Context of the app under test.
21 | val appContext = InstrumentationRegistry.getInstrumentation().targetContext
22 | assertEquals("com.example.search.test", appContext.packageName)
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/feature/search/src/main/java/com/squirtles/feature/search/SearchUiConstants.kt:
--------------------------------------------------------------------------------
1 | package com.squirtles.feature.search
2 |
3 | import androidx.compose.ui.unit.dp
4 |
5 | object SearchUiConstants {
6 | val SearchBarHeight = 56.dp
7 | val DefaultPadding = 16.dp
8 | val ItemSpacing = 24.dp
9 | val ImageSize = 56.dp
10 | }
11 |
12 |
13 |
--------------------------------------------------------------------------------
/feature/search/src/main/java/com/squirtles/feature/search/SearchUiState.kt:
--------------------------------------------------------------------------------
1 | package com.squirtles.feature.search
2 |
3 | sealed class SearchUiState {
4 | data object HotResult : SearchUiState()
5 | data object SearchResult : SearchUiState()
6 | }
7 |
--------------------------------------------------------------------------------
/feature/search/src/main/java/com/squirtles/feature/search/navigation/SearchNavigation.kt:
--------------------------------------------------------------------------------
1 | package com.squirtles.feature.search.navigation
2 |
3 | import androidx.navigation.NavController
4 | import androidx.navigation.NavGraphBuilder
5 | import androidx.navigation.NavOptions
6 | import androidx.navigation.compose.composable
7 | import com.squirtles.core.model.Song
8 | import com.squirtles.core.navigation.MainRoute
9 | import com.squirtles.feature.search.SearchMusicScreen
10 |
11 | fun NavController.navigateSearch(navOptions: NavOptions? = null) {
12 | navigate(MainRoute.Search, navOptions)
13 | }
14 |
15 | fun NavGraphBuilder.searchNavGraph(
16 | onBackClick: () -> Unit,
17 | onItemClick: (Song) -> Unit,
18 | ) {
19 | composable {
20 | SearchMusicScreen(
21 | onBackClick = onBackClick,
22 | onItemClick = onItemClick, // Create 이동
23 | )
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/feature/search/src/main/res/values/strings.xml:
--------------------------------------------------------------------------------
1 |
2 | MusicRoad
3 |
4 | 상단 바 뒤로 가기 버튼
5 |
6 |
7 | 검색
8 | 검색 결과
9 | 노래 앨범 이미지
10 | 노래 검색 버튼
11 | 검색 결과가 없습니다.
12 | 검색 결과를 불러올 수 없습니다.
13 |
14 |
--------------------------------------------------------------------------------
/feature/search/src/test/java/com/squirtles/feature/search/ExampleUnitTest.kt:
--------------------------------------------------------------------------------
1 | package com.example.search
2 |
3 | import org.junit.Test
4 |
5 | import org.junit.Assert.*
6 |
7 | /**
8 | * Example local unit test, which will execute on the development machine (host).
9 | *
10 | * See [testing documentation](http://d.android.com/tools/testing).
11 | */
12 | class ExampleUnitTest {
13 | @Test
14 | fun addition_isCorrect() {
15 | assertEquals(4, 2 + 2)
16 | }
17 | }
18 |
--------------------------------------------------------------------------------
/feature/userinfo/.gitignore:
--------------------------------------------------------------------------------
1 | /build
--------------------------------------------------------------------------------
/feature/userinfo/build.gradle.kts:
--------------------------------------------------------------------------------
1 | plugins {
2 | alias(libs.plugins.musicroad.feature)
3 | }
4 |
5 | android {
6 | namespace = "com.squirtles.feature.userinfo"
7 | }
8 |
9 | dependencies {
10 | implementation(projects.core.account)
11 | implementation(projects.core.preference)
12 | implementation(projects.domain.user)
13 | implementation(projects.domain.preference)
14 |
15 | implementation(libs.coil)
16 | implementation(libs.coil.compose)
17 |
18 | testImplementation(libs.junit)
19 | androidTestImplementation(libs.bundles.test)
20 | }
21 |
--------------------------------------------------------------------------------
/feature/userinfo/proguard-rules.pro:
--------------------------------------------------------------------------------
1 | # Add project specific ProGuard rules here.
2 | # You can control the set of applied configuration files using the
3 | # proguardFiles setting in build.gradle.
4 | #
5 | # For more details, see
6 | # http://developer.android.com/guide/developing/tools/proguard.html
7 |
8 | # If your project uses WebView with JS, uncomment the following
9 | # and specify the fully qualified class name to the JavaScript interface
10 | # class:
11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview {
12 | # public *;
13 | #}
14 |
15 | # Uncomment this to preserve the line number information for
16 | # debugging stack traces.
17 | #-keepattributes SourceFile,LineNumberTable
18 |
19 | # If you keep the line number information, uncomment this to
20 | # hide the original source file name.
21 | #-renamesourcefileattribute SourceFile
--------------------------------------------------------------------------------
/feature/userinfo/src/androidTest/java/com/squirtles/feature/userinfo/ExampleInstrumentedTest.kt:
--------------------------------------------------------------------------------
1 | package com.squirtles.userinfo
2 |
3 | import androidx.test.platform.app.InstrumentationRegistry
4 | import androidx.test.ext.junit.runners.AndroidJUnit4
5 |
6 | import org.junit.Test
7 | import org.junit.runner.RunWith
8 |
9 | import org.junit.Assert.*
10 |
11 | /**
12 | * Instrumented test, which will execute on an Android device.
13 | *
14 | * See [testing documentation](http://d.android.com/tools/testing).
15 | */
16 | @RunWith(AndroidJUnit4::class)
17 | class ExampleInstrumentedTest {
18 | @Test
19 | fun useAppContext() {
20 | // Context of the app under test.
21 | val appContext = InstrumentationRegistry.getInstrumentation().targetContext
22 | assertEquals("com.squirtles.userinfo.test", appContext.packageName)
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/feature/userinfo/src/main/java/com/squirtles/feature/userinfo/UserInfoConstants.kt:
--------------------------------------------------------------------------------
1 | package com.squirtles.feature.userinfo
2 |
3 | import androidx.compose.ui.unit.dp
4 | import com.squirtles.core.model.User
5 |
6 | internal object UserInfoConstants {
7 | const val USERNAME_PATTERN = "^[ㄱ-ㅎ|ㅏ-ㅣ가-힣a-zA-Z0-9]+$"
8 | val DEFAULT_USER = User("", "", "", null, listOf())
9 |
10 | // UI
11 | val MENU_PADDING_HORIZONTAL = 24.dp
12 | val MENU_PADDING_VERTICAL = 8.dp
13 | }
14 |
--------------------------------------------------------------------------------
/feature/userinfo/src/main/java/com/squirtles/feature/userinfo/components/MenuItem.kt:
--------------------------------------------------------------------------------
1 | package com.squirtles.feature.userinfo.components
2 |
3 | import androidx.compose.ui.graphics.Color
4 | import androidx.compose.ui.graphics.vector.ImageVector
5 | import com.squirtles.core.common.ui.theme.White
6 |
7 | data class MenuItem(
8 | val imageVector: ImageVector,
9 | val contentDescription: String,
10 | val iconColor: Color = White,
11 | val menuTitle: String,
12 | val onMenuClick: () -> Unit
13 | )
14 |
--------------------------------------------------------------------------------
/feature/userinfo/src/main/res/drawable/img_user_default_profile.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/boostcampwm-2024/and06-musicroad/d893dff01a193094f208e5a4c877d4e8ea93423b/feature/userinfo/src/main/res/drawable/img_user_default_profile.jpg
--------------------------------------------------------------------------------
/feature/userinfo/src/main/res/drawable/soundeffectnone.xml:
--------------------------------------------------------------------------------
1 |
6 |
11 |
12 |
--------------------------------------------------------------------------------
/feature/userinfo/src/test/java/com/squirtles/feature/userinfo/ExampleUnitTest.kt:
--------------------------------------------------------------------------------
1 | package com.squirtles.userinfo
2 |
3 | import org.junit.Test
4 |
5 | import org.junit.Assert.*
6 |
7 | /**
8 | * Example local unit test, which will execute on the development machine (host).
9 | *
10 | * See [testing documentation](http://d.android.com/tools/testing).
11 | */
12 | class ExampleUnitTest {
13 | @Test
14 | fun addition_isCorrect() {
15 | assertEquals(4, 2 + 2)
16 | }
17 | }
18 |
--------------------------------------------------------------------------------
/gradle.properties:
--------------------------------------------------------------------------------
1 | # Project-wide Gradle settings.
2 | # IDE (e.g. Android Studio) users:
3 | # Gradle settings configured through the IDE *will override*
4 | # any settings specified in this file.
5 | # For more details on how to configure your build environment visit
6 | # http://www.gradle.org/docs/current/userguide/build_environment.html
7 | # Specifies the JVM arguments used for the daemon process.
8 | # The setting is particularly useful for tweaking memory settings.
9 | org.gradle.jvmargs=-Xmx4048m -Dfile.encoding=UTF-8
10 | # When configured, Gradle will run in incubating parallel mode.
11 | # This option should only be used with decoupled projects. For more details, visit
12 | # https://developer.android.com/r/tools/gradle-multi-project-decoupled-projects
13 | # org.gradle.parallel=true
14 | # AndroidX package structure to make it clearer which packages are bundled with the
15 | # Android operating system, and which are packaged with your app's APK
16 | # https://developer.android.com/topic/libraries/support-library/androidx-rn
17 | android.useAndroidX=true
18 | # Kotlin code style for this project: "official" or "obsolete":
19 | kotlin.code.style=official
20 | # Enables namespacing of each library's R class so that its R class includes only the
21 | # resources declared in the library itself and none from the library's dependencies,
22 | # thereby reducing the size of the R class for that library
23 | android.nonTransitiveRClass=true
24 |
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/boostcampwm-2024/and06-musicroad/d893dff01a193094f208e5a4c877d4e8ea93423b/gradle/wrapper/gradle-wrapper.jar
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.properties:
--------------------------------------------------------------------------------
1 | #Sun Nov 03 13:33:31 KST 2024
2 | distributionBase=GRADLE_USER_HOME
3 | distributionPath=wrapper/dists
4 | distributionUrl=https\://services.gradle.org/distributions/gradle-8.7-bin.zip
5 | zipStoreBase=GRADLE_USER_HOME
6 | zipStorePath=wrapper/dists
7 |
--------------------------------------------------------------------------------