├── .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 | 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 | --------------------------------------------------------------------------------