From c51c2ef47b855caa00b3605f933762e8c407cd47 Mon Sep 17 00:00:00 2001 From: miller198 Date: Tue, 28 Jan 2025 00:24:30 +0900 Subject: [PATCH 01/62] =?UTF-8?q?[feature]=20core=20=EB=AA=A8=EB=93=88=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/build.gradle.kts | 20 +- build.gradle.kts | 3 +- core/common/.gitignore | 1 + core/common/build.gradle.kts | 61 +++++ core/common/consumer-rules.pro | 0 core/common/proguard-rules.pro | 21 ++ .../example/common/ExampleInstrumentedTest.kt | 24 ++ core/common/src/main/AndroidManifest.xml | 4 + .../java/com/example/common/ui/AlbumImage.kt | 39 ++++ .../java/com/example/common/ui/Constants.kt | 17 ++ .../example/common/ui/CreatedByPickText.kt | 64 ++++++ .../com/example/common/ui/DefaultTopAppBar.kt | 56 +++++ .../example/common/ui/MessageAlertDialog.kt | 126 +++++++++++ .../com/example/common/ui/PickInfoText.kt | 102 +++++++++ .../main/java/com/example/common/ui/Spacer.kt | 14 ++ .../java/com/example/common/ui/theme/Color.kt | 21 ++ .../java/com/example/common/ui/theme/Theme.kt | 45 ++++ .../java/com/example/common/ui/theme/Type.kt | 34 +++ .../src/main/res/drawable/ic_favorite.xml | 10 + core/common/src/main/res/values/strings.xml | 15 ++ .../com/example/common/ExampleUnitTest.kt | 17 ++ core/mediaservice/.gitignore | 1 + core/mediaservice/build.gradle.kts | 51 +++++ core/mediaservice/consumer-rules.pro | 0 core/mediaservice/proguard-rules.pro | 21 ++ .../mediaservice/ExampleInstrumentedTest.kt | 24 ++ .../mediaservice/src/main/AndroidManifest.xml | 4 + .../CustomMediaSessionCallback.kt | 96 ++++++++ .../mediaservice/MediaControllerProvider.kt | 58 +++++ .../mediaservice/MediaNotificationProvider.kt | 120 ++++++++++ .../mediaservice/MediaPlayerService.kt | 62 ++++++ .../example/mediaservice/PlayerCommands.kt | 40 ++++ .../example/mediaservice/di/MediaDiModule.kt | 101 +++++++++ .../res/drawable/ic_musicroad_foreground.xml | 56 +++++ .../example/mediaservice/ExampleUnitTest.kt | 17 ++ core/model/.gitignore | 1 + core/model/build.gradle.kts | 19 ++ .../main/java/com/example/model/MusicVideo.kt | 13 ++ .../src/main/java/com/example/model/Order.kt | 7 + .../src/main/java/com/example/model/Pick.kt | 28 +++ .../java/com/example/model/PlayerState.kt | 11 + .../src/main/java/com/example/model/Song.kt | 25 +++ .../src/main/java/com/example/model/User.kt | 7 + core/navigation/.gitignore | 1 + core/navigation/build.gradle.kts | 17 ++ .../java/com/example/navigation/MainRoute.kt | 15 ++ .../java/com/example/navigation/MapRoute.kt | 9 + .../com/example/navigation/ProfileRoute.kt | 16 ++ .../main/java/com/example/navigation/Route.kt | 9 + .../com/example/navigation/SearchRoute.kt | 11 + core/picklist/.gitignore | 1 + core/picklist/build.gradle.kts | 60 +++++ core/picklist/consumer-rules.pro | 0 core/picklist/proguard-rules.pro | 21 ++ .../picklist/ExampleInstrumentedTest.kt | 24 ++ core/picklist/src/main/AndroidManifest.xml | 4 + .../java/com/example/picklist/Extension.kt | 17 ++ .../com/example/picklist/OrderBottomSheet.kt | 134 +++++++++++ .../com/example/picklist/PickListContents.kt | 210 ++++++++++++++++++ .../java/com/example/picklist/PickListItem.kt | 107 +++++++++ .../java/com/example/picklist/PickListType.kt | 5 + .../com/example/picklist/PickListUiState.kt | 10 + core/picklist/src/main/res/values/strings.xml | 16 ++ .../com/example/picklist/ExampleUnitTest.kt | 17 ++ core/util/.gitignore | 1 + core/util/build.gradle.kts | 45 ++++ core/util/consumer-rules.pro | 0 core/util/proguard-rules.pro | 21 ++ .../example/util/ExampleInstrumentedTest.kt | 24 ++ core/util/src/main/AndroidManifest.xml | 4 + .../java/com/example/util/SerializableType.kt | 22 ++ .../java/com/example/util/ThrottleFirst.kt | 18 ++ .../java/com/example/util/ExampleUnitTest.kt | 17 ++ gradle/libs.versions.toml | 16 +- settings.gradle.kts | 6 + 75 files changed, 2318 insertions(+), 16 deletions(-) create mode 100644 core/common/.gitignore create mode 100644 core/common/build.gradle.kts create mode 100644 core/common/consumer-rules.pro create mode 100644 core/common/proguard-rules.pro create mode 100644 core/common/src/androidTest/java/com/example/common/ExampleInstrumentedTest.kt create mode 100644 core/common/src/main/AndroidManifest.xml create mode 100644 core/common/src/main/java/com/example/common/ui/AlbumImage.kt create mode 100644 core/common/src/main/java/com/example/common/ui/Constants.kt create mode 100644 core/common/src/main/java/com/example/common/ui/CreatedByPickText.kt create mode 100644 core/common/src/main/java/com/example/common/ui/DefaultTopAppBar.kt create mode 100644 core/common/src/main/java/com/example/common/ui/MessageAlertDialog.kt create mode 100644 core/common/src/main/java/com/example/common/ui/PickInfoText.kt create mode 100644 core/common/src/main/java/com/example/common/ui/Spacer.kt create mode 100644 core/common/src/main/java/com/example/common/ui/theme/Color.kt create mode 100644 core/common/src/main/java/com/example/common/ui/theme/Theme.kt create mode 100644 core/common/src/main/java/com/example/common/ui/theme/Type.kt create mode 100644 core/common/src/main/res/drawable/ic_favorite.xml create mode 100644 core/common/src/main/res/values/strings.xml create mode 100644 core/common/src/test/java/com/example/common/ExampleUnitTest.kt create mode 100644 core/mediaservice/.gitignore create mode 100644 core/mediaservice/build.gradle.kts create mode 100644 core/mediaservice/consumer-rules.pro create mode 100644 core/mediaservice/proguard-rules.pro create mode 100644 core/mediaservice/src/androidTest/java/com/example/mediaservice/ExampleInstrumentedTest.kt create mode 100644 core/mediaservice/src/main/AndroidManifest.xml create mode 100644 core/mediaservice/src/main/java/com/example/mediaservice/CustomMediaSessionCallback.kt create mode 100644 core/mediaservice/src/main/java/com/example/mediaservice/MediaControllerProvider.kt create mode 100644 core/mediaservice/src/main/java/com/example/mediaservice/MediaNotificationProvider.kt create mode 100644 core/mediaservice/src/main/java/com/example/mediaservice/MediaPlayerService.kt create mode 100644 core/mediaservice/src/main/java/com/example/mediaservice/PlayerCommands.kt create mode 100644 core/mediaservice/src/main/java/com/example/mediaservice/di/MediaDiModule.kt create mode 100644 core/mediaservice/src/main/res/drawable/ic_musicroad_foreground.xml create mode 100644 core/mediaservice/src/test/java/com/example/mediaservice/ExampleUnitTest.kt create mode 100644 core/model/.gitignore create mode 100644 core/model/build.gradle.kts create mode 100644 core/model/src/main/java/com/example/model/MusicVideo.kt create mode 100644 core/model/src/main/java/com/example/model/Order.kt create mode 100644 core/model/src/main/java/com/example/model/Pick.kt create mode 100644 core/model/src/main/java/com/example/model/PlayerState.kt create mode 100644 core/model/src/main/java/com/example/model/Song.kt create mode 100644 core/model/src/main/java/com/example/model/User.kt create mode 100644 core/navigation/.gitignore create mode 100644 core/navigation/build.gradle.kts create mode 100644 core/navigation/src/main/java/com/example/navigation/MainRoute.kt create mode 100644 core/navigation/src/main/java/com/example/navigation/MapRoute.kt create mode 100644 core/navigation/src/main/java/com/example/navigation/ProfileRoute.kt create mode 100644 core/navigation/src/main/java/com/example/navigation/Route.kt create mode 100644 core/navigation/src/main/java/com/example/navigation/SearchRoute.kt create mode 100644 core/picklist/.gitignore create mode 100644 core/picklist/build.gradle.kts create mode 100644 core/picklist/consumer-rules.pro create mode 100644 core/picklist/proguard-rules.pro create mode 100644 core/picklist/src/androidTest/java/com/example/picklist/ExampleInstrumentedTest.kt create mode 100644 core/picklist/src/main/AndroidManifest.xml create mode 100644 core/picklist/src/main/java/com/example/picklist/Extension.kt create mode 100644 core/picklist/src/main/java/com/example/picklist/OrderBottomSheet.kt create mode 100644 core/picklist/src/main/java/com/example/picklist/PickListContents.kt create mode 100644 core/picklist/src/main/java/com/example/picklist/PickListItem.kt create mode 100644 core/picklist/src/main/java/com/example/picklist/PickListType.kt create mode 100644 core/picklist/src/main/java/com/example/picklist/PickListUiState.kt create mode 100644 core/picklist/src/main/res/values/strings.xml create mode 100644 core/picklist/src/test/java/com/example/picklist/ExampleUnitTest.kt create mode 100644 core/util/.gitignore create mode 100644 core/util/build.gradle.kts create mode 100644 core/util/consumer-rules.pro create mode 100644 core/util/proguard-rules.pro create mode 100644 core/util/src/androidTest/java/com/example/util/ExampleInstrumentedTest.kt create mode 100644 core/util/src/main/AndroidManifest.xml create mode 100644 core/util/src/main/java/com/example/util/SerializableType.kt create mode 100644 core/util/src/main/java/com/example/util/ThrottleFirst.kt create mode 100644 core/util/src/test/java/com/example/util/ExampleUnitTest.kt diff --git a/app/build.gradle.kts b/app/build.gradle.kts index c4d0a167..4546a590 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -82,7 +82,7 @@ android { compose = true } composeOptions { - kotlinCompilerExtensionVersion = "1.5.1" + kotlinCompilerExtensionVersion = "1.5.10" } packaging { resources { @@ -98,28 +98,30 @@ dependencies { implementation(libs.androidx.core.ktx) implementation(libs.androidx.lifecycle.runtime.ktx) - implementation(libs.androidx.activity.compose) - implementation(platform(libs.androidx.compose.bom)) implementation(libs.androidx.ui) implementation(libs.androidx.ui.graphics) - implementation(libs.androidx.ui.tooling.preview) - implementation(libs.androidx.material3) - implementation(libs.androidx.material.icons.extended) implementation(libs.androidx.constraintlayout) implementation(libs.androidx.ui.viewbinding) - implementation(libs.androidx.navigation.compose) implementation(libs.androidx.core.splashscreen) - implementation(libs.androidx.compose.material) implementation(libs.androidx.animation) testImplementation(libs.junit) androidTestImplementation(libs.androidx.junit) androidTestImplementation(libs.androidx.espresso.core) - androidTestImplementation(platform(libs.androidx.compose.bom)) androidTestImplementation(libs.androidx.ui.test.junit4) debugImplementation(libs.androidx.ui.tooling) debugImplementation(libs.androidx.ui.test.manifest) implementation(libs.kotlinx.immutable) + // Compose + implementation(libs.androidx.activity.compose) + implementation(platform(libs.androidx.compose.bom)) + implementation(libs.androidx.compose.material) + androidTestImplementation(platform(libs.androidx.compose.bom)) + implementation(libs.androidx.navigation.compose) + implementation(libs.androidx.compose.material3) + implementation(libs.androidx.material.icons.extended) + implementation(libs.androidx.ui.tooling.preview) + // Hilt implementation(libs.hilt.android) ksp(libs.hilt.android.compiler) diff --git a/build.gradle.kts b/build.gradle.kts index 4a8f4158..d04c04a8 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -8,4 +8,5 @@ plugins { alias(libs.plugins.kotlin.serialization) apply false alias(libs.plugins.google.services) apply false alias(libs.plugins.firebase.crashlytics) apply false -} \ No newline at end of file + alias(libs.plugins.jetbrains.kotlin.jvm) apply false +} diff --git a/core/common/.gitignore b/core/common/.gitignore new file mode 100644 index 00000000..42afabfd --- /dev/null +++ b/core/common/.gitignore @@ -0,0 +1 @@ +/build \ No newline at end of file diff --git a/core/common/build.gradle.kts b/core/common/build.gradle.kts new file mode 100644 index 00000000..43d977e1 --- /dev/null +++ b/core/common/build.gradle.kts @@ -0,0 +1,61 @@ +plugins { + alias(libs.plugins.android.library) + alias(libs.plugins.jetbrains.kotlin.android) +} + +android { + namespace = "com.example.common" + compileSdk = 34 + + buildFeatures { + compose = true + } + + composeOptions { + kotlinCompilerExtensionVersion = "1.5.10" + } + + defaultConfig { + minSdk = 26 + + testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner" + consumerProguardFiles("consumer-rules.pro") + } + + buildTypes { + release { + isMinifyEnabled = false + proguardFiles(getDefaultProguardFile("proguard-android-optimize.txt"), "proguard-rules.pro") + } + } + compileOptions { + sourceCompatibility = JavaVersion.VERSION_1_8 + targetCompatibility = JavaVersion.VERSION_1_8 + } + kotlinOptions { + jvmTarget = "1.8" + } +} + +dependencies { + + implementation(libs.androidx.core.ktx) + implementation(libs.androidx.appcompat) + implementation(libs.material) + implementation(libs.androidx.runtime.android) + testImplementation(libs.junit) + androidTestImplementation(libs.androidx.junit) + androidTestImplementation(libs.androidx.espresso.core) + + // Compose + implementation(platform(libs.androidx.compose.bom)) + implementation(libs.androidx.compose.material) + implementation(libs.androidx.compose.material3) + implementation(libs.androidx.material.icons.extended) + implementation(libs.androidx.ui.tooling.preview) + + // Coil + implementation(libs.coil) + implementation(libs.coil.compose) + implementation(libs.coil.network.okhttp) +} diff --git a/core/common/consumer-rules.pro b/core/common/consumer-rules.pro new file mode 100644 index 00000000..e69de29b diff --git a/core/common/proguard-rules.pro b/core/common/proguard-rules.pro new file mode 100644 index 00000000..481bb434 --- /dev/null +++ b/core/common/proguard-rules.pro @@ -0,0 +1,21 @@ +# Add project specific ProGuard rules here. +# You can control the set of applied configuration files using the +# proguardFiles setting in build.gradle. +# +# For more details, see +# http://developer.android.com/guide/developing/tools/proguard.html + +# If your project uses WebView with JS, uncomment the following +# and specify the fully qualified class name to the JavaScript interface +# class: +#-keepclassmembers class fqcn.of.javascript.interface.for.webview { +# public *; +#} + +# Uncomment this to preserve the line number information for +# debugging stack traces. +#-keepattributes SourceFile,LineNumberTable + +# If you keep the line number information, uncomment this to +# hide the original source file name. +#-renamesourcefileattribute SourceFile \ No newline at end of file diff --git a/core/common/src/androidTest/java/com/example/common/ExampleInstrumentedTest.kt b/core/common/src/androidTest/java/com/example/common/ExampleInstrumentedTest.kt new file mode 100644 index 00000000..800c7dcc --- /dev/null +++ b/core/common/src/androidTest/java/com/example/common/ExampleInstrumentedTest.kt @@ -0,0 +1,24 @@ +package com.example.common + +import androidx.test.platform.app.InstrumentationRegistry +import androidx.test.ext.junit.runners.AndroidJUnit4 + +import org.junit.Test +import org.junit.runner.RunWith + +import org.junit.Assert.* + +/** + * Instrumented test, which will execute on an Android device. + * + * See [testing documentation](http://d.android.com/tools/testing). + */ +@RunWith(AndroidJUnit4::class) +class ExampleInstrumentedTest { + @Test + fun useAppContext() { + // Context of the app under test. + val appContext = InstrumentationRegistry.getInstrumentation().targetContext + assertEquals("com.example.common.test", appContext.packageName) + } +} diff --git a/core/common/src/main/AndroidManifest.xml b/core/common/src/main/AndroidManifest.xml new file mode 100644 index 00000000..8bdb7e14 --- /dev/null +++ b/core/common/src/main/AndroidManifest.xml @@ -0,0 +1,4 @@ + + + + diff --git a/core/common/src/main/java/com/example/common/ui/AlbumImage.kt b/core/common/src/main/java/com/example/common/ui/AlbumImage.kt new file mode 100644 index 00000000..411a9696 --- /dev/null +++ b/core/common/src/main/java/com/example/common/ui/AlbumImage.kt @@ -0,0 +1,39 @@ +package com.example.common.ui + +import android.util.Size +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.painter.ColorPainter +import androidx.compose.ui.layout.ContentScale +import androidx.compose.ui.platform.LocalContext +import androidx.compose.ui.res.stringResource +import coil3.compose.AsyncImage +import coil3.request.ImageRequest +import coil3.request.crossfade +import com.example.common.R +import com.example.common.ui.theme.Gray + +@Composable +fun AlbumImage( + imageUrl: String?, + modifier: Modifier = Modifier, + contentDescription: String = stringResource(R.string.map_album_image_description), +) { + AsyncImage( + model = ImageRequest.Builder(LocalContext.current) + .data(imageUrl) + .crossfade(true) + .build(), + contentDescription = contentDescription, + modifier = modifier, + placeholder = ColorPainter(Gray), + error = ColorPainter(Gray), + contentScale = ContentScale.Crop, + ) +} + +fun String.toImageUrlWithSize(size: Size): String? { + return if (isEmpty()) null + else replace("{w}", size.width.toString()) + .replace("{h}", size.height.toString()) +} diff --git a/core/common/src/main/java/com/example/common/ui/Constants.kt b/core/common/src/main/java/com/example/common/ui/Constants.kt new file mode 100644 index 00000000..839e2965 --- /dev/null +++ b/core/common/src/main/java/com/example/common/ui/Constants.kt @@ -0,0 +1,17 @@ +package com.example.common.ui + +import android.util.Size +import androidx.compose.ui.unit.dp +import com.example.common.ui.theme.Black +import com.example.common.ui.theme.Primary + +object Constants { + val DEFAULT_PADDING = 16.dp + + val REQUEST_IMAGE_SIZE_DEFAULT = Size(300, 300) + + val COLOR_STOPS = arrayOf( + 0.0f to Primary, + 0.25f to Black + ) +} diff --git a/core/common/src/main/java/com/example/common/ui/CreatedByPickText.kt b/core/common/src/main/java/com/example/common/ui/CreatedByPickText.kt new file mode 100644 index 00000000..6500f34f --- /dev/null +++ b/core/common/src/main/java/com/example/common/ui/CreatedByPickText.kt @@ -0,0 +1,64 @@ +package com.example.common.ui + +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.text.SpanStyle +import androidx.compose.ui.text.TextStyle +import androidx.compose.ui.text.buildAnnotatedString +import androidx.compose.ui.text.style.TextDecoration +import androidx.compose.ui.text.style.TextOverflow +import androidx.compose.ui.text.withStyle +import com.example.common.R + +@Composable +fun CreatedBySelfText( + modifier: Modifier = Modifier, + showUnderline: Boolean = false, + color: Color = MaterialTheme.colorScheme.onSurface, + style: TextStyle = MaterialTheme.typography.bodyMedium +) { + val myPickDescription = buildAnnotatedString { + withStyle(style = SpanStyle(textDecoration = if (showUnderline) TextDecoration.Underline else null)) { + append(stringResource(R.string.pick_created_by_self_1)) + } + append(" ${stringResource(R.string.pick_created_by_self_2)}") + } + + Text( + text = myPickDescription, + modifier = modifier, + color = color, + overflow = TextOverflow.Ellipsis, + maxLines = 1, + style = style + ) +} + +@Composable +fun CreatedByOtherUserText( + userName: String, + modifier: Modifier = Modifier, + showUnderline: Boolean = false, + color: Color = MaterialTheme.colorScheme.onSurface, + style: TextStyle = MaterialTheme.typography.bodyMedium +) { + Text( + text = userName, + modifier = modifier, + color = color, + textDecoration = if (showUnderline) TextDecoration.Underline else null, + overflow = TextOverflow.Ellipsis, + maxLines = 1, + style = style + ) + + Text( + text = stringResource(id = R.string.map_info_window_pick_user), + color = color, + style = style, + ) +} diff --git a/core/common/src/main/java/com/example/common/ui/DefaultTopAppBar.kt b/core/common/src/main/java/com/example/common/ui/DefaultTopAppBar.kt new file mode 100644 index 00000000..f3e93041 --- /dev/null +++ b/core/common/src/main/java/com/example/common/ui/DefaultTopAppBar.kt @@ -0,0 +1,56 @@ +package com.example.common.ui + +import androidx.compose.foundation.layout.RowScope +import androidx.compose.foundation.layout.displayCutoutPadding +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.automirrored.filled.ArrowBack +import androidx.compose.material3.CenterAlignedTopAppBar +import androidx.compose.material3.ExperimentalMaterial3Api +import androidx.compose.material3.Icon +import androidx.compose.material3.IconButton +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.Text +import androidx.compose.material3.TopAppBarDefaults +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.text.TextStyle +import androidx.compose.ui.text.font.FontWeight +import com.example.common.R +import com.example.common.ui.theme.White + +@OptIn(ExperimentalMaterial3Api::class) +@Composable +fun DefaultTopAppBar( + title: String, + titleStyle: TextStyle = MaterialTheme.typography.titleLarge.copy(fontWeight = FontWeight.Bold), + onBackClick: () -> Unit, + actions: @Composable RowScope.() -> Unit = {}, +) { + CenterAlignedTopAppBar( + title = { + Text( + text = title, + style = titleStyle + ) + }, + modifier = Modifier.displayCutoutPadding(), + navigationIcon = { + IconButton( + onClick = onBackClick + ) { + Icon( + imageVector = Icons.AutoMirrored.Filled.ArrowBack, + contentDescription = stringResource(R.string.top_app_bar_back_description), + tint = White + ) + } + }, + actions = actions, + colors = TopAppBarDefaults.centerAlignedTopAppBarColors().copy( + containerColor = Color.Transparent, + titleContentColor = White + ) + ) +} diff --git a/core/common/src/main/java/com/example/common/ui/MessageAlertDialog.kt b/core/common/src/main/java/com/example/common/ui/MessageAlertDialog.kt new file mode 100644 index 00000000..5f15c450 --- /dev/null +++ b/core/common/src/main/java/com/example/common/ui/MessageAlertDialog.kt @@ -0,0 +1,126 @@ +package com.example.common.ui + +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.RowScope +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.material3.BasicAlertDialog +import androidx.compose.material3.ButtonDefaults +import androidx.compose.material3.ExperimentalMaterial3Api +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.Surface +import androidx.compose.material3.Text +import androidx.compose.material3.TextButton +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.dp +import com.example.common.R +import com.example.common.ui.theme.Black +import com.example.common.ui.theme.MusicRoadTheme +import com.example.common.ui.theme.Primary +import com.example.common.ui.theme.White + +@OptIn(ExperimentalMaterial3Api::class) +@Composable +internal fun MessageAlertDialog( + onDismissRequest: () -> Unit, + title: String, + body: String, + buttons: @Composable RowScope.() -> Unit, +) { + BasicAlertDialog( + onDismissRequest = { onDismissRequest() }, + ) { + Surface( + modifier = Modifier.fillMaxWidth(), + shape = RoundedCornerShape(16.dp), + color = White + ) { + Column( + modifier = Modifier.padding(24.dp), + verticalArrangement = Arrangement.Center + ) { + Text( + text = title, + color = Black, + fontWeight = FontWeight.Bold, + style = MaterialTheme.typography.bodyLarge + ) + + VerticalSpacer(8) + + Text( + text = body, + color = Black, + style = MaterialTheme.typography.bodyLarge + ) + + VerticalSpacer(24) + + Row( + modifier = Modifier.fillMaxWidth(), + horizontalArrangement = Arrangement.End, + verticalAlignment = Alignment.CenterVertically, + content = buttons + ) + } + } + } +} + +@Composable +internal fun DialogTextButton( + onClick: () -> Unit, + text: String, + textColor: Color = Black, + buttonColor: Color = Color.Transparent, + fontWeight: FontWeight? = null, +) { + TextButton( + onClick = onClick, + colors = ButtonDefaults.buttonColors().copy( + containerColor = buttonColor, + contentColor = textColor + ) + ) { + Text( + text = text, + fontWeight = fontWeight, + ) + } +} + +@Preview(showBackground = true) +@Composable +private fun DeletePickDialogPreview() { + MusicRoadTheme { + MessageAlertDialog( + onDismissRequest = {}, + title = stringResource(R.string.delete_pick_dialog_title), + body = stringResource(R.string.delete_pick_dialog_body), + buttons = { + DialogTextButton( + onClick = {}, + text = "취소" + ) + + HorizontalSpacer(8) + + DialogTextButton( + onClick = {}, + text = "삭제하기", + textColor = Primary, + fontWeight = FontWeight.Bold + ) + } + ) + } +} diff --git a/core/common/src/main/java/com/example/common/ui/PickInfoText.kt b/core/common/src/main/java/com/example/common/ui/PickInfoText.kt new file mode 100644 index 00000000..6e165d67 --- /dev/null +++ b/core/common/src/main/java/com/example/common/ui/PickInfoText.kt @@ -0,0 +1,102 @@ +package com.example.common.ui + +import androidx.compose.material3.Icon +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.res.painterResource +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.text.SpanStyle +import androidx.compose.ui.text.TextStyle +import androidx.compose.ui.text.buildAnnotatedString +import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.text.style.TextOverflow +import androidx.compose.ui.text.withStyle +import com.example.common.R +import com.example.common.ui.theme.Primary + +@Composable +fun SongInfoText( + songInfo: String, + color: Color = MaterialTheme.colorScheme.onSurface +) { + Text( + text = songInfo, + color = color, + overflow = TextOverflow.Ellipsis, + maxLines = 1, + style = MaterialTheme.typography.titleMedium, + ) +} + +@Composable +fun FavoriteCountText( + favoriteCount: Int, + iconTint: Color = Primary, + color: Color = MaterialTheme.colorScheme.onSurface, + style: TextStyle = MaterialTheme.typography.bodyMedium +) { + Icon( + painter = painterResource(id = R.drawable.ic_favorite), + contentDescription = stringResource(R.string.map_info_window_favorite_count_icon_description), + tint = iconTint + ) + + Text( + text = " $favoriteCount", + color = color, + style = style, + ) +} + +@Composable +fun CommentText( + comment: String, + color: Color = MaterialTheme.colorScheme.onSecondary, + overflow: TextOverflow = TextOverflow.Ellipsis, + maxLines: Int = 1, + style: TextStyle = MaterialTheme.typography.bodyMedium +) { + Text( + text = comment, + color = color, + overflow = overflow, + maxLines = maxLines, + style = style, + ) +} + +@Composable +fun CountText( + totalCount: Int, + modifier: Modifier = Modifier, + countLabel: String = stringResource(R.string.total_count_text), + defaultColor: Color = MaterialTheme.colorScheme.onSurface, + pointColor: Color = MaterialTheme.colorScheme.primary, + style: TextStyle = MaterialTheme.typography.titleMedium +) { + Text( + text = buildAnnotatedString { + withStyle( + SpanStyle( + color = defaultColor, + fontWeight = FontWeight.Bold + ) + ) { + append("$countLabel ") + } + withStyle( + SpanStyle( + color = pointColor, + fontWeight = FontWeight.Bold + ) + ) { + append("$totalCount") + } + }, + modifier = modifier, + style = style + ) +} diff --git a/core/common/src/main/java/com/example/common/ui/Spacer.kt b/core/common/src/main/java/com/example/common/ui/Spacer.kt new file mode 100644 index 00000000..4baa7ecb --- /dev/null +++ b/core/common/src/main/java/com/example/common/ui/Spacer.kt @@ -0,0 +1,14 @@ +package com.example.common.ui + +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.width +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import androidx.compose.ui.unit.dp + +@Composable +fun VerticalSpacer(height: Int) = Spacer(Modifier.height(height.dp)) + +@Composable +fun HorizontalSpacer(width: Int) = Spacer(Modifier.width(width.dp)) diff --git a/core/common/src/main/java/com/example/common/ui/theme/Color.kt b/core/common/src/main/java/com/example/common/ui/theme/Color.kt new file mode 100644 index 00000000..d1beb5e1 --- /dev/null +++ b/core/common/src/main/java/com/example/common/ui/theme/Color.kt @@ -0,0 +1,21 @@ +package com.example.common.ui.theme + +import androidx.compose.ui.graphics.Color + +val Primary = Color(0xFFFF5F61) +val Primary80 = Color(0xFFFFB3B0) +val Primary50 = Color(0xFFAD625F) +val Primary20 = Color(0xFF571D1E) +val Blue = Color(0xFF6B84FF) + +val Purple = Color(0xFFBB8280) +val Purple15 = Color(0x26BB8280) +val PurpleGrey = Color(0xFFB4AEAE) + +val Black = Color(0xFF000000) +val Dark = Color(0xFF151515) +val DarkGray = Color(0xFF646464) +val Gray = Color(0xFFAAAAAA) +val White = Color(0xFFFFFFFF) + +val PlayerBackground = Color(0xFF353535) diff --git a/core/common/src/main/java/com/example/common/ui/theme/Theme.kt b/core/common/src/main/java/com/example/common/ui/theme/Theme.kt new file mode 100644 index 00000000..6d4909df --- /dev/null +++ b/core/common/src/main/java/com/example/common/ui/theme/Theme.kt @@ -0,0 +1,45 @@ +package com.example.common.ui.theme + +import androidx.compose.foundation.isSystemInDarkTheme +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.darkColorScheme +import androidx.compose.material3.lightColorScheme +import androidx.compose.runtime.Composable + +private val DarkColorScheme = darkColorScheme( + primary = Primary, + onPrimary = Black, + primaryContainer = White, + onPrimaryContainer = Black, + secondary = Blue, + tertiary = Purple, + surface = Black, + onSurface = White, + onSurfaceVariant = DarkGray, + onSecondary = Gray +) + +private val LightColorScheme = lightColorScheme( + primary = Primary, + onPrimary = White, + primaryContainer = Dark, + onPrimaryContainer = White, + secondary = Blue, + tertiary = Purple, + surface = White, + onSurface = Black, + onSurfaceVariant = Gray, + onSecondary = DarkGray +) + +@Composable +fun MusicRoadTheme( + darkTheme: Boolean = isSystemInDarkTheme(), + content: @Composable () -> Unit +) { + MaterialTheme( + colorScheme = if (darkTheme) DarkColorScheme else LightColorScheme, + typography = Typography, + content = content + ) +} diff --git a/core/common/src/main/java/com/example/common/ui/theme/Type.kt b/core/common/src/main/java/com/example/common/ui/theme/Type.kt new file mode 100644 index 00000000..c5aebc5b --- /dev/null +++ b/core/common/src/main/java/com/example/common/ui/theme/Type.kt @@ -0,0 +1,34 @@ +package com.example.common.ui.theme + +import androidx.compose.material3.Typography +import androidx.compose.ui.text.TextStyle +import androidx.compose.ui.text.font.FontFamily +import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.unit.sp + +// Set of Material typography styles to start with +val Typography = Typography( + bodyLarge = TextStyle( + fontFamily = FontFamily.Default, + fontWeight = FontWeight.Normal, + fontSize = 16.sp, + lineHeight = 24.sp, + letterSpacing = 0.5.sp + ) + /* Other default text styles to override + titleLarge = TextStyle( + fontFamily = FontFamily.Default, + fontWeight = FontWeight.Normal, + fontSize = 22.sp, + lineHeight = 28.sp, + letterSpacing = 0.sp + ), + labelSmall = TextStyle( + fontFamily = FontFamily.Default, + fontWeight = FontWeight.Medium, + fontSize = 11.sp, + lineHeight = 16.sp, + letterSpacing = 0.5.sp + ) + */ +) diff --git a/core/common/src/main/res/drawable/ic_favorite.xml b/core/common/src/main/res/drawable/ic_favorite.xml new file mode 100644 index 00000000..67b2268e --- /dev/null +++ b/core/common/src/main/res/drawable/ic_favorite.xml @@ -0,0 +1,10 @@ + + + + \ No newline at end of file diff --git a/core/common/src/main/res/values/strings.xml b/core/common/src/main/res/values/strings.xml new file mode 100644 index 00000000..12775013 --- /dev/null +++ b/core/common/src/main/res/values/strings.xml @@ -0,0 +1,15 @@ + + 앨범 이미지 + + 내가 + 등록한 픽 + 님의 픽 + + 상단 바 뒤로 가기 버튼 + + 삭제하시겠습니까? + 등록하신 픽이 삭제됩니다. + + 픽을 담은 개수 + 전체 + diff --git a/core/common/src/test/java/com/example/common/ExampleUnitTest.kt b/core/common/src/test/java/com/example/common/ExampleUnitTest.kt new file mode 100644 index 00000000..a55853e0 --- /dev/null +++ b/core/common/src/test/java/com/example/common/ExampleUnitTest.kt @@ -0,0 +1,17 @@ +package com.example.common + +import org.junit.Test + +import org.junit.Assert.* + +/** + * Example local unit test, which will execute on the development machine (host). + * + * See [testing documentation](http://d.android.com/tools/testing). + */ +class ExampleUnitTest { + @Test + fun addition_isCorrect() { + assertEquals(4, 2 + 2) + } +} diff --git a/core/mediaservice/.gitignore b/core/mediaservice/.gitignore new file mode 100644 index 00000000..42afabfd --- /dev/null +++ b/core/mediaservice/.gitignore @@ -0,0 +1 @@ +/build \ No newline at end of file diff --git a/core/mediaservice/build.gradle.kts b/core/mediaservice/build.gradle.kts new file mode 100644 index 00000000..808de15f --- /dev/null +++ b/core/mediaservice/build.gradle.kts @@ -0,0 +1,51 @@ +plugins { + alias(libs.plugins.android.library) + alias(libs.plugins.jetbrains.kotlin.android) + alias(libs.plugins.ksp) + alias(libs.plugins.hilt) +} + +android { + namespace = "com.example.mediaservice" + compileSdk = 34 + + defaultConfig { + minSdk = 26 + + testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner" + consumerProguardFiles("consumer-rules.pro") + } + + buildTypes { + release { + isMinifyEnabled = false + proguardFiles(getDefaultProguardFile("proguard-android-optimize.txt"), "proguard-rules.pro") + } + } + compileOptions { + sourceCompatibility = JavaVersion.VERSION_1_8 + targetCompatibility = JavaVersion.VERSION_1_8 + } + kotlinOptions { + jvmTarget = "1.8" + } +} + +dependencies { + implementation(libs.androidx.core.ktx) + implementation(libs.androidx.appcompat) + implementation(libs.material) + testImplementation(libs.junit) + androidTestImplementation(libs.androidx.junit) + androidTestImplementation(libs.androidx.espresso.core) + + // Hilt + implementation(libs.hilt.android) + ksp(libs.hilt.android.compiler) + implementation(libs.inject) + + // ExoPlayer + implementation(libs.androidx.media3.exoplayer) + implementation(libs.androidx.media3.exoplayer.dash) + implementation(libs.androidx.media3.session) +} diff --git a/core/mediaservice/consumer-rules.pro b/core/mediaservice/consumer-rules.pro new file mode 100644 index 00000000..e69de29b diff --git a/core/mediaservice/proguard-rules.pro b/core/mediaservice/proguard-rules.pro new file mode 100644 index 00000000..481bb434 --- /dev/null +++ b/core/mediaservice/proguard-rules.pro @@ -0,0 +1,21 @@ +# Add project specific ProGuard rules here. +# You can control the set of applied configuration files using the +# proguardFiles setting in build.gradle. +# +# For more details, see +# http://developer.android.com/guide/developing/tools/proguard.html + +# If your project uses WebView with JS, uncomment the following +# and specify the fully qualified class name to the JavaScript interface +# class: +#-keepclassmembers class fqcn.of.javascript.interface.for.webview { +# public *; +#} + +# Uncomment this to preserve the line number information for +# debugging stack traces. +#-keepattributes SourceFile,LineNumberTable + +# If you keep the line number information, uncomment this to +# hide the original source file name. +#-renamesourcefileattribute SourceFile \ No newline at end of file diff --git a/core/mediaservice/src/androidTest/java/com/example/mediaservice/ExampleInstrumentedTest.kt b/core/mediaservice/src/androidTest/java/com/example/mediaservice/ExampleInstrumentedTest.kt new file mode 100644 index 00000000..d7dfa445 --- /dev/null +++ b/core/mediaservice/src/androidTest/java/com/example/mediaservice/ExampleInstrumentedTest.kt @@ -0,0 +1,24 @@ +package com.example.mediaservice + +import androidx.test.platform.app.InstrumentationRegistry +import androidx.test.ext.junit.runners.AndroidJUnit4 + +import org.junit.Test +import org.junit.runner.RunWith + +import org.junit.Assert.* + +/** + * Instrumented test, which will execute on an Android device. + * + * See [testing documentation](http://d.android.com/tools/testing). + */ +@RunWith(AndroidJUnit4::class) +class ExampleInstrumentedTest { + @Test + fun useAppContext() { + // Context of the app under test. + val appContext = InstrumentationRegistry.getInstrumentation().targetContext + assertEquals("com.example.mediaservice.test", appContext.packageName) + } +} diff --git a/core/mediaservice/src/main/AndroidManifest.xml b/core/mediaservice/src/main/AndroidManifest.xml new file mode 100644 index 00000000..8bdb7e14 --- /dev/null +++ b/core/mediaservice/src/main/AndroidManifest.xml @@ -0,0 +1,4 @@ + + + + diff --git a/core/mediaservice/src/main/java/com/example/mediaservice/CustomMediaSessionCallback.kt b/core/mediaservice/src/main/java/com/example/mediaservice/CustomMediaSessionCallback.kt new file mode 100644 index 00000000..ddf184f3 --- /dev/null +++ b/core/mediaservice/src/main/java/com/example/mediaservice/CustomMediaSessionCallback.kt @@ -0,0 +1,96 @@ +package com.example.mediaservice + +import android.os.Build +import android.os.Bundle +import androidx.annotation.OptIn +import androidx.media3.common.util.UnstableApi +import androidx.media3.session.CommandButton +import androidx.media3.session.MediaSession +import androidx.media3.session.SessionCommand +import androidx.media3.session.SessionResult +import com.google.common.util.concurrent.Futures +import com.google.common.util.concurrent.ListenableFuture + +const val SEEK_TO_DURATION = 5_000L + +@OptIn(UnstableApi::class) +internal class CustomMediaSessionCallback : MediaSession.Callback { + + /* MediaSession이 MediaController의 연결 요청을 수락할 때 사용할 수 있는 명령어 집합을 구성하고 반환 */ + override fun onConnect( + session: MediaSession, + controller: MediaSession.ControllerInfo + ): MediaSession.ConnectionResult = + + // 컨트롤러가 미디어 알림과 연관된 컨트롤러인지 확인 + if (session.isMediaNotificationController(controller)) { + val connectionResult = super.onConnect(session, controller) + val availableSessionCommands = connectionResult.availableSessionCommands.buildUpon() + var customCommands = PlayerCommands.entries.toList() + + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE) { + // 안드로이드 14 (API 34) 이상 + customCommands = customCommands.reversed() + } + + customCommands.forEach { commandButton -> + commandButton.sessionCommand.let { + it + availableSessionCommands.add(it) + } + } + + MediaSession.ConnectionResult.AcceptedResultBuilder(session) + .setAvailableSessionCommands(availableSessionCommands.build()) + .setCustomLayout( + customCommands + .filter { + it.customAction == PlayerCommands.SEEK_REWIND.customAction + || it.customAction == PlayerCommands.SEEK_FORWARD.customAction + }.map { + CommandButton.Builder() + .setDisplayName(it.displayName) + .setIconResId(it.iconResId(session.player.isPlaying)) + .setSessionCommand(it.sessionCommand) + .build() + } + ) + .build() + } else { + MediaSession.ConnectionResult.AcceptedResultBuilder(session).build() + } + + // 사용자 정의 명령이 수신되었을 때 호출되는 콜백 + override fun onCustomCommand( + session: MediaSession, + controller: MediaSession.ControllerInfo, + customCommand: SessionCommand, + args: Bundle + ): ListenableFuture { + + when (customCommand.customAction) { + PlayerCommands.SEEK_REWIND.customAction -> { + session.player.run { + seekTo(currentPosition - SEEK_TO_DURATION) + } + } + + PlayerCommands.PLAY_AND_PAUSE.customAction -> { + if (!session.player.isPlaying) session.player.play() + else session.player.pause() + } + + PlayerCommands.SEEK_FORWARD.customAction -> { + session.player.run { + seekTo(currentPosition + SEEK_TO_DURATION) + } + } + + else -> { + // Do nothing + } + } + + return Futures.immediateFuture(SessionResult(SessionResult.RESULT_SUCCESS)) + } +} diff --git a/core/mediaservice/src/main/java/com/example/mediaservice/MediaControllerProvider.kt b/core/mediaservice/src/main/java/com/example/mediaservice/MediaControllerProvider.kt new file mode 100644 index 00000000..647294ee --- /dev/null +++ b/core/mediaservice/src/main/java/com/example/mediaservice/MediaControllerProvider.kt @@ -0,0 +1,58 @@ +package com.example.mediaservice + +import androidx.media3.common.util.UnstableApi +import androidx.media3.session.MediaController +import com.google.common.util.concurrent.FutureCallback +import com.google.common.util.concurrent.Futures +import com.google.common.util.concurrent.ListenableFuture +import com.google.common.util.concurrent.MoreExecutors +import kotlinx.coroutines.cancel +import kotlinx.coroutines.channels.awaitClose +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.callbackFlow +import javax.inject.Inject +import javax.inject.Singleton +import kotlin.coroutines.cancellation.CancellationException + +interface MediaControllerProvider { + val mediaControllerFlow: Flow + val audioSessionFlow: Flow +} + +/* mediaControllerFuture: MediaController를 비동기적으로 제공하는 ListenableFuture */ +@UnstableApi +@Singleton +class MediaControllerProviderImpl @Inject constructor( + private val audioSessionId: Int, + mediaControllerFuture: ListenableFuture +) : MediaControllerProvider { + + /* mediaControllerFlow가 처음 구독될 때 callbackFlow가 실행 */ + override val mediaControllerFlow: Flow = callbackFlow { + /* Futures.addCallback을 사용하여 mediaControllerFuture의 결과를 기다림 */ + Futures.addCallback( + mediaControllerFuture, + object : FutureCallback { + + /* MediaController 객체가 준비되면 trySend(result)를 통해 Flow로 결과를 전송 + 즉, mediaControllerFlow를 구독하고 있는 곳에 MediaController 객체가 전달 */ + override fun onSuccess(result: MediaController) { + result.setPlaybackSpeed(1.0f) + trySend(result) + } + + override fun onFailure(t: Throwable) { + cancel(CancellationException(t.message)) + } + }, + MoreExecutors.directExecutor() + ) + + awaitClose { } + } + + override val audioSessionFlow = callbackFlow { + trySend(audioSessionId) + awaitClose { } + } +} diff --git a/core/mediaservice/src/main/java/com/example/mediaservice/MediaNotificationProvider.kt b/core/mediaservice/src/main/java/com/example/mediaservice/MediaNotificationProvider.kt new file mode 100644 index 00000000..e6159137 --- /dev/null +++ b/core/mediaservice/src/main/java/com/example/mediaservice/MediaNotificationProvider.kt @@ -0,0 +1,120 @@ +package com.example.mediaservice + +import android.annotation.SuppressLint +import android.app.NotificationChannel +import android.app.NotificationManager +import android.app.PendingIntent +import android.content.ComponentName +import android.content.Context +import android.content.Intent +import android.os.Build +import androidx.annotation.OptIn +import androidx.core.app.NotificationCompat +import androidx.core.graphics.drawable.IconCompat +import androidx.media3.common.Player +import androidx.media3.common.util.UnstableApi +import androidx.media3.session.MediaNotification +import androidx.media3.session.MediaSession +import androidx.media3.session.MediaStyleNotificationHelper +import javax.inject.Inject + +interface MediaNotificationProvider { + + @OptIn(UnstableApi::class) + fun createMediaNotification(actionFactory: MediaNotification.ActionFactory): MediaNotification +} + +class MediaNotificationProviderImpl @Inject constructor( + private val context: Context, + private val mediaSession: MediaSession +) : MediaNotificationProvider { + + @OptIn(UnstableApi::class) + override fun createMediaNotification(actionFactory: MediaNotification.ActionFactory): MediaNotification { + val notificationManager: NotificationManager = + context.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager + + makeNotificationChannel(notificationManager) + + val mediaItem = mediaSession.player.currentMediaItem + + val notificationBuilder = NotificationCompat.Builder( + context, + NOTIFICATION_CHANNEL_ID.toString() + ).apply { + priority = NotificationCompat.PRIORITY_DEFAULT + setSilent(true) + setSmallIcon(R.drawable.ic_musicroad_foreground) + setContentTitle(mediaItem?.mediaMetadata?.title) + setContentIntent(createNotifyPendingIntent()) + setDeleteIntent( + actionFactory.createMediaActionPendingIntent( + mediaSession, + Player.COMMAND_STOP.toLong() + ) + ) + setStyle( + MediaStyleNotificationHelper + .MediaStyle(mediaSession) + ) + setOngoing(true) + + PlayerCommands.entries.forEach { commandButton -> + addAction( + actionFactory.createCustomAction( + mediaSession, + IconCompat.createWithResource( + context, + commandButton.iconResId(mediaSession.player.isPlaying) + ), + commandButton.displayName, + commandButton.customAction, + commandButton.sessionCommand.customExtras + ) + ) + } + } + + return MediaNotification( + NOTIFICATION_CHANNEL_ID, + notificationBuilder.build() + ) + } + + /* 알림 누르면 이동할 intent 설정 */ + private fun createNotifyPendingIntent(): PendingIntent = + PendingIntent.getActivity( + context, + 0, + Intent().apply { + action = Intent.ACTION_VIEW + component = ComponentName(context, TARGET_ACTIVITY) + flags = Intent.FLAG_ACTIVITY_CLEAR_TOP or Intent.FLAG_ACTIVITY_SINGLE_TOP + }, + PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE + ) + + /* Notification Channel 생성 */ + @SuppressLint("ObsoleteSdkInt") + private fun makeNotificationChannel(notificationManager: NotificationManager) { + // Android 8.0 미만 버전에서는 Notification Channel을 생성할 필요가 없음 -> 앱 단일 Channel 가짐 + if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O || + notificationManager.getNotificationChannel(NOTIFICATION_CHANNEL_ID.toString()) != null + ) { + return + } + + val channel = NotificationChannel( + NOTIFICATION_CHANNEL_ID.toString(), + "MediaPlayer", + NotificationManager.IMPORTANCE_DEFAULT + ) + + notificationManager.createNotificationChannel(channel) + } + + companion object { + const val NOTIFICATION_CHANNEL_ID = 100 + const val TARGET_ACTIVITY = "com.squirtles.musicroad.main.MainActivity" + } +} diff --git a/core/mediaservice/src/main/java/com/example/mediaservice/MediaPlayerService.kt b/core/mediaservice/src/main/java/com/example/mediaservice/MediaPlayerService.kt new file mode 100644 index 00000000..4dff67ca --- /dev/null +++ b/core/mediaservice/src/main/java/com/example/mediaservice/MediaPlayerService.kt @@ -0,0 +1,62 @@ +package com.example.mediaservice + +import android.content.Intent +import android.os.Bundle +import androidx.annotation.OptIn +import androidx.media3.common.Player +import androidx.media3.common.util.UnstableApi +import androidx.media3.session.CommandButton +import androidx.media3.session.MediaNotification +import androidx.media3.session.MediaSession +import androidx.media3.session.MediaSessionService +import com.google.common.collect.ImmutableList +import dagger.hilt.android.AndroidEntryPoint +import javax.inject.Inject + +@OptIn(UnstableApi::class) +@AndroidEntryPoint +class MediaPlayerService : MediaSessionService() { + @Inject + lateinit var mediaNotificationProvider: MediaNotificationProvider + + @Inject + lateinit var mediaSession: MediaSession + + override fun onCreate() { + super.onCreate() + + setMediaNotificationProvider(object : MediaNotification.Provider { + override fun createNotification( + mediaSession: MediaSession, + customLayout: ImmutableList, + actionFactory: MediaNotification.ActionFactory, + onNotificationChangedCallback: MediaNotification.Provider.Callback + ): MediaNotification = + mediaNotificationProvider.createMediaNotification(actionFactory) + + override fun handleCustomCommand(session: MediaSession, action: String, extras: Bundle): Boolean = false + }) + } + + // The user dismissed the app from the recent tasks + override fun onTaskRemoved(rootIntent: Intent?) { + val player = mediaSession.player + if (!player.playWhenReady + || player.mediaItemCount == 0 + || player.playbackState == Player.STATE_ENDED + ) { + // stopSelf() + } + } + + override fun onGetSession(controllerInfo: MediaSession.ControllerInfo): MediaSession = mediaSession + + // Remember to release the player and media session in onDestroy + override fun onDestroy() { + mediaSession.run { + player.release() + release() + } + super.onDestroy() + } +} diff --git a/core/mediaservice/src/main/java/com/example/mediaservice/PlayerCommands.kt b/core/mediaservice/src/main/java/com/example/mediaservice/PlayerCommands.kt new file mode 100644 index 00000000..705e1923 --- /dev/null +++ b/core/mediaservice/src/main/java/com/example/mediaservice/PlayerCommands.kt @@ -0,0 +1,40 @@ +package com.example.mediaservice + +import android.os.Bundle +import androidx.media3.session.SessionCommand + +private const val ACTION_SEEK_FORWARD = "action_seek_forward" +private const val ACTION_SEEK_REWIND = "action_seek_rewind" +private const val ACTION_PLAY_AND_PAUSE = "action_play_and_pause" + +enum class PlayerCommands( + val customAction: String, + val displayName: String, + val iconResId: (Boolean) -> Int, + val sessionCommand: SessionCommand, +) { + SEEK_REWIND( + customAction = ACTION_SEEK_REWIND, + displayName = "SeekRewind", + iconResId = { androidx.media3.session.R.drawable.media3_icon_skip_back_5 }, + sessionCommand = SessionCommand(ACTION_SEEK_REWIND, Bundle.EMPTY) + ), + PLAY_AND_PAUSE( + customAction = ACTION_PLAY_AND_PAUSE, + displayName = "PlayPause", + iconResId = { isPlaying -> + if (isPlaying) { + androidx.media3.session.R.drawable.media3_icon_pause + } else { + androidx.media3.session.R.drawable.media3_icon_play + } + }, + sessionCommand = SessionCommand(ACTION_PLAY_AND_PAUSE, Bundle.EMPTY) + ), + SEEK_FORWARD( + customAction = ACTION_SEEK_FORWARD, + displayName = "SeekForward", + iconResId = { androidx.media3.session.R.drawable.media3_icon_skip_forward_5 }, + sessionCommand = SessionCommand(ACTION_SEEK_FORWARD, Bundle.EMPTY) + ), +} diff --git a/core/mediaservice/src/main/java/com/example/mediaservice/di/MediaDiModule.kt b/core/mediaservice/src/main/java/com/example/mediaservice/di/MediaDiModule.kt new file mode 100644 index 00000000..fc4799cb --- /dev/null +++ b/core/mediaservice/src/main/java/com/example/mediaservice/di/MediaDiModule.kt @@ -0,0 +1,101 @@ +package com.example.mediaservice.di + +import android.content.ComponentName +import android.content.Context +import androidx.annotation.OptIn +import androidx.media3.common.AudioAttributes +import androidx.media3.common.util.UnstableApi +import androidx.media3.exoplayer.ExoPlayer +import androidx.media3.session.MediaController +import androidx.media3.session.MediaSession +import androidx.media3.session.SessionToken +import com.example.mediaservice.CustomMediaSessionCallback +import com.example.mediaservice.MediaControllerProvider +import com.example.mediaservice.MediaControllerProviderImpl +import com.example.mediaservice.MediaNotificationProvider +import com.example.mediaservice.MediaNotificationProviderImpl +import com.example.mediaservice.MediaPlayerService +import com.google.common.util.concurrent.ListenableFuture +import dagger.Binds +import dagger.Module +import dagger.Provides +import dagger.hilt.InstallIn +import dagger.hilt.android.qualifiers.ApplicationContext +import dagger.hilt.components.SingletonComponent +import javax.inject.Singleton + + +@Module +@InstallIn(SingletonComponent::class) +abstract class MediaServiceBinds { + + @Binds + abstract fun bindsMediaNotificationProvider( + mediaNotification: MediaNotificationProviderImpl + ): MediaNotificationProvider + + @OptIn(UnstableApi::class) + @Binds + abstract fun bindsMediaControllerProvider( + mediaControllerProvider: MediaControllerProviderImpl + ): MediaControllerProvider +} + +@Module +@InstallIn(SingletonComponent::class) +object MediaServiceModule { + + @Singleton + @Provides + fun providesExoPlayer( + @ApplicationContext context: Context, + ): ExoPlayer = + ExoPlayer.Builder(context) + .setAudioAttributes(AudioAttributes.DEFAULT, true) + .build() + + @OptIn(UnstableApi::class) + @Singleton + @Provides + fun provideAudioSessionId( + exoPlayer: ExoPlayer + ): Int = exoPlayer.audioSessionId + + @Singleton + @Provides + fun providesMediaSession( + @ApplicationContext context: Context, + player: ExoPlayer, + ): MediaSession = + MediaSession.Builder(context, player) + .setCallback(CustomMediaSessionCallback()) + .build() + + @Singleton + @Provides + fun providesMediaNotificationManager( + @ApplicationContext context: Context, + mediaSession: MediaSession, + ): MediaNotificationProviderImpl = + MediaNotificationProviderImpl(context, mediaSession) + + @Singleton + @Provides + fun providesSessionToken( + @ApplicationContext context: Context + ): SessionToken { + val sessionToken = SessionToken(context, ComponentName(context, MediaPlayerService::class.java)) + return sessionToken + } + + + @Singleton + @Provides + fun providesListenableFutureMediaController( + @ApplicationContext context: Context, + sessionToken: SessionToken + ): ListenableFuture = + MediaController + .Builder(context, sessionToken) + .buildAsync() +} diff --git a/core/mediaservice/src/main/res/drawable/ic_musicroad_foreground.xml b/core/mediaservice/src/main/res/drawable/ic_musicroad_foreground.xml new file mode 100644 index 00000000..ba25c130 --- /dev/null +++ b/core/mediaservice/src/main/res/drawable/ic_musicroad_foreground.xml @@ -0,0 +1,56 @@ + + + + + + + + + + + + + + + + + diff --git a/core/mediaservice/src/test/java/com/example/mediaservice/ExampleUnitTest.kt b/core/mediaservice/src/test/java/com/example/mediaservice/ExampleUnitTest.kt new file mode 100644 index 00000000..e9395953 --- /dev/null +++ b/core/mediaservice/src/test/java/com/example/mediaservice/ExampleUnitTest.kt @@ -0,0 +1,17 @@ +package com.example.mediaservice + +import org.junit.Test + +import org.junit.Assert.* + +/** + * Example local unit test, which will execute on the development machine (host). + * + * See [testing documentation](http://d.android.com/tools/testing). + */ +class ExampleUnitTest { + @Test + fun addition_isCorrect() { + assertEquals(4, 2 + 2) + } +} diff --git a/core/model/.gitignore b/core/model/.gitignore new file mode 100644 index 00000000..42afabfd --- /dev/null +++ b/core/model/.gitignore @@ -0,0 +1 @@ +/build \ No newline at end of file diff --git a/core/model/build.gradle.kts b/core/model/build.gradle.kts new file mode 100644 index 00000000..47e6f1ab --- /dev/null +++ b/core/model/build.gradle.kts @@ -0,0 +1,19 @@ +plugins { + id("java-library") + alias(libs.plugins.jetbrains.kotlin.jvm) + alias(libs.plugins.kotlin.serialization) +} + +java { + sourceCompatibility = JavaVersion.VERSION_1_8 + targetCompatibility = JavaVersion.VERSION_1_8 +} + +kotlin { + jvmToolchain(8) +} + +dependencies { + // Serialization + implementation(libs.kotlinx.serialization.json) +} diff --git a/core/model/src/main/java/com/example/model/MusicVideo.kt b/core/model/src/main/java/com/example/model/MusicVideo.kt new file mode 100644 index 00000000..3f327200 --- /dev/null +++ b/core/model/src/main/java/com/example/model/MusicVideo.kt @@ -0,0 +1,13 @@ +package com.example.model + +import java.time.LocalDate + +data class MusicVideo( + val id: String, + val songName: String, + val artistName: String, + val albumName: String, + val releaseDate: LocalDate, + val previewUrl: String, + val thumbnailUrl: String +) diff --git a/core/model/src/main/java/com/example/model/Order.kt b/core/model/src/main/java/com/example/model/Order.kt new file mode 100644 index 00000000..bcf7dd21 --- /dev/null +++ b/core/model/src/main/java/com/example/model/Order.kt @@ -0,0 +1,7 @@ +package com.example.model + +enum class Order { + LATEST, + OLDEST, + FAVORITE_DESC, +} diff --git a/core/model/src/main/java/com/example/model/Pick.kt b/core/model/src/main/java/com/example/model/Pick.kt new file mode 100644 index 00000000..4c9aa335 --- /dev/null +++ b/core/model/src/main/java/com/example/model/Pick.kt @@ -0,0 +1,28 @@ +package com.example.model + +/** + * 앱에서 사용하기 위한 Pick 정보 데이터클래스 + */ +data class Pick( + val id: String, + val song: Song, + val comment: String, + val favoriteCount: Int = 0, + val createdBy: Creator, + val createdAt: String, + val location: LocationPoint, + val musicVideoUrl: String = "", + val musicVideoThumbnailUrl: String = "" +) + +data class LocationPoint( + val latitude: Double, + val longitude: Double +) { + /* TODO: Location 변환 함수 */ +} + +data class Creator( + val userId: String, + val userName: String, +) diff --git a/core/model/src/main/java/com/example/model/PlayerState.kt b/core/model/src/main/java/com/example/model/PlayerState.kt new file mode 100644 index 00000000..e40b2e2b --- /dev/null +++ b/core/model/src/main/java/com/example/model/PlayerState.kt @@ -0,0 +1,11 @@ +package com.example.model + +data class PlayerState( + val id: String = "", + val isLoading: Boolean = false, + val isPlaying: Boolean = false, + val hasNext: Boolean = false, + val currentPosition: Long = 0L, + val duration: Long = 30_000L, + val bufferPercentage: Int = 0, +) diff --git a/core/model/src/main/java/com/example/model/Song.kt b/core/model/src/main/java/com/example/model/Song.kt new file mode 100644 index 00000000..ceb1c1c0 --- /dev/null +++ b/core/model/src/main/java/com/example/model/Song.kt @@ -0,0 +1,25 @@ +package com.example.model + +import kotlinx.serialization.Serializable + +/** + * 애플뮤직에서 불러온 노래 정보를 비즈니스 로직에서 사용하기 위해 변환한 클래스 + */ +@Serializable +data class Song( + val id: String, + val songName: String, + val artistName: String, + val albumName: String, + val imageUrl: String, + val genreNames: List, + val bgColor: Int, + val externalUrl: String, + val previewUrl: String, +) { + fun getImageUrlWithSize(width: Int, height: Int): String? { + return if (imageUrl.isEmpty()) null + else imageUrl.replace("{w}", width.toString()) + .replace("{h}", height.toString()) + } +} diff --git a/core/model/src/main/java/com/example/model/User.kt b/core/model/src/main/java/com/example/model/User.kt new file mode 100644 index 00000000..ca29f4f3 --- /dev/null +++ b/core/model/src/main/java/com/example/model/User.kt @@ -0,0 +1,7 @@ +package com.example.model + +data class User( + val userId: String, + val userName: String, + val myPicks: List +) diff --git a/core/navigation/.gitignore b/core/navigation/.gitignore new file mode 100644 index 00000000..42afabfd --- /dev/null +++ b/core/navigation/.gitignore @@ -0,0 +1 @@ +/build \ No newline at end of file diff --git a/core/navigation/build.gradle.kts b/core/navigation/build.gradle.kts new file mode 100644 index 00000000..38061110 --- /dev/null +++ b/core/navigation/build.gradle.kts @@ -0,0 +1,17 @@ +plugins { + id("java-library") + alias(libs.plugins.jetbrains.kotlin.jvm) + alias(libs.plugins.kotlin.serialization) +} + +java { + sourceCompatibility = JavaVersion.VERSION_21 + targetCompatibility = JavaVersion.VERSION_21 +} + +dependencies { + implementation(projects.core.model) + + // Serialization + implementation(libs.kotlinx.serialization.json) +} diff --git a/core/navigation/src/main/java/com/example/navigation/MainRoute.kt b/core/navigation/src/main/java/com/example/navigation/MainRoute.kt new file mode 100644 index 00000000..377459d6 --- /dev/null +++ b/core/navigation/src/main/java/com/example/navigation/MainRoute.kt @@ -0,0 +1,15 @@ +package com.example.navigation + +import kotlinx.serialization.Serializable + +@Serializable +sealed interface MainRoute : Route { + @Serializable + data object Search : MainRoute + + @Serializable + data class Favorite(val userId: String) : MainRoute + + @Serializable + data class Profile(val userId: String) : MainRoute +} diff --git a/core/navigation/src/main/java/com/example/navigation/MapRoute.kt b/core/navigation/src/main/java/com/example/navigation/MapRoute.kt new file mode 100644 index 00000000..efddc0a4 --- /dev/null +++ b/core/navigation/src/main/java/com/example/navigation/MapRoute.kt @@ -0,0 +1,9 @@ +package com.example.navigation + +import kotlinx.serialization.Serializable + +@Serializable +sealed interface MapRoute : Route { + @Serializable + data class PickDetail(val pickId: String) : MapRoute +} diff --git a/core/navigation/src/main/java/com/example/navigation/ProfileRoute.kt b/core/navigation/src/main/java/com/example/navigation/ProfileRoute.kt new file mode 100644 index 00000000..44621d44 --- /dev/null +++ b/core/navigation/src/main/java/com/example/navigation/ProfileRoute.kt @@ -0,0 +1,16 @@ +package com.example.navigation + +import kotlinx.serialization.Serializable + +@Serializable +sealed interface ProfileRoute : Route { + @Serializable + data class MyPicks(val userId: String) : ProfileRoute + + @Serializable + data object Setting : ProfileRoute + + @Serializable + data object Notification : ProfileRoute +} + diff --git a/core/navigation/src/main/java/com/example/navigation/Route.kt b/core/navigation/src/main/java/com/example/navigation/Route.kt new file mode 100644 index 00000000..45366dff --- /dev/null +++ b/core/navigation/src/main/java/com/example/navigation/Route.kt @@ -0,0 +1,9 @@ +package com.example.navigation + +import kotlinx.serialization.Serializable + +@Serializable +sealed interface Route { + @Serializable + data object Map : Route +} diff --git a/core/navigation/src/main/java/com/example/navigation/SearchRoute.kt b/core/navigation/src/main/java/com/example/navigation/SearchRoute.kt new file mode 100644 index 00000000..a03dec38 --- /dev/null +++ b/core/navigation/src/main/java/com/example/navigation/SearchRoute.kt @@ -0,0 +1,11 @@ +package com.example.navigation + +import com.example.model.Song +import kotlinx.serialization.Serializable +import kotlin.reflect.typeOf + +@Serializable +sealed interface SearchRoute : Route { + @Serializable + data class Create(val song: Song) : SearchRoute +} diff --git a/core/picklist/.gitignore b/core/picklist/.gitignore new file mode 100644 index 00000000..42afabfd --- /dev/null +++ b/core/picklist/.gitignore @@ -0,0 +1 @@ +/build \ No newline at end of file diff --git a/core/picklist/build.gradle.kts b/core/picklist/build.gradle.kts new file mode 100644 index 00000000..49319587 --- /dev/null +++ b/core/picklist/build.gradle.kts @@ -0,0 +1,60 @@ +plugins { + alias(libs.plugins.android.library) + alias(libs.plugins.jetbrains.kotlin.android) +} + +android { + namespace = "com.example.picklist" + compileSdk = 34 + + buildFeatures { + compose = true + } + + composeOptions { + kotlinCompilerExtensionVersion = "1.5.10" + } + + defaultConfig { + minSdk = 26 + + testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner" + consumerProguardFiles("consumer-rules.pro") + } + + buildTypes { + release { + isMinifyEnabled = false + proguardFiles(getDefaultProguardFile("proguard-android-optimize.txt"), "proguard-rules.pro") + } + } + compileOptions { + sourceCompatibility = JavaVersion.VERSION_1_8 + targetCompatibility = JavaVersion.VERSION_1_8 + } + kotlinOptions { + jvmTarget = "1.8" + } +} + +dependencies { + implementation(projects.core.model) + implementation(projects.core.common) + + implementation(libs.androidx.core.ktx) + implementation(libs.androidx.appcompat) + implementation(libs.material) + testImplementation(libs.junit) + androidTestImplementation(libs.androidx.junit) + androidTestImplementation(libs.androidx.espresso.core) + + // Compose + implementation(platform(libs.androidx.compose.bom)) + implementation(libs.androidx.compose.material) + implementation(libs.androidx.compose.material3) + implementation(libs.androidx.material.icons.extended) + implementation(libs.androidx.ui.tooling.preview) + + // Coil + implementation(libs.coil.compose) +} diff --git a/core/picklist/consumer-rules.pro b/core/picklist/consumer-rules.pro new file mode 100644 index 00000000..e69de29b diff --git a/core/picklist/proguard-rules.pro b/core/picklist/proguard-rules.pro new file mode 100644 index 00000000..481bb434 --- /dev/null +++ b/core/picklist/proguard-rules.pro @@ -0,0 +1,21 @@ +# Add project specific ProGuard rules here. +# You can control the set of applied configuration files using the +# proguardFiles setting in build.gradle. +# +# For more details, see +# http://developer.android.com/guide/developing/tools/proguard.html + +# If your project uses WebView with JS, uncomment the following +# and specify the fully qualified class name to the JavaScript interface +# class: +#-keepclassmembers class fqcn.of.javascript.interface.for.webview { +# public *; +#} + +# Uncomment this to preserve the line number information for +# debugging stack traces. +#-keepattributes SourceFile,LineNumberTable + +# If you keep the line number information, uncomment this to +# hide the original source file name. +#-renamesourcefileattribute SourceFile \ No newline at end of file diff --git a/core/picklist/src/androidTest/java/com/example/picklist/ExampleInstrumentedTest.kt b/core/picklist/src/androidTest/java/com/example/picklist/ExampleInstrumentedTest.kt new file mode 100644 index 00000000..789902a4 --- /dev/null +++ b/core/picklist/src/androidTest/java/com/example/picklist/ExampleInstrumentedTest.kt @@ -0,0 +1,24 @@ +package com.example.picklist + +import androidx.test.platform.app.InstrumentationRegistry +import androidx.test.ext.junit.runners.AndroidJUnit4 + +import org.junit.Test +import org.junit.runner.RunWith + +import org.junit.Assert.* + +/** + * Instrumented test, which will execute on an Android device. + * + * See [testing documentation](http://d.android.com/tools/testing). + */ +@RunWith(AndroidJUnit4::class) +class ExampleInstrumentedTest { + @Test + fun useAppContext() { + // Context of the app under test. + val appContext = InstrumentationRegistry.getInstrumentation().targetContext + assertEquals("com.example.picklist.test", appContext.packageName) + } +} diff --git a/core/picklist/src/main/AndroidManifest.xml b/core/picklist/src/main/AndroidManifest.xml new file mode 100644 index 00000000..8bdb7e14 --- /dev/null +++ b/core/picklist/src/main/AndroidManifest.xml @@ -0,0 +1,4 @@ + + + + diff --git a/core/picklist/src/main/java/com/example/picklist/Extension.kt b/core/picklist/src/main/java/com/example/picklist/Extension.kt new file mode 100644 index 00000000..0413e18a --- /dev/null +++ b/core/picklist/src/main/java/com/example/picklist/Extension.kt @@ -0,0 +1,17 @@ +package com.example.picklist + +import com.example.model.Order +import com.example.model.Pick + +fun List.setOrderedList(order: Order): PickListUiState.Success { + return PickListUiState.Success( + pickList = when (order) { + Order.LATEST -> this + + Order.OLDEST -> this.reversed() + + Order.FAVORITE_DESC -> this.sortedByDescending { it.favoriteCount } + }, + order = order + ) +} diff --git a/core/picklist/src/main/java/com/example/picklist/OrderBottomSheet.kt b/core/picklist/src/main/java/com/example/picklist/OrderBottomSheet.kt new file mode 100644 index 00000000..2dd14475 --- /dev/null +++ b/core/picklist/src/main/java/com/example/picklist/OrderBottomSheet.kt @@ -0,0 +1,134 @@ +package com.example.picklist + +import androidx.compose.foundation.clickable +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.displayCutoutPadding +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.navigationBarsPadding +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.wrapContentHeight +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.filled.Check +import androidx.compose.material3.ExperimentalMaterial3Api +import androidx.compose.material3.HorizontalDivider +import androidx.compose.material3.Icon +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.ModalBottomSheet +import androidx.compose.material3.Text +import androidx.compose.material3.rememberModalBottomSheetState +import androidx.compose.runtime.Composable +import androidx.compose.runtime.rememberCoroutineScope +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.text.style.TextAlign +import com.example.common.ui.Constants +import com.example.common.ui.theme.Dark +import com.example.common.ui.theme.Primary +import com.example.common.ui.theme.White +import com.example.model.Order +import kotlinx.coroutines.launch + +@OptIn(ExperimentalMaterial3Api::class) +@Composable +fun OrderBottomSheet( + isFavoritePicks: Boolean, + currentOrder: Order, + onDismissRequest: () -> Unit, + onOrderClick: (Order) -> Unit, +) { + val sheetState = rememberModalBottomSheetState() + val scope = rememberCoroutineScope() + val orderList = listOf( + stringResource(if (isFavoritePicks) R.string.latest_favorite_order else R.string.latest_create_order) to Order.LATEST, + stringResource(if (isFavoritePicks) R.string.oldest_favorite_order else R.string.oldest_create_order) to Order.OLDEST, + stringResource(R.string.favorite_count_desc) to Order.FAVORITE_DESC, + ) + + ModalBottomSheet( + onDismissRequest = onDismissRequest, + modifier = Modifier + .fillMaxWidth() + .wrapContentHeight() + .displayCutoutPadding() + .navigationBarsPadding(), + sheetState = sheetState, + containerColor = Dark, + scrimColor = MaterialTheme.colorScheme.surface.copy(alpha = 0.32f), + ) { + Column( + modifier = Modifier.fillMaxWidth(), + horizontalAlignment = Alignment.CenterHorizontally + ) { + orderList.forEach { (orderText, order) -> + BottomSheetMenu( + text = orderText, + isSelected = currentOrder == order, + ) { + scope + .launch { + if (currentOrder != order) { + onOrderClick(order) + } + sheetState.hide() + } + .invokeOnCompletion { + if (!sheetState.isVisible) { + onDismissRequest() + } + } + } + } + + HorizontalDivider(color = White) + BottomSheetMenu( + text = stringResource(R.string.close_button_text), + textAlign = TextAlign.Center, + ) { + scope + .launch { sheetState.hide() } + .invokeOnCompletion { + if (!sheetState.isVisible) { + onDismissRequest() + } + } + } + } + } +} + +@Composable +private fun BottomSheetMenu( + text: String, + textAlign: TextAlign = TextAlign.Start, + isSelected: Boolean = false, + onClick: () -> Unit, +) { + Row( + modifier = Modifier + .fillMaxWidth() + .clickable { onClick() } + .padding(Constants.DEFAULT_PADDING), + horizontalArrangement = Arrangement.SpaceBetween, + verticalAlignment = Alignment.CenterVertically + ) { + Text( + text = text, + modifier = Modifier.weight(1f), + color = if (isSelected) Primary else White, + fontWeight = if (isSelected) FontWeight.Bold else FontWeight.Normal, + textAlign = textAlign, + ) + + if (isSelected) { + Icon( + imageVector = Icons.Default.Check, + contentDescription = stringResource(R.string.selected_icon_description), + tint = Primary + ) + } + } +} diff --git a/core/picklist/src/main/java/com/example/picklist/PickListContents.kt b/core/picklist/src/main/java/com/example/picklist/PickListContents.kt new file mode 100644 index 00000000..db539602 --- /dev/null +++ b/core/picklist/src/main/java/com/example/picklist/PickListContents.kt @@ -0,0 +1,210 @@ +package com.example.picklist + +import android.content.res.Configuration +import androidx.compose.foundation.background +import androidx.compose.foundation.clickable +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.displayCutoutPadding +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size +import androidx.compose.foundation.layout.wrapContentSize +import androidx.compose.foundation.lazy.LazyColumn +import androidx.compose.foundation.lazy.items +import androidx.compose.foundation.shape.CircleShape +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.Scaffold +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.clip +import androidx.compose.ui.graphics.Brush +import androidx.compose.ui.graphics.Color.Companion.White +import androidx.compose.ui.platform.LocalConfiguration +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.unit.dp +import androidx.wear.compose.material.CircularProgressIndicator +import com.example.common.ui.Constants.COLOR_STOPS +import com.example.common.ui.Constants.DEFAULT_PADDING +import com.example.common.ui.CountText +import com.example.common.ui.DefaultTopAppBar +import com.example.common.ui.VerticalSpacer +import com.example.common.ui.theme.Primary +import com.example.model.Order + +@Composable +fun PickListContents( + showOrderBottomSheet: Boolean, + pickListType: PickListType, + uiState: PickListUiState, + onBackClick: () -> Unit, + onItemClick: (String) -> Unit, + setListOrder: (Order) -> Unit, + setOrderBottomSheetVisibility: (Boolean) -> Unit +) { + val isLandscape = LocalConfiguration.current.orientation == Configuration.ORIENTATION_LANDSCAPE + + Scaffold( + topBar = { + DefaultTopAppBar( + title = stringResource( + when (pickListType) { + PickListType.FAVORITE -> R.string.favorite_picks_top_app_bar_title + PickListType.CREATED -> R.string.my_picks_top_app_bar_title + } + ), + onBackClick = onBackClick + ) + }, + ) { innerPadding -> + Box( + modifier = Modifier + .fillMaxSize() + .background(Brush.verticalGradient(colorStops = COLOR_STOPS)) + .padding(innerPadding) + .then(if (isLandscape) Modifier.displayCutoutPadding() else Modifier) + ) { + when (uiState) { + PickListUiState.Loading -> { + CircularProgressIndicator( + modifier = Modifier + .size(36.dp) + .align(Alignment.Center), + indicatorColor = Primary + ) + } + + is PickListUiState.Success -> { + val pickList = uiState.pickList + val order = uiState.order + + Column( + modifier = Modifier.fillMaxSize() + ) { + Row( + modifier = Modifier + .fillMaxWidth() + .padding(horizontal = DEFAULT_PADDING), + horizontalArrangement = Arrangement.SpaceBetween, + verticalAlignment = Alignment.CenterVertically + ) { + CountText( + totalCount = pickList.size, + defaultColor = White, + ) + + Box( + modifier = Modifier + .wrapContentSize() + .clip(CircleShape) + .clickable { setOrderBottomSheetVisibility(true) } + ) { + Text( + text = getOrderString( + pickListType = pickListType, + order = order + ), + modifier = Modifier.padding( + horizontal = DEFAULT_PADDING / 2, + vertical = DEFAULT_PADDING / 4 + ), + color = White, + style = MaterialTheme.typography.bodyMedium + ) + } + } + + VerticalSpacer(8) + + if (pickList.isEmpty()) { + Box( + modifier = Modifier.fillMaxSize(), + contentAlignment = Alignment.Center + ) { + Text( + text = stringResource( + when (pickListType) { + PickListType.FAVORITE -> R.string.favorite_picks_empty + PickListType.CREATED -> R.string.my_picks_empty + } + ), + color = White, + style = MaterialTheme.typography.titleMedium + ) + } + } else { + LazyColumn( + modifier = Modifier.weight(1f) + ) { + items( + count = pickList.count(), + key = { pickList[it].id }, + itemContent = { + PickListItem( + song = pickList[it].song, + isCreatedByOthers = pickListType == PickListType.FAVORITE, + createUserName = pickList[it].createdBy.userName, + favoriteCount = pickList[it].favoriteCount, + comment = pickList[it].comment, + createdAt = pickList[it].createdAt, + onItemClick = { onItemClick(pickList[it].id) } + ) + } + ) + } + } + } + } + + PickListUiState.Error -> { + Text( + text = stringResource(R.string.error_loading_pick_list), + modifier = Modifier.align(Alignment.Center), + color = White, + style = MaterialTheme.typography.bodyLarge + ) + // TODO: 다시하기 버튼 같은 거 만들어서 요청 다시 하게 할 수 있도록 만드는 것도 고려해보기 + } + } + } + } + + if (showOrderBottomSheet) { + OrderBottomSheet( + isFavoritePicks = pickListType == PickListType.FAVORITE, + currentOrder = (uiState as PickListUiState.Success).order, + onDismissRequest = { setOrderBottomSheetVisibility(false) }, + onOrderClick = { order -> + setListOrder(order) + }, + ) + } +} + +@Composable +private fun getOrderString(pickListType: PickListType, order: Order): String { + return "${ + stringResource( + when (order) { + Order.LATEST -> + when (pickListType) { + PickListType.FAVORITE -> R.string.latest_favorite_order + PickListType.CREATED -> R.string.latest_create_order + } + + Order.OLDEST -> + when (pickListType) { + PickListType.FAVORITE -> R.string.oldest_favorite_order + PickListType.CREATED -> R.string.oldest_create_order + } + + Order.FAVORITE_DESC -> R.string.favorite_count_desc + } + ) + } ▼" +} diff --git a/core/picklist/src/main/java/com/example/picklist/PickListItem.kt b/core/picklist/src/main/java/com/example/picklist/PickListItem.kt new file mode 100644 index 00000000..3be24d7b --- /dev/null +++ b/core/picklist/src/main/java/com/example/picklist/PickListItem.kt @@ -0,0 +1,107 @@ +package com.example.picklist + +import androidx.compose.foundation.clickable +import androidx.compose.foundation.interaction.MutableInteractionSource +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.Text +import androidx.compose.material3.ripple +import androidx.compose.runtime.Composable +import androidx.compose.runtime.remember +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.clip +import androidx.compose.ui.text.style.TextOverflow +import androidx.compose.ui.unit.dp +import com.example.common.ui.AlbumImage +import com.example.common.ui.CommentText +import com.example.common.ui.Constants +import com.example.common.ui.CreatedByOtherUserText +import com.example.common.ui.FavoriteCountText +import com.example.common.ui.HorizontalSpacer +import com.example.common.ui.SongInfoText +import com.example.common.ui.theme.Gray +import com.example.common.ui.theme.White +import com.example.model.Song + +@Composable +fun PickListItem( + song: Song, + isCreatedByOthers: Boolean, + createUserName: String, + favoriteCount: Int, + comment: String, + createdAt: String, + onItemClick: () -> Unit, +) { + Row( + modifier = Modifier + .fillMaxWidth() + .clickable( + interactionSource = remember { MutableInteractionSource() }, + indication = ripple(color = White), + ) { onItemClick() } + .padding( + horizontal = Constants.DEFAULT_PADDING, + vertical = Constants.DEFAULT_PADDING / 2 + ), + horizontalArrangement = Arrangement.spacedBy(Constants.DEFAULT_PADDING), + verticalAlignment = Alignment.CenterVertically + ) { + AlbumImage( + imageUrl = song.getImageUrlWithSize(Constants.REQUEST_IMAGE_SIZE_DEFAULT.width, Constants.REQUEST_IMAGE_SIZE_DEFAULT.height), + modifier = Modifier + .size(64.dp) + .clip(RoundedCornerShape(8.dp)) + ) + + Column( + modifier = Modifier.weight(1f) + ) { + SongInfoText( + songInfo = "${song.songName} - ${song.artistName}", + color = White + ) + + Row( + verticalAlignment = Alignment.CenterVertically + ) { + if (isCreatedByOthers) { + CreatedByOtherUserText( + userName = createUserName, + modifier = Modifier.weight(weight = 1f, fill = false), + color = Gray + ) + } else { + Text( + text = createdAt, + modifier = Modifier.weight(weight = 1f, fill = false), + color = Gray, + overflow = TextOverflow.Ellipsis, + maxLines = 1, + style = MaterialTheme.typography.bodyMedium, + ) + } + + HorizontalSpacer(8) + + FavoriteCountText( + favoriteCount = favoriteCount, + iconTint = Gray, + color = Gray + ) + } + + CommentText( + comment = comment, + color = Gray + ) + } + } +} diff --git a/core/picklist/src/main/java/com/example/picklist/PickListType.kt b/core/picklist/src/main/java/com/example/picklist/PickListType.kt new file mode 100644 index 00000000..baeacf72 --- /dev/null +++ b/core/picklist/src/main/java/com/example/picklist/PickListType.kt @@ -0,0 +1,5 @@ +package com.example.picklist + +enum class PickListType { + FAVORITE, CREATED +} diff --git a/core/picklist/src/main/java/com/example/picklist/PickListUiState.kt b/core/picklist/src/main/java/com/example/picklist/PickListUiState.kt new file mode 100644 index 00000000..9ebe307a --- /dev/null +++ b/core/picklist/src/main/java/com/example/picklist/PickListUiState.kt @@ -0,0 +1,10 @@ +package com.example.picklist + +import com.example.model.Order +import com.example.model.Pick + +sealed class PickListUiState { + data object Loading : PickListUiState() + data class Success(val pickList: List, val order: Order) : PickListUiState() + data object Error : PickListUiState() +} diff --git a/core/picklist/src/main/res/values/strings.xml b/core/picklist/src/main/res/values/strings.xml new file mode 100644 index 00000000..267dec2a --- /dev/null +++ b/core/picklist/src/main/res/values/strings.xml @@ -0,0 +1,16 @@ + + 최근 등록순 + 최근 담은순 + 과거 담은순 + 과거 등록순 + 담기 많은순 + 체크 아이콘 + 닫기 + + 픽 보관함 + 등록한 픽 + + 담은 픽이 없습니다 + 등록한 픽이 없습니다 + 일시적인 오류가 발생했습니다. + diff --git a/core/picklist/src/test/java/com/example/picklist/ExampleUnitTest.kt b/core/picklist/src/test/java/com/example/picklist/ExampleUnitTest.kt new file mode 100644 index 00000000..1bf523f2 --- /dev/null +++ b/core/picklist/src/test/java/com/example/picklist/ExampleUnitTest.kt @@ -0,0 +1,17 @@ +package com.example.picklist + +import org.junit.Test + +import org.junit.Assert.* + +/** + * Example local unit test, which will execute on the development machine (host). + * + * See [testing documentation](http://d.android.com/tools/testing). + */ +class ExampleUnitTest { + @Test + fun addition_isCorrect() { + assertEquals(4, 2 + 2) + } +} diff --git a/core/util/.gitignore b/core/util/.gitignore new file mode 100644 index 00000000..42afabfd --- /dev/null +++ b/core/util/.gitignore @@ -0,0 +1 @@ +/build \ No newline at end of file diff --git a/core/util/build.gradle.kts b/core/util/build.gradle.kts new file mode 100644 index 00000000..8e376d47 --- /dev/null +++ b/core/util/build.gradle.kts @@ -0,0 +1,45 @@ +plugins { + alias(libs.plugins.android.library) + alias(libs.plugins.jetbrains.kotlin.android) + alias(libs.plugins.kotlin.serialization) +} + +android { + namespace = "com.example.util" + compileSdk = 34 + + defaultConfig { + minSdk = 26 + + testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner" + consumerProguardFiles("consumer-rules.pro") + } + + buildTypes { + release { + isMinifyEnabled = false + proguardFiles(getDefaultProguardFile("proguard-android-optimize.txt"), "proguard-rules.pro") + } + } + compileOptions { + sourceCompatibility = JavaVersion.VERSION_1_8 + targetCompatibility = JavaVersion.VERSION_1_8 + } + kotlinOptions { + jvmTarget = "1.8" + } +} + +dependencies { + + implementation(libs.androidx.core.ktx) + implementation(libs.androidx.appcompat) + implementation(libs.androidx.navigation.common.ktx) + implementation(libs.material) + testImplementation(libs.junit) + androidTestImplementation(libs.androidx.junit) + androidTestImplementation(libs.androidx.espresso.core) + + // Serialization + implementation(libs.kotlinx.serialization.json) +} diff --git a/core/util/consumer-rules.pro b/core/util/consumer-rules.pro new file mode 100644 index 00000000..e69de29b diff --git a/core/util/proguard-rules.pro b/core/util/proguard-rules.pro new file mode 100644 index 00000000..481bb434 --- /dev/null +++ b/core/util/proguard-rules.pro @@ -0,0 +1,21 @@ +# Add project specific ProGuard rules here. +# You can control the set of applied configuration files using the +# proguardFiles setting in build.gradle. +# +# For more details, see +# http://developer.android.com/guide/developing/tools/proguard.html + +# If your project uses WebView with JS, uncomment the following +# and specify the fully qualified class name to the JavaScript interface +# class: +#-keepclassmembers class fqcn.of.javascript.interface.for.webview { +# public *; +#} + +# Uncomment this to preserve the line number information for +# debugging stack traces. +#-keepattributes SourceFile,LineNumberTable + +# If you keep the line number information, uncomment this to +# hide the original source file name. +#-renamesourcefileattribute SourceFile \ No newline at end of file diff --git a/core/util/src/androidTest/java/com/example/util/ExampleInstrumentedTest.kt b/core/util/src/androidTest/java/com/example/util/ExampleInstrumentedTest.kt new file mode 100644 index 00000000..451b6124 --- /dev/null +++ b/core/util/src/androidTest/java/com/example/util/ExampleInstrumentedTest.kt @@ -0,0 +1,24 @@ +package com.example.util + +import androidx.test.platform.app.InstrumentationRegistry +import androidx.test.ext.junit.runners.AndroidJUnit4 + +import org.junit.Test +import org.junit.runner.RunWith + +import org.junit.Assert.* + +/** + * Instrumented test, which will execute on an Android device. + * + * See [testing documentation](http://d.android.com/tools/testing). + */ +@RunWith(AndroidJUnit4::class) +class ExampleInstrumentedTest { + @Test + fun useAppContext() { + // Context of the app under test. + val appContext = InstrumentationRegistry.getInstrumentation().targetContext + assertEquals("com.example.util.test", appContext.packageName) + } +} diff --git a/core/util/src/main/AndroidManifest.xml b/core/util/src/main/AndroidManifest.xml new file mode 100644 index 00000000..8bdb7e14 --- /dev/null +++ b/core/util/src/main/AndroidManifest.xml @@ -0,0 +1,4 @@ + + + + diff --git a/core/util/src/main/java/com/example/util/SerializableType.kt b/core/util/src/main/java/com/example/util/SerializableType.kt new file mode 100644 index 00000000..9f03efc7 --- /dev/null +++ b/core/util/src/main/java/com/example/util/SerializableType.kt @@ -0,0 +1,22 @@ +package com.example.util + +import android.os.Bundle +import androidx.navigation.NavType +import kotlinx.serialization.encodeToString +import kotlinx.serialization.json.Json + +inline fun serializableType( + isNullableAllowed: Boolean = false, + json: Json = Json, +) = object : NavType(isNullableAllowed = isNullableAllowed) { + override fun get(bundle: Bundle, key: String) = + bundle.getString(key)?.let(json::decodeFromString) + + override fun parseValue(value: String): T = json.decodeFromString(value) + + override fun serializeAsValue(value: T): String = json.encodeToString(value) + + override fun put(bundle: Bundle, key: String, value: T) { + bundle.putString(key, json.encodeToString(value)) + } +} diff --git a/core/util/src/main/java/com/example/util/ThrottleFirst.kt b/core/util/src/main/java/com/example/util/ThrottleFirst.kt new file mode 100644 index 00000000..d9b3bb9f --- /dev/null +++ b/core/util/src/main/java/com/example/util/ThrottleFirst.kt @@ -0,0 +1,18 @@ +package com.example.util + +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.flow + +internal fun Flow.throttleFirst(periodMillis: Long): Flow { + require(periodMillis > 0) { "period should be positive" } + return flow { + var lastTime = 0L + collect { value -> + val currentTime = System.currentTimeMillis() + if (currentTime - lastTime >= periodMillis) { + lastTime = currentTime + emit(value) + } + } + } +} diff --git a/core/util/src/test/java/com/example/util/ExampleUnitTest.kt b/core/util/src/test/java/com/example/util/ExampleUnitTest.kt new file mode 100644 index 00000000..797793ac --- /dev/null +++ b/core/util/src/test/java/com/example/util/ExampleUnitTest.kt @@ -0,0 +1,17 @@ +package com.example.util + +import org.junit.Test + +import org.junit.Assert.* + +/** + * Example local unit test, which will execute on the development machine (host). + * + * See [testing documentation](http://d.android.com/tools/testing). + */ +class ExampleUnitTest { + @Test + fun addition_isCorrect() { + assertEquals(4, 2 + 2) + } +} diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 5abea9ea..7c86ae7d 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -22,12 +22,11 @@ hiltNavigationCompose = "1.2.0" inject = "1" junit = "4.13.2" junitVersion = "1.2.1" -kotlin = "1.9.0" +kotlin = "1.9.22" kotlinxCoroutinesPlayServices = "1.9.0" kotlinxCoroutinesCore = "1.9.0" -kotlinxCoroutinesGuava = "1.9.0" kotlinxSerializationJson = "1.6.0" -ksp = "1.9.0-1.0.12" +ksp = "1.9.22-1.0.16" lifecycleRuntimeKtx = "2.8.7" mapSdk = "3.19.1" material = "1.12.0" @@ -41,6 +40,9 @@ splashscreen = "1.0.1" uiViewbinding = "1.7.5" pagingComposeAndroid = "3.3.4" kotlinxImmutable = "0.3.7" +jetbrainsKotlinJvm = "1.9.22" +navigationCommonKtx = "2.8.5" +runtimeAndroid = "1.7.6" [libraries] # AndroidX @@ -66,13 +68,12 @@ androidx-ui-tooling = { group = "androidx.compose.ui", name = "ui-tooling" } androidx-ui-tooling-preview = { group = "androidx.compose.ui", name = "ui-tooling-preview" } androidx-ui-test-manifest = { group = "androidx.compose.ui", name = "ui-test-manifest" } androidx-ui-test-junit4 = { group = "androidx.compose.ui", name = "ui-test-junit4" } -androidx-material3 = { group = "androidx.compose.material3", name = "material3" } +androidx-compose-material3 = { group = "androidx.compose.material3", name = "material3" } androidx-appcompat = { group = "androidx.appcompat", name = "appcompat", version.ref = "appcompat" } androidx-navigation-compose = { module = "androidx.navigation:navigation-compose", version.ref = "navigationCompose" } androidx-core-splashscreen = { module = "androidx.core:core-splashscreen", version.ref = "splashscreen" } androidx-compose-material = { group = "androidx.wear.compose", name = "compose-material", version.ref = "composeMaterial" } kotlinx-coroutines-core = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-core", version.ref = "kotlinxCoroutinesCore" } -kotlinx-coroutines-guava = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-guava", version.ref = "kotlinxCoroutinesGuava" } kotlinx-immutable = { group = "org.jetbrains.kotlinx", name = "kotlinx-collections-immutable", version.ref = "kotlinxImmutable" } # Firebase @@ -123,6 +124,8 @@ coil = { module = "io.coil-kt.coil3:coil", version.ref = "coil" } coil-compose = { module = "io.coil-kt.coil3:coil-compose", version.ref = "coil" } coil-network-okhttp = { module = "io.coil-kt.coil3:coil-network-okhttp", version.ref = "coil" } androidx-paging-compose-android = { group = "androidx.paging", name = "paging-compose-android", version.ref = "pagingComposeAndroid" } +androidx-navigation-common-ktx = { group = "androidx.navigation", name = "navigation-common-ktx", version.ref = "navigationCommonKtx" } +androidx-runtime-android = { group = "androidx.compose.runtime", name = "runtime-android", version.ref = "runtimeAndroid" } # Plugins [plugins] @@ -133,4 +136,5 @@ ksp = { id = "com.google.devtools.ksp", version.ref = "ksp" } hilt = { id = "com.google.dagger.hilt.android", version.ref = "hilt" } kotlin-serialization = { id = "org.jetbrains.kotlin.plugin.serialization", version.ref = "kotlin" } google-services = { id = "com.google.gms.google-services", version.ref = "googleServices" } -firebase-crashlytics = { id = "com.google.firebase.crashlytics", version.ref = "firebaseCrashlytics" } \ No newline at end of file +firebase-crashlytics = { id = "com.google.firebase.crashlytics", version.ref = "firebaseCrashlytics" } +jetbrains-kotlin-jvm = { id = "org.jetbrains.kotlin.jvm", version.ref = "jetbrainsKotlinJvm" } diff --git a/settings.gradle.kts b/settings.gradle.kts index f0a67411..9ccb47a5 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -26,3 +26,9 @@ include(":domain") include(":data") include(":mediaservice") enableFeaturePreview("TYPESAFE_PROJECT_ACCESSORS") +include(":core:model") +include(":core:navigation") +include(":core:util") +include(":core:common") +include(":core:picklist") +include(":core:mediaservice") From b11d5e20788a89a7e61b8b9c457e6b05fa51b90e Mon Sep 17 00:00:00 2001 From: miller198 Date: Sat, 1 Feb 2025 21:11:06 +0900 Subject: [PATCH 02/62] =?UTF-8?q?[style]=20=EC=BD=94=EB=93=9C=EC=9C=84?= =?UTF-8?q?=EC=B9=98=EC=A1=B0=EC=A0=95,=20=ED=95=84=EC=9A=94=EC=97=86?= =?UTF-8?q?=EB=8A=94=20=EB=B3=80=EC=88=98=20=EC=82=AD=EC=A0=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../com/squirtles/musicroad/map/MapViewModel.kt | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/app/src/main/java/com/squirtles/musicroad/map/MapViewModel.kt b/app/src/main/java/com/squirtles/musicroad/map/MapViewModel.kt index 163861e2..60aea594 100644 --- a/app/src/main/java/com/squirtles/musicroad/map/MapViewModel.kt +++ b/app/src/main/java/com/squirtles/musicroad/map/MapViewModel.kt @@ -22,12 +22,6 @@ import kotlinx.coroutines.flow.asStateFlow import kotlinx.coroutines.launch import javax.inject.Inject -data class MarkerState( - val prevClickedMarker: Marker? = null, // 이전에 클릭한 마커(클러스터 마커 & 단말 마커) - val clusterPickList: List? = null, // 클러스터 마커의 픽 정보 - val curPickId: String? = null // 현재 선택한 마커의 pick id -) - @HiltViewModel class MapViewModel @Inject constructor( getLastLocationUseCase: GetLastLocationUseCase, @@ -53,7 +47,6 @@ class MapViewModel @Inject constructor( // FIXME : 네이버맵의 LocationChangeListener에서 실시간으로 변하는 위치 정보 -> 더 나은 방법이 있으면 고쳐주세요 private var _currentLocation: Location? = null - val curLocation get() = _currentLocation // LocalDataSource에 저장되는 위치 정보 // Firestore 데이터 쿼리 작업 최소화 및 위치데이터 공유 용도 @@ -194,3 +187,9 @@ class MapViewModel @Inject constructor( private const val MARKER_SCALE = 1.5 } } + +data class MarkerState( + val prevClickedMarker: Marker? = null, // 이전에 클릭한 마커(클러스터 마커 & 단말 마커) + val clusterPickList: List? = null, // 클러스터 마커의 픽 정보 + val curPickId: String? = null // 현재 선택한 마커의 pick id +) From 0bb1ed652da0d6542b8871f9440b07cbf9bf2eae Mon Sep 17 00:00:00 2001 From: miller198 Date: Sun, 2 Feb 2025 00:01:02 +0900 Subject: [PATCH 03/62] =?UTF-8?q?[refactor]=20firebase=20repository=20?= =?UTF-8?q?=EA=B8=B0=EB=8A=A5=EB=B3=84=20=EB=B6=84=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../common/picklist/PickListViewModel.kt | 4 +- .../musicroad/create/CreatePickViewModel.kt | 2 +- .../musicroad/detail/DetailViewModel.kt | 4 +- .../squirtles/musicroad/main/MainViewModel.kt | 2 +- .../musicroad/mypick/MyPickListViewModel.kt | 4 +- .../applemusic/AppleMusicDataSourceImpl.kt | 2 +- .../remote/firebase/FirebaseDataSourceImpl.kt | 2 +- .../java/com/squirtles/data/di/DataModule.kt | 30 +++++- .../repository/AppleMusicRepositoryImpl.kt | 17 +--- .../FirebaseFavoriteRepositoryImpl.kt | 35 +++++++ .../repository/FirebasePickRepositoryImpl.kt | 47 +++++++++ .../data/repository/FirebaseRepositoryImpl.kt | 96 +------------------ .../repository/FirebaseUserRepositoryImpl.kt | 30 ++++++ .../domain/firebase/FirebaseRepository.kt | 21 ---- .../domain/remote/RemoteRepository.kt | 11 +++ .../applemusic/AppleMusicException.kt | 2 +- .../applemusic/AppleMusicRemoteDataSource.kt | 2 +- .../applemusic/AppleMusicRepository.kt | 2 +- .../firebase/FirebaseException.kt | 13 ++- .../firebase/FirebaseFavoriteRepository.kt | 11 +++ .../remote/firebase/FirebasePickRepository.kt | 20 ++++ .../firebase/FirebaseRemoteDataSource.kt | 11 ++- .../remote/firebase/FirebaseRepository.kt | 4 + .../remote/firebase/FirebaseUserRepository.kt | 19 ++++ .../usecase/favorite/CreateFavoriteUseCase.kt | 6 +- .../usecase/favorite/DeleteFavoriteUseCase.kt | 10 +- .../favorite/FetchFavoritePicksUseCase.kt | 6 +- .../favorite/FetchIsFavoriteUseCase.kt | 11 +++ .../usecase/music/FetchMusicVideoUseCase.kt | 2 +- .../domain/usecase/music/FetchSongsUseCase.kt | 2 +- .../usecase/mypick/CreatePickUseCase.kt | 11 --- .../usecase/mypick/DeletePickUseCase.kt | 12 --- .../domain/usecase/pick/CreatePickUseCase.kt | 11 +++ .../domain/usecase/pick/DeletePickUseCase.kt | 12 +++ .../usecase/pick/FetchIsFavoriteUseCase.kt | 11 --- .../{mypick => pick}/FetchMyPicksUseCase.kt | 8 +- .../usecase/pick/FetchPickInAreaUseCase.kt | 11 --- .../domain/usecase/pick/FetchPickUseCase.kt | 8 +- ...rface.kt => RemovePickUseCaseInterface.kt} | 2 +- .../usecase/user/CreateNewUserUseCase.kt | 8 +- .../usecase/user/FetchUserByIdUseCase.kt | 6 +- .../usecase/user/UpdateUserNameUseCase.kt | 6 +- 42 files changed, 310 insertions(+), 224 deletions(-) create mode 100644 data/src/main/java/com/squirtles/data/repository/FirebaseFavoriteRepositoryImpl.kt create mode 100644 data/src/main/java/com/squirtles/data/repository/FirebasePickRepositoryImpl.kt create mode 100644 data/src/main/java/com/squirtles/data/repository/FirebaseUserRepositoryImpl.kt delete mode 100644 domain/src/main/java/com/squirtles/domain/firebase/FirebaseRepository.kt create mode 100644 domain/src/main/java/com/squirtles/domain/remote/RemoteRepository.kt rename domain/src/main/java/com/squirtles/domain/{ => remote}/applemusic/AppleMusicException.kt (89%) rename domain/src/main/java/com/squirtles/domain/{ => remote}/applemusic/AppleMusicRemoteDataSource.kt (89%) rename domain/src/main/java/com/squirtles/domain/{ => remote}/applemusic/AppleMusicRepository.kt (89%) rename domain/src/main/java/com/squirtles/domain/{ => remote}/firebase/FirebaseException.kt (59%) create mode 100644 domain/src/main/java/com/squirtles/domain/remote/firebase/FirebaseFavoriteRepository.kt create mode 100644 domain/src/main/java/com/squirtles/domain/remote/firebase/FirebasePickRepository.kt rename domain/src/main/java/com/squirtles/domain/{ => remote}/firebase/FirebaseRemoteDataSource.kt (87%) create mode 100644 domain/src/main/java/com/squirtles/domain/remote/firebase/FirebaseRepository.kt create mode 100644 domain/src/main/java/com/squirtles/domain/remote/firebase/FirebaseUserRepository.kt create mode 100644 domain/src/main/java/com/squirtles/domain/usecase/favorite/FetchIsFavoriteUseCase.kt delete mode 100644 domain/src/main/java/com/squirtles/domain/usecase/mypick/CreatePickUseCase.kt delete mode 100644 domain/src/main/java/com/squirtles/domain/usecase/mypick/DeletePickUseCase.kt create mode 100644 domain/src/main/java/com/squirtles/domain/usecase/pick/CreatePickUseCase.kt create mode 100644 domain/src/main/java/com/squirtles/domain/usecase/pick/DeletePickUseCase.kt delete mode 100644 domain/src/main/java/com/squirtles/domain/usecase/pick/FetchIsFavoriteUseCase.kt rename domain/src/main/java/com/squirtles/domain/usecase/{mypick => pick}/FetchMyPicksUseCase.kt (54%) delete mode 100644 domain/src/main/java/com/squirtles/domain/usecase/pick/FetchPickInAreaUseCase.kt rename domain/src/main/java/com/squirtles/domain/usecase/picklist/{DeletePickListUseCaseInterface.kt => RemovePickUseCaseInterface.kt} (75%) diff --git a/app/src/main/java/com/squirtles/musicroad/common/picklist/PickListViewModel.kt b/app/src/main/java/com/squirtles/musicroad/common/picklist/PickListViewModel.kt index f2a552e1..4bfd268f 100644 --- a/app/src/main/java/com/squirtles/musicroad/common/picklist/PickListViewModel.kt +++ b/app/src/main/java/com/squirtles/musicroad/common/picklist/PickListViewModel.kt @@ -5,9 +5,9 @@ import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope import com.squirtles.domain.model.Order import com.squirtles.domain.model.Pick -import com.squirtles.domain.usecase.picklist.DeletePickListUseCaseInterface import com.squirtles.domain.usecase.picklist.FetchPickListUseCaseInterface import com.squirtles.domain.usecase.picklist.GetPickListOrderUseCaseInterface +import com.squirtles.domain.usecase.picklist.RemovePickUseCaseInterface import com.squirtles.domain.usecase.picklist.SavePickListOrderUseCaseInterface import kotlinx.coroutines.async import kotlinx.coroutines.awaitAll @@ -19,7 +19,7 @@ abstract class PickListViewModel( val fetchPickListUseCase: FetchPickListUseCaseInterface, val getPickListOrderUseCase: GetPickListOrderUseCaseInterface, val savePickListOrderUseCase: SavePickListOrderUseCaseInterface, - val removePickUseCase: DeletePickListUseCaseInterface + val removePickUseCase: RemovePickUseCaseInterface ) : ViewModel() { private var pickList: List = emptyList() diff --git a/app/src/main/java/com/squirtles/musicroad/create/CreatePickViewModel.kt b/app/src/main/java/com/squirtles/musicroad/create/CreatePickViewModel.kt index b1a7cbdc..77fe81cd 100644 --- a/app/src/main/java/com/squirtles/musicroad/create/CreatePickViewModel.kt +++ b/app/src/main/java/com/squirtles/musicroad/create/CreatePickViewModel.kt @@ -12,7 +12,7 @@ import com.squirtles.domain.model.Pick import com.squirtles.domain.model.Song import com.squirtles.domain.usecase.location.GetLastLocationUseCase import com.squirtles.domain.usecase.music.FetchMusicVideoUseCase -import com.squirtles.domain.usecase.mypick.CreatePickUseCase +import com.squirtles.domain.usecase.pick.CreatePickUseCase import com.squirtles.domain.usecase.user.GetCurrentUserUseCase import com.squirtles.musicroad.navigation.SearchRoute import com.squirtles.musicroad.utils.throttleFirst diff --git a/app/src/main/java/com/squirtles/musicroad/detail/DetailViewModel.kt b/app/src/main/java/com/squirtles/musicroad/detail/DetailViewModel.kt index 247b19c1..de9da35d 100644 --- a/app/src/main/java/com/squirtles/musicroad/detail/DetailViewModel.kt +++ b/app/src/main/java/com/squirtles/musicroad/detail/DetailViewModel.kt @@ -10,8 +10,8 @@ import com.squirtles.domain.model.Pick import com.squirtles.domain.model.Song import com.squirtles.domain.usecase.favorite.CreateFavoriteUseCase import com.squirtles.domain.usecase.favorite.DeleteFavoriteUseCase -import com.squirtles.domain.usecase.mypick.DeletePickUseCase -import com.squirtles.domain.usecase.pick.FetchIsFavoriteUseCase +import com.squirtles.domain.usecase.favorite.FetchIsFavoriteUseCase +import com.squirtles.domain.usecase.pick.DeletePickUseCase import com.squirtles.domain.usecase.pick.FetchPickUseCase import com.squirtles.domain.usecase.user.GetCurrentUserUseCase import com.squirtles.musicroad.utils.throttleFirst diff --git a/app/src/main/java/com/squirtles/musicroad/main/MainViewModel.kt b/app/src/main/java/com/squirtles/musicroad/main/MainViewModel.kt index 800e7876..a20d3a25 100644 --- a/app/src/main/java/com/squirtles/musicroad/main/MainViewModel.kt +++ b/app/src/main/java/com/squirtles/musicroad/main/MainViewModel.kt @@ -2,7 +2,7 @@ package com.squirtles.musicroad.main import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope -import com.squirtles.domain.firebase.FirebaseException +import com.squirtles.domain.remote.firebase.FirebaseException import com.squirtles.domain.usecase.user.CreateNewUserUseCase import com.squirtles.domain.usecase.user.FetchUserUseCase import com.squirtles.domain.usecase.user.GetUserIdFromDataStoreUseCase diff --git a/app/src/main/java/com/squirtles/musicroad/mypick/MyPickListViewModel.kt b/app/src/main/java/com/squirtles/musicroad/mypick/MyPickListViewModel.kt index dfc83ca4..ffe6ce9e 100644 --- a/app/src/main/java/com/squirtles/musicroad/mypick/MyPickListViewModel.kt +++ b/app/src/main/java/com/squirtles/musicroad/mypick/MyPickListViewModel.kt @@ -1,9 +1,9 @@ package com.squirtles.musicroad.mypick -import com.squirtles.domain.usecase.mypick.DeletePickUseCase -import com.squirtles.domain.usecase.mypick.FetchMyPicksUseCase import com.squirtles.domain.usecase.order.GetMyPickListOrderUseCase import com.squirtles.domain.usecase.order.SaveMyPickListOrderUseCase +import com.squirtles.domain.usecase.pick.DeletePickUseCase +import com.squirtles.domain.usecase.pick.FetchMyPicksUseCase import com.squirtles.musicroad.common.picklist.PickListViewModel import dagger.hilt.android.lifecycle.HiltViewModel import javax.inject.Inject diff --git a/data/src/main/java/com/squirtles/data/datasource/remote/applemusic/AppleMusicDataSourceImpl.kt b/data/src/main/java/com/squirtles/data/datasource/remote/applemusic/AppleMusicDataSourceImpl.kt index 38d8f8f4..b6aa32f7 100644 --- a/data/src/main/java/com/squirtles/data/datasource/remote/applemusic/AppleMusicDataSourceImpl.kt +++ b/data/src/main/java/com/squirtles/data/datasource/remote/applemusic/AppleMusicDataSourceImpl.kt @@ -7,9 +7,9 @@ import com.squirtles.data.datasource.remote.applemusic.SearchSongsPagingSource.C import com.squirtles.data.datasource.remote.applemusic.api.AppleMusicApi import com.squirtles.data.datasource.remote.applemusic.model.SearchResponse import com.squirtles.data.mapper.toMusicVideo -import com.squirtles.domain.applemusic.AppleMusicRemoteDataSource import com.squirtles.domain.model.MusicVideo import com.squirtles.domain.model.Song +import com.squirtles.domain.remote.applemusic.AppleMusicRemoteDataSource import kotlinx.coroutines.flow.Flow import retrofit2.Response import javax.inject.Inject diff --git a/data/src/main/java/com/squirtles/data/datasource/remote/firebase/FirebaseDataSourceImpl.kt b/data/src/main/java/com/squirtles/data/datasource/remote/firebase/FirebaseDataSourceImpl.kt index 7222d661..b19e45f3 100644 --- a/data/src/main/java/com/squirtles/data/datasource/remote/firebase/FirebaseDataSourceImpl.kt +++ b/data/src/main/java/com/squirtles/data/datasource/remote/firebase/FirebaseDataSourceImpl.kt @@ -18,9 +18,9 @@ import com.squirtles.data.datasource.remote.firebase.model.FirebaseUser import com.squirtles.data.mapper.toFirebasePick import com.squirtles.data.mapper.toPick import com.squirtles.data.mapper.toUser -import com.squirtles.domain.firebase.FirebaseRemoteDataSource import com.squirtles.domain.model.Pick import com.squirtles.domain.model.User +import com.squirtles.domain.remote.firebase.FirebaseRemoteDataSource import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.launch diff --git a/data/src/main/java/com/squirtles/data/di/DataModule.kt b/data/src/main/java/com/squirtles/data/di/DataModule.kt index e3363344..ec6dbc96 100644 --- a/data/src/main/java/com/squirtles/data/di/DataModule.kt +++ b/data/src/main/java/com/squirtles/data/di/DataModule.kt @@ -7,14 +7,20 @@ import com.squirtles.data.datasource.remote.applemusic.AppleMusicDataSourceImpl import com.squirtles.data.datasource.remote.applemusic.api.AppleMusicApi import com.squirtles.data.datasource.remote.firebase.FirebaseDataSourceImpl import com.squirtles.data.repository.AppleMusicRepositoryImpl +import com.squirtles.data.repository.FirebaseFavoriteRepositoryImpl +import com.squirtles.data.repository.FirebasePickRepositoryImpl import com.squirtles.data.repository.FirebaseRepositoryImpl +import com.squirtles.data.repository.FirebaseUserRepositoryImpl import com.squirtles.data.repository.LocalRepositoryImpl -import com.squirtles.domain.applemusic.AppleMusicRemoteDataSource -import com.squirtles.domain.firebase.FirebaseRemoteDataSource import com.squirtles.domain.local.LocalDataSource -import com.squirtles.domain.applemusic.AppleMusicRepository -import com.squirtles.domain.firebase.FirebaseRepository import com.squirtles.domain.local.LocalRepository +import com.squirtles.domain.remote.applemusic.AppleMusicRemoteDataSource +import com.squirtles.domain.remote.applemusic.AppleMusicRepository +import com.squirtles.domain.remote.firebase.FirebaseFavoriteRepository +import com.squirtles.domain.remote.firebase.FirebasePickRepository +import com.squirtles.domain.remote.firebase.FirebaseRemoteDataSource +import com.squirtles.domain.remote.firebase.FirebaseRepository +import com.squirtles.domain.remote.firebase.FirebaseUserRepository import dagger.Module import dagger.Provides import dagger.hilt.InstallIn @@ -41,6 +47,22 @@ internal object DataModule { fun provideFirebaseRepository(firebaseRemoteDataSource: FirebaseRemoteDataSource): FirebaseRepository = FirebaseRepositoryImpl(firebaseRemoteDataSource) + @Provides + @Singleton + fun provideFirebaseFavoriteRepository(firebaseRemoteDataSource: FirebaseRemoteDataSource): FirebaseFavoriteRepository = + FirebaseFavoriteRepositoryImpl(firebaseRemoteDataSource) + + @Provides + @Singleton + fun provideFirebaseUserRepository(firebaseRemoteDataSource: FirebaseRemoteDataSource): FirebaseUserRepository = + FirebaseUserRepositoryImpl(firebaseRemoteDataSource) + + @Provides + @Singleton + fun provideFirebasePickRepository(firebaseRemoteDataSource: FirebaseRemoteDataSource): FirebasePickRepository = + FirebasePickRepositoryImpl(firebaseRemoteDataSource) + + @Provides @Singleton fun provideFirebaseRemoteDataSource(db: FirebaseFirestore): FirebaseRemoteDataSource = diff --git a/data/src/main/java/com/squirtles/data/repository/AppleMusicRepositoryImpl.kt b/data/src/main/java/com/squirtles/data/repository/AppleMusicRepositoryImpl.kt index 1b8c20a7..911c1ba0 100644 --- a/data/src/main/java/com/squirtles/data/repository/AppleMusicRepositoryImpl.kt +++ b/data/src/main/java/com/squirtles/data/repository/AppleMusicRepositoryImpl.kt @@ -1,17 +1,18 @@ package com.squirtles.data.repository import androidx.paging.PagingData -import com.squirtles.domain.applemusic.AppleMusicRemoteDataSource -import com.squirtles.domain.applemusic.AppleMusicException import com.squirtles.domain.model.MusicVideo import com.squirtles.domain.model.Song -import com.squirtles.domain.applemusic.AppleMusicRepository +import com.squirtles.domain.remote.RemoteRepository +import com.squirtles.domain.remote.applemusic.AppleMusicException +import com.squirtles.domain.remote.applemusic.AppleMusicRemoteDataSource +import com.squirtles.domain.remote.applemusic.AppleMusicRepository import kotlinx.coroutines.flow.Flow import javax.inject.Inject class AppleMusicRepositoryImpl @Inject constructor( private val appleMusicDataSource: AppleMusicRemoteDataSource -) : AppleMusicRepository { +) : AppleMusicRepository, RemoteRepository() { override fun searchSongs(searchText: String): Flow> = appleMusicDataSource.searchSongs(searchText) @@ -33,12 +34,4 @@ class AppleMusicRepositoryImpl @Inject constructor( call() ?: throw appleMusicException } } - - private suspend fun handleResult( - call: suspend () -> T - ): Result { - return runCatching { - call() - } - } } diff --git a/data/src/main/java/com/squirtles/data/repository/FirebaseFavoriteRepositoryImpl.kt b/data/src/main/java/com/squirtles/data/repository/FirebaseFavoriteRepositoryImpl.kt new file mode 100644 index 00000000..2e95dbdc --- /dev/null +++ b/data/src/main/java/com/squirtles/data/repository/FirebaseFavoriteRepositoryImpl.kt @@ -0,0 +1,35 @@ +package com.squirtles.data.repository + +import com.squirtles.domain.model.Pick +import com.squirtles.domain.remote.RemoteRepository +import com.squirtles.domain.remote.firebase.FirebaseFavoriteRepository +import com.squirtles.domain.remote.firebase.FirebaseRemoteDataSource + + +class FirebaseFavoriteRepositoryImpl( + private val firebaseRemoteDataSource: FirebaseRemoteDataSource +) : FirebaseFavoriteRepository, RemoteRepository() { + override suspend fun fetchIsFavorite(pickId: String, userId: String): Result { + return handleResult { + firebaseRemoteDataSource.fetchIsFavorite(pickId, userId) + } + } + + override suspend fun createFavorite(pickId: String, userId: String): Result { + return handleResult { + firebaseRemoteDataSource.createFavorite(pickId, userId) + } + } + + override suspend fun deleteFavorite(pickId: String, userId: String): Result { + return handleResult { + firebaseRemoteDataSource.deleteFavorite(pickId, userId) + } + } + + override suspend fun fetchFavoritePicks(userId: String): Result> { + return handleResult { + firebaseRemoteDataSource.fetchFavoritePicks(userId) + } + } +} diff --git a/data/src/main/java/com/squirtles/data/repository/FirebasePickRepositoryImpl.kt b/data/src/main/java/com/squirtles/data/repository/FirebasePickRepositoryImpl.kt new file mode 100644 index 00000000..a8965ac2 --- /dev/null +++ b/data/src/main/java/com/squirtles/data/repository/FirebasePickRepositoryImpl.kt @@ -0,0 +1,47 @@ +package com.squirtles.data.repository + +import com.squirtles.domain.model.Pick +import com.squirtles.domain.remote.RemoteRepository +import com.squirtles.domain.remote.firebase.FirebaseException +import com.squirtles.domain.remote.firebase.FirebasePickRepository +import com.squirtles.domain.remote.firebase.FirebaseRemoteDataSource + +class FirebasePickRepositoryImpl( + private val firebaseRemoteDataSource: FirebaseRemoteDataSource +) : FirebasePickRepository, RemoteRepository() { + + override suspend fun createPick(pick: Pick): Result { + return handleResult { + firebaseRemoteDataSource.createPick(pick) + } + } + + override suspend fun deletePick(pickId: String, userId: String): Result { + return handleResult { + firebaseRemoteDataSource.deletePick(pickId, userId) + } + } + + override suspend fun fetchPick(pickID: String): Result { + return handleResult(FirebaseException.NoSuchPickException()) { + firebaseRemoteDataSource.fetchPick(pickID) + } + } + + override suspend fun fetchMyPicks(userId: String): Result> { + return handleResult { + firebaseRemoteDataSource.fetchMyPicks(userId) + } + } + + override suspend fun fetchPicksInArea( + lat: Double, + lng: Double, + radiusInM: Double + ): Result> { + val pickList = firebaseRemoteDataSource.fetchPicksInArea(lat, lng, radiusInM) + return handleResult(FirebaseException.NoSuchPickInRadiusException()) { + pickList.ifEmpty { null } + } + } +} diff --git a/data/src/main/java/com/squirtles/data/repository/FirebaseRepositoryImpl.kt b/data/src/main/java/com/squirtles/data/repository/FirebaseRepositoryImpl.kt index 3657257b..78d3be87 100644 --- a/data/src/main/java/com/squirtles/data/repository/FirebaseRepositoryImpl.kt +++ b/data/src/main/java/com/squirtles/data/repository/FirebaseRepositoryImpl.kt @@ -1,94 +1,16 @@ package com.squirtles.data.repository -import com.squirtles.domain.firebase.FirebaseRemoteDataSource -import com.squirtles.domain.firebase.FirebaseException -import com.squirtles.domain.model.Pick -import com.squirtles.domain.model.User -import com.squirtles.domain.firebase.FirebaseRepository +import com.squirtles.domain.remote.RemoteRepository +import com.squirtles.domain.remote.firebase.FirebaseException +import com.squirtles.domain.remote.firebase.FirebaseRemoteDataSource +import com.squirtles.domain.remote.firebase.FirebaseRepository import javax.inject.Inject import javax.inject.Singleton @Singleton class FirebaseRepositoryImpl @Inject constructor( private val firebaseRemoteDataSource: FirebaseRemoteDataSource -) : FirebaseRepository { - - override suspend fun createUser(): Result { - return handleResult(FirebaseException.CreatedUserFailedException()) { - firebaseRemoteDataSource.createUser() - } - } - - override suspend fun fetchUser(userId: String): Result { - return handleResult(FirebaseException.UserNotFoundException()) { - firebaseRemoteDataSource.fetchUser(userId) - } - } - - override suspend fun updateUserName(userId: String, newUserName: String): Result { - return handleResult { - firebaseRemoteDataSource.updateUserName(userId, newUserName) - } - } - - override suspend fun fetchPick(pickID: String): Result { - return handleResult(FirebaseException.NoSuchPickException()) { - firebaseRemoteDataSource.fetchPick(pickID) - } - } - - override suspend fun fetchPicksInArea( - lat: Double, - lng: Double, - radiusInM: Double - ): Result> { - val pickList = firebaseRemoteDataSource.fetchPicksInArea(lat, lng, radiusInM) - return handleResult(FirebaseException.NoSuchPickInRadiusException()) { - pickList.ifEmpty { null } - } - } - - override suspend fun createPick(pick: Pick): Result { - return handleResult { - firebaseRemoteDataSource.createPick(pick) - } - } - - override suspend fun deletePick(pickId: String, userId: String): Result { - return handleResult { - firebaseRemoteDataSource.deletePick(pickId, userId) - } - } - - override suspend fun fetchMyPicks(userId: String): Result> { - return handleResult { - firebaseRemoteDataSource.fetchMyPicks(userId) - } - } - - override suspend fun fetchFavoritePicks(userId: String): Result> { - return handleResult { - firebaseRemoteDataSource.fetchFavoritePicks(userId) - } - } - - override suspend fun fetchIsFavorite(pickId: String, userId: String): Result { - return handleResult { - firebaseRemoteDataSource.fetchIsFavorite(pickId, userId) - } - } - - override suspend fun createFavorite(pickId: String, userId: String): Result { - return handleResult { - firebaseRemoteDataSource.createFavorite(pickId, userId) - } - } - - override suspend fun deleteFavorite(pickId: String, userId: String): Result { - return handleResult { - firebaseRemoteDataSource.deleteFavorite(pickId, userId) - } - } +) : FirebaseRepository, RemoteRepository() { private suspend fun handleResult( firebaseRepositoryException: FirebaseException, @@ -98,12 +20,4 @@ class FirebaseRepositoryImpl @Inject constructor( call() ?: throw firebaseRepositoryException } } - - private suspend fun handleResult( - call: suspend () -> T - ): Result { - return runCatching { - call() - } - } } diff --git a/data/src/main/java/com/squirtles/data/repository/FirebaseUserRepositoryImpl.kt b/data/src/main/java/com/squirtles/data/repository/FirebaseUserRepositoryImpl.kt new file mode 100644 index 00000000..175ec986 --- /dev/null +++ b/data/src/main/java/com/squirtles/data/repository/FirebaseUserRepositoryImpl.kt @@ -0,0 +1,30 @@ +package com.squirtles.data.repository + +import com.squirtles.domain.model.User +import com.squirtles.domain.remote.RemoteRepository +import com.squirtles.domain.remote.firebase.FirebaseException +import com.squirtles.domain.remote.firebase.FirebaseRemoteDataSource +import com.squirtles.domain.remote.firebase.FirebaseUserRepository + +class FirebaseUserRepositoryImpl( + private val firebaseRemoteDataSource: FirebaseRemoteDataSource +) : FirebaseUserRepository, RemoteRepository() { + + override suspend fun createUser(): Result { + return handleResult(FirebaseException.CreatedUserFailedException()) { + firebaseRemoteDataSource.createUser() + } + } + + override suspend fun fetchUser(userId: String): Result { + return handleResult(FirebaseException.UserNotFoundException()) { + firebaseRemoteDataSource.fetchUser(userId) + } + } + + override suspend fun updateUserName(userId: String, newUserName: String): Result { + return handleResult { + firebaseRemoteDataSource.updateUserName(userId, newUserName) + } + } +} diff --git a/domain/src/main/java/com/squirtles/domain/firebase/FirebaseRepository.kt b/domain/src/main/java/com/squirtles/domain/firebase/FirebaseRepository.kt deleted file mode 100644 index 1c024a73..00000000 --- a/domain/src/main/java/com/squirtles/domain/firebase/FirebaseRepository.kt +++ /dev/null @@ -1,21 +0,0 @@ -package com.squirtles.domain.firebase - -import com.squirtles.domain.model.Pick -import com.squirtles.domain.model.User - -interface FirebaseRepository { - suspend fun createUser(): Result - suspend fun fetchUser(userId: String): Result - suspend fun updateUserName(userId: String, newUserName: String): Result - - suspend fun fetchPick(pickID: String): Result - suspend fun fetchPicksInArea(lat: Double, lng: Double, radiusInM: Double): Result> - suspend fun createPick(pick: Pick): Result - suspend fun deletePick(pickId: String, userId: String): Result - - suspend fun fetchMyPicks(userId: String): Result> - suspend fun fetchFavoritePicks(userId: String): Result> - suspend fun fetchIsFavorite(pickId: String, userId: String): Result - suspend fun createFavorite(pickId: String, userId: String): Result - suspend fun deleteFavorite(pickId: String, userId: String): Result -} diff --git a/domain/src/main/java/com/squirtles/domain/remote/RemoteRepository.kt b/domain/src/main/java/com/squirtles/domain/remote/RemoteRepository.kt new file mode 100644 index 00000000..f53f3f0d --- /dev/null +++ b/domain/src/main/java/com/squirtles/domain/remote/RemoteRepository.kt @@ -0,0 +1,11 @@ +package com.squirtles.domain.remote + +abstract class RemoteRepository { + suspend fun handleResult( + call: suspend () -> T + ): Result { + return runCatching { + call() + } + } +} diff --git a/domain/src/main/java/com/squirtles/domain/applemusic/AppleMusicException.kt b/domain/src/main/java/com/squirtles/domain/remote/applemusic/AppleMusicException.kt similarity index 89% rename from domain/src/main/java/com/squirtles/domain/applemusic/AppleMusicException.kt rename to domain/src/main/java/com/squirtles/domain/remote/applemusic/AppleMusicException.kt index 779ea832..6425e2f4 100644 --- a/domain/src/main/java/com/squirtles/domain/applemusic/AppleMusicException.kt +++ b/domain/src/main/java/com/squirtles/domain/remote/applemusic/AppleMusicException.kt @@ -1,4 +1,4 @@ -package com.squirtles.domain.applemusic +package com.squirtles.domain.remote.applemusic /** * 400 에러가 여러 종류가 있는데 이를 구분할 용도로 만든 예외 클래스 diff --git a/domain/src/main/java/com/squirtles/domain/applemusic/AppleMusicRemoteDataSource.kt b/domain/src/main/java/com/squirtles/domain/remote/applemusic/AppleMusicRemoteDataSource.kt similarity index 89% rename from domain/src/main/java/com/squirtles/domain/applemusic/AppleMusicRemoteDataSource.kt rename to domain/src/main/java/com/squirtles/domain/remote/applemusic/AppleMusicRemoteDataSource.kt index 752dd499..cd9cf6f1 100644 --- a/domain/src/main/java/com/squirtles/domain/applemusic/AppleMusicRemoteDataSource.kt +++ b/domain/src/main/java/com/squirtles/domain/remote/applemusic/AppleMusicRemoteDataSource.kt @@ -1,4 +1,4 @@ -package com.squirtles.domain.applemusic +package com.squirtles.domain.remote.applemusic import androidx.paging.PagingData import com.squirtles.domain.model.MusicVideo diff --git a/domain/src/main/java/com/squirtles/domain/applemusic/AppleMusicRepository.kt b/domain/src/main/java/com/squirtles/domain/remote/applemusic/AppleMusicRepository.kt similarity index 89% rename from domain/src/main/java/com/squirtles/domain/applemusic/AppleMusicRepository.kt rename to domain/src/main/java/com/squirtles/domain/remote/applemusic/AppleMusicRepository.kt index 4df96e51..97505553 100644 --- a/domain/src/main/java/com/squirtles/domain/applemusic/AppleMusicRepository.kt +++ b/domain/src/main/java/com/squirtles/domain/remote/applemusic/AppleMusicRepository.kt @@ -1,4 +1,4 @@ -package com.squirtles.domain.applemusic +package com.squirtles.domain.remote.applemusic import androidx.paging.PagingData import com.squirtles.domain.model.MusicVideo diff --git a/domain/src/main/java/com/squirtles/domain/firebase/FirebaseException.kt b/domain/src/main/java/com/squirtles/domain/remote/firebase/FirebaseException.kt similarity index 59% rename from domain/src/main/java/com/squirtles/domain/firebase/FirebaseException.kt rename to domain/src/main/java/com/squirtles/domain/remote/firebase/FirebaseException.kt index 946d7310..1b7289ed 100644 --- a/domain/src/main/java/com/squirtles/domain/firebase/FirebaseException.kt +++ b/domain/src/main/java/com/squirtles/domain/remote/firebase/FirebaseException.kt @@ -1,8 +1,13 @@ -package com.squirtles.domain.firebase +package com.squirtles.domain.remote.firebase sealed class FirebaseException(override val message: String) : Exception() { - data class CreatedUserFailedException(override val message: String = "Failed to create a user") : FirebaseException(message) - data class UserNotFoundException(override val message: String = "Failed to fetch a user") : FirebaseException(message) + data class CreatedUserFailedException(override val message: String = "Failed to create a user") : + FirebaseException(message) + + data class UserNotFoundException(override val message: String = "Failed to fetch a user") : + FirebaseException(message) + data class NoSuchPickException(override val message: String = "No such pick") : FirebaseException(message) - data class NoSuchPickInRadiusException(override val message: String = "No such pick in area") : FirebaseException(message) + data class NoSuchPickInRadiusException(override val message: String = "No such pick in area") : + FirebaseException(message) } diff --git a/domain/src/main/java/com/squirtles/domain/remote/firebase/FirebaseFavoriteRepository.kt b/domain/src/main/java/com/squirtles/domain/remote/firebase/FirebaseFavoriteRepository.kt new file mode 100644 index 00000000..4a243f39 --- /dev/null +++ b/domain/src/main/java/com/squirtles/domain/remote/firebase/FirebaseFavoriteRepository.kt @@ -0,0 +1,11 @@ +package com.squirtles.domain.remote.firebase + +import com.squirtles.domain.model.Pick + +interface FirebaseFavoriteRepository { + // Favorite + suspend fun fetchIsFavorite(pickId: String, userId: String): Result + suspend fun createFavorite(pickId: String, userId: String): Result + suspend fun deleteFavorite(pickId: String, userId: String): Result + suspend fun fetchFavoritePicks(userId: String): Result> +} diff --git a/domain/src/main/java/com/squirtles/domain/remote/firebase/FirebasePickRepository.kt b/domain/src/main/java/com/squirtles/domain/remote/firebase/FirebasePickRepository.kt new file mode 100644 index 00000000..25944ab7 --- /dev/null +++ b/domain/src/main/java/com/squirtles/domain/remote/firebase/FirebasePickRepository.kt @@ -0,0 +1,20 @@ +package com.squirtles.domain.remote.firebase + +import com.squirtles.domain.model.Pick + +interface FirebasePickRepository { + suspend fun createPick(pick: Pick): Result + suspend fun deletePick(pickId: String, userId: String): Result + suspend fun fetchPick(pickID: String): Result + suspend fun fetchPicksInArea(lat: Double, lng: Double, radiusInM: Double): Result> + suspend fun fetchMyPicks(userId: String): Result> + + suspend fun handleResult( + firebaseRepositoryException: FirebaseException, + call: suspend () -> T? + ): Result { + return runCatching { + call() ?: throw firebaseRepositoryException + } + } +} diff --git a/domain/src/main/java/com/squirtles/domain/firebase/FirebaseRemoteDataSource.kt b/domain/src/main/java/com/squirtles/domain/remote/firebase/FirebaseRemoteDataSource.kt similarity index 87% rename from domain/src/main/java/com/squirtles/domain/firebase/FirebaseRemoteDataSource.kt rename to domain/src/main/java/com/squirtles/domain/remote/firebase/FirebaseRemoteDataSource.kt index 7e7f1f45..0ec3e1b3 100644 --- a/domain/src/main/java/com/squirtles/domain/firebase/FirebaseRemoteDataSource.kt +++ b/domain/src/main/java/com/squirtles/domain/remote/firebase/FirebaseRemoteDataSource.kt @@ -1,22 +1,29 @@ -package com.squirtles.domain.firebase +package com.squirtles.domain.remote.firebase import com.squirtles.domain.model.Pick import com.squirtles.domain.model.User interface FirebaseRemoteDataSource { + + // user suspend fun createUser(): User? suspend fun fetchUser(userId: String): User? suspend fun updateUserName(userId: String, newUserName: String): Boolean + // fetch pick suspend fun fetchPick(pickID: String): Pick? suspend fun fetchPicksInArea(lat: Double, lng: Double, radiusInM: Double): List + + // pick CRUD suspend fun createPick(pick: Pick): String suspend fun deletePick(pickId: String, userId: String): Boolean + // pickList suspend fun fetchMyPicks(userId: String): List suspend fun fetchFavoritePicks(userId: String): List + + // favorite suspend fun fetchIsFavorite(pickId: String, userId: String): Boolean suspend fun createFavorite(pickId: String, userId: String): Boolean suspend fun deleteFavorite(pickId: String, userId: String): Boolean -// suspend fun updatePick(pick: Pick) } diff --git a/domain/src/main/java/com/squirtles/domain/remote/firebase/FirebaseRepository.kt b/domain/src/main/java/com/squirtles/domain/remote/firebase/FirebaseRepository.kt new file mode 100644 index 00000000..4ca7d7aa --- /dev/null +++ b/domain/src/main/java/com/squirtles/domain/remote/firebase/FirebaseRepository.kt @@ -0,0 +1,4 @@ +package com.squirtles.domain.remote.firebase + +interface FirebaseRepository { +} diff --git a/domain/src/main/java/com/squirtles/domain/remote/firebase/FirebaseUserRepository.kt b/domain/src/main/java/com/squirtles/domain/remote/firebase/FirebaseUserRepository.kt new file mode 100644 index 00000000..094b19ff --- /dev/null +++ b/domain/src/main/java/com/squirtles/domain/remote/firebase/FirebaseUserRepository.kt @@ -0,0 +1,19 @@ +package com.squirtles.domain.remote.firebase + +import com.squirtles.domain.model.User + +interface FirebaseUserRepository { + // user + suspend fun createUser(): Result + suspend fun fetchUser(userId: String): Result + suspend fun updateUserName(userId: String, newUserName: String): Result + + suspend fun handleResult( + firebaseRepositoryException: FirebaseException, + call: suspend () -> T? + ): Result { + return runCatching { + call() ?: throw firebaseRepositoryException + } + } +} diff --git a/domain/src/main/java/com/squirtles/domain/usecase/favorite/CreateFavoriteUseCase.kt b/domain/src/main/java/com/squirtles/domain/usecase/favorite/CreateFavoriteUseCase.kt index 93292a95..259f253b 100644 --- a/domain/src/main/java/com/squirtles/domain/usecase/favorite/CreateFavoriteUseCase.kt +++ b/domain/src/main/java/com/squirtles/domain/usecase/favorite/CreateFavoriteUseCase.kt @@ -1,11 +1,11 @@ package com.squirtles.domain.usecase.favorite -import com.squirtles.domain.firebase.FirebaseRepository +import com.squirtles.domain.remote.firebase.FirebaseFavoriteRepository import javax.inject.Inject class CreateFavoriteUseCase @Inject constructor( - private val firebaseRepository: FirebaseRepository + private val favoriteRepository: FirebaseFavoriteRepository ) { suspend operator fun invoke(pickId: String, userId: String) = - firebaseRepository.createFavorite(pickId, userId) + favoriteRepository.createFavorite(pickId, userId) } diff --git a/domain/src/main/java/com/squirtles/domain/usecase/favorite/DeleteFavoriteUseCase.kt b/domain/src/main/java/com/squirtles/domain/usecase/favorite/DeleteFavoriteUseCase.kt index e2a485f9..f9eba9ef 100644 --- a/domain/src/main/java/com/squirtles/domain/usecase/favorite/DeleteFavoriteUseCase.kt +++ b/domain/src/main/java/com/squirtles/domain/usecase/favorite/DeleteFavoriteUseCase.kt @@ -1,12 +1,12 @@ package com.squirtles.domain.usecase.favorite -import com.squirtles.domain.firebase.FirebaseRepository -import com.squirtles.domain.usecase.picklist.DeletePickListUseCaseInterface +import com.squirtles.domain.remote.firebase.FirebaseFavoriteRepository +import com.squirtles.domain.usecase.picklist.RemovePickUseCaseInterface import javax.inject.Inject class DeleteFavoriteUseCase @Inject constructor( - private val firebaseRepository: FirebaseRepository -) : DeletePickListUseCaseInterface { + private val favoriteRepository: FirebaseFavoriteRepository +) : RemovePickUseCaseInterface { override suspend operator fun invoke(pickId: String, userId: String) = - firebaseRepository.deleteFavorite(pickId, userId) + favoriteRepository.deleteFavorite(pickId, userId) } diff --git a/domain/src/main/java/com/squirtles/domain/usecase/favorite/FetchFavoritePicksUseCase.kt b/domain/src/main/java/com/squirtles/domain/usecase/favorite/FetchFavoritePicksUseCase.kt index ab3b8459..98ec2c28 100644 --- a/domain/src/main/java/com/squirtles/domain/usecase/favorite/FetchFavoritePicksUseCase.kt +++ b/domain/src/main/java/com/squirtles/domain/usecase/favorite/FetchFavoritePicksUseCase.kt @@ -1,11 +1,11 @@ package com.squirtles.domain.usecase.favorite -import com.squirtles.domain.firebase.FirebaseRepository +import com.squirtles.domain.remote.firebase.FirebaseFavoriteRepository import com.squirtles.domain.usecase.picklist.FetchPickListUseCaseInterface import javax.inject.Inject class FetchFavoritePicksUseCase @Inject constructor( - private val firebaseRepository: FirebaseRepository + private val favoriteRepository: FirebaseFavoriteRepository ) : FetchPickListUseCaseInterface { - override suspend operator fun invoke(userId: String) = firebaseRepository.fetchFavoritePicks(userId) + override suspend operator fun invoke(userId: String) = favoriteRepository.fetchFavoritePicks(userId) } diff --git a/domain/src/main/java/com/squirtles/domain/usecase/favorite/FetchIsFavoriteUseCase.kt b/domain/src/main/java/com/squirtles/domain/usecase/favorite/FetchIsFavoriteUseCase.kt new file mode 100644 index 00000000..43e6d5a5 --- /dev/null +++ b/domain/src/main/java/com/squirtles/domain/usecase/favorite/FetchIsFavoriteUseCase.kt @@ -0,0 +1,11 @@ +package com.squirtles.domain.usecase.favorite + +import com.squirtles.domain.remote.firebase.FirebaseFavoriteRepository +import javax.inject.Inject + +class FetchIsFavoriteUseCase @Inject constructor( + private val favoriteRepository: FirebaseFavoriteRepository +) { + suspend operator fun invoke(pickId: String, userId: String) = + favoriteRepository.fetchIsFavorite(pickId, userId) +} diff --git a/domain/src/main/java/com/squirtles/domain/usecase/music/FetchMusicVideoUseCase.kt b/domain/src/main/java/com/squirtles/domain/usecase/music/FetchMusicVideoUseCase.kt index 1f42c35b..afba97c3 100644 --- a/domain/src/main/java/com/squirtles/domain/usecase/music/FetchMusicVideoUseCase.kt +++ b/domain/src/main/java/com/squirtles/domain/usecase/music/FetchMusicVideoUseCase.kt @@ -2,7 +2,7 @@ package com.squirtles.domain.usecase.music import com.squirtles.domain.model.MusicVideo import com.squirtles.domain.model.Song -import com.squirtles.domain.applemusic.AppleMusicRepository +import com.squirtles.domain.remote.applemusic.AppleMusicRepository import javax.inject.Inject class FetchMusicVideoUseCase @Inject constructor( diff --git a/domain/src/main/java/com/squirtles/domain/usecase/music/FetchSongsUseCase.kt b/domain/src/main/java/com/squirtles/domain/usecase/music/FetchSongsUseCase.kt index 54370883..f4c3e2d3 100644 --- a/domain/src/main/java/com/squirtles/domain/usecase/music/FetchSongsUseCase.kt +++ b/domain/src/main/java/com/squirtles/domain/usecase/music/FetchSongsUseCase.kt @@ -1,6 +1,6 @@ package com.squirtles.domain.usecase.music -import com.squirtles.domain.applemusic.AppleMusicRepository +import com.squirtles.domain.remote.applemusic.AppleMusicRepository import javax.inject.Inject class FetchSongsUseCase @Inject constructor( diff --git a/domain/src/main/java/com/squirtles/domain/usecase/mypick/CreatePickUseCase.kt b/domain/src/main/java/com/squirtles/domain/usecase/mypick/CreatePickUseCase.kt deleted file mode 100644 index f0816446..00000000 --- a/domain/src/main/java/com/squirtles/domain/usecase/mypick/CreatePickUseCase.kt +++ /dev/null @@ -1,11 +0,0 @@ -package com.squirtles.domain.usecase.mypick - -import com.squirtles.domain.model.Pick -import com.squirtles.domain.firebase.FirebaseRepository -import javax.inject.Inject - -class CreatePickUseCase @Inject constructor( - private val firebaseRepository: FirebaseRepository -) { - suspend operator fun invoke(pick: Pick): Result = firebaseRepository.createPick(pick) -} diff --git a/domain/src/main/java/com/squirtles/domain/usecase/mypick/DeletePickUseCase.kt b/domain/src/main/java/com/squirtles/domain/usecase/mypick/DeletePickUseCase.kt deleted file mode 100644 index 98985e75..00000000 --- a/domain/src/main/java/com/squirtles/domain/usecase/mypick/DeletePickUseCase.kt +++ /dev/null @@ -1,12 +0,0 @@ -package com.squirtles.domain.usecase.mypick - -import com.squirtles.domain.firebase.FirebaseRepository -import com.squirtles.domain.usecase.picklist.DeletePickListUseCaseInterface -import javax.inject.Inject - -class DeletePickUseCase @Inject constructor( - private val firebaseRepository: FirebaseRepository -) : DeletePickListUseCaseInterface { - override suspend operator fun invoke(pickId: String, userId: String): Result = - firebaseRepository.deletePick(pickId, userId) -} diff --git a/domain/src/main/java/com/squirtles/domain/usecase/pick/CreatePickUseCase.kt b/domain/src/main/java/com/squirtles/domain/usecase/pick/CreatePickUseCase.kt new file mode 100644 index 00000000..cb8a33ef --- /dev/null +++ b/domain/src/main/java/com/squirtles/domain/usecase/pick/CreatePickUseCase.kt @@ -0,0 +1,11 @@ +package com.squirtles.domain.usecase.pick + +import com.squirtles.domain.model.Pick +import com.squirtles.domain.remote.firebase.FirebasePickRepository +import javax.inject.Inject + +class CreatePickUseCase @Inject constructor( + private val pickRepository: FirebasePickRepository +) { + suspend operator fun invoke(pick: Pick): Result = pickRepository.createPick(pick) +} diff --git a/domain/src/main/java/com/squirtles/domain/usecase/pick/DeletePickUseCase.kt b/domain/src/main/java/com/squirtles/domain/usecase/pick/DeletePickUseCase.kt new file mode 100644 index 00000000..582c25ba --- /dev/null +++ b/domain/src/main/java/com/squirtles/domain/usecase/pick/DeletePickUseCase.kt @@ -0,0 +1,12 @@ +package com.squirtles.domain.usecase.pick + +import com.squirtles.domain.remote.firebase.FirebasePickRepository +import com.squirtles.domain.usecase.picklist.RemovePickUseCaseInterface +import javax.inject.Inject + +class DeletePickUseCase @Inject constructor( + private val pickRepository: FirebasePickRepository +) : RemovePickUseCaseInterface { + override suspend operator fun invoke(pickId: String, userId: String): Result = + pickRepository.deletePick(pickId, userId) +} diff --git a/domain/src/main/java/com/squirtles/domain/usecase/pick/FetchIsFavoriteUseCase.kt b/domain/src/main/java/com/squirtles/domain/usecase/pick/FetchIsFavoriteUseCase.kt deleted file mode 100644 index 41ff0315..00000000 --- a/domain/src/main/java/com/squirtles/domain/usecase/pick/FetchIsFavoriteUseCase.kt +++ /dev/null @@ -1,11 +0,0 @@ -package com.squirtles.domain.usecase.pick - -import com.squirtles.domain.firebase.FirebaseRepository -import javax.inject.Inject - -class FetchIsFavoriteUseCase @Inject constructor( - private val firebaseRepository: FirebaseRepository -) { - suspend operator fun invoke(pickId: String, userId: String) = - firebaseRepository.fetchIsFavorite(pickId, userId) -} diff --git a/domain/src/main/java/com/squirtles/domain/usecase/mypick/FetchMyPicksUseCase.kt b/domain/src/main/java/com/squirtles/domain/usecase/pick/FetchMyPicksUseCase.kt similarity index 54% rename from domain/src/main/java/com/squirtles/domain/usecase/mypick/FetchMyPicksUseCase.kt rename to domain/src/main/java/com/squirtles/domain/usecase/pick/FetchMyPicksUseCase.kt index 4774ad82..e0e04e3f 100644 --- a/domain/src/main/java/com/squirtles/domain/usecase/mypick/FetchMyPicksUseCase.kt +++ b/domain/src/main/java/com/squirtles/domain/usecase/pick/FetchMyPicksUseCase.kt @@ -1,12 +1,12 @@ -package com.squirtles.domain.usecase.mypick +package com.squirtles.domain.usecase.pick -import com.squirtles.domain.firebase.FirebaseRepository +import com.squirtles.domain.remote.firebase.FirebasePickRepository import com.squirtles.domain.usecase.picklist.FetchPickListUseCaseInterface import javax.inject.Inject class FetchMyPicksUseCase @Inject constructor( - private val firebaseRepository: FirebaseRepository + private val pickRepository: FirebasePickRepository ) : FetchPickListUseCaseInterface { override suspend operator fun invoke(userId: String) = - firebaseRepository.fetchMyPicks(userId) + pickRepository.fetchMyPicks(userId) } diff --git a/domain/src/main/java/com/squirtles/domain/usecase/pick/FetchPickInAreaUseCase.kt b/domain/src/main/java/com/squirtles/domain/usecase/pick/FetchPickInAreaUseCase.kt deleted file mode 100644 index 053d230e..00000000 --- a/domain/src/main/java/com/squirtles/domain/usecase/pick/FetchPickInAreaUseCase.kt +++ /dev/null @@ -1,11 +0,0 @@ -package com.squirtles.domain.usecase.pick - -import com.squirtles.domain.firebase.FirebaseRepository -import javax.inject.Inject - -class FetchPickInAreaUseCase @Inject constructor( - private val firebaseRepository: FirebaseRepository -) { - suspend operator fun invoke(lat: Double, lng: Double, radiusInM: Double) = - firebaseRepository.fetchPicksInArea(lat, lng, radiusInM) -} diff --git a/domain/src/main/java/com/squirtles/domain/usecase/pick/FetchPickUseCase.kt b/domain/src/main/java/com/squirtles/domain/usecase/pick/FetchPickUseCase.kt index 37c7e62b..7debcf4b 100644 --- a/domain/src/main/java/com/squirtles/domain/usecase/pick/FetchPickUseCase.kt +++ b/domain/src/main/java/com/squirtles/domain/usecase/pick/FetchPickUseCase.kt @@ -1,14 +1,14 @@ package com.squirtles.domain.usecase.pick -import com.squirtles.domain.firebase.FirebaseRepository +import com.squirtles.domain.remote.firebase.FirebasePickRepository import javax.inject.Inject class FetchPickUseCase @Inject constructor( - private val firebaseRepository: FirebaseRepository + private val pickRepository: FirebasePickRepository ) { suspend operator fun invoke(pickId: String) = - firebaseRepository.fetchPick(pickId) + pickRepository.fetchPick(pickId) suspend operator fun invoke(lat: Double, lng: Double, radiusInM: Double) = - firebaseRepository.fetchPicksInArea(lat, lng, radiusInM) + pickRepository.fetchPicksInArea(lat, lng, radiusInM) } diff --git a/domain/src/main/java/com/squirtles/domain/usecase/picklist/DeletePickListUseCaseInterface.kt b/domain/src/main/java/com/squirtles/domain/usecase/picklist/RemovePickUseCaseInterface.kt similarity index 75% rename from domain/src/main/java/com/squirtles/domain/usecase/picklist/DeletePickListUseCaseInterface.kt rename to domain/src/main/java/com/squirtles/domain/usecase/picklist/RemovePickUseCaseInterface.kt index 3a7a58ed..06c90a4f 100644 --- a/domain/src/main/java/com/squirtles/domain/usecase/picklist/DeletePickListUseCaseInterface.kt +++ b/domain/src/main/java/com/squirtles/domain/usecase/picklist/RemovePickUseCaseInterface.kt @@ -1,5 +1,5 @@ package com.squirtles.domain.usecase.picklist -interface DeletePickListUseCaseInterface { +interface RemovePickUseCaseInterface { suspend operator fun invoke(pickId: String, userId: String): Result } diff --git a/domain/src/main/java/com/squirtles/domain/usecase/user/CreateNewUserUseCase.kt b/domain/src/main/java/com/squirtles/domain/usecase/user/CreateNewUserUseCase.kt index 65730f1b..431b0fa6 100644 --- a/domain/src/main/java/com/squirtles/domain/usecase/user/CreateNewUserUseCase.kt +++ b/domain/src/main/java/com/squirtles/domain/usecase/user/CreateNewUserUseCase.kt @@ -1,17 +1,17 @@ package com.squirtles.domain.usecase.user -import com.squirtles.domain.model.User -import com.squirtles.domain.firebase.FirebaseRepository import com.squirtles.domain.local.LocalRepository +import com.squirtles.domain.model.User +import com.squirtles.domain.remote.firebase.FirebaseUserRepository import javax.inject.Inject class CreateNewUserUseCase @Inject constructor( private val localRepository: LocalRepository, - private val firebaseRepository: FirebaseRepository + private val userRepository: FirebaseUserRepository ) { suspend operator fun invoke(): Result { - val createdUser = firebaseRepository.createUser() // Firebase에 유저 생성 + val createdUser = userRepository.createUser() // Firebase에 유저 생성 .onSuccess { user -> localRepository.writeUserIdDataStore(user.userId) // 생성된 유저의 userId를 DataStore에 저장 후 user 반환 localRepository.saveCurrentUser(user) // 생성된 유저를 LocalDataSource currentUser 에 저장 diff --git a/domain/src/main/java/com/squirtles/domain/usecase/user/FetchUserByIdUseCase.kt b/domain/src/main/java/com/squirtles/domain/usecase/user/FetchUserByIdUseCase.kt index 780d551b..15542bdb 100644 --- a/domain/src/main/java/com/squirtles/domain/usecase/user/FetchUserByIdUseCase.kt +++ b/domain/src/main/java/com/squirtles/domain/usecase/user/FetchUserByIdUseCase.kt @@ -1,11 +1,11 @@ package com.squirtles.domain.usecase.user -import com.squirtles.domain.firebase.FirebaseRepository +import com.squirtles.domain.remote.firebase.FirebaseUserRepository import javax.inject.Inject class FetchUserByIdUseCase @Inject constructor( - private val firebaseRepository: FirebaseRepository + private val userRepository: FirebaseUserRepository ) { suspend operator fun invoke(userId: String) = - firebaseRepository.fetchUser(userId) + userRepository.fetchUser(userId) } diff --git a/domain/src/main/java/com/squirtles/domain/usecase/user/UpdateUserNameUseCase.kt b/domain/src/main/java/com/squirtles/domain/usecase/user/UpdateUserNameUseCase.kt index a31c886b..e2c9a123 100644 --- a/domain/src/main/java/com/squirtles/domain/usecase/user/UpdateUserNameUseCase.kt +++ b/domain/src/main/java/com/squirtles/domain/usecase/user/UpdateUserNameUseCase.kt @@ -1,11 +1,11 @@ package com.squirtles.domain.usecase.user -import com.squirtles.domain.firebase.FirebaseRepository +import com.squirtles.domain.remote.firebase.FirebaseUserRepository import javax.inject.Inject class UpdateUserNameUseCase @Inject constructor( - private val firebaseRepository: FirebaseRepository + private val userRepository: FirebaseUserRepository ) { suspend operator fun invoke(userId: String, newUserName: String) = - firebaseRepository.updateUserName(userId, newUserName) + userRepository.updateUserName(userId, newUserName) } From bceff1a64a657e329aea326ce3652bf5a208d7ed Mon Sep 17 00:00:00 2001 From: miller198 Date: Tue, 11 Feb 2025 21:02:10 +0900 Subject: [PATCH 04/62] =?UTF-8?q?[refactor]=20develop=20merge=20=ED=9B=84?= =?UTF-8?q?=20=EC=A0=95=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/com/squirtles/musicroad/main/MainViewModel.kt | 2 +- .../remote/firebase/FirebaseDataSourceImpl.kt | 2 +- data/src/main/java/com/squirtles/data/di/DataModule.kt | 2 +- .../data/repository/FirebaseFavoriteRepositoryImpl.kt | 2 +- .../data/repository/FirebasePickRepositoryImpl.kt | 2 +- .../data/repository/FirebaseRepositoryImpl.kt | 2 +- .../data/repository/FirebaseUserRepositoryImpl.kt | 10 +++++++--- .../domain/remote/firebase/FirebaseUserRepository.kt | 2 +- .../domain/usecase/user/CreateGoogleIdUserUseCase.kt | 6 +++--- 9 files changed, 17 insertions(+), 13 deletions(-) diff --git a/app/src/main/java/com/squirtles/musicroad/main/MainViewModel.kt b/app/src/main/java/com/squirtles/musicroad/main/MainViewModel.kt index a4096af8..90e52527 100644 --- a/app/src/main/java/com/squirtles/musicroad/main/MainViewModel.kt +++ b/app/src/main/java/com/squirtles/musicroad/main/MainViewModel.kt @@ -2,7 +2,7 @@ package com.squirtles.musicroad.main import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope -import com.squirtles.domain.firebase.FirebaseException +import com.squirtles.domain.remote.firebase.FirebaseException import com.squirtles.domain.usecase.user.FetchUserUseCase import com.squirtles.domain.usecase.user.GetUserIdFromDataStoreUseCase import dagger.hilt.android.lifecycle.HiltViewModel diff --git a/data/src/main/java/com/squirtles/data/datasource/remote/firebase/FirebaseDataSourceImpl.kt b/data/src/main/java/com/squirtles/data/datasource/remote/firebase/FirebaseDataSourceImpl.kt index 891714f9..41b205f7 100644 --- a/data/src/main/java/com/squirtles/data/datasource/remote/firebase/FirebaseDataSourceImpl.kt +++ b/data/src/main/java/com/squirtles/data/datasource/remote/firebase/FirebaseDataSourceImpl.kt @@ -18,9 +18,9 @@ import com.squirtles.data.datasource.remote.firebase.model.FirebaseUser import com.squirtles.data.mapper.toFirebasePick import com.squirtles.data.mapper.toPick import com.squirtles.data.mapper.toUser +import com.squirtles.domain.firebase.FirebaseRemoteDataSource import com.squirtles.domain.model.Pick import com.squirtles.domain.model.User -import com.squirtles.domain.remote.firebase.FirebaseRemoteDataSource import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.launch diff --git a/data/src/main/java/com/squirtles/data/di/DataModule.kt b/data/src/main/java/com/squirtles/data/di/DataModule.kt index ec6dbc96..81600bad 100644 --- a/data/src/main/java/com/squirtles/data/di/DataModule.kt +++ b/data/src/main/java/com/squirtles/data/di/DataModule.kt @@ -12,13 +12,13 @@ import com.squirtles.data.repository.FirebasePickRepositoryImpl import com.squirtles.data.repository.FirebaseRepositoryImpl import com.squirtles.data.repository.FirebaseUserRepositoryImpl import com.squirtles.data.repository.LocalRepositoryImpl +import com.squirtles.domain.firebase.FirebaseRemoteDataSource import com.squirtles.domain.local.LocalDataSource import com.squirtles.domain.local.LocalRepository import com.squirtles.domain.remote.applemusic.AppleMusicRemoteDataSource import com.squirtles.domain.remote.applemusic.AppleMusicRepository import com.squirtles.domain.remote.firebase.FirebaseFavoriteRepository import com.squirtles.domain.remote.firebase.FirebasePickRepository -import com.squirtles.domain.remote.firebase.FirebaseRemoteDataSource import com.squirtles.domain.remote.firebase.FirebaseRepository import com.squirtles.domain.remote.firebase.FirebaseUserRepository import dagger.Module diff --git a/data/src/main/java/com/squirtles/data/repository/FirebaseFavoriteRepositoryImpl.kt b/data/src/main/java/com/squirtles/data/repository/FirebaseFavoriteRepositoryImpl.kt index 2e95dbdc..d652f751 100644 --- a/data/src/main/java/com/squirtles/data/repository/FirebaseFavoriteRepositoryImpl.kt +++ b/data/src/main/java/com/squirtles/data/repository/FirebaseFavoriteRepositoryImpl.kt @@ -1,9 +1,9 @@ package com.squirtles.data.repository +import com.squirtles.domain.firebase.FirebaseRemoteDataSource import com.squirtles.domain.model.Pick import com.squirtles.domain.remote.RemoteRepository import com.squirtles.domain.remote.firebase.FirebaseFavoriteRepository -import com.squirtles.domain.remote.firebase.FirebaseRemoteDataSource class FirebaseFavoriteRepositoryImpl( diff --git a/data/src/main/java/com/squirtles/data/repository/FirebasePickRepositoryImpl.kt b/data/src/main/java/com/squirtles/data/repository/FirebasePickRepositoryImpl.kt index a8965ac2..f2eb10a0 100644 --- a/data/src/main/java/com/squirtles/data/repository/FirebasePickRepositoryImpl.kt +++ b/data/src/main/java/com/squirtles/data/repository/FirebasePickRepositoryImpl.kt @@ -1,10 +1,10 @@ package com.squirtles.data.repository +import com.squirtles.domain.firebase.FirebaseRemoteDataSource import com.squirtles.domain.model.Pick import com.squirtles.domain.remote.RemoteRepository import com.squirtles.domain.remote.firebase.FirebaseException import com.squirtles.domain.remote.firebase.FirebasePickRepository -import com.squirtles.domain.remote.firebase.FirebaseRemoteDataSource class FirebasePickRepositoryImpl( private val firebaseRemoteDataSource: FirebaseRemoteDataSource diff --git a/data/src/main/java/com/squirtles/data/repository/FirebaseRepositoryImpl.kt b/data/src/main/java/com/squirtles/data/repository/FirebaseRepositoryImpl.kt index 78d3be87..0dd193fe 100644 --- a/data/src/main/java/com/squirtles/data/repository/FirebaseRepositoryImpl.kt +++ b/data/src/main/java/com/squirtles/data/repository/FirebaseRepositoryImpl.kt @@ -1,8 +1,8 @@ package com.squirtles.data.repository +import com.squirtles.domain.firebase.FirebaseRemoteDataSource import com.squirtles.domain.remote.RemoteRepository import com.squirtles.domain.remote.firebase.FirebaseException -import com.squirtles.domain.remote.firebase.FirebaseRemoteDataSource import com.squirtles.domain.remote.firebase.FirebaseRepository import javax.inject.Inject import javax.inject.Singleton diff --git a/data/src/main/java/com/squirtles/data/repository/FirebaseUserRepositoryImpl.kt b/data/src/main/java/com/squirtles/data/repository/FirebaseUserRepositoryImpl.kt index 175ec986..9e421254 100644 --- a/data/src/main/java/com/squirtles/data/repository/FirebaseUserRepositoryImpl.kt +++ b/data/src/main/java/com/squirtles/data/repository/FirebaseUserRepositoryImpl.kt @@ -1,18 +1,22 @@ package com.squirtles.data.repository +import com.squirtles.domain.firebase.FirebaseRemoteDataSource import com.squirtles.domain.model.User import com.squirtles.domain.remote.RemoteRepository import com.squirtles.domain.remote.firebase.FirebaseException -import com.squirtles.domain.remote.firebase.FirebaseRemoteDataSource import com.squirtles.domain.remote.firebase.FirebaseUserRepository class FirebaseUserRepositoryImpl( private val firebaseRemoteDataSource: FirebaseRemoteDataSource ) : FirebaseUserRepository, RemoteRepository() { - override suspend fun createUser(): Result { + override suspend fun createGoogleIdUser( + userId: String, + userName: String?, + userProfileImage: String? + ): Result { return handleResult(FirebaseException.CreatedUserFailedException()) { - firebaseRemoteDataSource.createUser() + firebaseRemoteDataSource.createGoogleIdUser(userId, userName, userProfileImage) } } diff --git a/domain/src/main/java/com/squirtles/domain/remote/firebase/FirebaseUserRepository.kt b/domain/src/main/java/com/squirtles/domain/remote/firebase/FirebaseUserRepository.kt index 094b19ff..409a138f 100644 --- a/domain/src/main/java/com/squirtles/domain/remote/firebase/FirebaseUserRepository.kt +++ b/domain/src/main/java/com/squirtles/domain/remote/firebase/FirebaseUserRepository.kt @@ -4,7 +4,7 @@ import com.squirtles.domain.model.User interface FirebaseUserRepository { // user - suspend fun createUser(): Result + suspend fun createGoogleIdUser(userId: String, userName: String?, userProfileImage: String?): Result suspend fun fetchUser(userId: String): Result suspend fun updateUserName(userId: String, newUserName: String): Result diff --git a/domain/src/main/java/com/squirtles/domain/usecase/user/CreateGoogleIdUserUseCase.kt b/domain/src/main/java/com/squirtles/domain/usecase/user/CreateGoogleIdUserUseCase.kt index f0f1a9d0..240866b0 100644 --- a/domain/src/main/java/com/squirtles/domain/usecase/user/CreateGoogleIdUserUseCase.kt +++ b/domain/src/main/java/com/squirtles/domain/usecase/user/CreateGoogleIdUserUseCase.kt @@ -1,20 +1,20 @@ package com.squirtles.domain.usecase.user -import com.squirtles.domain.firebase.FirebaseRepository import com.squirtles.domain.local.LocalRepository import com.squirtles.domain.model.User +import com.squirtles.domain.remote.firebase.FirebaseUserRepository import javax.inject.Inject class CreateGoogleIdUserUseCase @Inject constructor( private val localRepository: LocalRepository, - private val firebaseRepository: FirebaseRepository + private val userRepository: FirebaseUserRepository ) { suspend operator fun invoke( userId: String, userName: String? = null, userProfileImage: String? = null ): Result { - val createdUser = firebaseRepository.createGoogleIdUser(userId, userName, userProfileImage) + val createdUser = userRepository.createGoogleIdUser(userId, userName, userProfileImage) .onSuccess { user -> // 생성된 유저의 userId 저장 후 user 반환 localRepository.saveUserIdDataStore(user.userId) From b88c7292ca01bb5803ac07f9c1c28a8bda359c47 Mon Sep 17 00:00:00 2001 From: miller198 Date: Tue, 11 Feb 2025 22:27:13 +0900 Subject: [PATCH 05/62] =?UTF-8?q?[chore]=20=ED=8C=A8=ED=82=A4=EC=A7=80=20?= =?UTF-8?q?=EC=A0=95=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../squirtles/musicroad/main/MainViewModel.kt | 2 +- .../datasource/local/LocalDataSourceImpl.kt | 2 +- .../applemusic/AppleMusicDataSourceImpl.kt | 2 +- .../remote/firebase/FirebaseDataSourceImpl.kt | 2 +- .../java/com/squirtles/data/di/DataModule.kt | 30 +++++++++---------- .../{ => local}/LocalRepositoryImpl.kt | 6 ++-- .../applemusic}/AppleMusicRepositoryImpl.kt | 10 +++---- .../FirebaseFavoriteRepositoryImpl.kt | 8 ++--- .../firebase}/FirebasePickRepositoryImpl.kt | 10 +++---- .../firebase}/FirebaseRepositoryImpl.kt | 10 +++---- .../firebase}/FirebaseUserRepositoryImpl.kt | 10 +++---- .../{ => datasource}/local/LocalDataSource.kt | 2 +- .../applemusic/AppleMusicRemoteDataSource.kt | 2 +- .../firebase/FirebaseRemoteDataSource.kt | 15 +++++++++- .../remote/firebase/FirebaseRepository.kt | 4 --- .../{ => repository}/local/LocalRepository.kt | 2 +- .../remote/RemoteRepository.kt | 2 +- .../remote/applemusic/AppleMusicException.kt | 2 +- .../remote/applemusic/AppleMusicRepository.kt | 2 +- .../remote/firebase/FirebaseException.kt | 2 +- .../firebase/FirebaseFavoriteRepository.kt | 2 +- .../remote/firebase/FirebasePickRepository.kt | 2 +- .../remote/firebase/FirebaseRepository.kt | 4 +++ .../remote/firebase/FirebaseUserRepository.kt | 2 +- .../usecase/favorite/CreateFavoriteUseCase.kt | 2 +- .../usecase/favorite/DeleteFavoriteUseCase.kt | 2 +- .../favorite/FetchFavoritePicksUseCase.kt | 2 +- .../favorite/FetchIsFavoriteUseCase.kt | 2 +- .../location/GetLastLocationUseCase.kt | 2 +- .../location/SaveLastLocationUseCase.kt | 2 +- .../usecase/music/FetchMusicVideoUseCase.kt | 2 +- .../domain/usecase/music/FetchSongsUseCase.kt | 2 +- .../order/GetFavoriteListOrderUseCase.kt | 2 +- .../order/GetMyPickListOrderUseCase.kt | 2 +- .../order/SaveFavoriteListOrderUseCase.kt | 2 +- .../order/SaveMyPickListOrderUseCase.kt | 2 +- .../domain/usecase/pick/CreatePickUseCase.kt | 2 +- .../domain/usecase/pick/DeletePickUseCase.kt | 2 +- .../usecase/pick/FetchMyPicksUseCase.kt | 2 +- .../domain/usecase/pick/FetchPickUseCase.kt | 2 +- .../domain/usecase/user/ClearUserUseCase.kt | 2 +- .../usecase/user/CreateGoogleIdUserUseCase.kt | 4 +-- .../usecase/user/FetchUserByIdUseCase.kt | 2 +- .../domain/usecase/user/FetchUserUseCase.kt | 2 +- .../usecase/user/GetCurrentUserUseCase.kt | 2 +- .../user/GetUserIdFromDataStoreUseCase.kt | 2 +- .../usecase/user/UpdateUserNameUseCase.kt | 2 +- 47 files changed, 98 insertions(+), 85 deletions(-) rename data/src/main/java/com/squirtles/data/repository/{ => local}/LocalRepositoryImpl.kt (90%) rename data/src/main/java/com/squirtles/data/repository/{ => remote/applemusic}/AppleMusicRepositoryImpl.kt (75%) rename data/src/main/java/com/squirtles/data/repository/{ => remote/firebase}/FirebaseFavoriteRepositoryImpl.kt (78%) rename data/src/main/java/com/squirtles/data/repository/{ => remote/firebase}/FirebasePickRepositoryImpl.kt (78%) rename data/src/main/java/com/squirtles/data/repository/{ => remote/firebase}/FirebaseRepositoryImpl.kt (58%) rename data/src/main/java/com/squirtles/data/repository/{ => remote/firebase}/FirebaseUserRepositoryImpl.kt (73%) rename domain/src/main/java/com/squirtles/domain/{ => datasource}/local/LocalDataSource.kt (93%) rename domain/src/main/java/com/squirtles/domain/{ => datasource}/remote/applemusic/AppleMusicRemoteDataSource.kt (86%) rename domain/src/main/java/com/squirtles/domain/{ => datasource}/remote/firebase/FirebaseRemoteDataSource.kt (66%) delete mode 100644 domain/src/main/java/com/squirtles/domain/remote/firebase/FirebaseRepository.kt rename domain/src/main/java/com/squirtles/domain/{ => repository}/local/LocalRepository.kt (94%) rename domain/src/main/java/com/squirtles/domain/{ => repository}/remote/RemoteRepository.kt (79%) rename domain/src/main/java/com/squirtles/domain/{ => repository}/remote/applemusic/AppleMusicException.kt (87%) rename domain/src/main/java/com/squirtles/domain/{ => repository}/remote/applemusic/AppleMusicRepository.kt (87%) rename domain/src/main/java/com/squirtles/domain/{ => repository}/remote/firebase/FirebaseException.kt (91%) rename domain/src/main/java/com/squirtles/domain/{ => repository}/remote/firebase/FirebaseFavoriteRepository.kt (87%) rename domain/src/main/java/com/squirtles/domain/{ => repository}/remote/firebase/FirebasePickRepository.kt (92%) create mode 100644 domain/src/main/java/com/squirtles/domain/repository/remote/firebase/FirebaseRepository.kt rename domain/src/main/java/com/squirtles/domain/{ => repository}/remote/firebase/FirebaseUserRepository.kt (91%) diff --git a/app/src/main/java/com/squirtles/musicroad/main/MainViewModel.kt b/app/src/main/java/com/squirtles/musicroad/main/MainViewModel.kt index 90e52527..b959bb7f 100644 --- a/app/src/main/java/com/squirtles/musicroad/main/MainViewModel.kt +++ b/app/src/main/java/com/squirtles/musicroad/main/MainViewModel.kt @@ -2,7 +2,7 @@ package com.squirtles.musicroad.main import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope -import com.squirtles.domain.remote.firebase.FirebaseException +import com.squirtles.domain.repository.remote.firebase.FirebaseException import com.squirtles.domain.usecase.user.FetchUserUseCase import com.squirtles.domain.usecase.user.GetUserIdFromDataStoreUseCase import dagger.hilt.android.lifecycle.HiltViewModel diff --git a/data/src/main/java/com/squirtles/data/datasource/local/LocalDataSourceImpl.kt b/data/src/main/java/com/squirtles/data/datasource/local/LocalDataSourceImpl.kt index 9c421ec1..e5824a05 100644 --- a/data/src/main/java/com/squirtles/data/datasource/local/LocalDataSourceImpl.kt +++ b/data/src/main/java/com/squirtles/data/datasource/local/LocalDataSourceImpl.kt @@ -5,7 +5,7 @@ import android.location.Location import androidx.datastore.preferences.core.edit import androidx.datastore.preferences.core.stringPreferencesKey import androidx.datastore.preferences.preferencesDataStore -import com.squirtles.domain.local.LocalDataSource +import com.squirtles.domain.datasource.local.LocalDataSource import com.squirtles.domain.model.Order import com.squirtles.domain.model.User import kotlinx.coroutines.flow.Flow diff --git a/data/src/main/java/com/squirtles/data/datasource/remote/applemusic/AppleMusicDataSourceImpl.kt b/data/src/main/java/com/squirtles/data/datasource/remote/applemusic/AppleMusicDataSourceImpl.kt index b6aa32f7..e0c6a47e 100644 --- a/data/src/main/java/com/squirtles/data/datasource/remote/applemusic/AppleMusicDataSourceImpl.kt +++ b/data/src/main/java/com/squirtles/data/datasource/remote/applemusic/AppleMusicDataSourceImpl.kt @@ -7,9 +7,9 @@ import com.squirtles.data.datasource.remote.applemusic.SearchSongsPagingSource.C import com.squirtles.data.datasource.remote.applemusic.api.AppleMusicApi import com.squirtles.data.datasource.remote.applemusic.model.SearchResponse import com.squirtles.data.mapper.toMusicVideo +import com.squirtles.domain.datasource.remote.applemusic.AppleMusicRemoteDataSource import com.squirtles.domain.model.MusicVideo import com.squirtles.domain.model.Song -import com.squirtles.domain.remote.applemusic.AppleMusicRemoteDataSource import kotlinx.coroutines.flow.Flow import retrofit2.Response import javax.inject.Inject diff --git a/data/src/main/java/com/squirtles/data/datasource/remote/firebase/FirebaseDataSourceImpl.kt b/data/src/main/java/com/squirtles/data/datasource/remote/firebase/FirebaseDataSourceImpl.kt index 41b205f7..8b216dad 100644 --- a/data/src/main/java/com/squirtles/data/datasource/remote/firebase/FirebaseDataSourceImpl.kt +++ b/data/src/main/java/com/squirtles/data/datasource/remote/firebase/FirebaseDataSourceImpl.kt @@ -18,7 +18,7 @@ import com.squirtles.data.datasource.remote.firebase.model.FirebaseUser import com.squirtles.data.mapper.toFirebasePick import com.squirtles.data.mapper.toPick import com.squirtles.data.mapper.toUser -import com.squirtles.domain.firebase.FirebaseRemoteDataSource +import com.squirtles.domain.datasource.remote.firebase.FirebaseRemoteDataSource import com.squirtles.domain.model.Pick import com.squirtles.domain.model.User import kotlinx.coroutines.CoroutineScope diff --git a/data/src/main/java/com/squirtles/data/di/DataModule.kt b/data/src/main/java/com/squirtles/data/di/DataModule.kt index 81600bad..b1bfe047 100644 --- a/data/src/main/java/com/squirtles/data/di/DataModule.kt +++ b/data/src/main/java/com/squirtles/data/di/DataModule.kt @@ -6,21 +6,21 @@ import com.squirtles.data.datasource.local.LocalDataSourceImpl import com.squirtles.data.datasource.remote.applemusic.AppleMusicDataSourceImpl import com.squirtles.data.datasource.remote.applemusic.api.AppleMusicApi import com.squirtles.data.datasource.remote.firebase.FirebaseDataSourceImpl -import com.squirtles.data.repository.AppleMusicRepositoryImpl -import com.squirtles.data.repository.FirebaseFavoriteRepositoryImpl -import com.squirtles.data.repository.FirebasePickRepositoryImpl -import com.squirtles.data.repository.FirebaseRepositoryImpl -import com.squirtles.data.repository.FirebaseUserRepositoryImpl -import com.squirtles.data.repository.LocalRepositoryImpl -import com.squirtles.domain.firebase.FirebaseRemoteDataSource -import com.squirtles.domain.local.LocalDataSource -import com.squirtles.domain.local.LocalRepository -import com.squirtles.domain.remote.applemusic.AppleMusicRemoteDataSource -import com.squirtles.domain.remote.applemusic.AppleMusicRepository -import com.squirtles.domain.remote.firebase.FirebaseFavoriteRepository -import com.squirtles.domain.remote.firebase.FirebasePickRepository -import com.squirtles.domain.remote.firebase.FirebaseRepository -import com.squirtles.domain.remote.firebase.FirebaseUserRepository +import com.squirtles.data.repository.local.LocalRepositoryImpl +import com.squirtles.data.repository.remote.applemusic.AppleMusicRepositoryImpl +import com.squirtles.data.repository.remote.firebase.FirebaseFavoriteRepositoryImpl +import com.squirtles.data.repository.remote.firebase.FirebasePickRepositoryImpl +import com.squirtles.data.repository.remote.firebase.FirebaseRepositoryImpl +import com.squirtles.data.repository.remote.firebase.FirebaseUserRepositoryImpl +import com.squirtles.domain.datasource.local.LocalDataSource +import com.squirtles.domain.datasource.remote.applemusic.AppleMusicRemoteDataSource +import com.squirtles.domain.datasource.remote.firebase.FirebaseRemoteDataSource +import com.squirtles.domain.repository.local.LocalRepository +import com.squirtles.domain.repository.remote.applemusic.AppleMusicRepository +import com.squirtles.domain.repository.remote.firebase.FirebaseFavoriteRepository +import com.squirtles.domain.repository.remote.firebase.FirebasePickRepository +import com.squirtles.domain.repository.remote.firebase.FirebaseRepository +import com.squirtles.domain.repository.remote.firebase.FirebaseUserRepository import dagger.Module import dagger.Provides import dagger.hilt.InstallIn diff --git a/data/src/main/java/com/squirtles/data/repository/LocalRepositoryImpl.kt b/data/src/main/java/com/squirtles/data/repository/local/LocalRepositoryImpl.kt similarity index 90% rename from data/src/main/java/com/squirtles/data/repository/LocalRepositoryImpl.kt rename to data/src/main/java/com/squirtles/data/repository/local/LocalRepositoryImpl.kt index c698674f..07eb771e 100644 --- a/data/src/main/java/com/squirtles/data/repository/LocalRepositoryImpl.kt +++ b/data/src/main/java/com/squirtles/data/repository/local/LocalRepositoryImpl.kt @@ -1,10 +1,10 @@ -package com.squirtles.data.repository +package com.squirtles.data.repository.local import android.location.Location -import com.squirtles.domain.local.LocalDataSource -import com.squirtles.domain.local.LocalRepository +import com.squirtles.domain.datasource.local.LocalDataSource import com.squirtles.domain.model.Order import com.squirtles.domain.model.User +import com.squirtles.domain.repository.local.LocalRepository import kotlinx.coroutines.flow.Flow import javax.inject.Inject diff --git a/data/src/main/java/com/squirtles/data/repository/AppleMusicRepositoryImpl.kt b/data/src/main/java/com/squirtles/data/repository/remote/applemusic/AppleMusicRepositoryImpl.kt similarity index 75% rename from data/src/main/java/com/squirtles/data/repository/AppleMusicRepositoryImpl.kt rename to data/src/main/java/com/squirtles/data/repository/remote/applemusic/AppleMusicRepositoryImpl.kt index 911c1ba0..8c9215da 100644 --- a/data/src/main/java/com/squirtles/data/repository/AppleMusicRepositoryImpl.kt +++ b/data/src/main/java/com/squirtles/data/repository/remote/applemusic/AppleMusicRepositoryImpl.kt @@ -1,12 +1,12 @@ -package com.squirtles.data.repository +package com.squirtles.data.repository.remote.applemusic import androidx.paging.PagingData +import com.squirtles.domain.datasource.remote.applemusic.AppleMusicRemoteDataSource import com.squirtles.domain.model.MusicVideo import com.squirtles.domain.model.Song -import com.squirtles.domain.remote.RemoteRepository -import com.squirtles.domain.remote.applemusic.AppleMusicException -import com.squirtles.domain.remote.applemusic.AppleMusicRemoteDataSource -import com.squirtles.domain.remote.applemusic.AppleMusicRepository +import com.squirtles.domain.repository.remote.RemoteRepository +import com.squirtles.domain.repository.remote.applemusic.AppleMusicException +import com.squirtles.domain.repository.remote.applemusic.AppleMusicRepository import kotlinx.coroutines.flow.Flow import javax.inject.Inject diff --git a/data/src/main/java/com/squirtles/data/repository/FirebaseFavoriteRepositoryImpl.kt b/data/src/main/java/com/squirtles/data/repository/remote/firebase/FirebaseFavoriteRepositoryImpl.kt similarity index 78% rename from data/src/main/java/com/squirtles/data/repository/FirebaseFavoriteRepositoryImpl.kt rename to data/src/main/java/com/squirtles/data/repository/remote/firebase/FirebaseFavoriteRepositoryImpl.kt index d652f751..be842509 100644 --- a/data/src/main/java/com/squirtles/data/repository/FirebaseFavoriteRepositoryImpl.kt +++ b/data/src/main/java/com/squirtles/data/repository/remote/firebase/FirebaseFavoriteRepositoryImpl.kt @@ -1,9 +1,9 @@ -package com.squirtles.data.repository +package com.squirtles.data.repository.remote.firebase -import com.squirtles.domain.firebase.FirebaseRemoteDataSource +import com.squirtles.domain.datasource.remote.firebase.FirebaseRemoteDataSource import com.squirtles.domain.model.Pick -import com.squirtles.domain.remote.RemoteRepository -import com.squirtles.domain.remote.firebase.FirebaseFavoriteRepository +import com.squirtles.domain.repository.remote.RemoteRepository +import com.squirtles.domain.repository.remote.firebase.FirebaseFavoriteRepository class FirebaseFavoriteRepositoryImpl( diff --git a/data/src/main/java/com/squirtles/data/repository/FirebasePickRepositoryImpl.kt b/data/src/main/java/com/squirtles/data/repository/remote/firebase/FirebasePickRepositoryImpl.kt similarity index 78% rename from data/src/main/java/com/squirtles/data/repository/FirebasePickRepositoryImpl.kt rename to data/src/main/java/com/squirtles/data/repository/remote/firebase/FirebasePickRepositoryImpl.kt index f2eb10a0..15af9f43 100644 --- a/data/src/main/java/com/squirtles/data/repository/FirebasePickRepositoryImpl.kt +++ b/data/src/main/java/com/squirtles/data/repository/remote/firebase/FirebasePickRepositoryImpl.kt @@ -1,10 +1,10 @@ -package com.squirtles.data.repository +package com.squirtles.data.repository.remote.firebase -import com.squirtles.domain.firebase.FirebaseRemoteDataSource +import com.squirtles.domain.datasource.remote.firebase.FirebaseRemoteDataSource import com.squirtles.domain.model.Pick -import com.squirtles.domain.remote.RemoteRepository -import com.squirtles.domain.remote.firebase.FirebaseException -import com.squirtles.domain.remote.firebase.FirebasePickRepository +import com.squirtles.domain.repository.remote.RemoteRepository +import com.squirtles.domain.repository.remote.firebase.FirebaseException +import com.squirtles.domain.repository.remote.firebase.FirebasePickRepository class FirebasePickRepositoryImpl( private val firebaseRemoteDataSource: FirebaseRemoteDataSource diff --git a/data/src/main/java/com/squirtles/data/repository/FirebaseRepositoryImpl.kt b/data/src/main/java/com/squirtles/data/repository/remote/firebase/FirebaseRepositoryImpl.kt similarity index 58% rename from data/src/main/java/com/squirtles/data/repository/FirebaseRepositoryImpl.kt rename to data/src/main/java/com/squirtles/data/repository/remote/firebase/FirebaseRepositoryImpl.kt index 0dd193fe..fdceb01b 100644 --- a/data/src/main/java/com/squirtles/data/repository/FirebaseRepositoryImpl.kt +++ b/data/src/main/java/com/squirtles/data/repository/remote/firebase/FirebaseRepositoryImpl.kt @@ -1,9 +1,9 @@ -package com.squirtles.data.repository +package com.squirtles.data.repository.remote.firebase -import com.squirtles.domain.firebase.FirebaseRemoteDataSource -import com.squirtles.domain.remote.RemoteRepository -import com.squirtles.domain.remote.firebase.FirebaseException -import com.squirtles.domain.remote.firebase.FirebaseRepository +import com.squirtles.domain.datasource.remote.firebase.FirebaseRemoteDataSource +import com.squirtles.domain.repository.remote.RemoteRepository +import com.squirtles.domain.repository.remote.firebase.FirebaseException +import com.squirtles.domain.repository.remote.firebase.FirebaseRepository import javax.inject.Inject import javax.inject.Singleton diff --git a/data/src/main/java/com/squirtles/data/repository/FirebaseUserRepositoryImpl.kt b/data/src/main/java/com/squirtles/data/repository/remote/firebase/FirebaseUserRepositoryImpl.kt similarity index 73% rename from data/src/main/java/com/squirtles/data/repository/FirebaseUserRepositoryImpl.kt rename to data/src/main/java/com/squirtles/data/repository/remote/firebase/FirebaseUserRepositoryImpl.kt index 9e421254..767da751 100644 --- a/data/src/main/java/com/squirtles/data/repository/FirebaseUserRepositoryImpl.kt +++ b/data/src/main/java/com/squirtles/data/repository/remote/firebase/FirebaseUserRepositoryImpl.kt @@ -1,10 +1,10 @@ -package com.squirtles.data.repository +package com.squirtles.data.repository.remote.firebase -import com.squirtles.domain.firebase.FirebaseRemoteDataSource +import com.squirtles.domain.datasource.remote.firebase.FirebaseRemoteDataSource import com.squirtles.domain.model.User -import com.squirtles.domain.remote.RemoteRepository -import com.squirtles.domain.remote.firebase.FirebaseException -import com.squirtles.domain.remote.firebase.FirebaseUserRepository +import com.squirtles.domain.repository.remote.RemoteRepository +import com.squirtles.domain.repository.remote.firebase.FirebaseException +import com.squirtles.domain.repository.remote.firebase.FirebaseUserRepository class FirebaseUserRepositoryImpl( private val firebaseRemoteDataSource: FirebaseRemoteDataSource diff --git a/domain/src/main/java/com/squirtles/domain/local/LocalDataSource.kt b/domain/src/main/java/com/squirtles/domain/datasource/local/LocalDataSource.kt similarity index 93% rename from domain/src/main/java/com/squirtles/domain/local/LocalDataSource.kt rename to domain/src/main/java/com/squirtles/domain/datasource/local/LocalDataSource.kt index 9999d3b3..8beb1872 100644 --- a/domain/src/main/java/com/squirtles/domain/local/LocalDataSource.kt +++ b/domain/src/main/java/com/squirtles/domain/datasource/local/LocalDataSource.kt @@ -1,4 +1,4 @@ -package com.squirtles.domain.local +package com.squirtles.domain.datasource.local import android.location.Location import com.squirtles.domain.model.Order diff --git a/domain/src/main/java/com/squirtles/domain/remote/applemusic/AppleMusicRemoteDataSource.kt b/domain/src/main/java/com/squirtles/domain/datasource/remote/applemusic/AppleMusicRemoteDataSource.kt similarity index 86% rename from domain/src/main/java/com/squirtles/domain/remote/applemusic/AppleMusicRemoteDataSource.kt rename to domain/src/main/java/com/squirtles/domain/datasource/remote/applemusic/AppleMusicRemoteDataSource.kt index cd9cf6f1..2eadd934 100644 --- a/domain/src/main/java/com/squirtles/domain/remote/applemusic/AppleMusicRemoteDataSource.kt +++ b/domain/src/main/java/com/squirtles/domain/datasource/remote/applemusic/AppleMusicRemoteDataSource.kt @@ -1,4 +1,4 @@ -package com.squirtles.domain.remote.applemusic +package com.squirtles.domain.datasource.remote.applemusic import androidx.paging.PagingData import com.squirtles.domain.model.MusicVideo diff --git a/domain/src/main/java/com/squirtles/domain/remote/firebase/FirebaseRemoteDataSource.kt b/domain/src/main/java/com/squirtles/domain/datasource/remote/firebase/FirebaseRemoteDataSource.kt similarity index 66% rename from domain/src/main/java/com/squirtles/domain/remote/firebase/FirebaseRemoteDataSource.kt rename to domain/src/main/java/com/squirtles/domain/datasource/remote/firebase/FirebaseRemoteDataSource.kt index 7faad196..2f0e7503 100644 --- a/domain/src/main/java/com/squirtles/domain/remote/firebase/FirebaseRemoteDataSource.kt +++ b/domain/src/main/java/com/squirtles/domain/datasource/remote/firebase/FirebaseRemoteDataSource.kt @@ -1,4 +1,4 @@ -package com.squirtles.domain.firebase +package com.squirtles.domain.datasource.remote.firebase import com.squirtles.domain.model.Pick import com.squirtles.domain.model.User @@ -26,4 +26,17 @@ interface FirebaseRemoteDataSource { suspend fun createFavorite(pickId: String, userId: String): Boolean suspend fun deleteFavorite(pickId: String, userId: String): Boolean // suspend fun updatePick(pick: Pick) + + companion object { + protected const val TAG_LOG = "FirebaseDataSourceImpl" + + protected const val COLLECTION_FAVORITES = "favorites" + protected const val COLLECTION_PICKS = "picks" + protected const val COLLECTION_USERS = "users" + + protected const val FIELD_PICK_ID = "pickId" + protected const val FIELD_USER_ID = "userId" + protected const val FIELD_ADDED_AT = "addedAt" + protected const val FIELD_MY_PICKS = "myPicks" + } } diff --git a/domain/src/main/java/com/squirtles/domain/remote/firebase/FirebaseRepository.kt b/domain/src/main/java/com/squirtles/domain/remote/firebase/FirebaseRepository.kt deleted file mode 100644 index 4ca7d7aa..00000000 --- a/domain/src/main/java/com/squirtles/domain/remote/firebase/FirebaseRepository.kt +++ /dev/null @@ -1,4 +0,0 @@ -package com.squirtles.domain.remote.firebase - -interface FirebaseRepository { -} diff --git a/domain/src/main/java/com/squirtles/domain/local/LocalRepository.kt b/domain/src/main/java/com/squirtles/domain/repository/local/LocalRepository.kt similarity index 94% rename from domain/src/main/java/com/squirtles/domain/local/LocalRepository.kt rename to domain/src/main/java/com/squirtles/domain/repository/local/LocalRepository.kt index fe3402ad..c20fee86 100644 --- a/domain/src/main/java/com/squirtles/domain/local/LocalRepository.kt +++ b/domain/src/main/java/com/squirtles/domain/repository/local/LocalRepository.kt @@ -1,4 +1,4 @@ -package com.squirtles.domain.local +package com.squirtles.domain.repository.local import android.location.Location import com.squirtles.domain.model.Order diff --git a/domain/src/main/java/com/squirtles/domain/remote/RemoteRepository.kt b/domain/src/main/java/com/squirtles/domain/repository/remote/RemoteRepository.kt similarity index 79% rename from domain/src/main/java/com/squirtles/domain/remote/RemoteRepository.kt rename to domain/src/main/java/com/squirtles/domain/repository/remote/RemoteRepository.kt index f53f3f0d..6fd8c1fd 100644 --- a/domain/src/main/java/com/squirtles/domain/remote/RemoteRepository.kt +++ b/domain/src/main/java/com/squirtles/domain/repository/remote/RemoteRepository.kt @@ -1,4 +1,4 @@ -package com.squirtles.domain.remote +package com.squirtles.domain.repository.remote abstract class RemoteRepository { suspend fun handleResult( diff --git a/domain/src/main/java/com/squirtles/domain/remote/applemusic/AppleMusicException.kt b/domain/src/main/java/com/squirtles/domain/repository/remote/applemusic/AppleMusicException.kt similarity index 87% rename from domain/src/main/java/com/squirtles/domain/remote/applemusic/AppleMusicException.kt rename to domain/src/main/java/com/squirtles/domain/repository/remote/applemusic/AppleMusicException.kt index 6425e2f4..d0db3f8a 100644 --- a/domain/src/main/java/com/squirtles/domain/remote/applemusic/AppleMusicException.kt +++ b/domain/src/main/java/com/squirtles/domain/repository/remote/applemusic/AppleMusicException.kt @@ -1,4 +1,4 @@ -package com.squirtles.domain.remote.applemusic +package com.squirtles.domain.repository.remote.applemusic /** * 400 에러가 여러 종류가 있는데 이를 구분할 용도로 만든 예외 클래스 diff --git a/domain/src/main/java/com/squirtles/domain/remote/applemusic/AppleMusicRepository.kt b/domain/src/main/java/com/squirtles/domain/repository/remote/applemusic/AppleMusicRepository.kt similarity index 87% rename from domain/src/main/java/com/squirtles/domain/remote/applemusic/AppleMusicRepository.kt rename to domain/src/main/java/com/squirtles/domain/repository/remote/applemusic/AppleMusicRepository.kt index 97505553..c159c1b8 100644 --- a/domain/src/main/java/com/squirtles/domain/remote/applemusic/AppleMusicRepository.kt +++ b/domain/src/main/java/com/squirtles/domain/repository/remote/applemusic/AppleMusicRepository.kt @@ -1,4 +1,4 @@ -package com.squirtles.domain.remote.applemusic +package com.squirtles.domain.repository.remote.applemusic import androidx.paging.PagingData import com.squirtles.domain.model.MusicVideo diff --git a/domain/src/main/java/com/squirtles/domain/remote/firebase/FirebaseException.kt b/domain/src/main/java/com/squirtles/domain/repository/remote/firebase/FirebaseException.kt similarity index 91% rename from domain/src/main/java/com/squirtles/domain/remote/firebase/FirebaseException.kt rename to domain/src/main/java/com/squirtles/domain/repository/remote/firebase/FirebaseException.kt index 1b7289ed..49d82486 100644 --- a/domain/src/main/java/com/squirtles/domain/remote/firebase/FirebaseException.kt +++ b/domain/src/main/java/com/squirtles/domain/repository/remote/firebase/FirebaseException.kt @@ -1,4 +1,4 @@ -package com.squirtles.domain.remote.firebase +package com.squirtles.domain.repository.remote.firebase sealed class FirebaseException(override val message: String) : Exception() { data class CreatedUserFailedException(override val message: String = "Failed to create a user") : diff --git a/domain/src/main/java/com/squirtles/domain/remote/firebase/FirebaseFavoriteRepository.kt b/domain/src/main/java/com/squirtles/domain/repository/remote/firebase/FirebaseFavoriteRepository.kt similarity index 87% rename from domain/src/main/java/com/squirtles/domain/remote/firebase/FirebaseFavoriteRepository.kt rename to domain/src/main/java/com/squirtles/domain/repository/remote/firebase/FirebaseFavoriteRepository.kt index 4a243f39..015b6dbd 100644 --- a/domain/src/main/java/com/squirtles/domain/remote/firebase/FirebaseFavoriteRepository.kt +++ b/domain/src/main/java/com/squirtles/domain/repository/remote/firebase/FirebaseFavoriteRepository.kt @@ -1,4 +1,4 @@ -package com.squirtles.domain.remote.firebase +package com.squirtles.domain.repository.remote.firebase import com.squirtles.domain.model.Pick diff --git a/domain/src/main/java/com/squirtles/domain/remote/firebase/FirebasePickRepository.kt b/domain/src/main/java/com/squirtles/domain/repository/remote/firebase/FirebasePickRepository.kt similarity index 92% rename from domain/src/main/java/com/squirtles/domain/remote/firebase/FirebasePickRepository.kt rename to domain/src/main/java/com/squirtles/domain/repository/remote/firebase/FirebasePickRepository.kt index 25944ab7..5aee47ee 100644 --- a/domain/src/main/java/com/squirtles/domain/remote/firebase/FirebasePickRepository.kt +++ b/domain/src/main/java/com/squirtles/domain/repository/remote/firebase/FirebasePickRepository.kt @@ -1,4 +1,4 @@ -package com.squirtles.domain.remote.firebase +package com.squirtles.domain.repository.remote.firebase import com.squirtles.domain.model.Pick diff --git a/domain/src/main/java/com/squirtles/domain/repository/remote/firebase/FirebaseRepository.kt b/domain/src/main/java/com/squirtles/domain/repository/remote/firebase/FirebaseRepository.kt new file mode 100644 index 00000000..15d78028 --- /dev/null +++ b/domain/src/main/java/com/squirtles/domain/repository/remote/firebase/FirebaseRepository.kt @@ -0,0 +1,4 @@ +package com.squirtles.domain.repository.remote.firebase + +interface FirebaseRepository { +} diff --git a/domain/src/main/java/com/squirtles/domain/remote/firebase/FirebaseUserRepository.kt b/domain/src/main/java/com/squirtles/domain/repository/remote/firebase/FirebaseUserRepository.kt similarity index 91% rename from domain/src/main/java/com/squirtles/domain/remote/firebase/FirebaseUserRepository.kt rename to domain/src/main/java/com/squirtles/domain/repository/remote/firebase/FirebaseUserRepository.kt index 409a138f..21869909 100644 --- a/domain/src/main/java/com/squirtles/domain/remote/firebase/FirebaseUserRepository.kt +++ b/domain/src/main/java/com/squirtles/domain/repository/remote/firebase/FirebaseUserRepository.kt @@ -1,4 +1,4 @@ -package com.squirtles.domain.remote.firebase +package com.squirtles.domain.repository.remote.firebase import com.squirtles.domain.model.User diff --git a/domain/src/main/java/com/squirtles/domain/usecase/favorite/CreateFavoriteUseCase.kt b/domain/src/main/java/com/squirtles/domain/usecase/favorite/CreateFavoriteUseCase.kt index 259f253b..45df3822 100644 --- a/domain/src/main/java/com/squirtles/domain/usecase/favorite/CreateFavoriteUseCase.kt +++ b/domain/src/main/java/com/squirtles/domain/usecase/favorite/CreateFavoriteUseCase.kt @@ -1,6 +1,6 @@ package com.squirtles.domain.usecase.favorite -import com.squirtles.domain.remote.firebase.FirebaseFavoriteRepository +import com.squirtles.domain.repository.remote.firebase.FirebaseFavoriteRepository import javax.inject.Inject class CreateFavoriteUseCase @Inject constructor( diff --git a/domain/src/main/java/com/squirtles/domain/usecase/favorite/DeleteFavoriteUseCase.kt b/domain/src/main/java/com/squirtles/domain/usecase/favorite/DeleteFavoriteUseCase.kt index f9eba9ef..126f74c9 100644 --- a/domain/src/main/java/com/squirtles/domain/usecase/favorite/DeleteFavoriteUseCase.kt +++ b/domain/src/main/java/com/squirtles/domain/usecase/favorite/DeleteFavoriteUseCase.kt @@ -1,6 +1,6 @@ package com.squirtles.domain.usecase.favorite -import com.squirtles.domain.remote.firebase.FirebaseFavoriteRepository +import com.squirtles.domain.repository.remote.firebase.FirebaseFavoriteRepository import com.squirtles.domain.usecase.picklist.RemovePickUseCaseInterface import javax.inject.Inject diff --git a/domain/src/main/java/com/squirtles/domain/usecase/favorite/FetchFavoritePicksUseCase.kt b/domain/src/main/java/com/squirtles/domain/usecase/favorite/FetchFavoritePicksUseCase.kt index 98ec2c28..cc5c3c45 100644 --- a/domain/src/main/java/com/squirtles/domain/usecase/favorite/FetchFavoritePicksUseCase.kt +++ b/domain/src/main/java/com/squirtles/domain/usecase/favorite/FetchFavoritePicksUseCase.kt @@ -1,6 +1,6 @@ package com.squirtles.domain.usecase.favorite -import com.squirtles.domain.remote.firebase.FirebaseFavoriteRepository +import com.squirtles.domain.repository.remote.firebase.FirebaseFavoriteRepository import com.squirtles.domain.usecase.picklist.FetchPickListUseCaseInterface import javax.inject.Inject diff --git a/domain/src/main/java/com/squirtles/domain/usecase/favorite/FetchIsFavoriteUseCase.kt b/domain/src/main/java/com/squirtles/domain/usecase/favorite/FetchIsFavoriteUseCase.kt index 43e6d5a5..c75d3d30 100644 --- a/domain/src/main/java/com/squirtles/domain/usecase/favorite/FetchIsFavoriteUseCase.kt +++ b/domain/src/main/java/com/squirtles/domain/usecase/favorite/FetchIsFavoriteUseCase.kt @@ -1,6 +1,6 @@ package com.squirtles.domain.usecase.favorite -import com.squirtles.domain.remote.firebase.FirebaseFavoriteRepository +import com.squirtles.domain.repository.remote.firebase.FirebaseFavoriteRepository import javax.inject.Inject class FetchIsFavoriteUseCase @Inject constructor( diff --git a/domain/src/main/java/com/squirtles/domain/usecase/location/GetLastLocationUseCase.kt b/domain/src/main/java/com/squirtles/domain/usecase/location/GetLastLocationUseCase.kt index d081a9d9..47d9f298 100644 --- a/domain/src/main/java/com/squirtles/domain/usecase/location/GetLastLocationUseCase.kt +++ b/domain/src/main/java/com/squirtles/domain/usecase/location/GetLastLocationUseCase.kt @@ -1,6 +1,6 @@ package com.squirtles.domain.usecase.location -import com.squirtles.domain.local.LocalRepository +import com.squirtles.domain.repository.local.LocalRepository import javax.inject.Inject class GetLastLocationUseCase @Inject constructor( diff --git a/domain/src/main/java/com/squirtles/domain/usecase/location/SaveLastLocationUseCase.kt b/domain/src/main/java/com/squirtles/domain/usecase/location/SaveLastLocationUseCase.kt index 8e333af9..fc4fd098 100644 --- a/domain/src/main/java/com/squirtles/domain/usecase/location/SaveLastLocationUseCase.kt +++ b/domain/src/main/java/com/squirtles/domain/usecase/location/SaveLastLocationUseCase.kt @@ -1,7 +1,7 @@ package com.squirtles.domain.usecase.location import android.location.Location -import com.squirtles.domain.local.LocalRepository +import com.squirtles.domain.repository.local.LocalRepository import javax.inject.Inject class SaveLastLocationUseCase @Inject constructor( diff --git a/domain/src/main/java/com/squirtles/domain/usecase/music/FetchMusicVideoUseCase.kt b/domain/src/main/java/com/squirtles/domain/usecase/music/FetchMusicVideoUseCase.kt index afba97c3..8ed50e29 100644 --- a/domain/src/main/java/com/squirtles/domain/usecase/music/FetchMusicVideoUseCase.kt +++ b/domain/src/main/java/com/squirtles/domain/usecase/music/FetchMusicVideoUseCase.kt @@ -2,7 +2,7 @@ package com.squirtles.domain.usecase.music import com.squirtles.domain.model.MusicVideo import com.squirtles.domain.model.Song -import com.squirtles.domain.remote.applemusic.AppleMusicRepository +import com.squirtles.domain.repository.remote.applemusic.AppleMusicRepository import javax.inject.Inject class FetchMusicVideoUseCase @Inject constructor( diff --git a/domain/src/main/java/com/squirtles/domain/usecase/music/FetchSongsUseCase.kt b/domain/src/main/java/com/squirtles/domain/usecase/music/FetchSongsUseCase.kt index f4c3e2d3..d822bc92 100644 --- a/domain/src/main/java/com/squirtles/domain/usecase/music/FetchSongsUseCase.kt +++ b/domain/src/main/java/com/squirtles/domain/usecase/music/FetchSongsUseCase.kt @@ -1,6 +1,6 @@ package com.squirtles.domain.usecase.music -import com.squirtles.domain.remote.applemusic.AppleMusicRepository +import com.squirtles.domain.repository.remote.applemusic.AppleMusicRepository import javax.inject.Inject class FetchSongsUseCase @Inject constructor( diff --git a/domain/src/main/java/com/squirtles/domain/usecase/order/GetFavoriteListOrderUseCase.kt b/domain/src/main/java/com/squirtles/domain/usecase/order/GetFavoriteListOrderUseCase.kt index 98a20b6a..2745f4d2 100644 --- a/domain/src/main/java/com/squirtles/domain/usecase/order/GetFavoriteListOrderUseCase.kt +++ b/domain/src/main/java/com/squirtles/domain/usecase/order/GetFavoriteListOrderUseCase.kt @@ -1,6 +1,6 @@ package com.squirtles.domain.usecase.order -import com.squirtles.domain.local.LocalRepository +import com.squirtles.domain.repository.local.LocalRepository import com.squirtles.domain.usecase.picklist.GetPickListOrderUseCaseInterface import javax.inject.Inject diff --git a/domain/src/main/java/com/squirtles/domain/usecase/order/GetMyPickListOrderUseCase.kt b/domain/src/main/java/com/squirtles/domain/usecase/order/GetMyPickListOrderUseCase.kt index b41e7d66..3ab3425b 100644 --- a/domain/src/main/java/com/squirtles/domain/usecase/order/GetMyPickListOrderUseCase.kt +++ b/domain/src/main/java/com/squirtles/domain/usecase/order/GetMyPickListOrderUseCase.kt @@ -1,6 +1,6 @@ package com.squirtles.domain.usecase.order -import com.squirtles.domain.local.LocalRepository +import com.squirtles.domain.repository.local.LocalRepository import com.squirtles.domain.usecase.picklist.GetPickListOrderUseCaseInterface import javax.inject.Inject diff --git a/domain/src/main/java/com/squirtles/domain/usecase/order/SaveFavoriteListOrderUseCase.kt b/domain/src/main/java/com/squirtles/domain/usecase/order/SaveFavoriteListOrderUseCase.kt index 8b7ed05f..7f0bb87e 100644 --- a/domain/src/main/java/com/squirtles/domain/usecase/order/SaveFavoriteListOrderUseCase.kt +++ b/domain/src/main/java/com/squirtles/domain/usecase/order/SaveFavoriteListOrderUseCase.kt @@ -1,7 +1,7 @@ package com.squirtles.domain.usecase.order -import com.squirtles.domain.local.LocalRepository import com.squirtles.domain.model.Order +import com.squirtles.domain.repository.local.LocalRepository import com.squirtles.domain.usecase.picklist.SavePickListOrderUseCaseInterface import javax.inject.Inject diff --git a/domain/src/main/java/com/squirtles/domain/usecase/order/SaveMyPickListOrderUseCase.kt b/domain/src/main/java/com/squirtles/domain/usecase/order/SaveMyPickListOrderUseCase.kt index 0ca672d7..0766a36a 100644 --- a/domain/src/main/java/com/squirtles/domain/usecase/order/SaveMyPickListOrderUseCase.kt +++ b/domain/src/main/java/com/squirtles/domain/usecase/order/SaveMyPickListOrderUseCase.kt @@ -1,7 +1,7 @@ package com.squirtles.domain.usecase.order -import com.squirtles.domain.local.LocalRepository import com.squirtles.domain.model.Order +import com.squirtles.domain.repository.local.LocalRepository import com.squirtles.domain.usecase.picklist.SavePickListOrderUseCaseInterface import javax.inject.Inject diff --git a/domain/src/main/java/com/squirtles/domain/usecase/pick/CreatePickUseCase.kt b/domain/src/main/java/com/squirtles/domain/usecase/pick/CreatePickUseCase.kt index cb8a33ef..b32e034e 100644 --- a/domain/src/main/java/com/squirtles/domain/usecase/pick/CreatePickUseCase.kt +++ b/domain/src/main/java/com/squirtles/domain/usecase/pick/CreatePickUseCase.kt @@ -1,7 +1,7 @@ package com.squirtles.domain.usecase.pick import com.squirtles.domain.model.Pick -import com.squirtles.domain.remote.firebase.FirebasePickRepository +import com.squirtles.domain.repository.remote.firebase.FirebasePickRepository import javax.inject.Inject class CreatePickUseCase @Inject constructor( diff --git a/domain/src/main/java/com/squirtles/domain/usecase/pick/DeletePickUseCase.kt b/domain/src/main/java/com/squirtles/domain/usecase/pick/DeletePickUseCase.kt index 582c25ba..61e51a44 100644 --- a/domain/src/main/java/com/squirtles/domain/usecase/pick/DeletePickUseCase.kt +++ b/domain/src/main/java/com/squirtles/domain/usecase/pick/DeletePickUseCase.kt @@ -1,6 +1,6 @@ package com.squirtles.domain.usecase.pick -import com.squirtles.domain.remote.firebase.FirebasePickRepository +import com.squirtles.domain.repository.remote.firebase.FirebasePickRepository import com.squirtles.domain.usecase.picklist.RemovePickUseCaseInterface import javax.inject.Inject diff --git a/domain/src/main/java/com/squirtles/domain/usecase/pick/FetchMyPicksUseCase.kt b/domain/src/main/java/com/squirtles/domain/usecase/pick/FetchMyPicksUseCase.kt index e0e04e3f..9907dbc4 100644 --- a/domain/src/main/java/com/squirtles/domain/usecase/pick/FetchMyPicksUseCase.kt +++ b/domain/src/main/java/com/squirtles/domain/usecase/pick/FetchMyPicksUseCase.kt @@ -1,6 +1,6 @@ package com.squirtles.domain.usecase.pick -import com.squirtles.domain.remote.firebase.FirebasePickRepository +import com.squirtles.domain.repository.remote.firebase.FirebasePickRepository import com.squirtles.domain.usecase.picklist.FetchPickListUseCaseInterface import javax.inject.Inject diff --git a/domain/src/main/java/com/squirtles/domain/usecase/pick/FetchPickUseCase.kt b/domain/src/main/java/com/squirtles/domain/usecase/pick/FetchPickUseCase.kt index 7debcf4b..9be6e135 100644 --- a/domain/src/main/java/com/squirtles/domain/usecase/pick/FetchPickUseCase.kt +++ b/domain/src/main/java/com/squirtles/domain/usecase/pick/FetchPickUseCase.kt @@ -1,6 +1,6 @@ package com.squirtles.domain.usecase.pick -import com.squirtles.domain.remote.firebase.FirebasePickRepository +import com.squirtles.domain.repository.remote.firebase.FirebasePickRepository import javax.inject.Inject class FetchPickUseCase @Inject constructor( diff --git a/domain/src/main/java/com/squirtles/domain/usecase/user/ClearUserUseCase.kt b/domain/src/main/java/com/squirtles/domain/usecase/user/ClearUserUseCase.kt index 7f89fe9f..0c81e48c 100644 --- a/domain/src/main/java/com/squirtles/domain/usecase/user/ClearUserUseCase.kt +++ b/domain/src/main/java/com/squirtles/domain/usecase/user/ClearUserUseCase.kt @@ -1,6 +1,6 @@ package com.squirtles.domain.usecase.user -import com.squirtles.domain.local.LocalRepository +import com.squirtles.domain.repository.local.LocalRepository import javax.inject.Inject class ClearUserUseCase @Inject constructor( diff --git a/domain/src/main/java/com/squirtles/domain/usecase/user/CreateGoogleIdUserUseCase.kt b/domain/src/main/java/com/squirtles/domain/usecase/user/CreateGoogleIdUserUseCase.kt index 240866b0..6e82beb7 100644 --- a/domain/src/main/java/com/squirtles/domain/usecase/user/CreateGoogleIdUserUseCase.kt +++ b/domain/src/main/java/com/squirtles/domain/usecase/user/CreateGoogleIdUserUseCase.kt @@ -1,8 +1,8 @@ package com.squirtles.domain.usecase.user -import com.squirtles.domain.local.LocalRepository import com.squirtles.domain.model.User -import com.squirtles.domain.remote.firebase.FirebaseUserRepository +import com.squirtles.domain.repository.local.LocalRepository +import com.squirtles.domain.repository.remote.firebase.FirebaseUserRepository import javax.inject.Inject class CreateGoogleIdUserUseCase @Inject constructor( diff --git a/domain/src/main/java/com/squirtles/domain/usecase/user/FetchUserByIdUseCase.kt b/domain/src/main/java/com/squirtles/domain/usecase/user/FetchUserByIdUseCase.kt index 15542bdb..8712e00a 100644 --- a/domain/src/main/java/com/squirtles/domain/usecase/user/FetchUserByIdUseCase.kt +++ b/domain/src/main/java/com/squirtles/domain/usecase/user/FetchUserByIdUseCase.kt @@ -1,6 +1,6 @@ package com.squirtles.domain.usecase.user -import com.squirtles.domain.remote.firebase.FirebaseUserRepository +import com.squirtles.domain.repository.remote.firebase.FirebaseUserRepository import javax.inject.Inject class FetchUserByIdUseCase @Inject constructor( diff --git a/domain/src/main/java/com/squirtles/domain/usecase/user/FetchUserUseCase.kt b/domain/src/main/java/com/squirtles/domain/usecase/user/FetchUserUseCase.kt index c6e755d5..3dc692d5 100644 --- a/domain/src/main/java/com/squirtles/domain/usecase/user/FetchUserUseCase.kt +++ b/domain/src/main/java/com/squirtles/domain/usecase/user/FetchUserUseCase.kt @@ -1,7 +1,7 @@ package com.squirtles.domain.usecase.user -import com.squirtles.domain.local.LocalRepository import com.squirtles.domain.model.User +import com.squirtles.domain.repository.local.LocalRepository import javax.inject.Inject class FetchUserUseCase @Inject constructor( diff --git a/domain/src/main/java/com/squirtles/domain/usecase/user/GetCurrentUserUseCase.kt b/domain/src/main/java/com/squirtles/domain/usecase/user/GetCurrentUserUseCase.kt index e9d77326..cc260d68 100644 --- a/domain/src/main/java/com/squirtles/domain/usecase/user/GetCurrentUserUseCase.kt +++ b/domain/src/main/java/com/squirtles/domain/usecase/user/GetCurrentUserUseCase.kt @@ -1,6 +1,6 @@ package com.squirtles.domain.usecase.user -import com.squirtles.domain.local.LocalRepository +import com.squirtles.domain.repository.local.LocalRepository import javax.inject.Inject class GetCurrentUserUseCase @Inject constructor( diff --git a/domain/src/main/java/com/squirtles/domain/usecase/user/GetUserIdFromDataStoreUseCase.kt b/domain/src/main/java/com/squirtles/domain/usecase/user/GetUserIdFromDataStoreUseCase.kt index 0cfe6fec..5451b6a6 100644 --- a/domain/src/main/java/com/squirtles/domain/usecase/user/GetUserIdFromDataStoreUseCase.kt +++ b/domain/src/main/java/com/squirtles/domain/usecase/user/GetUserIdFromDataStoreUseCase.kt @@ -1,6 +1,6 @@ package com.squirtles.domain.usecase.user -import com.squirtles.domain.local.LocalRepository +import com.squirtles.domain.repository.local.LocalRepository import javax.inject.Inject class GetUserIdFromDataStoreUseCase @Inject constructor( diff --git a/domain/src/main/java/com/squirtles/domain/usecase/user/UpdateUserNameUseCase.kt b/domain/src/main/java/com/squirtles/domain/usecase/user/UpdateUserNameUseCase.kt index e2c9a123..62920bda 100644 --- a/domain/src/main/java/com/squirtles/domain/usecase/user/UpdateUserNameUseCase.kt +++ b/domain/src/main/java/com/squirtles/domain/usecase/user/UpdateUserNameUseCase.kt @@ -1,6 +1,6 @@ package com.squirtles.domain.usecase.user -import com.squirtles.domain.remote.firebase.FirebaseUserRepository +import com.squirtles.domain.repository.remote.firebase.FirebaseUserRepository import javax.inject.Inject class UpdateUserNameUseCase @Inject constructor( From 84767e911ec77e21582533847881004eded771f0 Mon Sep 17 00:00:00 2001 From: miller198 Date: Tue, 11 Feb 2025 22:28:46 +0900 Subject: [PATCH 06/62] =?UTF-8?q?[refactor]=20datasource=20=EC=A0=95?= =?UTF-8?q?=EB=A6=AC=20=EC=A4=91?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../firebase/FirebasePickDataSourceImpl.kt | 235 ++++++++++++++++++ .../remote/firebase/FirebasePickDataSource.kt | 11 + 2 files changed, 246 insertions(+) create mode 100644 data/src/main/java/com/squirtles/data/datasource/remote/firebase/FirebasePickDataSourceImpl.kt create mode 100644 domain/src/main/java/com/squirtles/domain/datasource/remote/firebase/FirebasePickDataSource.kt diff --git a/data/src/main/java/com/squirtles/data/datasource/remote/firebase/FirebasePickDataSourceImpl.kt b/data/src/main/java/com/squirtles/data/datasource/remote/firebase/FirebasePickDataSourceImpl.kt new file mode 100644 index 00000000..37f72cb6 --- /dev/null +++ b/data/src/main/java/com/squirtles/data/datasource/remote/firebase/FirebasePickDataSourceImpl.kt @@ -0,0 +1,235 @@ +package com.squirtles.data.datasource.remote.firebase + +import android.util.Log +import com.firebase.geofire.GeoFireUtils +import com.firebase.geofire.GeoLocation +import com.google.android.gms.tasks.Task +import com.google.android.gms.tasks.Tasks +import com.google.firebase.firestore.DocumentReference +import com.google.firebase.firestore.DocumentSnapshot +import com.google.firebase.firestore.FieldValue +import com.google.firebase.firestore.FirebaseFirestore +import com.google.firebase.firestore.Query +import com.google.firebase.firestore.QuerySnapshot +import com.google.firebase.firestore.toObject +import com.squirtles.data.datasource.remote.firebase.FirebaseDataSourceImpl.Companion.COLLECTION_FAVORITES +import com.squirtles.data.datasource.remote.firebase.FirebaseDataSourceImpl.Companion.COLLECTION_PICKS +import com.squirtles.data.datasource.remote.firebase.FirebaseDataSourceImpl.Companion.COLLECTION_USERS +import com.squirtles.data.datasource.remote.firebase.FirebaseDataSourceImpl.Companion.FIELD_MY_PICKS +import com.squirtles.data.datasource.remote.firebase.FirebaseDataSourceImpl.Companion.FIELD_PICK_ID +import com.squirtles.data.datasource.remote.firebase.FirebaseDataSourceImpl.Companion.TAG_LOG +import com.squirtles.data.datasource.remote.firebase.model.FirebasePick +import com.squirtles.data.datasource.remote.firebase.model.FirebaseUser +import com.squirtles.data.mapper.toFirebasePick +import com.squirtles.data.mapper.toPick +import com.squirtles.domain.datasource.remote.firebase.FirebasePickDataSource +import com.squirtles.domain.model.Pick +import kotlinx.coroutines.suspendCancellableCoroutine +import kotlinx.coroutines.tasks.await +import javax.inject.Inject +import javax.inject.Singleton +import kotlin.coroutines.resume +import kotlin.coroutines.resumeWithException + +@Singleton +class FirebasePickDataSourceImpl @Inject constructor( + private val db: FirebaseFirestore +) : FirebasePickDataSource { + + /** + * Fetches a pick by ID from Firestore. + * @param pickID The ID of the pick to fetch. + * @return The fetched pick, or null if the pick does not exist on firestore. + */ + override suspend fun fetchPick(pickID: String): Pick? { + return suspendCancellableCoroutine { continuation -> + db.collection("picks").document(pickID).get() + .addOnSuccessListener { document -> + val firestorePick = document.toObject()?.copy(id = pickID) + val resultPick = firestorePick?.toPick() + continuation.resume(resultPick) + } + .addOnFailureListener { exception -> + Log.e("FirebaseDataSourceImpl", "Failed to fetch a pick", exception) + continuation.resumeWithException(exception) + } + } + } + + /** + * Fetches picks within a given radius from Firestore. + * @param lat The latitude of the center of the search area. + * @param lng The longitude of the center of the search area. + * @param radiusInM The radius in meters of the search area. + * @return A list of picks within the specified radius, ordered by distance from the center. can be empty. + */ + override suspend fun fetchPicksInArea( + lat: Double, + lng: Double, + radiusInM: Double + ): List { + val center = GeoLocation(lat, lng) + val bounds = GeoFireUtils.getGeoHashQueryBounds(center, radiusInM) + + val queries: MutableList = ArrayList() + val tasks: MutableList> = ArrayList() + val matchingPicks: MutableList = ArrayList() + + bounds.forEach { bound -> + val query = db.collection("picks") + .orderBy("geoHash") + .startAt(bound.startHash) + .endAt(bound.endHash) + queries.add(query) + } + + try { + queries.forEach { query -> + tasks.add(query.get()) + } + Tasks.whenAllComplete(tasks).await() + } catch (exception: Exception) { + Log.e("FirebaseDataSourceImpl", "Failed to fetch picks", exception) + throw exception + } + + tasks.forEach { task -> + val snap = task.result + snap.documents.forEach { doc -> + if (isAccurate(doc, center, radiusInM)) { + doc.toObject()?.run { + matchingPicks.add(this.toPick().copy(id = doc.id)) + } + } + } + } + + return matchingPicks + } + + /** + * GeoHash의 FP 문제 - Geohash의 쿼리가 정확하지 않으며 클라이언트 측에서 거짓양성 결과를 필터링해야 합니다. + * 이러한 추가 읽기로 인해 앱에 비용과 지연 시간이 추가됩니다. + * @param doc The pick document to check. + * @param center The center of the search area. + * @param radiusInM The radius in meters of the search area. + * @return True if the pick is within the specified radius, false otherwise. + */ + private fun isAccurate(doc: DocumentSnapshot, center: GeoLocation, radiusInM: Double): Boolean { + val location = doc.getGeoPoint("location") ?: return false + + val docLocation = GeoLocation(location.latitude, location.longitude) + val distanceInM = GeoFireUtils.getDistanceBetween(docLocation, center) + + return distanceInM <= radiusInM + } + + /** + * Creates a new pick in Firestore. + * @param pick The pick to create. + * @return The created pick. + */ + override suspend fun createPick(pick: Pick): String = + suspendCancellableCoroutine { continuation -> + val firebasePick = pick.toFirebasePick() + + // add() 메소드는 Cloud Firestore에서 ID를 자동으로 생성 + db.collection("picks").add(firebasePick) + .addOnSuccessListener { documentReference -> + val pickId = documentReference.id + // 유저의 픽 정보 업데이트 + updateCurrentUserPick(pick.createdBy.userId, pickId) + .addOnCompleteListener { task -> + if (task.isSuccessful) { + continuation.resume(pickId) + } else { + continuation.resumeWithException( + task.exception ?: Exception("Failed to updating user pick info") + ) + } + } + } + .addOnFailureListener { exception -> + Log.e("FirebaseDataSourceImpl", "Failed to create a pick", exception) + continuation.resumeWithException(exception) + } + } + + override suspend fun deletePick(pickId: String, userId: String): Boolean { + val pickDocument = db.collection(COLLECTION_PICKS).document(pickId) + val userDocument = db.collection(COLLECTION_USERS).document(userId) + val favoriteDocuments = fetchFavoriteDocuments(pickId) + + return suspendCancellableCoroutine { continuation -> + db.runTransaction { transaction -> + transaction.delete(pickDocument) + + favoriteDocuments.forEach { document -> + transaction.delete(document) + } + + transaction.update(userDocument, FIELD_MY_PICKS, FieldValue.arrayRemove(pickId)) + }.addOnSuccessListener { _ -> + continuation.resume(true) + }.addOnFailureListener { e -> + Log.w(TAG_LOG, "Transaction failure.", e) + continuation.resumeWithException(e) + } + } + } + + override suspend fun fetchMyPicks(userId: String): List { + val userDocument = fetchUserDocument(userId) + if (userDocument.exists().not()) throw Exception("No user info in database") + + val tasks = mutableListOf>() + val myPicks = mutableListOf() + + try { + userDocument.toObject()?.myPicks?.forEach { pickId -> + tasks.add( + db.collection(COLLECTION_PICKS) + .document(pickId) + .get() + ) + } + Tasks.whenAllComplete(tasks).await() + } catch (exception: Exception) { + Log.e("FirebaseDataSourceImpl", "Failed to fetch my picks", exception) + throw exception + } + + tasks.forEach { task -> + task.result.toObject()?.run { + myPicks.add(this.toPick().copy(id = task.result.id)) + } + } + + return myPicks.reversed() + } + + private fun updateCurrentUserPick(userId: String, pickId: String): Task { + val userDoc = db.collection("users").document(userId) + return userDoc.update("myPicks", FieldValue.arrayUnion(pickId)) + } + + private suspend fun fetchFavoriteDocuments(pickId: String): List { + return suspendCancellableCoroutine { continuation -> + db.collection(COLLECTION_FAVORITES) + .whereEqualTo(FIELD_PICK_ID, pickId) + .get() + .addOnSuccessListener { querySnapShot -> + val documentIds = querySnapShot.documents.map { it.id } + val documentRefs = mutableListOf() + documentIds.forEach { id -> + documentRefs.add(db.collection(COLLECTION_FAVORITES).document(id)) + } + continuation.resume(documentRefs) + } + .addOnFailureListener { e -> + Log.w(TAG_LOG, "Failed to fetch favorite documents id", e) + continuation.resumeWithException(e) + } + } + } +} diff --git a/domain/src/main/java/com/squirtles/domain/datasource/remote/firebase/FirebasePickDataSource.kt b/domain/src/main/java/com/squirtles/domain/datasource/remote/firebase/FirebasePickDataSource.kt new file mode 100644 index 00000000..02752290 --- /dev/null +++ b/domain/src/main/java/com/squirtles/domain/datasource/remote/firebase/FirebasePickDataSource.kt @@ -0,0 +1,11 @@ +package com.squirtles.domain.datasource.remote.firebase + +import com.squirtles.domain.model.Pick + +interface FirebasePickDataSource { + suspend fun createPick(pick: Pick): String + suspend fun deletePick(pickId: String, userId: String): Boolean + suspend fun fetchPick(pickID: String): Pick? + suspend fun fetchPicksInArea(lat: Double, lng: Double, radiusInM: Double): List + suspend fun fetchMyPicks(userId: String): List +} From 7122fbde7ce7d707992624e245561d490da54762 Mon Sep 17 00:00:00 2001 From: miller198 Date: Wed, 12 Feb 2025 00:53:26 +0900 Subject: [PATCH 07/62] =?UTF-8?q?[refactor]=20Firebase=20Datasource=20?= =?UTF-8?q?=EB=B6=84=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../remote/firebase/CloudFunctionHelper.kt | 2 + .../remote/firebase/FirebaseDataSourceImpl.kt | 467 ------------------ .../FirebaseFavoriteDataSourceImpl.kt | 181 +++++++ .../firebase/FirebasePickDataSourceImpl.kt | 76 ++- .../firebase/FirebaseUserDataSourceImpl.kt | 77 +++ .../java/com/squirtles/data/di/DataModule.kt | 48 +- .../FirebaseFavoriteRepositoryImpl.kt | 19 +- .../firebase/FirebasePickRepositoryImpl.kt | 19 +- .../remote/firebase/FirebaseRepositoryImpl.kt | 23 - .../firebase/FirebaseUserRepositoryImpl.kt | 12 +- .../firebase/FirebaseDataSourceConstants.kt | 12 + .../firebase/FirebaseFavoriteDataSource.kt | 10 + .../firebase/FirebaseRemoteDataSource.kt | 42 -- .../remote/firebase/FirebaseUserDataSource.kt | 9 + .../remote/firebase/FirebaseRepository.kt | 4 - 15 files changed, 390 insertions(+), 611 deletions(-) delete mode 100644 data/src/main/java/com/squirtles/data/datasource/remote/firebase/FirebaseDataSourceImpl.kt create mode 100644 data/src/main/java/com/squirtles/data/datasource/remote/firebase/FirebaseFavoriteDataSourceImpl.kt create mode 100644 data/src/main/java/com/squirtles/data/datasource/remote/firebase/FirebaseUserDataSourceImpl.kt delete mode 100644 data/src/main/java/com/squirtles/data/repository/remote/firebase/FirebaseRepositoryImpl.kt create mode 100644 domain/src/main/java/com/squirtles/domain/datasource/remote/firebase/FirebaseDataSourceConstants.kt create mode 100644 domain/src/main/java/com/squirtles/domain/datasource/remote/firebase/FirebaseFavoriteDataSource.kt delete mode 100644 domain/src/main/java/com/squirtles/domain/datasource/remote/firebase/FirebaseRemoteDataSource.kt create mode 100644 domain/src/main/java/com/squirtles/domain/datasource/remote/firebase/FirebaseUserDataSource.kt delete mode 100644 domain/src/main/java/com/squirtles/domain/repository/remote/firebase/FirebaseRepository.kt diff --git a/data/src/main/java/com/squirtles/data/datasource/remote/firebase/CloudFunctionHelper.kt b/data/src/main/java/com/squirtles/data/datasource/remote/firebase/CloudFunctionHelper.kt index 96b43a41..7cccf7f5 100644 --- a/data/src/main/java/com/squirtles/data/datasource/remote/firebase/CloudFunctionHelper.kt +++ b/data/src/main/java/com/squirtles/data/datasource/remote/firebase/CloudFunctionHelper.kt @@ -5,7 +5,9 @@ import com.google.firebase.functions.ktx.functions import com.google.firebase.ktx.Firebase import com.squirtles.data.BuildConfig import kotlinx.coroutines.tasks.await +import javax.inject.Singleton +@Singleton class CloudFunctionHelper { private val functions: FirebaseFunctions = Firebase.functions diff --git a/data/src/main/java/com/squirtles/data/datasource/remote/firebase/FirebaseDataSourceImpl.kt b/data/src/main/java/com/squirtles/data/datasource/remote/firebase/FirebaseDataSourceImpl.kt deleted file mode 100644 index 8b216dad..00000000 --- a/data/src/main/java/com/squirtles/data/datasource/remote/firebase/FirebaseDataSourceImpl.kt +++ /dev/null @@ -1,467 +0,0 @@ -package com.squirtles.data.datasource.remote.firebase - -import android.util.Log -import com.firebase.geofire.GeoFireUtils -import com.firebase.geofire.GeoLocation -import com.google.android.gms.tasks.Task -import com.google.android.gms.tasks.Tasks -import com.google.firebase.firestore.DocumentReference -import com.google.firebase.firestore.DocumentSnapshot -import com.google.firebase.firestore.FieldValue -import com.google.firebase.firestore.FirebaseFirestore -import com.google.firebase.firestore.Query -import com.google.firebase.firestore.QuerySnapshot -import com.google.firebase.firestore.toObject -import com.squirtles.data.datasource.remote.firebase.model.FirebaseFavorite -import com.squirtles.data.datasource.remote.firebase.model.FirebasePick -import com.squirtles.data.datasource.remote.firebase.model.FirebaseUser -import com.squirtles.data.mapper.toFirebasePick -import com.squirtles.data.mapper.toPick -import com.squirtles.data.mapper.toUser -import com.squirtles.domain.datasource.remote.firebase.FirebaseRemoteDataSource -import com.squirtles.domain.model.Pick -import com.squirtles.domain.model.User -import kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.launch -import kotlinx.coroutines.suspendCancellableCoroutine -import kotlinx.coroutines.tasks.await -import javax.inject.Inject -import javax.inject.Singleton -import kotlin.coroutines.resume -import kotlin.coroutines.resumeWithException - -@Singleton -class FirebaseDataSourceImpl @Inject constructor( - private val db: FirebaseFirestore -) : FirebaseRemoteDataSource { - - private val cloudFunctionHelper = CloudFunctionHelper() - - override suspend fun createGoogleIdUser(userId: String, userName: String?, userProfileImage: String?): User? { - return suspendCancellableCoroutine { continuation -> - val documentReference = db.collection("users").document(userId) - documentReference.set(FirebaseUser(name = userName, profileImage = userProfileImage)) - .addOnSuccessListener { - documentReference.get() - .addOnSuccessListener { documentSnapshot -> - val savedUser = documentSnapshot.toObject() - continuation.resume( - savedUser?.toUser()?.copy(userId = documentReference.id) - ) - } - .addOnFailureListener { exception -> - continuation.resumeWithException(exception) - } - } - .addOnFailureListener { exception -> - Log.e("FirebaseDataSourceImpl", exception.message.toString()) - continuation.resumeWithException(exception) - } - } - } - - override suspend fun fetchUser(userId: String): User? { - return suspendCancellableCoroutine { continuation -> - db.collection("users").document(userId).get() - .addOnSuccessListener { document -> - val firebaseUser = document.toObject() - continuation.resume(firebaseUser?.toUser()?.copy(userId = userId)) - } - .addOnFailureListener { exception -> - continuation.resumeWithException(exception) - } - } - } - - override suspend fun updateUserName(userId: String, newUserName: String): Boolean { - return suspendCancellableCoroutine { continuation -> - db.runTransaction { transaction -> - val userRef = db.collection("users").document(userId) - val userDocument = transaction.get(userRef) - transaction.update(userRef, "name", newUserName) - - val myPicks = userDocument.get("myPicks")?.let { it as List } ?: emptyList() - myPicks.forEach { pickId -> - val pickRef = db.collection("picks").document(pickId) - transaction.update(pickRef, "createdBy.userName", newUserName) - } - }.addOnSuccessListener { - continuation.resume(true) - }.addOnFailureListener { exception -> - continuation.resumeWithException(exception) - } - } - } - - /** - * Fetches a pick by ID from Firestore. - * @param pickID The ID of the pick to fetch. - * @return The fetched pick, or null if the pick does not exist on firestore. - */ - override suspend fun fetchPick(pickID: String): Pick? { - return suspendCancellableCoroutine { continuation -> - db.collection("picks").document(pickID).get() - .addOnSuccessListener { document -> - val firestorePick = document.toObject()?.copy(id = pickID) - val resultPick = firestorePick?.toPick() - continuation.resume(resultPick) - } - .addOnFailureListener { exception -> - Log.e("FirebaseDataSourceImpl", "Failed to fetch a pick", exception) - continuation.resumeWithException(exception) - } - } - } - - /** - * Fetches picks within a given radius from Firestore. - * @param lat The latitude of the center of the search area. - * @param lng The longitude of the center of the search area. - * @param radiusInM The radius in meters of the search area. - * @return A list of picks within the specified radius, ordered by distance from the center. can be empty. - */ - override suspend fun fetchPicksInArea( - lat: Double, - lng: Double, - radiusInM: Double - ): List { - val center = GeoLocation(lat, lng) - val bounds = GeoFireUtils.getGeoHashQueryBounds(center, radiusInM) - - val queries: MutableList = ArrayList() - val tasks: MutableList> = ArrayList() - val matchingPicks: MutableList = ArrayList() - - bounds.forEach { bound -> - val query = db.collection("picks") - .orderBy("geoHash") - .startAt(bound.startHash) - .endAt(bound.endHash) - queries.add(query) - } - - try { - queries.forEach { query -> - tasks.add(query.get()) - } - Tasks.whenAllComplete(tasks).await() - } catch (exception: Exception) { - Log.e("FirebaseDataSourceImpl", "Failed to fetch picks", exception) - throw exception - } - - tasks.forEach { task -> - val snap = task.result - snap.documents.forEach { doc -> - if (isAccurate(doc, center, radiusInM)) { - doc.toObject()?.run { - matchingPicks.add(this.toPick().copy(id = doc.id)) - } - } - } - } - - return matchingPicks - } - - /** - * GeoHash의 FP 문제 - Geohash의 쿼리가 정확하지 않으며 클라이언트 측에서 거짓양성 결과를 필터링해야 합니다. - * 이러한 추가 읽기로 인해 앱에 비용과 지연 시간이 추가됩니다. - * @param doc The pick document to check. - * @param center The center of the search area. - * @param radiusInM The radius in meters of the search area. - * @return True if the pick is within the specified radius, false otherwise. - */ - private fun isAccurate(doc: DocumentSnapshot, center: GeoLocation, radiusInM: Double): Boolean { - val location = doc.getGeoPoint("location") ?: return false - - val docLocation = GeoLocation(location.latitude, location.longitude) - val distanceInM = GeoFireUtils.getDistanceBetween(docLocation, center) - - return distanceInM <= radiusInM - } - - /** - * Creates a new pick in Firestore. - * @param pick The pick to create. - * @return The created pick. - */ - override suspend fun createPick(pick: Pick): String = - suspendCancellableCoroutine { continuation -> - val firebasePick = pick.toFirebasePick() - - // add() 메소드는 Cloud Firestore에서 ID를 자동으로 생성 - db.collection("picks").add(firebasePick) - .addOnSuccessListener { documentReference -> - val pickId = documentReference.id - // 유저의 픽 정보 업데이트 - updateCurrentUserPick(pick.createdBy.userId, pickId) - .addOnCompleteListener { task -> - if (task.isSuccessful) { - continuation.resume(pickId) - } else { - continuation.resumeWithException( - task.exception ?: Exception("Failed to updating user pick info") - ) - } - } - } - .addOnFailureListener { exception -> - Log.e("FirebaseDataSourceImpl", "Failed to create a pick", exception) - continuation.resumeWithException(exception) - } - } - - override suspend fun deletePick(pickId: String, userId: String): Boolean { - val pickDocument = db.collection(COLLECTION_PICKS).document(pickId) - val userDocument = db.collection(COLLECTION_USERS).document(userId) - val favoriteDocuments = fetchFavoriteDocuments(pickId) - - return suspendCancellableCoroutine { continuation -> - db.runTransaction { transaction -> - transaction.delete(pickDocument) - - favoriteDocuments.forEach { document -> - transaction.delete(document) - } - - transaction.update(userDocument, FIELD_MY_PICKS, FieldValue.arrayRemove(pickId)) - }.addOnSuccessListener { _ -> - continuation.resume(true) - }.addOnFailureListener { e -> - Log.w(TAG_LOG, "Transaction failure.", e) - continuation.resumeWithException(e) - } - } - } - - override suspend fun fetchMyPicks(userId: String): List { - val userDocument = fetchUserDocument(userId) - if (userDocument.exists().not()) throw Exception("No user info in database") - - val tasks = mutableListOf>() - val myPicks = mutableListOf() - - try { - userDocument.toObject()?.myPicks?.forEach { pickId -> - tasks.add( - db.collection(COLLECTION_PICKS) - .document(pickId) - .get() - ) - } - Tasks.whenAllComplete(tasks).await() - } catch (exception: Exception) { - Log.e("FirebaseDataSourceImpl", "Failed to fetch my picks", exception) - throw exception - } - - tasks.forEach { task -> - task.result.toObject()?.run { - myPicks.add(this.toPick().copy(id = task.result.id)) - } - } - - return myPicks.reversed() - } - - override suspend fun fetchFavoritePicks(userId: String): List { - val favoriteDocuments = fetchFavoritesByUserId(userId) - - val tasks = mutableListOf>() - val favorites = mutableListOf() - - try { - favoriteDocuments.forEach { doc -> - tasks.add( - db.collection(COLLECTION_PICKS) - .document(doc.data[FIELD_PICK_ID].toString()) - .get() - ) - } - Tasks.whenAllComplete(tasks).await() - } catch (exception: Exception) { - Log.e("FirebaseDataSourceImpl", "Failed to get favorite picks", exception) - throw exception - } - tasks.forEach { task -> - task.result.toObject()?.run { - favorites.add(this.toPick().copy(id = task.result.id)) - } - } - - return favorites - } - - override suspend fun fetchIsFavorite(pickId: String, userId: String): Boolean { - val favoriteDocument = fetchFavoriteByPickIdAndUserId(pickId, userId) - return favoriteDocument.isEmpty.not() - } - - override suspend fun createFavorite(pickId: String, userId: String): Boolean { - return suspendCancellableCoroutine { continuation -> - val firebaseFavorite = FirebaseFavorite( - pickId = pickId, - userId = userId - ) - - db.collection(COLLECTION_FAVORITES) - .add(firebaseFavorite) - .addOnSuccessListener { - // favorites에 문서 생성 후 클라우드 함수가 완료됐을 때 담기 완료 - CoroutineScope(Dispatchers.IO).launch { - try { - updateFavoriteCount(pickId) // 클라우드 함수 호출 - continuation.resume(true) - } catch (e: Exception) { - continuation.resumeWithException(e) - } - } - } - .addOnFailureListener { exception -> - Log.e("FirebaseDataSourceImpl", "Failed to create favorite", exception) - continuation.resumeWithException(exception) - } - } - } - - override suspend fun deleteFavorite(pickId: String, userId: String): Boolean { - val favoriteDocument = fetchFavoriteByPickIdAndUserId(pickId, userId) - return suspendCancellableCoroutine { continuation -> - favoriteDocument.forEach { document -> - db.collection(COLLECTION_FAVORITES).document(document.id) - .delete() - .addOnSuccessListener { - // favorites에 문서 삭제 후 클라우드 함수가 완료됐을 때 담기 해제 완료 - CoroutineScope(Dispatchers.IO).launch { - try { - updateFavoriteCount(pickId) // 클라우드 함수 호출 - continuation.resume(true) - } catch (e: Exception) { - continuation.resumeWithException(e) - } - } - } - .addOnFailureListener { exception -> - Log.w( - "FirebaseDataSourceImpl", - "Error deleting favorite document", - exception - ) - continuation.resumeWithException(exception) - } - } - } - } - - private fun updateCurrentUserPick(userId: String, pickId: String): Task { - val userDoc = db.collection("users").document(userId) - return userDoc.update("myPicks", FieldValue.arrayUnion(pickId)) - } - - private suspend fun fetchFavoriteByPickIdAndUserId( - pickId: String, - userId: String - ): QuerySnapshot { - return suspendCancellableCoroutine { continuation -> - db.collection(COLLECTION_FAVORITES) - .whereEqualTo(FIELD_PICK_ID, pickId) - .whereEqualTo(FIELD_USER_ID, userId) - .get() - .addOnSuccessListener { result -> - continuation.resume(result) - } - .addOnFailureListener { exception -> - Log.w( - "FirebaseDataSourceImpl", - "Error at fetching favorite document", - exception - ) - continuation.resumeWithException(exception) - } - } - } - - private suspend fun fetchFavoritesByUserId(userId: String): QuerySnapshot { - return suspendCancellableCoroutine { continuation -> - db.collection(COLLECTION_FAVORITES) - .whereEqualTo(FIELD_USER_ID, userId) - .orderBy(FIELD_ADDED_AT, Query.Direction.DESCENDING) - .get() - .addOnSuccessListener { result -> - continuation.resume(result) - } - .addOnFailureListener { exception -> - Log.w( - "FirebaseDataSourceImpl", - "Error at fetching favorite documents", - exception - ) - continuation.resumeWithException(exception) - } - } - } - - private suspend fun fetchUserDocument(userId: String): DocumentSnapshot { - return suspendCancellableCoroutine { continuation -> - db.collection(COLLECTION_USERS).document(userId) - .get() - .addOnSuccessListener { document -> - continuation.resume(document) - } - .addOnFailureListener { exception -> - Log.e("FirebaseDataSourceImpl", "Failed to get user document", exception) - continuation.resumeWithException(exception) - } - } - } - - private suspend fun updateFavoriteCount(pickId: String) { - try { - val result = cloudFunctionHelper.updateFavoriteCount(pickId) - result.onSuccess { - Log.d("FirebaseDataSourceImpl", "Success to update favorite count") - }.onFailure { exception -> - Log.e("FirebaseDataSourceImpl", "Failed to update favorite count", exception) - throw exception - } - } catch (e: Exception) { - Log.e("FirebaseDataSourceImpl", "Exception occurred while updating favorite count", e) - throw e - } - } - - private suspend fun fetchFavoriteDocuments(pickId: String): List { - return suspendCancellableCoroutine { continuation -> - db.collection(COLLECTION_FAVORITES) - .whereEqualTo(FIELD_PICK_ID, pickId) - .get() - .addOnSuccessListener { querySnapShot -> - val documentIds = querySnapShot.documents.map { it.id } - val documentRefs = mutableListOf() - documentIds.forEach { id -> - documentRefs.add(db.collection(COLLECTION_FAVORITES).document(id)) - } - continuation.resume(documentRefs) - } - .addOnFailureListener { e -> - Log.w(TAG_LOG, "Failed to fetch favorite documents id", e) - continuation.resumeWithException(e) - } - } - } - - companion object { - private const val TAG_LOG = "FirebaseDataSourceImpl" - - private const val COLLECTION_FAVORITES = "favorites" - private const val COLLECTION_PICKS = "picks" - private const val COLLECTION_USERS = "users" - - private const val FIELD_PICK_ID = "pickId" - private const val FIELD_USER_ID = "userId" - private const val FIELD_ADDED_AT = "addedAt" - private const val FIELD_MY_PICKS = "myPicks" - } -} diff --git a/data/src/main/java/com/squirtles/data/datasource/remote/firebase/FirebaseFavoriteDataSourceImpl.kt b/data/src/main/java/com/squirtles/data/datasource/remote/firebase/FirebaseFavoriteDataSourceImpl.kt new file mode 100644 index 00000000..216681e9 --- /dev/null +++ b/data/src/main/java/com/squirtles/data/datasource/remote/firebase/FirebaseFavoriteDataSourceImpl.kt @@ -0,0 +1,181 @@ +package com.squirtles.data.datasource.remote.firebase + +import android.util.Log +import com.google.android.gms.tasks.Task +import com.google.android.gms.tasks.Tasks +import com.google.firebase.firestore.DocumentSnapshot +import com.google.firebase.firestore.FirebaseFirestore +import com.google.firebase.firestore.Query +import com.google.firebase.firestore.QuerySnapshot +import com.google.firebase.firestore.toObject +import com.squirtles.data.datasource.remote.firebase.model.FirebaseFavorite +import com.squirtles.data.datasource.remote.firebase.model.FirebasePick +import com.squirtles.data.mapper.toPick +import com.squirtles.domain.datasource.remote.firebase.FirebaseDataSourceConstants.COLLECTION_FAVORITES +import com.squirtles.domain.datasource.remote.firebase.FirebaseDataSourceConstants.COLLECTION_PICKS +import com.squirtles.domain.datasource.remote.firebase.FirebaseDataSourceConstants.FIELD_ADDED_AT +import com.squirtles.domain.datasource.remote.firebase.FirebaseDataSourceConstants.FIELD_PICK_ID +import com.squirtles.domain.datasource.remote.firebase.FirebaseDataSourceConstants.FIELD_USER_ID +import com.squirtles.domain.datasource.remote.firebase.FirebaseFavoriteDataSource +import com.squirtles.domain.model.Pick +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.launch +import kotlinx.coroutines.suspendCancellableCoroutine +import kotlinx.coroutines.tasks.await +import javax.inject.Inject +import javax.inject.Singleton +import kotlin.coroutines.resume +import kotlin.coroutines.resumeWithException + +@Singleton +class FirebaseFavoriteDataSourceImpl @Inject constructor( + private val db: FirebaseFirestore, + private val cloudFunctionHelper: CloudFunctionHelper +) : FirebaseFavoriteDataSource { + + override suspend fun fetchFavoritePicks(userId: String): List { + val favoriteDocuments = fetchFavoritesByUserId(userId) + + val tasks = mutableListOf>() + val favorites = mutableListOf() + + try { + favoriteDocuments.forEach { doc -> + tasks.add( + db.collection(COLLECTION_PICKS) + .document(doc.data[FIELD_PICK_ID].toString()) + .get() + ) + } + Tasks.whenAllComplete(tasks).await() + } catch (exception: Exception) { + Log.e("FirebaseDataSourceImpl", "Failed to get favorite picks", exception) + throw exception + } + tasks.forEach { task -> + task.result.toObject()?.run { + favorites.add(this.toPick().copy(id = task.result.id)) + } + } + + return favorites + } + + override suspend fun fetchIsFavorite(pickId: String, userId: String): Boolean { + val favoriteDocument = fetchFavoriteByPickIdAndUserId(pickId, userId) + return favoriteDocument.isEmpty.not() + } + + override suspend fun createFavorite(pickId: String, userId: String): Boolean { + return suspendCancellableCoroutine { continuation -> + val firebaseFavorite = FirebaseFavorite( + pickId = pickId, + userId = userId + ) + + db.collection(COLLECTION_FAVORITES) + .add(firebaseFavorite) + .addOnSuccessListener { + // favorites에 문서 생성 후 클라우드 함수가 완료됐을 때 담기 완료 + CoroutineScope(Dispatchers.IO).launch { + try { + updateFavoriteCount(pickId) // 클라우드 함수 호출 + continuation.resume(true) + } catch (e: Exception) { + continuation.resumeWithException(e) + } + } + } + .addOnFailureListener { exception -> + Log.e("FirebaseDataSourceImpl", "Failed to create favorite", exception) + continuation.resumeWithException(exception) + } + } + } + + override suspend fun deleteFavorite(pickId: String, userId: String): Boolean { + val favoriteDocument = fetchFavoriteByPickIdAndUserId(pickId, userId) + return suspendCancellableCoroutine { continuation -> + favoriteDocument.forEach { document -> + db.collection(COLLECTION_FAVORITES).document(document.id) + .delete() + .addOnSuccessListener { + // favorites에 문서 삭제 후 클라우드 함수가 완료됐을 때 담기 해제 완료 + CoroutineScope(Dispatchers.IO).launch { + try { + updateFavoriteCount(pickId) // 클라우드 함수 호출 + continuation.resume(true) + } catch (e: Exception) { + continuation.resumeWithException(e) + } + } + } + .addOnFailureListener { exception -> + Log.w( + "FirebaseDataSourceImpl", + "Error deleting favorite document", + exception + ) + continuation.resumeWithException(exception) + } + } + } + } + + private suspend fun fetchFavoriteByPickIdAndUserId(pickId: String, userId: String): QuerySnapshot { + return suspendCancellableCoroutine { continuation -> + db.collection(COLLECTION_FAVORITES) + .whereEqualTo(FIELD_PICK_ID, pickId) + .whereEqualTo(FIELD_USER_ID, userId) + .get() + .addOnSuccessListener { result -> + continuation.resume(result) + } + .addOnFailureListener { exception -> + Log.w( + "FirebaseDataSourceImpl", + "Error at fetching favorite document", + exception + ) + continuation.resumeWithException(exception) + } + } + } + + private suspend fun fetchFavoritesByUserId(userId: String): QuerySnapshot { + val query = db.collection(COLLECTION_FAVORITES) + .whereEqualTo(FIELD_USER_ID, userId) + .orderBy(FIELD_ADDED_AT, Query.Direction.DESCENDING) + + return executeQuery(query) + } + + private suspend fun executeQuery(query: Query): QuerySnapshot { + return suspendCancellableCoroutine { continuation -> + query.get() + .addOnSuccessListener { result -> + continuation.resume(result) + } + .addOnFailureListener { exception -> + Log.w("FirebaseDataSourceImpl", "Error fetching favorite documents", exception) + continuation.resumeWithException(exception) + } + } + } + + private suspend fun updateFavoriteCount(pickId: String) { + try { + val result = cloudFunctionHelper.updateFavoriteCount(pickId) + result.onSuccess { + Log.d("FirebaseDataSourceImpl", "Success to update favorite count") + }.onFailure { exception -> + Log.e("FirebaseDataSourceImpl", "Failed to update favorite count", exception) + throw exception + } + } catch (e: Exception) { + Log.e("FirebaseDataSourceImpl", "Exception occurred while updating favorite count", e) + throw e + } + } +} diff --git a/data/src/main/java/com/squirtles/data/datasource/remote/firebase/FirebasePickDataSourceImpl.kt b/data/src/main/java/com/squirtles/data/datasource/remote/firebase/FirebasePickDataSourceImpl.kt index 37f72cb6..720017a4 100644 --- a/data/src/main/java/com/squirtles/data/datasource/remote/firebase/FirebasePickDataSourceImpl.kt +++ b/data/src/main/java/com/squirtles/data/datasource/remote/firebase/FirebasePickDataSourceImpl.kt @@ -12,16 +12,16 @@ import com.google.firebase.firestore.FirebaseFirestore import com.google.firebase.firestore.Query import com.google.firebase.firestore.QuerySnapshot import com.google.firebase.firestore.toObject -import com.squirtles.data.datasource.remote.firebase.FirebaseDataSourceImpl.Companion.COLLECTION_FAVORITES -import com.squirtles.data.datasource.remote.firebase.FirebaseDataSourceImpl.Companion.COLLECTION_PICKS -import com.squirtles.data.datasource.remote.firebase.FirebaseDataSourceImpl.Companion.COLLECTION_USERS -import com.squirtles.data.datasource.remote.firebase.FirebaseDataSourceImpl.Companion.FIELD_MY_PICKS -import com.squirtles.data.datasource.remote.firebase.FirebaseDataSourceImpl.Companion.FIELD_PICK_ID -import com.squirtles.data.datasource.remote.firebase.FirebaseDataSourceImpl.Companion.TAG_LOG import com.squirtles.data.datasource.remote.firebase.model.FirebasePick import com.squirtles.data.datasource.remote.firebase.model.FirebaseUser import com.squirtles.data.mapper.toFirebasePick import com.squirtles.data.mapper.toPick +import com.squirtles.domain.datasource.remote.firebase.FirebaseDataSourceConstants.COLLECTION_FAVORITES +import com.squirtles.domain.datasource.remote.firebase.FirebaseDataSourceConstants.COLLECTION_PICKS +import com.squirtles.domain.datasource.remote.firebase.FirebaseDataSourceConstants.COLLECTION_USERS +import com.squirtles.domain.datasource.remote.firebase.FirebaseDataSourceConstants.FIELD_MY_PICKS +import com.squirtles.domain.datasource.remote.firebase.FirebaseDataSourceConstants.FIELD_PICK_ID +import com.squirtles.domain.datasource.remote.firebase.FirebaseDataSourceConstants.TAG_LOG import com.squirtles.domain.datasource.remote.firebase.FirebasePickDataSource import com.squirtles.domain.model.Pick import kotlinx.coroutines.suspendCancellableCoroutine @@ -36,11 +36,7 @@ class FirebasePickDataSourceImpl @Inject constructor( private val db: FirebaseFirestore ) : FirebasePickDataSource { - /** - * Fetches a pick by ID from Firestore. - * @param pickID The ID of the pick to fetch. - * @return The fetched pick, or null if the pick does not exist on firestore. - */ + /* Fetches a pick by ID from Firestore */ override suspend fun fetchPick(pickID: String): Pick? { return suspendCancellableCoroutine { continuation -> db.collection("picks").document(pickID).get() @@ -56,13 +52,7 @@ class FirebasePickDataSourceImpl @Inject constructor( } } - /** - * Fetches picks within a given radius from Firestore. - * @param lat The latitude of the center of the search area. - * @param lng The longitude of the center of the search area. - * @param radiusInM The radius in meters of the search area. - * @return A list of picks within the specified radius, ordered by distance from the center. can be empty. - */ + /* Fetches picks within a given radius from Firestore */ override suspend fun fetchPicksInArea( lat: Double, lng: Double, @@ -107,28 +97,7 @@ class FirebasePickDataSourceImpl @Inject constructor( return matchingPicks } - /** - * GeoHash의 FP 문제 - Geohash의 쿼리가 정확하지 않으며 클라이언트 측에서 거짓양성 결과를 필터링해야 합니다. - * 이러한 추가 읽기로 인해 앱에 비용과 지연 시간이 추가됩니다. - * @param doc The pick document to check. - * @param center The center of the search area. - * @param radiusInM The radius in meters of the search area. - * @return True if the pick is within the specified radius, false otherwise. - */ - private fun isAccurate(doc: DocumentSnapshot, center: GeoLocation, radiusInM: Double): Boolean { - val location = doc.getGeoPoint("location") ?: return false - - val docLocation = GeoLocation(location.latitude, location.longitude) - val distanceInM = GeoFireUtils.getDistanceBetween(docLocation, center) - - return distanceInM <= radiusInM - } - - /** - * Creates a new pick in Firestore. - * @param pick The pick to create. - * @return The created pick. - */ + /* Creates a new pick in Firestore */ override suspend fun createPick(pick: Pick): String = suspendCancellableCoroutine { continuation -> val firebasePick = pick.toFirebasePick() @@ -232,4 +201,31 @@ class FirebasePickDataSourceImpl @Inject constructor( } } } + + private suspend fun fetchUserDocument(userId: String): DocumentSnapshot { + return suspendCancellableCoroutine { continuation -> + db.collection(COLLECTION_USERS).document(userId) + .get() + .addOnSuccessListener { document -> + continuation.resume(document) + } + .addOnFailureListener { exception -> + Log.e("FirebaseDataSourceImpl", "Failed to get user document", exception) + continuation.resumeWithException(exception) + } + } + } + + /** + * GeoHash의 FP 문제 - Geohash의 쿼리가 정확하지 않으며 클라이언트 측에서 거짓양성 결과를 필터링해야 합니다. + * 이러한 추가 읽기로 인해 앱에 비용과 지연 시간이 추가됩니다. + */ + private fun isAccurate(doc: DocumentSnapshot, center: GeoLocation, radiusInM: Double): Boolean { + val location = doc.getGeoPoint("location") ?: return false + + val docLocation = GeoLocation(location.latitude, location.longitude) + val distanceInM = GeoFireUtils.getDistanceBetween(docLocation, center) + + return distanceInM <= radiusInM + } } diff --git a/data/src/main/java/com/squirtles/data/datasource/remote/firebase/FirebaseUserDataSourceImpl.kt b/data/src/main/java/com/squirtles/data/datasource/remote/firebase/FirebaseUserDataSourceImpl.kt new file mode 100644 index 00000000..25e51a20 --- /dev/null +++ b/data/src/main/java/com/squirtles/data/datasource/remote/firebase/FirebaseUserDataSourceImpl.kt @@ -0,0 +1,77 @@ +package com.squirtles.data.datasource.remote.firebase + +import android.util.Log +import com.google.firebase.firestore.FirebaseFirestore +import com.google.firebase.firestore.toObject +import com.squirtles.data.datasource.remote.firebase.model.FirebaseUser +import com.squirtles.data.mapper.toUser +import com.squirtles.domain.datasource.remote.firebase.FirebaseUserDataSource +import com.squirtles.domain.model.User +import kotlinx.coroutines.suspendCancellableCoroutine +import javax.inject.Inject +import javax.inject.Singleton +import kotlin.coroutines.resume +import kotlin.coroutines.resumeWithException + +@Singleton +class FirebaseUserDataSourceImpl @Inject constructor( + private val db: FirebaseFirestore +): FirebaseUserDataSource { + + override suspend fun createGoogleIdUser(userId: String, userName: String?, userProfileImage: String?): User? { + return suspendCancellableCoroutine { continuation -> + val documentReference = db.collection("users").document(userId) + documentReference.set(FirebaseUser(name = userName, profileImage = userProfileImage)) + .addOnSuccessListener { + documentReference.get() + .addOnSuccessListener { documentSnapshot -> + val savedUser = documentSnapshot.toObject() + continuation.resume( + savedUser?.toUser()?.copy(userId = documentReference.id) + ) + } + .addOnFailureListener { exception -> + continuation.resumeWithException(exception) + } + } + .addOnFailureListener { exception -> + Log.e("FirebaseDataSourceImpl", exception.message.toString()) + continuation.resumeWithException(exception) + } + } + } + + override suspend fun fetchUser(userId: String): User? { + return suspendCancellableCoroutine { continuation -> + db.collection("users").document(userId).get() + .addOnSuccessListener { document -> + val firebaseUser = document.toObject() + continuation.resume(firebaseUser?.toUser()?.copy(userId = userId)) + } + .addOnFailureListener { exception -> + continuation.resumeWithException(exception) + } + } + } + + override suspend fun updateUserName(userId: String, newUserName: String): Boolean { + return suspendCancellableCoroutine { continuation -> + db.runTransaction { transaction -> + val userRef = db.collection("users").document(userId) + val userDocument = transaction.get(userRef) + transaction.update(userRef, "name", newUserName) + + val myPicks = userDocument.get("myPicks")?.let { it as List } ?: emptyList() + myPicks.forEach { pickId -> + val pickRef = db.collection("picks").document(pickId) + transaction.update(pickRef, "createdBy.userName", newUserName) + } + }.addOnSuccessListener { + continuation.resume(true) + }.addOnFailureListener { exception -> + continuation.resumeWithException(exception) + } + } + } + +} diff --git a/data/src/main/java/com/squirtles/data/di/DataModule.kt b/data/src/main/java/com/squirtles/data/di/DataModule.kt index b1bfe047..5edd7223 100644 --- a/data/src/main/java/com/squirtles/data/di/DataModule.kt +++ b/data/src/main/java/com/squirtles/data/di/DataModule.kt @@ -5,21 +5,24 @@ import com.google.firebase.firestore.FirebaseFirestore import com.squirtles.data.datasource.local.LocalDataSourceImpl import com.squirtles.data.datasource.remote.applemusic.AppleMusicDataSourceImpl import com.squirtles.data.datasource.remote.applemusic.api.AppleMusicApi -import com.squirtles.data.datasource.remote.firebase.FirebaseDataSourceImpl +import com.squirtles.data.datasource.remote.firebase.CloudFunctionHelper +import com.squirtles.data.datasource.remote.firebase.FirebaseFavoriteDataSourceImpl +import com.squirtles.data.datasource.remote.firebase.FirebasePickDataSourceImpl +import com.squirtles.data.datasource.remote.firebase.FirebaseUserDataSourceImpl import com.squirtles.data.repository.local.LocalRepositoryImpl import com.squirtles.data.repository.remote.applemusic.AppleMusicRepositoryImpl import com.squirtles.data.repository.remote.firebase.FirebaseFavoriteRepositoryImpl import com.squirtles.data.repository.remote.firebase.FirebasePickRepositoryImpl -import com.squirtles.data.repository.remote.firebase.FirebaseRepositoryImpl import com.squirtles.data.repository.remote.firebase.FirebaseUserRepositoryImpl import com.squirtles.domain.datasource.local.LocalDataSource import com.squirtles.domain.datasource.remote.applemusic.AppleMusicRemoteDataSource -import com.squirtles.domain.datasource.remote.firebase.FirebaseRemoteDataSource +import com.squirtles.domain.datasource.remote.firebase.FirebaseFavoriteDataSource +import com.squirtles.domain.datasource.remote.firebase.FirebasePickDataSource +import com.squirtles.domain.datasource.remote.firebase.FirebaseUserDataSource import com.squirtles.domain.repository.local.LocalRepository import com.squirtles.domain.repository.remote.applemusic.AppleMusicRepository import com.squirtles.domain.repository.remote.firebase.FirebaseFavoriteRepository import com.squirtles.domain.repository.remote.firebase.FirebasePickRepository -import com.squirtles.domain.repository.remote.firebase.FirebaseRepository import com.squirtles.domain.repository.remote.firebase.FirebaseUserRepository import dagger.Module import dagger.Provides @@ -32,6 +35,8 @@ import javax.inject.Singleton @InstallIn(SingletonComponent::class) internal object DataModule { + // LOCAL + @Provides @Singleton fun provideLocalRepository(localDataSource: LocalDataSource): LocalRepository = @@ -42,31 +47,46 @@ internal object DataModule { fun provideLocalDataSource(@ApplicationContext context: Context): LocalDataSource = LocalDataSourceImpl(context) + // FIREBASE REPOSITORY + + @Provides + @Singleton + fun provideFirebaseFavoriteRepository(firebaseFavoriteDataSource: FirebaseFavoriteDataSource): FirebaseFavoriteRepository = + FirebaseFavoriteRepositoryImpl(firebaseFavoriteDataSource) + @Provides @Singleton - fun provideFirebaseRepository(firebaseRemoteDataSource: FirebaseRemoteDataSource): FirebaseRepository = - FirebaseRepositoryImpl(firebaseRemoteDataSource) + fun provideFirebaseUserRepository(firebaseUserDataSource: FirebaseUserDataSource): FirebaseUserRepository = + FirebaseUserRepositoryImpl(firebaseUserDataSource) @Provides @Singleton - fun provideFirebaseFavoriteRepository(firebaseRemoteDataSource: FirebaseRemoteDataSource): FirebaseFavoriteRepository = - FirebaseFavoriteRepositoryImpl(firebaseRemoteDataSource) + fun provideFirebasePickRepository(firebasePickDataSource: FirebasePickDataSource): FirebasePickRepository = + FirebasePickRepositoryImpl(firebasePickDataSource) + + + // FIREBASE DATA SOURCE @Provides @Singleton - fun provideFirebaseUserRepository(firebaseRemoteDataSource: FirebaseRemoteDataSource): FirebaseUserRepository = - FirebaseUserRepositoryImpl(firebaseRemoteDataSource) + fun provideFirebasePickDataSource(db: FirebaseFirestore): FirebasePickDataSource = + FirebasePickDataSourceImpl(db) @Provides @Singleton - fun provideFirebasePickRepository(firebaseRemoteDataSource: FirebaseRemoteDataSource): FirebasePickRepository = - FirebasePickRepositoryImpl(firebaseRemoteDataSource) + fun provideFirebaseFavoriteDataSource(db: FirebaseFirestore, cloudFunctionHelper: CloudFunctionHelper): FirebaseFavoriteDataSource = + FirebaseFavoriteDataSourceImpl(db, cloudFunctionHelper) + @Provides + @Singleton + fun provideFirebaseUserDataSource(db: FirebaseFirestore): FirebaseUserDataSource = + FirebaseUserDataSourceImpl(db) @Provides @Singleton - fun provideFirebaseRemoteDataSource(db: FirebaseFirestore): FirebaseRemoteDataSource = - FirebaseDataSourceImpl(db) + fun provideCloudFunctionHelper(): CloudFunctionHelper = CloudFunctionHelper() + + // APPLE MUSIC @Provides @Singleton diff --git a/data/src/main/java/com/squirtles/data/repository/remote/firebase/FirebaseFavoriteRepositoryImpl.kt b/data/src/main/java/com/squirtles/data/repository/remote/firebase/FirebaseFavoriteRepositoryImpl.kt index be842509..e2f9a30d 100644 --- a/data/src/main/java/com/squirtles/data/repository/remote/firebase/FirebaseFavoriteRepositoryImpl.kt +++ b/data/src/main/java/com/squirtles/data/repository/remote/firebase/FirebaseFavoriteRepositoryImpl.kt @@ -1,35 +1,38 @@ package com.squirtles.data.repository.remote.firebase -import com.squirtles.domain.datasource.remote.firebase.FirebaseRemoteDataSource +import com.squirtles.domain.datasource.remote.firebase.FirebaseFavoriteDataSource import com.squirtles.domain.model.Pick import com.squirtles.domain.repository.remote.RemoteRepository import com.squirtles.domain.repository.remote.firebase.FirebaseFavoriteRepository +import javax.inject.Inject +import javax.inject.Singleton - -class FirebaseFavoriteRepositoryImpl( - private val firebaseRemoteDataSource: FirebaseRemoteDataSource +@Singleton +class FirebaseFavoriteRepositoryImpl @Inject constructor( + private val favoriteDataSource: FirebaseFavoriteDataSource ) : FirebaseFavoriteRepository, RemoteRepository() { + override suspend fun fetchIsFavorite(pickId: String, userId: String): Result { return handleResult { - firebaseRemoteDataSource.fetchIsFavorite(pickId, userId) + favoriteDataSource.fetchIsFavorite(pickId, userId) } } override suspend fun createFavorite(pickId: String, userId: String): Result { return handleResult { - firebaseRemoteDataSource.createFavorite(pickId, userId) + favoriteDataSource.createFavorite(pickId, userId) } } override suspend fun deleteFavorite(pickId: String, userId: String): Result { return handleResult { - firebaseRemoteDataSource.deleteFavorite(pickId, userId) + favoriteDataSource.deleteFavorite(pickId, userId) } } override suspend fun fetchFavoritePicks(userId: String): Result> { return handleResult { - firebaseRemoteDataSource.fetchFavoritePicks(userId) + favoriteDataSource.fetchFavoritePicks(userId) } } } diff --git a/data/src/main/java/com/squirtles/data/repository/remote/firebase/FirebasePickRepositoryImpl.kt b/data/src/main/java/com/squirtles/data/repository/remote/firebase/FirebasePickRepositoryImpl.kt index 15af9f43..ae2a486a 100644 --- a/data/src/main/java/com/squirtles/data/repository/remote/firebase/FirebasePickRepositoryImpl.kt +++ b/data/src/main/java/com/squirtles/data/repository/remote/firebase/FirebasePickRepositoryImpl.kt @@ -1,36 +1,39 @@ package com.squirtles.data.repository.remote.firebase -import com.squirtles.domain.datasource.remote.firebase.FirebaseRemoteDataSource +import com.squirtles.domain.datasource.remote.firebase.FirebasePickDataSource import com.squirtles.domain.model.Pick import com.squirtles.domain.repository.remote.RemoteRepository import com.squirtles.domain.repository.remote.firebase.FirebaseException import com.squirtles.domain.repository.remote.firebase.FirebasePickRepository +import javax.inject.Inject +import javax.inject.Singleton -class FirebasePickRepositoryImpl( - private val firebaseRemoteDataSource: FirebaseRemoteDataSource +@Singleton +class FirebasePickRepositoryImpl @Inject constructor( + private val pickDataSource: FirebasePickDataSource ) : FirebasePickRepository, RemoteRepository() { override suspend fun createPick(pick: Pick): Result { return handleResult { - firebaseRemoteDataSource.createPick(pick) + pickDataSource.createPick(pick) } } override suspend fun deletePick(pickId: String, userId: String): Result { return handleResult { - firebaseRemoteDataSource.deletePick(pickId, userId) + pickDataSource.deletePick(pickId, userId) } } override suspend fun fetchPick(pickID: String): Result { return handleResult(FirebaseException.NoSuchPickException()) { - firebaseRemoteDataSource.fetchPick(pickID) + pickDataSource.fetchPick(pickID) } } override suspend fun fetchMyPicks(userId: String): Result> { return handleResult { - firebaseRemoteDataSource.fetchMyPicks(userId) + pickDataSource.fetchMyPicks(userId) } } @@ -39,7 +42,7 @@ class FirebasePickRepositoryImpl( lng: Double, radiusInM: Double ): Result> { - val pickList = firebaseRemoteDataSource.fetchPicksInArea(lat, lng, radiusInM) + val pickList = pickDataSource.fetchPicksInArea(lat, lng, radiusInM) return handleResult(FirebaseException.NoSuchPickInRadiusException()) { pickList.ifEmpty { null } } diff --git a/data/src/main/java/com/squirtles/data/repository/remote/firebase/FirebaseRepositoryImpl.kt b/data/src/main/java/com/squirtles/data/repository/remote/firebase/FirebaseRepositoryImpl.kt deleted file mode 100644 index fdceb01b..00000000 --- a/data/src/main/java/com/squirtles/data/repository/remote/firebase/FirebaseRepositoryImpl.kt +++ /dev/null @@ -1,23 +0,0 @@ -package com.squirtles.data.repository.remote.firebase - -import com.squirtles.domain.datasource.remote.firebase.FirebaseRemoteDataSource -import com.squirtles.domain.repository.remote.RemoteRepository -import com.squirtles.domain.repository.remote.firebase.FirebaseException -import com.squirtles.domain.repository.remote.firebase.FirebaseRepository -import javax.inject.Inject -import javax.inject.Singleton - -@Singleton -class FirebaseRepositoryImpl @Inject constructor( - private val firebaseRemoteDataSource: FirebaseRemoteDataSource -) : FirebaseRepository, RemoteRepository() { - - private suspend fun handleResult( - firebaseRepositoryException: FirebaseException, - call: suspend () -> T? - ): Result { - return runCatching { - call() ?: throw firebaseRepositoryException - } - } -} diff --git a/data/src/main/java/com/squirtles/data/repository/remote/firebase/FirebaseUserRepositoryImpl.kt b/data/src/main/java/com/squirtles/data/repository/remote/firebase/FirebaseUserRepositoryImpl.kt index 767da751..5eb0eabb 100644 --- a/data/src/main/java/com/squirtles/data/repository/remote/firebase/FirebaseUserRepositoryImpl.kt +++ b/data/src/main/java/com/squirtles/data/repository/remote/firebase/FirebaseUserRepositoryImpl.kt @@ -1,13 +1,15 @@ package com.squirtles.data.repository.remote.firebase -import com.squirtles.domain.datasource.remote.firebase.FirebaseRemoteDataSource +import com.squirtles.domain.datasource.remote.firebase.FirebaseUserDataSource import com.squirtles.domain.model.User import com.squirtles.domain.repository.remote.RemoteRepository import com.squirtles.domain.repository.remote.firebase.FirebaseException import com.squirtles.domain.repository.remote.firebase.FirebaseUserRepository +import javax.inject.Singleton +@Singleton class FirebaseUserRepositoryImpl( - private val firebaseRemoteDataSource: FirebaseRemoteDataSource + private val userDataSource: FirebaseUserDataSource ) : FirebaseUserRepository, RemoteRepository() { override suspend fun createGoogleIdUser( @@ -16,19 +18,19 @@ class FirebaseUserRepositoryImpl( userProfileImage: String? ): Result { return handleResult(FirebaseException.CreatedUserFailedException()) { - firebaseRemoteDataSource.createGoogleIdUser(userId, userName, userProfileImage) + userDataSource.createGoogleIdUser(userId, userName, userProfileImage) } } override suspend fun fetchUser(userId: String): Result { return handleResult(FirebaseException.UserNotFoundException()) { - firebaseRemoteDataSource.fetchUser(userId) + userDataSource.fetchUser(userId) } } override suspend fun updateUserName(userId: String, newUserName: String): Result { return handleResult { - firebaseRemoteDataSource.updateUserName(userId, newUserName) + userDataSource.updateUserName(userId, newUserName) } } } diff --git a/domain/src/main/java/com/squirtles/domain/datasource/remote/firebase/FirebaseDataSourceConstants.kt b/domain/src/main/java/com/squirtles/domain/datasource/remote/firebase/FirebaseDataSourceConstants.kt new file mode 100644 index 00000000..865afede --- /dev/null +++ b/domain/src/main/java/com/squirtles/domain/datasource/remote/firebase/FirebaseDataSourceConstants.kt @@ -0,0 +1,12 @@ +package com.squirtles.domain.datasource.remote.firebase + +object FirebaseDataSourceConstants { + const val TAG_LOG = "FirebaseDataSourceImpl" + const val COLLECTION_FAVORITES = "favorites" + const val COLLECTION_PICKS = "picks" + const val COLLECTION_USERS = "users" + const val FIELD_PICK_ID = "pickId" + const val FIELD_USER_ID = "userId" + const val FIELD_ADDED_AT = "addedAt" + const val FIELD_MY_PICKS = "myPicks" +} diff --git a/domain/src/main/java/com/squirtles/domain/datasource/remote/firebase/FirebaseFavoriteDataSource.kt b/domain/src/main/java/com/squirtles/domain/datasource/remote/firebase/FirebaseFavoriteDataSource.kt new file mode 100644 index 00000000..a64210d9 --- /dev/null +++ b/domain/src/main/java/com/squirtles/domain/datasource/remote/firebase/FirebaseFavoriteDataSource.kt @@ -0,0 +1,10 @@ +package com.squirtles.domain.datasource.remote.firebase + +import com.squirtles.domain.model.Pick + +interface FirebaseFavoriteDataSource { + suspend fun fetchIsFavorite(pickId: String, userId: String): Boolean + suspend fun createFavorite(pickId: String, userId: String): Boolean + suspend fun deleteFavorite(pickId: String, userId: String): Boolean + suspend fun fetchFavoritePicks(userId: String): List +} diff --git a/domain/src/main/java/com/squirtles/domain/datasource/remote/firebase/FirebaseRemoteDataSource.kt b/domain/src/main/java/com/squirtles/domain/datasource/remote/firebase/FirebaseRemoteDataSource.kt deleted file mode 100644 index 2f0e7503..00000000 --- a/domain/src/main/java/com/squirtles/domain/datasource/remote/firebase/FirebaseRemoteDataSource.kt +++ /dev/null @@ -1,42 +0,0 @@ -package com.squirtles.domain.datasource.remote.firebase - -import com.squirtles.domain.model.Pick -import com.squirtles.domain.model.User - -interface FirebaseRemoteDataSource { - // user - suspend fun fetchUser(userId: String): User? - suspend fun createGoogleIdUser(userId: String, userName: String?, userProfileImage: String?): User? - suspend fun updateUserName(userId: String, newUserName: String): Boolean - - // fetch pick - suspend fun fetchPick(pickID: String): Pick? - suspend fun fetchPicksInArea(lat: Double, lng: Double, radiusInM: Double): List - - // pick CRUD - suspend fun createPick(pick: Pick): String - suspend fun deletePick(pickId: String, userId: String): Boolean - - // pickList - suspend fun fetchMyPicks(userId: String): List - suspend fun fetchFavoritePicks(userId: String): List - - // favorite - suspend fun fetchIsFavorite(pickId: String, userId: String): Boolean - suspend fun createFavorite(pickId: String, userId: String): Boolean - suspend fun deleteFavorite(pickId: String, userId: String): Boolean -// suspend fun updatePick(pick: Pick) - - companion object { - protected const val TAG_LOG = "FirebaseDataSourceImpl" - - protected const val COLLECTION_FAVORITES = "favorites" - protected const val COLLECTION_PICKS = "picks" - protected const val COLLECTION_USERS = "users" - - protected const val FIELD_PICK_ID = "pickId" - protected const val FIELD_USER_ID = "userId" - protected const val FIELD_ADDED_AT = "addedAt" - protected const val FIELD_MY_PICKS = "myPicks" - } -} diff --git a/domain/src/main/java/com/squirtles/domain/datasource/remote/firebase/FirebaseUserDataSource.kt b/domain/src/main/java/com/squirtles/domain/datasource/remote/firebase/FirebaseUserDataSource.kt new file mode 100644 index 00000000..58db5377 --- /dev/null +++ b/domain/src/main/java/com/squirtles/domain/datasource/remote/firebase/FirebaseUserDataSource.kt @@ -0,0 +1,9 @@ +package com.squirtles.domain.datasource.remote.firebase + +import com.squirtles.domain.model.User + +interface FirebaseUserDataSource { + suspend fun fetchUser(userId: String): User? + suspend fun createGoogleIdUser(userId: String, userName: String?, userProfileImage: String?): User? + suspend fun updateUserName(userId: String, newUserName: String): Boolean +} diff --git a/domain/src/main/java/com/squirtles/domain/repository/remote/firebase/FirebaseRepository.kt b/domain/src/main/java/com/squirtles/domain/repository/remote/firebase/FirebaseRepository.kt deleted file mode 100644 index 15d78028..00000000 --- a/domain/src/main/java/com/squirtles/domain/repository/remote/firebase/FirebaseRepository.kt +++ /dev/null @@ -1,4 +0,0 @@ -package com.squirtles.domain.repository.remote.firebase - -interface FirebaseRepository { -} From 2c9c8730c574452dc0245fa472fcfdc9e4cac620 Mon Sep 17 00:00:00 2001 From: miller198 Date: Fri, 14 Feb 2025 02:24:32 +0900 Subject: [PATCH 08/62] =?UTF-8?q?[refactor]=20Local=20Datasource,=20Reposi?= =?UTF-8?q?tory=20=EB=B6=84=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../datasource/local/LocalDataSourceImpl.kt | 2 + .../local/LocalUserDataSourceImpl.kt | 54 +++++++++++++ .../java/com/squirtles/data/di/DataModule.kt | 30 ++++++-- .../local/LocalLocationRepositoryImpl.kt | 18 +++++ .../local/LocalPickListOrderRepositoryImpl.kt | 22 ++++++ .../repository/local/LocalRepositoryImpl.kt | 76 ++++++++++--------- .../local/LocalUserRepositoryImpl.kt | 36 +++++++++ .../datasource/local/LocalUserDataSource.kt | 13 ++++ .../local/LocalLocationRepository.kt | 10 +++ .../local/LocalPickListOrderRepository.kt | 11 +++ .../repository/local/LocalRepository.kt | 26 ++++--- .../repository/local/LocalUserRepository.kt | 13 ++++ .../location/GetLastLocationUseCase.kt | 5 +- .../location/SaveLastLocationUseCase.kt | 5 +- .../order/GetFavoriteListOrderUseCase.kt | 5 +- .../order/GetMyPickListOrderUseCase.kt | 5 +- .../order/SaveFavoriteListOrderUseCase.kt | 5 +- .../order/SaveMyPickListOrderUseCase.kt | 5 +- .../domain/usecase/user/ClearUserUseCase.kt | 5 +- .../usecase/user/CreateGoogleIdUserUseCase.kt | 12 +-- .../usecase/user/FetchUserByIdUseCase.kt | 4 +- .../domain/usecase/user/FetchUserUseCase.kt | 8 +- .../usecase/user/GetCurrentUserUseCase.kt | 5 +- .../user/GetUserIdFromDataStoreUseCase.kt | 6 +- .../usecase/user/UpdateUserNameUseCase.kt | 4 +- 25 files changed, 298 insertions(+), 87 deletions(-) create mode 100644 data/src/main/java/com/squirtles/data/datasource/local/LocalUserDataSourceImpl.kt create mode 100644 data/src/main/java/com/squirtles/data/repository/local/LocalLocationRepositoryImpl.kt create mode 100644 data/src/main/java/com/squirtles/data/repository/local/LocalPickListOrderRepositoryImpl.kt create mode 100644 data/src/main/java/com/squirtles/data/repository/local/LocalUserRepositoryImpl.kt create mode 100644 domain/src/main/java/com/squirtles/domain/datasource/local/LocalUserDataSource.kt create mode 100644 domain/src/main/java/com/squirtles/domain/repository/local/LocalLocationRepository.kt create mode 100644 domain/src/main/java/com/squirtles/domain/repository/local/LocalPickListOrderRepository.kt create mode 100644 domain/src/main/java/com/squirtles/domain/repository/local/LocalUserRepository.kt diff --git a/data/src/main/java/com/squirtles/data/datasource/local/LocalDataSourceImpl.kt b/data/src/main/java/com/squirtles/data/datasource/local/LocalDataSourceImpl.kt index e5824a05..29fb6f79 100644 --- a/data/src/main/java/com/squirtles/data/datasource/local/LocalDataSourceImpl.kt +++ b/data/src/main/java/com/squirtles/data/datasource/local/LocalDataSourceImpl.kt @@ -14,7 +14,9 @@ import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.asStateFlow import kotlinx.coroutines.flow.map import javax.inject.Inject +import javax.inject.Singleton +@Singleton class LocalDataSourceImpl @Inject constructor( private val context: Context, ) : LocalDataSource { diff --git a/data/src/main/java/com/squirtles/data/datasource/local/LocalUserDataSourceImpl.kt b/data/src/main/java/com/squirtles/data/datasource/local/LocalUserDataSourceImpl.kt new file mode 100644 index 00000000..972d67a0 --- /dev/null +++ b/data/src/main/java/com/squirtles/data/datasource/local/LocalUserDataSourceImpl.kt @@ -0,0 +1,54 @@ +package com.squirtles.data.datasource.local + +import android.content.Context +import androidx.datastore.preferences.core.edit +import androidx.datastore.preferences.core.stringPreferencesKey +import androidx.datastore.preferences.preferencesDataStore +import com.squirtles.domain.datasource.local.LocalUserDataSource +import com.squirtles.domain.model.User +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.map +import javax.inject.Inject +import javax.inject.Singleton + +@Singleton +class LocalUserDataSourceImpl @Inject constructor( + private val context: Context, +) : LocalUserDataSource { + private val Context.dataStore by preferencesDataStore(name = USER_PREFERENCES_NAME) + + private var _currentUser: User? = null + override val currentUser: User? + get() = _currentUser + + override fun readUserIdDataStore(): Flow { + val dataStoreKey = stringPreferencesKey(USER_ID_KEY) + return context.dataStore.data.map { preferences -> + preferences[dataStoreKey] + } + } + + override suspend fun saveUserIdDataStore(userId: String) { + val dataStoreKey = stringPreferencesKey(USER_ID_KEY) + context.dataStore.edit { preferences -> + preferences[dataStoreKey] = userId + } + } + + override suspend fun saveCurrentUser(user: User) { + _currentUser = user + } + + override suspend fun clearUser() { + val dataStoreKey = stringPreferencesKey(USER_ID_KEY) + context.dataStore.edit { preferences -> + preferences.remove(dataStoreKey) + } + _currentUser = null + } + + companion object { + private const val USER_PREFERENCES_NAME = "user_preferences" + private const val USER_ID_KEY = "user_id" + } +} diff --git a/data/src/main/java/com/squirtles/data/di/DataModule.kt b/data/src/main/java/com/squirtles/data/di/DataModule.kt index 5edd7223..a0ca9db9 100644 --- a/data/src/main/java/com/squirtles/data/di/DataModule.kt +++ b/data/src/main/java/com/squirtles/data/di/DataModule.kt @@ -3,23 +3,31 @@ package com.squirtles.data.di import android.content.Context import com.google.firebase.firestore.FirebaseFirestore import com.squirtles.data.datasource.local.LocalDataSourceImpl +import com.squirtles.data.datasource.local.LocalUserDataSourceImpl import com.squirtles.data.datasource.remote.applemusic.AppleMusicDataSourceImpl import com.squirtles.data.datasource.remote.applemusic.api.AppleMusicApi import com.squirtles.data.datasource.remote.firebase.CloudFunctionHelper import com.squirtles.data.datasource.remote.firebase.FirebaseFavoriteDataSourceImpl import com.squirtles.data.datasource.remote.firebase.FirebasePickDataSourceImpl import com.squirtles.data.datasource.remote.firebase.FirebaseUserDataSourceImpl +import com.squirtles.data.repository.local.LocalLocationRepositoryImpl +import com.squirtles.data.repository.local.LocalPickListOrderRepositoryImpl import com.squirtles.data.repository.local.LocalRepositoryImpl +import com.squirtles.data.repository.local.LocalUserRepositoryImpl import com.squirtles.data.repository.remote.applemusic.AppleMusicRepositoryImpl import com.squirtles.data.repository.remote.firebase.FirebaseFavoriteRepositoryImpl import com.squirtles.data.repository.remote.firebase.FirebasePickRepositoryImpl import com.squirtles.data.repository.remote.firebase.FirebaseUserRepositoryImpl import com.squirtles.domain.datasource.local.LocalDataSource +import com.squirtles.domain.datasource.local.LocalUserDataSource import com.squirtles.domain.datasource.remote.applemusic.AppleMusicRemoteDataSource import com.squirtles.domain.datasource.remote.firebase.FirebaseFavoriteDataSource import com.squirtles.domain.datasource.remote.firebase.FirebasePickDataSource import com.squirtles.domain.datasource.remote.firebase.FirebaseUserDataSource +import com.squirtles.domain.repository.local.LocalLocationRepository +import com.squirtles.domain.repository.local.LocalPickListOrderRepository import com.squirtles.domain.repository.local.LocalRepository +import com.squirtles.domain.repository.local.LocalUserRepository import com.squirtles.domain.repository.remote.applemusic.AppleMusicRepository import com.squirtles.domain.repository.remote.firebase.FirebaseFavoriteRepository import com.squirtles.domain.repository.remote.firebase.FirebasePickRepository @@ -35,17 +43,29 @@ import javax.inject.Singleton @InstallIn(SingletonComponent::class) internal object DataModule { - // LOCAL + // LOCAL REPOSITORY @Provides @Singleton - fun provideLocalRepository(localDataSource: LocalDataSource): LocalRepository = - LocalRepositoryImpl(localDataSource) + fun provideLocalPickListOrderRepository(): LocalPickListOrderRepository = + LocalPickListOrderRepositoryImpl() @Provides @Singleton - fun provideLocalDataSource(@ApplicationContext context: Context): LocalDataSource = - LocalDataSourceImpl(context) + fun provideLocalLocationRepository(): LocalLocationRepository = + LocalLocationRepositoryImpl() + + @Provides + @Singleton + fun provideLocalUserRepository(userDataSource: LocalUserDataSource): LocalUserRepository = + LocalUserRepositoryImpl(userDataSource) + + // LOCAL DATA SOURCE + + @Provides + @Singleton + fun provideLocalUserDataSource(@ApplicationContext context: Context): LocalUserDataSource = + LocalUserDataSourceImpl(context) // FIREBASE REPOSITORY diff --git a/data/src/main/java/com/squirtles/data/repository/local/LocalLocationRepositoryImpl.kt b/data/src/main/java/com/squirtles/data/repository/local/LocalLocationRepositoryImpl.kt new file mode 100644 index 00000000..48a2983a --- /dev/null +++ b/data/src/main/java/com/squirtles/data/repository/local/LocalLocationRepositoryImpl.kt @@ -0,0 +1,18 @@ +package com.squirtles.data.repository.local + +import android.location.Location +import com.squirtles.domain.repository.local.LocalLocationRepository +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.StateFlow +import kotlinx.coroutines.flow.asStateFlow +import javax.inject.Singleton + +@Singleton +class LocalLocationRepositoryImpl: LocalLocationRepository { + private var _currentLocation: MutableStateFlow = MutableStateFlow(null) + override val lastLocation: StateFlow = _currentLocation.asStateFlow() + + override suspend fun saveCurrentLocation(location: Location) { + _currentLocation.emit(location) + } +} diff --git a/data/src/main/java/com/squirtles/data/repository/local/LocalPickListOrderRepositoryImpl.kt b/data/src/main/java/com/squirtles/data/repository/local/LocalPickListOrderRepositoryImpl.kt new file mode 100644 index 00000000..532bdc47 --- /dev/null +++ b/data/src/main/java/com/squirtles/data/repository/local/LocalPickListOrderRepositoryImpl.kt @@ -0,0 +1,22 @@ +package com.squirtles.data.repository.local + +import com.squirtles.domain.model.Order +import com.squirtles.domain.repository.local.LocalPickListOrderRepository +import javax.inject.Singleton + +@Singleton +class LocalPickListOrderRepositoryImpl: LocalPickListOrderRepository{ + private var _favoriteListOrder = Order.LATEST + override val favoriteListOrder get() = _favoriteListOrder + + private var _myListOrder = Order.LATEST + override val myListOrder get() = _myListOrder + + override suspend fun saveFavoriteListOrder(order: Order) { + _favoriteListOrder = order + } + + override suspend fun saveMyListOrder(order: Order) { + _myListOrder = order + } +} diff --git a/data/src/main/java/com/squirtles/data/repository/local/LocalRepositoryImpl.kt b/data/src/main/java/com/squirtles/data/repository/local/LocalRepositoryImpl.kt index 07eb771e..fea18ee1 100644 --- a/data/src/main/java/com/squirtles/data/repository/local/LocalRepositoryImpl.kt +++ b/data/src/main/java/com/squirtles/data/repository/local/LocalRepositoryImpl.kt @@ -7,45 +7,47 @@ import com.squirtles.domain.model.User import com.squirtles.domain.repository.local.LocalRepository import kotlinx.coroutines.flow.Flow import javax.inject.Inject +import javax.inject.Singleton +@Singleton class LocalRepositoryImpl @Inject constructor( private val localDataSource: LocalDataSource, ) : LocalRepository { - override val currentUser get() = localDataSource.currentUser - override val lastLocation get() = localDataSource.lastLocation - override val favoriteListOrder get() = localDataSource.favoriteListOrder - override val myListOrder get() = localDataSource.myListOrder - - override fun readUserIdDataStore(): Flow { - return localDataSource.readUserIdDataStore() - } - - override suspend fun saveUserIdDataStore(userId: String) { - localDataSource.saveUserIdDataStore(userId) - } - - override suspend fun saveCurrentUser(user: User) { - localDataSource.saveCurrentUser(user) - } - - override suspend fun clearUser(): Result { - return try { - localDataSource.clearUser() - Result.success(Unit) - } catch (e: Exception) { - Result.failure(e) - } - } - - override suspend fun saveCurrentLocation(location: Location) { - localDataSource.saveCurrentLocation(location) - } - - override suspend fun saveFavoriteListOrder(order: Order) { - localDataSource.saveFavoriteListOrder(order) - } - - override suspend fun saveMyListOrder(order: Order) { - localDataSource.saveMyListOrder(order) - } +// override val currentUser get() = localDataSource.currentUser +// override val lastLocation get() = localDataSource.lastLocation +// override val favoriteListOrder get() = localDataSource.favoriteListOrder +// override val myListOrder get() = localDataSource.myListOrder +// +// override fun readUserIdDataStore(): Flow { +// return localDataSource.readUserIdDataStore() +// } +// +// override suspend fun saveUserIdDataStore(userId: String) { +// localDataSource.saveUserIdDataStore(userId) +// } +// +// override suspend fun saveCurrentUser(user: User) { +// localDataSource.saveCurrentUser(user) +// } +// +// override suspend fun clearUser(): Result { +// return try { +// localDataSource.clearUser() +// Result.success(Unit) +// } catch (e: Exception) { +// Result.failure(e) +// } +// } +// +// override suspend fun saveCurrentLocation(location: Location) { +// localDataSource.saveCurrentLocation(location) +// } +// +// override suspend fun saveFavoriteListOrder(order: Order) { +// localDataSource.saveFavoriteListOrder(order) +// } +// +// override suspend fun saveMyListOrder(order: Order) { +// localDataSource.saveMyListOrder(order) +// } } diff --git a/data/src/main/java/com/squirtles/data/repository/local/LocalUserRepositoryImpl.kt b/data/src/main/java/com/squirtles/data/repository/local/LocalUserRepositoryImpl.kt new file mode 100644 index 00000000..ef497239 --- /dev/null +++ b/data/src/main/java/com/squirtles/data/repository/local/LocalUserRepositoryImpl.kt @@ -0,0 +1,36 @@ +package com.squirtles.data.repository.local + +import com.squirtles.domain.datasource.local.LocalUserDataSource +import com.squirtles.domain.model.User +import com.squirtles.domain.repository.local.LocalUserRepository +import kotlinx.coroutines.flow.Flow +import javax.inject.Inject +import javax.inject.Singleton + +@Singleton +class LocalUserRepositoryImpl @Inject constructor( + private val userDataSource: LocalUserDataSource +) : LocalUserRepository { + override val currentUser get() = userDataSource.currentUser + + override fun readUserIdDataStore(): Flow { + return userDataSource.readUserIdDataStore() + } + + override suspend fun saveUserIdDataStore(userId: String) { + userDataSource.saveUserIdDataStore(userId) + } + + override suspend fun saveCurrentUser(user: User) { + userDataSource.saveCurrentUser(user) + } + + override suspend fun clearUser(): Result { + return try { + userDataSource.clearUser() + Result.success(Unit) + } catch (e: Exception) { + Result.failure(e) + } + } +} diff --git a/domain/src/main/java/com/squirtles/domain/datasource/local/LocalUserDataSource.kt b/domain/src/main/java/com/squirtles/domain/datasource/local/LocalUserDataSource.kt new file mode 100644 index 00000000..3ed20efb --- /dev/null +++ b/domain/src/main/java/com/squirtles/domain/datasource/local/LocalUserDataSource.kt @@ -0,0 +1,13 @@ +package com.squirtles.domain.datasource.local + +import com.squirtles.domain.model.User +import kotlinx.coroutines.flow.Flow + +interface LocalUserDataSource { + val currentUser: User? + + fun readUserIdDataStore(): Flow + suspend fun saveUserIdDataStore(userId: String) + suspend fun saveCurrentUser(user: User) + suspend fun clearUser() +} diff --git a/domain/src/main/java/com/squirtles/domain/repository/local/LocalLocationRepository.kt b/domain/src/main/java/com/squirtles/domain/repository/local/LocalLocationRepository.kt new file mode 100644 index 00000000..9a27eaec --- /dev/null +++ b/domain/src/main/java/com/squirtles/domain/repository/local/LocalLocationRepository.kt @@ -0,0 +1,10 @@ +package com.squirtles.domain.repository.local + +import android.location.Location +import kotlinx.coroutines.flow.StateFlow + +interface LocalLocationRepository { + val lastLocation: StateFlow + + suspend fun saveCurrentLocation(location: Location) +} diff --git a/domain/src/main/java/com/squirtles/domain/repository/local/LocalPickListOrderRepository.kt b/domain/src/main/java/com/squirtles/domain/repository/local/LocalPickListOrderRepository.kt new file mode 100644 index 00000000..df1c67f1 --- /dev/null +++ b/domain/src/main/java/com/squirtles/domain/repository/local/LocalPickListOrderRepository.kt @@ -0,0 +1,11 @@ +package com.squirtles.domain.repository.local + +import com.squirtles.domain.model.Order + +interface LocalPickListOrderRepository { + val favoriteListOrder: Order // 픽 보관함 정렬 순서 + val myListOrder: Order // 등록한 픽 정렬 순서 + + suspend fun saveFavoriteListOrder(order: Order) + suspend fun saveMyListOrder(order: Order) +} diff --git a/domain/src/main/java/com/squirtles/domain/repository/local/LocalRepository.kt b/domain/src/main/java/com/squirtles/domain/repository/local/LocalRepository.kt index c20fee86..4c58cc25 100644 --- a/domain/src/main/java/com/squirtles/domain/repository/local/LocalRepository.kt +++ b/domain/src/main/java/com/squirtles/domain/repository/local/LocalRepository.kt @@ -7,16 +7,18 @@ import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.StateFlow interface LocalRepository { - val currentUser: User? - val lastLocation: StateFlow - val favoriteListOrder: Order // 픽 보관함 정렬 순서 - val myListOrder: Order // 등록한 픽 정렬 순서 - - fun readUserIdDataStore(): Flow - suspend fun saveUserIdDataStore(userId: String) - suspend fun saveCurrentUser(user: User) - suspend fun clearUser(): Result - suspend fun saveCurrentLocation(location: Location) - suspend fun saveFavoriteListOrder(order: Order) - suspend fun saveMyListOrder(order: Order) +// val currentUser: User? +// val lastLocation: StateFlow +// val favoriteListOrder: Order // 픽 보관함 정렬 순서 +// val myListOrder: Order // 등록한 픽 정렬 순서 +// +// fun readUserIdDataStore(): Flow +// suspend fun saveUserIdDataStore(userId: String) +// suspend fun saveCurrentUser(user: User) +// suspend fun clearUser(): Result +// +// suspend fun saveCurrentLocation(location: Location) +// +// suspend fun saveFavoriteListOrder(order: Order) +// suspend fun saveMyListOrder(order: Order) } diff --git a/domain/src/main/java/com/squirtles/domain/repository/local/LocalUserRepository.kt b/domain/src/main/java/com/squirtles/domain/repository/local/LocalUserRepository.kt new file mode 100644 index 00000000..3bfff38d --- /dev/null +++ b/domain/src/main/java/com/squirtles/domain/repository/local/LocalUserRepository.kt @@ -0,0 +1,13 @@ +package com.squirtles.domain.repository.local + +import com.squirtles.domain.model.User +import kotlinx.coroutines.flow.Flow + +interface LocalUserRepository { + val currentUser: User? + + fun readUserIdDataStore(): Flow + suspend fun saveUserIdDataStore(userId: String) + suspend fun saveCurrentUser(user: User) + suspend fun clearUser(): Result +} diff --git a/domain/src/main/java/com/squirtles/domain/usecase/location/GetLastLocationUseCase.kt b/domain/src/main/java/com/squirtles/domain/usecase/location/GetLastLocationUseCase.kt index 47d9f298..5070c35d 100644 --- a/domain/src/main/java/com/squirtles/domain/usecase/location/GetLastLocationUseCase.kt +++ b/domain/src/main/java/com/squirtles/domain/usecase/location/GetLastLocationUseCase.kt @@ -1,10 +1,11 @@ package com.squirtles.domain.usecase.location +import com.squirtles.domain.repository.local.LocalLocationRepository import com.squirtles.domain.repository.local.LocalRepository import javax.inject.Inject class GetLastLocationUseCase @Inject constructor( - private val localRepository: LocalRepository + private val localLocationRepository: LocalLocationRepository ) { - operator fun invoke() = localRepository.lastLocation + operator fun invoke() = localLocationRepository.lastLocation } diff --git a/domain/src/main/java/com/squirtles/domain/usecase/location/SaveLastLocationUseCase.kt b/domain/src/main/java/com/squirtles/domain/usecase/location/SaveLastLocationUseCase.kt index fc4fd098..3b98e2cb 100644 --- a/domain/src/main/java/com/squirtles/domain/usecase/location/SaveLastLocationUseCase.kt +++ b/domain/src/main/java/com/squirtles/domain/usecase/location/SaveLastLocationUseCase.kt @@ -1,11 +1,12 @@ package com.squirtles.domain.usecase.location import android.location.Location +import com.squirtles.domain.repository.local.LocalLocationRepository import com.squirtles.domain.repository.local.LocalRepository import javax.inject.Inject class SaveLastLocationUseCase @Inject constructor( - private val localRepository: LocalRepository + private val localLocationRepository: LocalLocationRepository ) { - suspend operator fun invoke(location: Location) = localRepository.saveCurrentLocation(location) + suspend operator fun invoke(location: Location) = localLocationRepository.saveCurrentLocation(location) } diff --git a/domain/src/main/java/com/squirtles/domain/usecase/order/GetFavoriteListOrderUseCase.kt b/domain/src/main/java/com/squirtles/domain/usecase/order/GetFavoriteListOrderUseCase.kt index 2745f4d2..9e4024ea 100644 --- a/domain/src/main/java/com/squirtles/domain/usecase/order/GetFavoriteListOrderUseCase.kt +++ b/domain/src/main/java/com/squirtles/domain/usecase/order/GetFavoriteListOrderUseCase.kt @@ -1,11 +1,12 @@ package com.squirtles.domain.usecase.order +import com.squirtles.domain.repository.local.LocalPickListOrderRepository import com.squirtles.domain.repository.local.LocalRepository import com.squirtles.domain.usecase.picklist.GetPickListOrderUseCaseInterface import javax.inject.Inject class GetFavoriteListOrderUseCase @Inject constructor( - private val localRepository: LocalRepository + private val localPickListOrderRepository: LocalPickListOrderRepository ) : GetPickListOrderUseCaseInterface { - override suspend operator fun invoke() = localRepository.favoriteListOrder + override suspend operator fun invoke() = localPickListOrderRepository.favoriteListOrder } diff --git a/domain/src/main/java/com/squirtles/domain/usecase/order/GetMyPickListOrderUseCase.kt b/domain/src/main/java/com/squirtles/domain/usecase/order/GetMyPickListOrderUseCase.kt index 3ab3425b..682193f7 100644 --- a/domain/src/main/java/com/squirtles/domain/usecase/order/GetMyPickListOrderUseCase.kt +++ b/domain/src/main/java/com/squirtles/domain/usecase/order/GetMyPickListOrderUseCase.kt @@ -1,11 +1,12 @@ package com.squirtles.domain.usecase.order +import com.squirtles.domain.repository.local.LocalPickListOrderRepository import com.squirtles.domain.repository.local.LocalRepository import com.squirtles.domain.usecase.picklist.GetPickListOrderUseCaseInterface import javax.inject.Inject class GetMyPickListOrderUseCase @Inject constructor( - private val localRepository: LocalRepository + private val localPickListOrderRepository: LocalPickListOrderRepository ) : GetPickListOrderUseCaseInterface { - override suspend operator fun invoke() = localRepository.myListOrder + override suspend operator fun invoke() = localPickListOrderRepository.myListOrder } diff --git a/domain/src/main/java/com/squirtles/domain/usecase/order/SaveFavoriteListOrderUseCase.kt b/domain/src/main/java/com/squirtles/domain/usecase/order/SaveFavoriteListOrderUseCase.kt index 7f0bb87e..73c56a04 100644 --- a/domain/src/main/java/com/squirtles/domain/usecase/order/SaveFavoriteListOrderUseCase.kt +++ b/domain/src/main/java/com/squirtles/domain/usecase/order/SaveFavoriteListOrderUseCase.kt @@ -1,12 +1,13 @@ package com.squirtles.domain.usecase.order import com.squirtles.domain.model.Order +import com.squirtles.domain.repository.local.LocalPickListOrderRepository import com.squirtles.domain.repository.local.LocalRepository import com.squirtles.domain.usecase.picklist.SavePickListOrderUseCaseInterface import javax.inject.Inject class SaveFavoriteListOrderUseCase @Inject constructor( - private val localRepository: LocalRepository + private val localPickListOrderRepository: LocalPickListOrderRepository ) : SavePickListOrderUseCaseInterface { - override suspend operator fun invoke(order: Order) = localRepository.saveFavoriteListOrder(order) + override suspend operator fun invoke(order: Order) = localPickListOrderRepository.saveFavoriteListOrder(order) } diff --git a/domain/src/main/java/com/squirtles/domain/usecase/order/SaveMyPickListOrderUseCase.kt b/domain/src/main/java/com/squirtles/domain/usecase/order/SaveMyPickListOrderUseCase.kt index 0766a36a..73a97c4a 100644 --- a/domain/src/main/java/com/squirtles/domain/usecase/order/SaveMyPickListOrderUseCase.kt +++ b/domain/src/main/java/com/squirtles/domain/usecase/order/SaveMyPickListOrderUseCase.kt @@ -1,12 +1,13 @@ package com.squirtles.domain.usecase.order import com.squirtles.domain.model.Order +import com.squirtles.domain.repository.local.LocalPickListOrderRepository import com.squirtles.domain.repository.local.LocalRepository import com.squirtles.domain.usecase.picklist.SavePickListOrderUseCaseInterface import javax.inject.Inject class SaveMyPickListOrderUseCase @Inject constructor( - private val localRepository: LocalRepository + private val localPickListOrderRepository: LocalPickListOrderRepository ) : SavePickListOrderUseCaseInterface { - override suspend operator fun invoke(order: Order) = localRepository.saveMyListOrder(order) + override suspend operator fun invoke(order: Order) = localPickListOrderRepository.saveMyListOrder(order) } diff --git a/domain/src/main/java/com/squirtles/domain/usecase/user/ClearUserUseCase.kt b/domain/src/main/java/com/squirtles/domain/usecase/user/ClearUserUseCase.kt index 0c81e48c..9340a567 100644 --- a/domain/src/main/java/com/squirtles/domain/usecase/user/ClearUserUseCase.kt +++ b/domain/src/main/java/com/squirtles/domain/usecase/user/ClearUserUseCase.kt @@ -1,10 +1,11 @@ package com.squirtles.domain.usecase.user import com.squirtles.domain.repository.local.LocalRepository +import com.squirtles.domain.repository.local.LocalUserRepository import javax.inject.Inject class ClearUserUseCase @Inject constructor( - private val localRepository: LocalRepository + private val localUserRepository: LocalUserRepository ) { - suspend operator fun invoke() = localRepository.clearUser() + suspend operator fun invoke() = localUserRepository.clearUser() } diff --git a/domain/src/main/java/com/squirtles/domain/usecase/user/CreateGoogleIdUserUseCase.kt b/domain/src/main/java/com/squirtles/domain/usecase/user/CreateGoogleIdUserUseCase.kt index 6e82beb7..6eb4b435 100644 --- a/domain/src/main/java/com/squirtles/domain/usecase/user/CreateGoogleIdUserUseCase.kt +++ b/domain/src/main/java/com/squirtles/domain/usecase/user/CreateGoogleIdUserUseCase.kt @@ -1,24 +1,24 @@ package com.squirtles.domain.usecase.user import com.squirtles.domain.model.User -import com.squirtles.domain.repository.local.LocalRepository +import com.squirtles.domain.repository.local.LocalUserRepository import com.squirtles.domain.repository.remote.firebase.FirebaseUserRepository import javax.inject.Inject class CreateGoogleIdUserUseCase @Inject constructor( - private val localRepository: LocalRepository, - private val userRepository: FirebaseUserRepository + private val localUserRepository: LocalUserRepository, + private val firebaseUserRepository: FirebaseUserRepository ) { suspend operator fun invoke( userId: String, userName: String? = null, userProfileImage: String? = null ): Result { - val createdUser = userRepository.createGoogleIdUser(userId, userName, userProfileImage) + val createdUser = firebaseUserRepository.createGoogleIdUser(userId, userName, userProfileImage) .onSuccess { user -> // 생성된 유저의 userId 저장 후 user 반환 - localRepository.saveUserIdDataStore(user.userId) - localRepository.saveCurrentUser(user) + localUserRepository.saveUserIdDataStore(user.userId) + localUserRepository.saveCurrentUser(user) } return createdUser } diff --git a/domain/src/main/java/com/squirtles/domain/usecase/user/FetchUserByIdUseCase.kt b/domain/src/main/java/com/squirtles/domain/usecase/user/FetchUserByIdUseCase.kt index 8712e00a..333197a6 100644 --- a/domain/src/main/java/com/squirtles/domain/usecase/user/FetchUserByIdUseCase.kt +++ b/domain/src/main/java/com/squirtles/domain/usecase/user/FetchUserByIdUseCase.kt @@ -4,8 +4,8 @@ import com.squirtles.domain.repository.remote.firebase.FirebaseUserRepository import javax.inject.Inject class FetchUserByIdUseCase @Inject constructor( - private val userRepository: FirebaseUserRepository + private val firebaseUserRepository: FirebaseUserRepository ) { suspend operator fun invoke(userId: String) = - userRepository.fetchUser(userId) + firebaseUserRepository.fetchUser(userId) } diff --git a/domain/src/main/java/com/squirtles/domain/usecase/user/FetchUserUseCase.kt b/domain/src/main/java/com/squirtles/domain/usecase/user/FetchUserUseCase.kt index 3dc692d5..c703199c 100644 --- a/domain/src/main/java/com/squirtles/domain/usecase/user/FetchUserUseCase.kt +++ b/domain/src/main/java/com/squirtles/domain/usecase/user/FetchUserUseCase.kt @@ -1,18 +1,18 @@ package com.squirtles.domain.usecase.user import com.squirtles.domain.model.User -import com.squirtles.domain.repository.local.LocalRepository +import com.squirtles.domain.repository.local.LocalUserRepository import javax.inject.Inject class FetchUserUseCase @Inject constructor( - private val localRepository: LocalRepository, + private val localUserRepository: LocalUserRepository, private val fetchUserByIdUseCase: FetchUserByIdUseCase ) { suspend operator fun invoke(userId: String): Result { val user = fetchUserByIdUseCase(userId) // userId가 있으면 Firestore에서 유저 가져오기 .onSuccess { user -> - localRepository.saveUserIdDataStore(user.userId) - localRepository.saveCurrentUser(user) // Firestore에서 가져온 user를 LocalDataSource에 저장 + localUserRepository.saveUserIdDataStore(user.userId) + localUserRepository.saveCurrentUser(user) // Firestore에서 가져온 user를 LocalDataSource에 저장 } return user } diff --git a/domain/src/main/java/com/squirtles/domain/usecase/user/GetCurrentUserUseCase.kt b/domain/src/main/java/com/squirtles/domain/usecase/user/GetCurrentUserUseCase.kt index cc260d68..5394350d 100644 --- a/domain/src/main/java/com/squirtles/domain/usecase/user/GetCurrentUserUseCase.kt +++ b/domain/src/main/java/com/squirtles/domain/usecase/user/GetCurrentUserUseCase.kt @@ -1,10 +1,11 @@ package com.squirtles.domain.usecase.user import com.squirtles.domain.repository.local.LocalRepository +import com.squirtles.domain.repository.local.LocalUserRepository import javax.inject.Inject class GetCurrentUserUseCase @Inject constructor( - private val localRepository: LocalRepository + private val localUserRepository: LocalUserRepository ) { - operator fun invoke() = localRepository.currentUser + operator fun invoke() = localUserRepository.currentUser } diff --git a/domain/src/main/java/com/squirtles/domain/usecase/user/GetUserIdFromDataStoreUseCase.kt b/domain/src/main/java/com/squirtles/domain/usecase/user/GetUserIdFromDataStoreUseCase.kt index 5451b6a6..6948ce15 100644 --- a/domain/src/main/java/com/squirtles/domain/usecase/user/GetUserIdFromDataStoreUseCase.kt +++ b/domain/src/main/java/com/squirtles/domain/usecase/user/GetUserIdFromDataStoreUseCase.kt @@ -1,10 +1,10 @@ package com.squirtles.domain.usecase.user -import com.squirtles.domain.repository.local.LocalRepository +import com.squirtles.domain.repository.local.LocalUserRepository import javax.inject.Inject class GetUserIdFromDataStoreUseCase @Inject constructor( - private val localRepository: LocalRepository + private val localUserRepository: LocalUserRepository ) { - operator fun invoke() = localRepository.readUserIdDataStore() + operator fun invoke() = localUserRepository.readUserIdDataStore() } diff --git a/domain/src/main/java/com/squirtles/domain/usecase/user/UpdateUserNameUseCase.kt b/domain/src/main/java/com/squirtles/domain/usecase/user/UpdateUserNameUseCase.kt index 62920bda..fb984671 100644 --- a/domain/src/main/java/com/squirtles/domain/usecase/user/UpdateUserNameUseCase.kt +++ b/domain/src/main/java/com/squirtles/domain/usecase/user/UpdateUserNameUseCase.kt @@ -4,8 +4,8 @@ import com.squirtles.domain.repository.remote.firebase.FirebaseUserRepository import javax.inject.Inject class UpdateUserNameUseCase @Inject constructor( - private val userRepository: FirebaseUserRepository + private val firebaseUserRepository: FirebaseUserRepository ) { suspend operator fun invoke(userId: String, newUserName: String) = - userRepository.updateUserName(userId, newUserName) + firebaseUserRepository.updateUserName(userId, newUserName) } From 1a3fa89aafad2afeab6ad3de93e33f82b7a2e353 Mon Sep 17 00:00:00 2001 From: miller198 Date: Fri, 14 Feb 2025 19:32:56 +0900 Subject: [PATCH 09/62] =?UTF-8?q?[chore]=20=EB=AA=A8=EB=93=88=ED=99=94=20?= =?UTF-8?q?=EC=9D=B4=EC=A0=84=20=ED=8C=A8=ED=82=A4=EC=A7=80=20=EC=A0=95?= =?UTF-8?q?=EB=A6=AC,=20di=20=EB=B6=84=EB=A6=AC,=20mapper=20=EB=B6=84?= =?UTF-8?q?=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../musicroad/account/AccountViewModel.kt | 6 +- .../common/picklist/PickListViewModel.kt | 10 +- .../musicroad/create/CreatePickViewModel.kt | 8 +- .../musicroad/detail/DetailViewModel.kt | 12 +- .../favorite/FavoriteListViewModel.kt | 10 +- .../squirtles/musicroad/main/MainViewModel.kt | 6 +- .../squirtles/musicroad/map/MapViewModel.kt | 8 +- .../musicroad/media/PlayerServiceViewModel.kt | 4 +- .../musicroad/mypick/MyPickListViewModel.kt | 10 +- .../musicroad/search/SearchViewModel.kt | 2 +- .../musicroad/userinfo/UserInfoViewModel.kt | 8 +- .../applemusic/AppleMusicDataSourceImpl.kt | 16 +-- .../applemusic/AppleMusicRepositoryImpl.kt | 11 +- .../applemusic/SearchSongsPagingSource.kt | 8 +- .../applemusic/api/AppleMusicApi.kt | 5 +- .../data/applemusic/di/AppleMusicDi.kt | 47 +++++++ .../model}/AppleMusicMapper.kt | 7 +- .../remote => }/applemusic/model/Artwork.kt | 2 +- .../applemusic/model/Attributes.kt | 2 +- .../remote => }/applemusic/model/Data.kt | 2 +- .../applemusic/model/MusicVideoResponse.kt | 2 +- .../remote => }/applemusic/model/Preview.kt | 2 +- .../remote => }/applemusic/model/Results.kt | 2 +- .../applemusic/model/SearchResponse.kt | 2 +- .../remote => }/applemusic/model/Songs.kt | 2 +- .../datasource/local/LocalDataSourceImpl.kt | 80 ------------ .../java/com/squirtles/data/di/DataModule.kt | 120 ------------------ .../CloudFunctionHelper.kt | 2 +- .../FirebaseFavoriteDataSourceImpl.kt | 20 +-- .../FirebaseFavoriteRepositoryImpl.kt | 9 +- .../data/favorite/di/FirebaseFavoriteDi.kt | 32 +++++ .../model/FirebaseFavorite.kt | 2 +- .../firebase/FirebaseDataSourceConstants.kt | 2 +- .../data/{di => firebase}/FirebaseModule.kt | 2 +- .../LocalLocationRepositoryImpl.kt | 4 +- .../squirtles/data/location/di/LocationDi.kt | 18 +++ .../ApiModule.kt => network/NetworkModule.kt} | 23 +--- .../LocalPickListOrderRepositoryImpl.kt | 6 +- .../com/squirtles/data/order/di/OrderDi.kt | 18 +++ .../FirebasePickDataSourceImpl.kt | 24 ++-- .../FirebasePickRepositoryImpl.kt | 11 +- .../java/com/squirtles/data/pick/di/PickDi.kt | 27 ++++ .../firebase => pick}/model/FirebasePick.kt | 12 +- .../model/Mapper.kt} | 66 +++------- .../repository/local/LocalRepositoryImpl.kt | 53 -------- .../FirebaseUserDataSourceImpl.kt | 8 +- .../FirebaseUserRepositoryImpl.kt | 11 +- .../local => user}/LocalUserDataSourceImpl.kt | 4 +- .../local => user}/LocalUserRepositoryImpl.kt | 6 +- .../java/com/squirtles/data/user/di/UserDi.kt | 42 ++++++ .../firebase => user}/model/FirebaseUser.kt | 4 +- .../com/squirtles/data/user/model/Mapper.kt | 10 ++ .../applemusic/AppleMusicException.kt | 2 +- .../applemusic/AppleMusicRemoteDataSource.kt | 3 +- .../applemusic/AppleMusicRepository.kt | 2 +- .../usecase}/FetchMusicVideoUseCase.kt | 4 +- .../usecase}/FetchSongsUseCase.kt | 4 +- .../datasource/local/LocalDataSource.kt | 22 ---- .../FirebaseFavoriteDataSource.kt | 2 +- .../FirebaseFavoriteRepository.kt | 5 +- .../usecase}/CreateFavoriteUseCase.kt | 4 +- .../usecase}/DeleteFavoriteUseCase.kt | 6 +- .../usecase}/FetchFavoritePicksUseCase.kt | 6 +- .../usecase}/FetchIsFavoriteUseCase.kt | 4 +- .../remote => }/firebase/FirebaseException.kt | 2 +- .../domain/firebase/FirebaseRepository.kt | 20 +++ .../LocalLocationRepository.kt | 2 +- .../usecase}/GetLastLocationUseCase.kt | 5 +- .../usecase}/SaveLastLocationUseCase.kt | 5 +- .../LocalPickListOrderRepository.kt | 2 +- .../usecase}/GetFavoriteListOrderUseCase.kt | 7 +- .../usecase}/GetMyPickListOrderUseCase.kt | 7 +- .../usecase}/SaveFavoriteListOrderUseCase.kt | 7 +- .../usecase}/SaveMyPickListOrderUseCase.kt | 7 +- .../FirebasePickDataSource.kt | 2 +- .../FirebasePickRepository.kt | 14 +- .../usecase}/CreatePickUseCase.kt | 4 +- .../usecase}/DeletePickUseCase.kt | 6 +- .../usecase}/FetchMyPicksUseCase.kt | 6 +- .../pick => pick/usecase}/FetchPickUseCase.kt | 4 +- .../picklist/FetchPickListUseCaseInterface.kt | 2 +- .../GetPickListOrderUseCaseInterface.kt | 2 +- .../picklist/RemovePickUseCaseInterface.kt | 2 +- .../SavePickListOrderUseCaseInterface.kt | 2 +- .../player/MediaPlayerListenerUseCase.kt | 2 +- .../player/MediaPlayerUseCase.kt | 2 +- .../repository/local/LocalRepository.kt | 24 ---- .../repository/remote/RemoteRepository.kt | 11 -- .../remote/firebase/FirebaseUserRepository.kt | 19 --- .../FirebaseUserDataSource.kt | 2 +- .../domain/user/FirebaseUserRepository.kt | 11 ++ .../local => user}/LocalUserDataSource.kt | 2 +- .../local => user}/LocalUserRepository.kt | 2 +- .../user => user/usecase}/ClearUserUseCase.kt | 5 +- .../usecase}/CreateGoogleIdUserUseCase.kt | 6 +- .../usecase}/FetchUserByIdUseCase.kt | 4 +- .../user => user/usecase}/FetchUserUseCase.kt | 4 +- .../usecase}/GetCurrentUserUseCase.kt | 5 +- .../usecase}/GetUserIdFromDataStoreUseCase.kt | 4 +- .../usecase}/UpdateUserNameUseCase.kt | 4 +- 100 files changed, 463 insertions(+), 626 deletions(-) rename data/src/main/java/com/squirtles/data/{datasource/remote => }/applemusic/AppleMusicDataSourceImpl.kt (76%) rename data/src/main/java/com/squirtles/data/{repository/remote => }/applemusic/AppleMusicRepositoryImpl.kt (71%) rename data/src/main/java/com/squirtles/data/{datasource/remote => }/applemusic/SearchSongsPagingSource.kt (92%) rename data/src/main/java/com/squirtles/data/{datasource/remote => }/applemusic/api/AppleMusicApi.kt (72%) create mode 100644 data/src/main/java/com/squirtles/data/applemusic/di/AppleMusicDi.kt rename data/src/main/java/com/squirtles/data/{mapper => applemusic/model}/AppleMusicMapper.kt (86%) rename data/src/main/java/com/squirtles/data/{datasource/remote => }/applemusic/model/Artwork.kt (73%) rename data/src/main/java/com/squirtles/data/{datasource/remote => }/applemusic/model/Attributes.kt (90%) rename data/src/main/java/com/squirtles/data/{datasource/remote => }/applemusic/model/Data.kt (67%) rename data/src/main/java/com/squirtles/data/{datasource/remote => }/applemusic/model/MusicVideoResponse.kt (65%) rename data/src/main/java/com/squirtles/data/{datasource/remote => }/applemusic/model/Preview.kt (73%) rename data/src/main/java/com/squirtles/data/{datasource/remote => }/applemusic/model/Results.kt (79%) rename data/src/main/java/com/squirtles/data/{datasource/remote => }/applemusic/model/SearchResponse.kt (64%) rename data/src/main/java/com/squirtles/data/{datasource/remote => }/applemusic/model/Songs.kt (68%) delete mode 100644 data/src/main/java/com/squirtles/data/datasource/local/LocalDataSourceImpl.kt delete mode 100644 data/src/main/java/com/squirtles/data/di/DataModule.kt rename data/src/main/java/com/squirtles/data/{datasource/remote/firebase => favorite}/CloudFunctionHelper.kt (95%) rename data/src/main/java/com/squirtles/data/{datasource/remote/firebase => favorite}/FirebaseFavoriteDataSourceImpl.kt (89%) rename data/src/main/java/com/squirtles/data/{repository/remote/firebase => favorite}/FirebaseFavoriteRepositoryImpl.kt (75%) create mode 100644 data/src/main/java/com/squirtles/data/favorite/di/FirebaseFavoriteDi.kt rename data/src/main/java/com/squirtles/data/{datasource/remote/firebase => favorite}/model/FirebaseFavorite.kt (80%) rename {domain/src/main/java/com/squirtles/domain/datasource/remote => data/src/main/java/com/squirtles/data}/firebase/FirebaseDataSourceConstants.kt (87%) rename data/src/main/java/com/squirtles/data/{di => firebase}/FirebaseModule.kt (93%) rename data/src/main/java/com/squirtles/data/{repository/local => location}/LocalLocationRepositoryImpl.kt (83%) create mode 100644 data/src/main/java/com/squirtles/data/location/di/LocationDi.kt rename data/src/main/java/com/squirtles/data/{di/ApiModule.kt => network/NetworkModule.kt} (67%) rename data/src/main/java/com/squirtles/data/{repository/local => order}/LocalPickListOrderRepositoryImpl.kt (81%) create mode 100644 data/src/main/java/com/squirtles/data/order/di/OrderDi.kt rename data/src/main/java/com/squirtles/data/{datasource/remote/firebase => pick}/FirebasePickDataSourceImpl.kt (90%) rename data/src/main/java/com/squirtles/data/{repository/remote/firebase => pick}/FirebasePickRepositoryImpl.kt (76%) create mode 100644 data/src/main/java/com/squirtles/data/pick/di/PickDi.kt rename data/src/main/java/com/squirtles/data/{datasource/remote/firebase => pick}/model/FirebasePick.kt (70%) rename data/src/main/java/com/squirtles/data/{mapper/FirebaseMapper.kt => pick/model/Mapper.kt} (76%) delete mode 100644 data/src/main/java/com/squirtles/data/repository/local/LocalRepositoryImpl.kt rename data/src/main/java/com/squirtles/data/{datasource/remote/firebase => user}/FirebaseUserDataSourceImpl.kt (92%) rename data/src/main/java/com/squirtles/data/{repository/remote/firebase => user}/FirebaseUserRepositoryImpl.kt (69%) rename data/src/main/java/com/squirtles/data/{datasource/local => user}/LocalUserDataSourceImpl.kt (93%) rename data/src/main/java/com/squirtles/data/{repository/local => user}/LocalUserRepositoryImpl.kt (83%) create mode 100644 data/src/main/java/com/squirtles/data/user/di/UserDi.kt rename data/src/main/java/com/squirtles/data/{datasource/remote/firebase => user}/model/FirebaseUser.kt (64%) create mode 100644 data/src/main/java/com/squirtles/data/user/model/Mapper.kt rename domain/src/main/java/com/squirtles/domain/{repository/remote => }/applemusic/AppleMusicException.kt (87%) rename domain/src/main/java/com/squirtles/domain/{datasource/remote => }/applemusic/AppleMusicRemoteDataSource.kt (75%) rename domain/src/main/java/com/squirtles/domain/{repository/remote => }/applemusic/AppleMusicRepository.kt (87%) rename domain/src/main/java/com/squirtles/domain/{usecase/music => applemusic/usecase}/FetchMusicVideoUseCase.kt (83%) rename domain/src/main/java/com/squirtles/domain/{usecase/music => applemusic/usecase}/FetchSongsUseCase.kt (65%) delete mode 100644 domain/src/main/java/com/squirtles/domain/datasource/local/LocalDataSource.kt rename domain/src/main/java/com/squirtles/domain/{datasource/remote/firebase => favorite}/FirebaseFavoriteDataSource.kt (86%) rename domain/src/main/java/com/squirtles/domain/{repository/remote/firebase => favorite}/FirebaseFavoriteRepository.kt (70%) rename domain/src/main/java/com/squirtles/domain/{usecase/favorite => favorite/usecase}/CreateFavoriteUseCase.kt (67%) rename domain/src/main/java/com/squirtles/domain/{usecase/favorite => favorite/usecase}/DeleteFavoriteUseCase.kt (60%) rename domain/src/main/java/com/squirtles/domain/{usecase/favorite => favorite/usecase}/FetchFavoritePicksUseCase.kt (58%) rename domain/src/main/java/com/squirtles/domain/{usecase/favorite => favorite/usecase}/FetchIsFavoriteUseCase.kt (68%) rename domain/src/main/java/com/squirtles/domain/{repository/remote => }/firebase/FirebaseException.kt (91%) create mode 100644 domain/src/main/java/com/squirtles/domain/firebase/FirebaseRepository.kt rename domain/src/main/java/com/squirtles/domain/{repository/local => location}/LocalLocationRepository.kt (82%) rename domain/src/main/java/com/squirtles/domain/{usecase/location => location/usecase}/GetLastLocationUseCase.kt (54%) rename domain/src/main/java/com/squirtles/domain/{usecase/location => location/usecase}/SaveLastLocationUseCase.kt (62%) rename domain/src/main/java/com/squirtles/domain/{repository/local => order}/LocalPickListOrderRepository.kt (86%) rename domain/src/main/java/com/squirtles/domain/{usecase/order => order/usecase}/GetFavoriteListOrderUseCase.kt (53%) rename domain/src/main/java/com/squirtles/domain/{usecase/order => order/usecase}/GetMyPickListOrderUseCase.kt (52%) rename domain/src/main/java/com/squirtles/domain/{usecase/order => order/usecase}/SaveFavoriteListOrderUseCase.kt (58%) rename domain/src/main/java/com/squirtles/domain/{usecase/order => order/usecase}/SaveMyPickListOrderUseCase.kt (57%) rename domain/src/main/java/com/squirtles/domain/{datasource/remote/firebase => pick}/FirebasePickDataSource.kt (87%) rename domain/src/main/java/com/squirtles/domain/{repository/remote/firebase => pick}/FirebasePickRepository.kt (54%) rename domain/src/main/java/com/squirtles/domain/{usecase/pick => pick/usecase}/CreatePickUseCase.kt (69%) rename domain/src/main/java/com/squirtles/domain/{usecase/pick => pick/usecase}/DeletePickUseCase.kt (61%) rename domain/src/main/java/com/squirtles/domain/{usecase/pick => pick/usecase}/FetchMyPicksUseCase.kt (58%) rename domain/src/main/java/com/squirtles/domain/{usecase/pick => pick/usecase}/FetchPickUseCase.kt (75%) rename domain/src/main/java/com/squirtles/domain/{usecase => }/picklist/FetchPickListUseCaseInterface.kt (76%) rename domain/src/main/java/com/squirtles/domain/{usecase => }/picklist/GetPickListOrderUseCaseInterface.kt (73%) rename domain/src/main/java/com/squirtles/domain/{usecase => }/picklist/RemovePickUseCaseInterface.kt (72%) rename domain/src/main/java/com/squirtles/domain/{usecase => }/picklist/SavePickListOrderUseCaseInterface.kt (74%) rename domain/src/main/java/com/squirtles/domain/{usecase => }/player/MediaPlayerListenerUseCase.kt (99%) rename domain/src/main/java/com/squirtles/domain/{usecase => }/player/MediaPlayerUseCase.kt (98%) delete mode 100644 domain/src/main/java/com/squirtles/domain/repository/local/LocalRepository.kt delete mode 100644 domain/src/main/java/com/squirtles/domain/repository/remote/RemoteRepository.kt delete mode 100644 domain/src/main/java/com/squirtles/domain/repository/remote/firebase/FirebaseUserRepository.kt rename domain/src/main/java/com/squirtles/domain/{datasource/remote/firebase => user}/FirebaseUserDataSource.kt (84%) create mode 100644 domain/src/main/java/com/squirtles/domain/user/FirebaseUserRepository.kt rename domain/src/main/java/com/squirtles/domain/{datasource/local => user}/LocalUserDataSource.kt (87%) rename domain/src/main/java/com/squirtles/domain/{repository/local => user}/LocalUserRepository.kt (87%) rename domain/src/main/java/com/squirtles/domain/{usecase/user => user/usecase}/ClearUserUseCase.kt (54%) rename domain/src/main/java/com/squirtles/domain/{usecase/user => user/usecase}/CreateGoogleIdUserUseCase.kt (80%) rename domain/src/main/java/com/squirtles/domain/{usecase/user => user/usecase}/FetchUserByIdUseCase.kt (67%) rename domain/src/main/java/com/squirtles/domain/{usecase/user => user/usecase}/FetchUserUseCase.kt (86%) rename domain/src/main/java/com/squirtles/domain/{usecase/user => user/usecase}/GetCurrentUserUseCase.kt (54%) rename domain/src/main/java/com/squirtles/domain/{usecase/user => user/usecase}/GetUserIdFromDataStoreUseCase.kt (67%) rename domain/src/main/java/com/squirtles/domain/{usecase/user => user/usecase}/UpdateUserNameUseCase.kt (70%) diff --git a/app/src/main/java/com/squirtles/musicroad/account/AccountViewModel.kt b/app/src/main/java/com/squirtles/musicroad/account/AccountViewModel.kt index b2150454..c9630c03 100644 --- a/app/src/main/java/com/squirtles/musicroad/account/AccountViewModel.kt +++ b/app/src/main/java/com/squirtles/musicroad/account/AccountViewModel.kt @@ -4,9 +4,9 @@ import android.util.Log import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope import com.google.android.libraries.identity.googleid.GoogleIdTokenCredential -import com.squirtles.domain.usecase.user.ClearUserUseCase -import com.squirtles.domain.usecase.user.CreateGoogleIdUserUseCase -import com.squirtles.domain.usecase.user.FetchUserUseCase +import com.squirtles.domain.user.usecase.ClearUserUseCase +import com.squirtles.domain.user.usecase.CreateGoogleIdUserUseCase +import com.squirtles.domain.user.usecase.FetchUserUseCase import dagger.hilt.android.lifecycle.HiltViewModel import kotlinx.coroutines.flow.MutableSharedFlow import kotlinx.coroutines.flow.asSharedFlow diff --git a/app/src/main/java/com/squirtles/musicroad/common/picklist/PickListViewModel.kt b/app/src/main/java/com/squirtles/musicroad/common/picklist/PickListViewModel.kt index b46dc5aa..33bbdb64 100644 --- a/app/src/main/java/com/squirtles/musicroad/common/picklist/PickListViewModel.kt +++ b/app/src/main/java/com/squirtles/musicroad/common/picklist/PickListViewModel.kt @@ -5,11 +5,11 @@ import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope import com.squirtles.domain.model.Order import com.squirtles.domain.model.Pick -import com.squirtles.domain.usecase.picklist.FetchPickListUseCaseInterface -import com.squirtles.domain.usecase.picklist.GetPickListOrderUseCaseInterface -import com.squirtles.domain.usecase.picklist.RemovePickUseCaseInterface -import com.squirtles.domain.usecase.picklist.SavePickListOrderUseCaseInterface -import com.squirtles.domain.usecase.user.GetCurrentUserUseCase +import com.squirtles.domain.picklist.FetchPickListUseCaseInterface +import com.squirtles.domain.picklist.GetPickListOrderUseCaseInterface +import com.squirtles.domain.picklist.RemovePickUseCaseInterface +import com.squirtles.domain.picklist.SavePickListOrderUseCaseInterface +import com.squirtles.domain.user.usecase.GetCurrentUserUseCase import kotlinx.coroutines.async import kotlinx.coroutines.awaitAll import kotlinx.coroutines.flow.MutableStateFlow diff --git a/app/src/main/java/com/squirtles/musicroad/create/CreatePickViewModel.kt b/app/src/main/java/com/squirtles/musicroad/create/CreatePickViewModel.kt index 9fa5793f..722f1526 100644 --- a/app/src/main/java/com/squirtles/musicroad/create/CreatePickViewModel.kt +++ b/app/src/main/java/com/squirtles/musicroad/create/CreatePickViewModel.kt @@ -10,10 +10,10 @@ import com.squirtles.domain.model.Creator import com.squirtles.domain.model.LocationPoint import com.squirtles.domain.model.Pick import com.squirtles.domain.model.Song -import com.squirtles.domain.usecase.location.GetLastLocationUseCase -import com.squirtles.domain.usecase.music.FetchMusicVideoUseCase -import com.squirtles.domain.usecase.pick.CreatePickUseCase -import com.squirtles.domain.usecase.user.GetCurrentUserUseCase +import com.squirtles.domain.location.usecase.GetLastLocationUseCase +import com.squirtles.domain.applemusic.usecase.FetchMusicVideoUseCase +import com.squirtles.domain.pick.usecase.CreatePickUseCase +import com.squirtles.domain.user.usecase.GetCurrentUserUseCase import com.squirtles.musicroad.navigation.SearchRoute import com.squirtles.musicroad.utils.throttleFirst import dagger.hilt.android.lifecycle.HiltViewModel diff --git a/app/src/main/java/com/squirtles/musicroad/detail/DetailViewModel.kt b/app/src/main/java/com/squirtles/musicroad/detail/DetailViewModel.kt index cb6cffbb..4955bd81 100644 --- a/app/src/main/java/com/squirtles/musicroad/detail/DetailViewModel.kt +++ b/app/src/main/java/com/squirtles/musicroad/detail/DetailViewModel.kt @@ -8,12 +8,12 @@ import com.squirtles.domain.model.Creator import com.squirtles.domain.model.LocationPoint import com.squirtles.domain.model.Pick import com.squirtles.domain.model.Song -import com.squirtles.domain.usecase.favorite.CreateFavoriteUseCase -import com.squirtles.domain.usecase.favorite.DeleteFavoriteUseCase -import com.squirtles.domain.usecase.favorite.FetchIsFavoriteUseCase -import com.squirtles.domain.usecase.pick.DeletePickUseCase -import com.squirtles.domain.usecase.pick.FetchPickUseCase -import com.squirtles.domain.usecase.user.GetCurrentUserUseCase +import com.squirtles.domain.favorite.usecase.CreateFavoriteUseCase +import com.squirtles.domain.favorite.usecase.DeleteFavoriteUseCase +import com.squirtles.domain.favorite.usecase.FetchIsFavoriteUseCase +import com.squirtles.domain.pick.usecase.DeletePickUseCase +import com.squirtles.domain.pick.usecase.FetchPickUseCase +import com.squirtles.domain.user.usecase.GetCurrentUserUseCase import com.squirtles.musicroad.utils.throttleFirst import dagger.hilt.android.lifecycle.HiltViewModel import kotlinx.coroutines.async diff --git a/app/src/main/java/com/squirtles/musicroad/favorite/FavoriteListViewModel.kt b/app/src/main/java/com/squirtles/musicroad/favorite/FavoriteListViewModel.kt index 5a29bc8c..a4e2b562 100644 --- a/app/src/main/java/com/squirtles/musicroad/favorite/FavoriteListViewModel.kt +++ b/app/src/main/java/com/squirtles/musicroad/favorite/FavoriteListViewModel.kt @@ -1,10 +1,10 @@ package com.squirtles.musicroad.favorite -import com.squirtles.domain.usecase.favorite.DeleteFavoriteUseCase -import com.squirtles.domain.usecase.favorite.FetchFavoritePicksUseCase -import com.squirtles.domain.usecase.order.GetFavoriteListOrderUseCase -import com.squirtles.domain.usecase.order.SaveFavoriteListOrderUseCase -import com.squirtles.domain.usecase.user.GetCurrentUserUseCase +import com.squirtles.domain.favorite.usecase.DeleteFavoriteUseCase +import com.squirtles.domain.favorite.usecase.FetchFavoritePicksUseCase +import com.squirtles.domain.order.usecase.GetFavoriteListOrderUseCase +import com.squirtles.domain.order.usecase.SaveFavoriteListOrderUseCase +import com.squirtles.domain.user.usecase.GetCurrentUserUseCase import com.squirtles.musicroad.common.picklist.PickListViewModel import dagger.hilt.android.lifecycle.HiltViewModel import javax.inject.Inject diff --git a/app/src/main/java/com/squirtles/musicroad/main/MainViewModel.kt b/app/src/main/java/com/squirtles/musicroad/main/MainViewModel.kt index b959bb7f..c599fd30 100644 --- a/app/src/main/java/com/squirtles/musicroad/main/MainViewModel.kt +++ b/app/src/main/java/com/squirtles/musicroad/main/MainViewModel.kt @@ -2,9 +2,9 @@ package com.squirtles.musicroad.main import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope -import com.squirtles.domain.repository.remote.firebase.FirebaseException -import com.squirtles.domain.usecase.user.FetchUserUseCase -import com.squirtles.domain.usecase.user.GetUserIdFromDataStoreUseCase +import com.squirtles.domain.firebase.FirebaseException +import com.squirtles.domain.user.usecase.FetchUserUseCase +import com.squirtles.domain.user.usecase.GetUserIdFromDataStoreUseCase import dagger.hilt.android.lifecycle.HiltViewModel import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.asStateFlow diff --git a/app/src/main/java/com/squirtles/musicroad/map/MapViewModel.kt b/app/src/main/java/com/squirtles/musicroad/map/MapViewModel.kt index 87cb6b35..b9bc5f92 100644 --- a/app/src/main/java/com/squirtles/musicroad/map/MapViewModel.kt +++ b/app/src/main/java/com/squirtles/musicroad/map/MapViewModel.kt @@ -10,10 +10,10 @@ import com.naver.maps.map.CameraPosition import com.naver.maps.map.clustering.Clusterer import com.naver.maps.map.overlay.Marker import com.squirtles.domain.model.Pick -import com.squirtles.domain.usecase.location.GetLastLocationUseCase -import com.squirtles.domain.usecase.location.SaveLastLocationUseCase -import com.squirtles.domain.usecase.pick.FetchPickUseCase -import com.squirtles.domain.usecase.user.GetCurrentUserUseCase +import com.squirtles.domain.location.usecase.GetLastLocationUseCase +import com.squirtles.domain.location.usecase.SaveLastLocationUseCase +import com.squirtles.domain.pick.usecase.FetchPickUseCase +import com.squirtles.domain.user.usecase.GetCurrentUserUseCase import com.squirtles.musicroad.map.marker.MarkerKey import dagger.hilt.android.lifecycle.HiltViewModel import kotlinx.coroutines.flow.MutableStateFlow diff --git a/app/src/main/java/com/squirtles/musicroad/media/PlayerServiceViewModel.kt b/app/src/main/java/com/squirtles/musicroad/media/PlayerServiceViewModel.kt index 769d92d7..e0727803 100644 --- a/app/src/main/java/com/squirtles/musicroad/media/PlayerServiceViewModel.kt +++ b/app/src/main/java/com/squirtles/musicroad/media/PlayerServiceViewModel.kt @@ -5,8 +5,8 @@ import androidx.lifecycle.viewModelScope import com.squirtles.domain.model.Pick import com.squirtles.domain.model.PlayerState import com.squirtles.domain.model.Song -import com.squirtles.domain.usecase.player.MediaPlayerListenerUseCase -import com.squirtles.domain.usecase.player.MediaPlayerUseCase +import com.squirtles.domain.player.MediaPlayerListenerUseCase +import com.squirtles.domain.player.MediaPlayerUseCase import dagger.hilt.android.lifecycle.HiltViewModel import kotlinx.coroutines.async import kotlinx.coroutines.flow.SharingStarted diff --git a/app/src/main/java/com/squirtles/musicroad/mypick/MyPickListViewModel.kt b/app/src/main/java/com/squirtles/musicroad/mypick/MyPickListViewModel.kt index 7cc4de98..2af13316 100644 --- a/app/src/main/java/com/squirtles/musicroad/mypick/MyPickListViewModel.kt +++ b/app/src/main/java/com/squirtles/musicroad/mypick/MyPickListViewModel.kt @@ -1,10 +1,10 @@ package com.squirtles.musicroad.mypick -import com.squirtles.domain.usecase.order.GetMyPickListOrderUseCase -import com.squirtles.domain.usecase.order.SaveMyPickListOrderUseCase -import com.squirtles.domain.usecase.pick.DeletePickUseCase -import com.squirtles.domain.usecase.pick.FetchMyPicksUseCase -import com.squirtles.domain.usecase.user.GetCurrentUserUseCase +import com.squirtles.domain.order.usecase.GetMyPickListOrderUseCase +import com.squirtles.domain.order.usecase.SaveMyPickListOrderUseCase +import com.squirtles.domain.pick.usecase.DeletePickUseCase +import com.squirtles.domain.pick.usecase.FetchMyPicksUseCase +import com.squirtles.domain.user.usecase.GetCurrentUserUseCase import com.squirtles.musicroad.common.picklist.PickListViewModel import dagger.hilt.android.lifecycle.HiltViewModel import javax.inject.Inject diff --git a/app/src/main/java/com/squirtles/musicroad/search/SearchViewModel.kt b/app/src/main/java/com/squirtles/musicroad/search/SearchViewModel.kt index 5e3a7905..dc6a92db 100644 --- a/app/src/main/java/com/squirtles/musicroad/search/SearchViewModel.kt +++ b/app/src/main/java/com/squirtles/musicroad/search/SearchViewModel.kt @@ -5,7 +5,7 @@ import androidx.lifecycle.viewModelScope import androidx.paging.PagingData import androidx.paging.cachedIn import com.squirtles.domain.model.Song -import com.squirtles.domain.usecase.music.FetchSongsUseCase +import com.squirtles.domain.applemusic.usecase.FetchSongsUseCase import com.squirtles.musicroad.create.SearchUiState import dagger.hilt.android.lifecycle.HiltViewModel import kotlinx.coroutines.FlowPreview diff --git a/app/src/main/java/com/squirtles/musicroad/userinfo/UserInfoViewModel.kt b/app/src/main/java/com/squirtles/musicroad/userinfo/UserInfoViewModel.kt index d8229436..b0b0189f 100644 --- a/app/src/main/java/com/squirtles/musicroad/userinfo/UserInfoViewModel.kt +++ b/app/src/main/java/com/squirtles/musicroad/userinfo/UserInfoViewModel.kt @@ -3,10 +3,10 @@ package com.squirtles.musicroad.userinfo import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope import com.squirtles.domain.model.User -import com.squirtles.domain.usecase.user.FetchUserByIdUseCase -import com.squirtles.domain.usecase.user.FetchUserUseCase -import com.squirtles.domain.usecase.user.GetCurrentUserUseCase -import com.squirtles.domain.usecase.user.UpdateUserNameUseCase +import com.squirtles.domain.user.usecase.FetchUserByIdUseCase +import com.squirtles.domain.user.usecase.FetchUserUseCase +import com.squirtles.domain.user.usecase.GetCurrentUserUseCase +import com.squirtles.domain.user.usecase.UpdateUserNameUseCase import dagger.hilt.android.lifecycle.HiltViewModel import kotlinx.coroutines.flow.MutableSharedFlow import kotlinx.coroutines.flow.MutableStateFlow diff --git a/data/src/main/java/com/squirtles/data/datasource/remote/applemusic/AppleMusicDataSourceImpl.kt b/data/src/main/java/com/squirtles/data/applemusic/AppleMusicDataSourceImpl.kt similarity index 76% rename from data/src/main/java/com/squirtles/data/datasource/remote/applemusic/AppleMusicDataSourceImpl.kt rename to data/src/main/java/com/squirtles/data/applemusic/AppleMusicDataSourceImpl.kt index e0c6a47e..fdf02cec 100644 --- a/data/src/main/java/com/squirtles/data/datasource/remote/applemusic/AppleMusicDataSourceImpl.kt +++ b/data/src/main/java/com/squirtles/data/applemusic/AppleMusicDataSourceImpl.kt @@ -1,13 +1,13 @@ -package com.squirtles.data.datasource.remote.applemusic +package com.squirtles.data.applemusic import androidx.paging.Pager import androidx.paging.PagingConfig import androidx.paging.PagingData -import com.squirtles.data.datasource.remote.applemusic.SearchSongsPagingSource.Companion.SEARCH_PAGE_SIZE -import com.squirtles.data.datasource.remote.applemusic.api.AppleMusicApi -import com.squirtles.data.datasource.remote.applemusic.model.SearchResponse -import com.squirtles.data.mapper.toMusicVideo -import com.squirtles.domain.datasource.remote.applemusic.AppleMusicRemoteDataSource +import com.squirtles.data.applemusic.SearchSongsPagingSource.Companion.SEARCH_PAGE_SIZE +import com.squirtles.data.applemusic.api.AppleMusicApi +import com.squirtles.data.applemusic.model.SearchResponse +import com.squirtles.data.applemusic.model.toMusicVideo +import com.squirtles.domain.applemusic.AppleMusicRemoteDataSource import com.squirtles.domain.model.MusicVideo import com.squirtles.domain.model.Song import kotlinx.coroutines.flow.Flow @@ -28,10 +28,6 @@ class AppleMusicDataSourceImpl @Inject constructor( ).flow } - override suspend fun searchSongById(songId: String): Song { - TODO("Not yet implemented") - } - override suspend fun searchMusicVideos(searchText: String): List { val searchResult = requestSearchApi(searchText, "music-videos") return searchResult.results.musicVideos?.data?.map { diff --git a/data/src/main/java/com/squirtles/data/repository/remote/applemusic/AppleMusicRepositoryImpl.kt b/data/src/main/java/com/squirtles/data/applemusic/AppleMusicRepositoryImpl.kt similarity index 71% rename from data/src/main/java/com/squirtles/data/repository/remote/applemusic/AppleMusicRepositoryImpl.kt rename to data/src/main/java/com/squirtles/data/applemusic/AppleMusicRepositoryImpl.kt index 8c9215da..9247dbd3 100644 --- a/data/src/main/java/com/squirtles/data/repository/remote/applemusic/AppleMusicRepositoryImpl.kt +++ b/data/src/main/java/com/squirtles/data/applemusic/AppleMusicRepositoryImpl.kt @@ -1,18 +1,17 @@ -package com.squirtles.data.repository.remote.applemusic +package com.squirtles.data.applemusic import androidx.paging.PagingData -import com.squirtles.domain.datasource.remote.applemusic.AppleMusicRemoteDataSource +import com.squirtles.domain.applemusic.AppleMusicRemoteDataSource import com.squirtles.domain.model.MusicVideo import com.squirtles.domain.model.Song -import com.squirtles.domain.repository.remote.RemoteRepository -import com.squirtles.domain.repository.remote.applemusic.AppleMusicException -import com.squirtles.domain.repository.remote.applemusic.AppleMusicRepository +import com.squirtles.domain.applemusic.AppleMusicException +import com.squirtles.domain.applemusic.AppleMusicRepository import kotlinx.coroutines.flow.Flow import javax.inject.Inject class AppleMusicRepositoryImpl @Inject constructor( private val appleMusicDataSource: AppleMusicRemoteDataSource -) : AppleMusicRepository, RemoteRepository() { +) : AppleMusicRepository { override fun searchSongs(searchText: String): Flow> = appleMusicDataSource.searchSongs(searchText) diff --git a/data/src/main/java/com/squirtles/data/datasource/remote/applemusic/SearchSongsPagingSource.kt b/data/src/main/java/com/squirtles/data/applemusic/SearchSongsPagingSource.kt similarity index 92% rename from data/src/main/java/com/squirtles/data/datasource/remote/applemusic/SearchSongsPagingSource.kt rename to data/src/main/java/com/squirtles/data/applemusic/SearchSongsPagingSource.kt index 1e2bf29c..45ba6acb 100644 --- a/data/src/main/java/com/squirtles/data/datasource/remote/applemusic/SearchSongsPagingSource.kt +++ b/data/src/main/java/com/squirtles/data/applemusic/SearchSongsPagingSource.kt @@ -1,9 +1,9 @@ -package com.squirtles.data.datasource.remote.applemusic +package com.squirtles.data.applemusic import androidx.paging.PagingSource import androidx.paging.PagingState -import com.squirtles.data.datasource.remote.applemusic.api.AppleMusicApi -import com.squirtles.data.mapper.toSong +import com.squirtles.data.applemusic.api.AppleMusicApi +import com.squirtles.data.applemusic.model.toSong import com.squirtles.domain.model.Song import retrofit2.HttpException import java.io.IOException @@ -58,4 +58,4 @@ class SearchSongsPagingSource( const val SEARCH_TYPES = "songs" const val SEARCH_PAGE_SIZE = 10 } -} \ No newline at end of file +} diff --git a/data/src/main/java/com/squirtles/data/datasource/remote/applemusic/api/AppleMusicApi.kt b/data/src/main/java/com/squirtles/data/applemusic/api/AppleMusicApi.kt similarity index 72% rename from data/src/main/java/com/squirtles/data/datasource/remote/applemusic/api/AppleMusicApi.kt rename to data/src/main/java/com/squirtles/data/applemusic/api/AppleMusicApi.kt index 2451700f..03eb3dff 100644 --- a/data/src/main/java/com/squirtles/data/datasource/remote/applemusic/api/AppleMusicApi.kt +++ b/data/src/main/java/com/squirtles/data/applemusic/api/AppleMusicApi.kt @@ -1,11 +1,10 @@ -package com.squirtles.data.datasource.remote.applemusic.api +package com.squirtles.data.applemusic.api -import com.squirtles.data.datasource.remote.applemusic.model.SearchResponse +import com.squirtles.data.applemusic.model.SearchResponse import retrofit2.Response import retrofit2.http.GET import retrofit2.http.Path import retrofit2.http.Query -import retrofit2.http.QueryMap interface AppleMusicApi { @GET("v1/catalog/{storefront}/search") diff --git a/data/src/main/java/com/squirtles/data/applemusic/di/AppleMusicDi.kt b/data/src/main/java/com/squirtles/data/applemusic/di/AppleMusicDi.kt new file mode 100644 index 00000000..66350d78 --- /dev/null +++ b/data/src/main/java/com/squirtles/data/applemusic/di/AppleMusicDi.kt @@ -0,0 +1,47 @@ +package com.squirtles.data.applemusic.di + +import com.squirtles.data.applemusic.AppleMusicDataSourceImpl +import com.squirtles.data.applemusic.AppleMusicRepositoryImpl +import com.squirtles.data.applemusic.api.AppleMusicApi +import com.squirtles.domain.applemusic.AppleMusicRemoteDataSource +import com.squirtles.domain.applemusic.AppleMusicRepository +import dagger.Module +import dagger.Provides +import dagger.hilt.InstallIn +import dagger.hilt.components.SingletonComponent +import okhttp3.OkHttpClient +import retrofit2.Converter +import retrofit2.Retrofit +import javax.inject.Singleton + + +@Module +@InstallIn(SingletonComponent::class) +object AppleMusicModule{ + + private const val BASE_APPLE_MUSIC_URL = "https://api.music.apple.com/" + + @Provides + @Singleton + fun provideAppleMusicApi( + appleOkHttpClient: OkHttpClient, + converterFactory: Converter.Factory, + ): AppleMusicApi { + return Retrofit.Builder() + .baseUrl(BASE_APPLE_MUSIC_URL) + .addConverterFactory(converterFactory) + .client(appleOkHttpClient) + .build() + .create(AppleMusicApi::class.java) + } + + @Provides + @Singleton + fun provideAppleMusicRepository(appleMusicDataSource: AppleMusicRemoteDataSource): AppleMusicRepository = + AppleMusicRepositoryImpl(appleMusicDataSource) + + @Provides + @Singleton + fun provideAppleMusicDataSource(api: AppleMusicApi): AppleMusicRemoteDataSource = + AppleMusicDataSourceImpl(api) +} diff --git a/data/src/main/java/com/squirtles/data/mapper/AppleMusicMapper.kt b/data/src/main/java/com/squirtles/data/applemusic/model/AppleMusicMapper.kt similarity index 86% rename from data/src/main/java/com/squirtles/data/mapper/AppleMusicMapper.kt rename to data/src/main/java/com/squirtles/data/applemusic/model/AppleMusicMapper.kt index 12ecd493..dd6e07c6 100644 --- a/data/src/main/java/com/squirtles/data/mapper/AppleMusicMapper.kt +++ b/data/src/main/java/com/squirtles/data/applemusic/model/AppleMusicMapper.kt @@ -1,13 +1,12 @@ -package com.squirtles.data.mapper +package com.squirtles.data.applemusic.model import androidx.core.graphics.toColorInt -import com.squirtles.data.datasource.remote.applemusic.model.Data import com.squirtles.domain.model.MusicVideo import com.squirtles.domain.model.Song import java.time.LocalDate import java.time.format.DateTimeFormatter -fun Data.toSong(): Song = Song( +internal fun Data.toSong(): Song = Song( id = id, songName = this.attributes.songName, artistName = this.attributes.artistName, @@ -19,7 +18,7 @@ fun Data.toSong(): Song = Song( previewUrl = this.attributes.previews[0].url.toString(), ) -fun Data.toMusicVideo(): MusicVideo = MusicVideo( +internal fun Data.toMusicVideo(): MusicVideo = MusicVideo( id = id, songName = this.attributes.songName, artistName = this.attributes.artistName, diff --git a/data/src/main/java/com/squirtles/data/datasource/remote/applemusic/model/Artwork.kt b/data/src/main/java/com/squirtles/data/applemusic/model/Artwork.kt similarity index 73% rename from data/src/main/java/com/squirtles/data/datasource/remote/applemusic/model/Artwork.kt rename to data/src/main/java/com/squirtles/data/applemusic/model/Artwork.kt index f62d864a..ccdcc5e2 100644 --- a/data/src/main/java/com/squirtles/data/datasource/remote/applemusic/model/Artwork.kt +++ b/data/src/main/java/com/squirtles/data/applemusic/model/Artwork.kt @@ -1,4 +1,4 @@ -package com.squirtles.data.datasource.remote.applemusic.model +package com.squirtles.data.applemusic.model import kotlinx.serialization.Serializable diff --git a/data/src/main/java/com/squirtles/data/datasource/remote/applemusic/model/Attributes.kt b/data/src/main/java/com/squirtles/data/applemusic/model/Attributes.kt similarity index 90% rename from data/src/main/java/com/squirtles/data/datasource/remote/applemusic/model/Attributes.kt rename to data/src/main/java/com/squirtles/data/applemusic/model/Attributes.kt index 6579a133..ecd71865 100644 --- a/data/src/main/java/com/squirtles/data/datasource/remote/applemusic/model/Attributes.kt +++ b/data/src/main/java/com/squirtles/data/applemusic/model/Attributes.kt @@ -1,4 +1,4 @@ -package com.squirtles.data.datasource.remote.applemusic.model +package com.squirtles.data.applemusic.model import kotlinx.serialization.SerialName import kotlinx.serialization.Serializable diff --git a/data/src/main/java/com/squirtles/data/datasource/remote/applemusic/model/Data.kt b/data/src/main/java/com/squirtles/data/applemusic/model/Data.kt similarity index 67% rename from data/src/main/java/com/squirtles/data/datasource/remote/applemusic/model/Data.kt rename to data/src/main/java/com/squirtles/data/applemusic/model/Data.kt index a2a491a2..5d8dcf82 100644 --- a/data/src/main/java/com/squirtles/data/datasource/remote/applemusic/model/Data.kt +++ b/data/src/main/java/com/squirtles/data/applemusic/model/Data.kt @@ -1,4 +1,4 @@ -package com.squirtles.data.datasource.remote.applemusic.model +package com.squirtles.data.applemusic.model import kotlinx.serialization.Serializable diff --git a/data/src/main/java/com/squirtles/data/datasource/remote/applemusic/model/MusicVideoResponse.kt b/data/src/main/java/com/squirtles/data/applemusic/model/MusicVideoResponse.kt similarity index 65% rename from data/src/main/java/com/squirtles/data/datasource/remote/applemusic/model/MusicVideoResponse.kt rename to data/src/main/java/com/squirtles/data/applemusic/model/MusicVideoResponse.kt index d0626150..1dc83d54 100644 --- a/data/src/main/java/com/squirtles/data/datasource/remote/applemusic/model/MusicVideoResponse.kt +++ b/data/src/main/java/com/squirtles/data/applemusic/model/MusicVideoResponse.kt @@ -1,4 +1,4 @@ -package com.squirtles.data.datasource.remote.applemusic.model +package com.squirtles.data.applemusic.model import kotlinx.serialization.Serializable diff --git a/data/src/main/java/com/squirtles/data/datasource/remote/applemusic/model/Preview.kt b/data/src/main/java/com/squirtles/data/applemusic/model/Preview.kt similarity index 73% rename from data/src/main/java/com/squirtles/data/datasource/remote/applemusic/model/Preview.kt rename to data/src/main/java/com/squirtles/data/applemusic/model/Preview.kt index 119e75db..2253cbc9 100644 --- a/data/src/main/java/com/squirtles/data/datasource/remote/applemusic/model/Preview.kt +++ b/data/src/main/java/com/squirtles/data/applemusic/model/Preview.kt @@ -1,4 +1,4 @@ -package com.squirtles.data.datasource.remote.applemusic.model +package com.squirtles.data.applemusic.model import kotlinx.serialization.Serializable diff --git a/data/src/main/java/com/squirtles/data/datasource/remote/applemusic/model/Results.kt b/data/src/main/java/com/squirtles/data/applemusic/model/Results.kt similarity index 79% rename from data/src/main/java/com/squirtles/data/datasource/remote/applemusic/model/Results.kt rename to data/src/main/java/com/squirtles/data/applemusic/model/Results.kt index 4cf960f2..5183ae97 100644 --- a/data/src/main/java/com/squirtles/data/datasource/remote/applemusic/model/Results.kt +++ b/data/src/main/java/com/squirtles/data/applemusic/model/Results.kt @@ -1,4 +1,4 @@ -package com.squirtles.data.datasource.remote.applemusic.model +package com.squirtles.data.applemusic.model import kotlinx.serialization.SerialName import kotlinx.serialization.Serializable diff --git a/data/src/main/java/com/squirtles/data/datasource/remote/applemusic/model/SearchResponse.kt b/data/src/main/java/com/squirtles/data/applemusic/model/SearchResponse.kt similarity index 64% rename from data/src/main/java/com/squirtles/data/datasource/remote/applemusic/model/SearchResponse.kt rename to data/src/main/java/com/squirtles/data/applemusic/model/SearchResponse.kt index 13e292d5..e184087e 100644 --- a/data/src/main/java/com/squirtles/data/datasource/remote/applemusic/model/SearchResponse.kt +++ b/data/src/main/java/com/squirtles/data/applemusic/model/SearchResponse.kt @@ -1,4 +1,4 @@ -package com.squirtles.data.datasource.remote.applemusic.model +package com.squirtles.data.applemusic.model import kotlinx.serialization.Serializable diff --git a/data/src/main/java/com/squirtles/data/datasource/remote/applemusic/model/Songs.kt b/data/src/main/java/com/squirtles/data/applemusic/model/Songs.kt similarity index 68% rename from data/src/main/java/com/squirtles/data/datasource/remote/applemusic/model/Songs.kt rename to data/src/main/java/com/squirtles/data/applemusic/model/Songs.kt index d49c03eb..f4fba366 100644 --- a/data/src/main/java/com/squirtles/data/datasource/remote/applemusic/model/Songs.kt +++ b/data/src/main/java/com/squirtles/data/applemusic/model/Songs.kt @@ -1,4 +1,4 @@ -package com.squirtles.data.datasource.remote.applemusic.model +package com.squirtles.data.applemusic.model import kotlinx.serialization.Serializable diff --git a/data/src/main/java/com/squirtles/data/datasource/local/LocalDataSourceImpl.kt b/data/src/main/java/com/squirtles/data/datasource/local/LocalDataSourceImpl.kt deleted file mode 100644 index 29fb6f79..00000000 --- a/data/src/main/java/com/squirtles/data/datasource/local/LocalDataSourceImpl.kt +++ /dev/null @@ -1,80 +0,0 @@ -package com.squirtles.data.datasource.local - -import android.content.Context -import android.location.Location -import androidx.datastore.preferences.core.edit -import androidx.datastore.preferences.core.stringPreferencesKey -import androidx.datastore.preferences.preferencesDataStore -import com.squirtles.domain.datasource.local.LocalDataSource -import com.squirtles.domain.model.Order -import com.squirtles.domain.model.User -import kotlinx.coroutines.flow.Flow -import kotlinx.coroutines.flow.MutableStateFlow -import kotlinx.coroutines.flow.StateFlow -import kotlinx.coroutines.flow.asStateFlow -import kotlinx.coroutines.flow.map -import javax.inject.Inject -import javax.inject.Singleton - -@Singleton -class LocalDataSourceImpl @Inject constructor( - private val context: Context, -) : LocalDataSource { - private val Context.dataStore by preferencesDataStore(name = USER_PREFERENCES_NAME) - - private var _currentUser: User? = null - override val currentUser: User? - get() = _currentUser - - private var _currentLocation: MutableStateFlow = MutableStateFlow(null) - override val lastLocation: StateFlow = _currentLocation.asStateFlow() - - private var _favoriteListOrder = Order.LATEST - override val favoriteListOrder get() = _favoriteListOrder - - private var _myListOrder = Order.LATEST - override val myListOrder get() = _myListOrder - - override fun readUserIdDataStore(): Flow { - val dataStoreKey = stringPreferencesKey(USER_ID_KEY) - return context.dataStore.data.map { preferences -> - preferences[dataStoreKey] - } - } - - override suspend fun saveUserIdDataStore(userId: String) { - val dataStoreKey = stringPreferencesKey(USER_ID_KEY) - context.dataStore.edit { preferences -> - preferences[dataStoreKey] = userId - } - } - - override suspend fun saveCurrentUser(user: User) { - _currentUser = user - } - - override suspend fun clearUser() { - val dataStoreKey = stringPreferencesKey(USER_ID_KEY) - context.dataStore.edit { preferences -> - preferences.remove(dataStoreKey) - } - _currentUser = null - } - - override suspend fun saveCurrentLocation(location: Location) { - _currentLocation.emit(location) - } - - override suspend fun saveFavoriteListOrder(order: Order) { - _favoriteListOrder = order - } - - override suspend fun saveMyListOrder(order: Order) { - _myListOrder = order - } - - companion object { - private const val USER_PREFERENCES_NAME = "user_preferences" - private const val USER_ID_KEY = "user_id" - } -} diff --git a/data/src/main/java/com/squirtles/data/di/DataModule.kt b/data/src/main/java/com/squirtles/data/di/DataModule.kt deleted file mode 100644 index a0ca9db9..00000000 --- a/data/src/main/java/com/squirtles/data/di/DataModule.kt +++ /dev/null @@ -1,120 +0,0 @@ -package com.squirtles.data.di - -import android.content.Context -import com.google.firebase.firestore.FirebaseFirestore -import com.squirtles.data.datasource.local.LocalDataSourceImpl -import com.squirtles.data.datasource.local.LocalUserDataSourceImpl -import com.squirtles.data.datasource.remote.applemusic.AppleMusicDataSourceImpl -import com.squirtles.data.datasource.remote.applemusic.api.AppleMusicApi -import com.squirtles.data.datasource.remote.firebase.CloudFunctionHelper -import com.squirtles.data.datasource.remote.firebase.FirebaseFavoriteDataSourceImpl -import com.squirtles.data.datasource.remote.firebase.FirebasePickDataSourceImpl -import com.squirtles.data.datasource.remote.firebase.FirebaseUserDataSourceImpl -import com.squirtles.data.repository.local.LocalLocationRepositoryImpl -import com.squirtles.data.repository.local.LocalPickListOrderRepositoryImpl -import com.squirtles.data.repository.local.LocalRepositoryImpl -import com.squirtles.data.repository.local.LocalUserRepositoryImpl -import com.squirtles.data.repository.remote.applemusic.AppleMusicRepositoryImpl -import com.squirtles.data.repository.remote.firebase.FirebaseFavoriteRepositoryImpl -import com.squirtles.data.repository.remote.firebase.FirebasePickRepositoryImpl -import com.squirtles.data.repository.remote.firebase.FirebaseUserRepositoryImpl -import com.squirtles.domain.datasource.local.LocalDataSource -import com.squirtles.domain.datasource.local.LocalUserDataSource -import com.squirtles.domain.datasource.remote.applemusic.AppleMusicRemoteDataSource -import com.squirtles.domain.datasource.remote.firebase.FirebaseFavoriteDataSource -import com.squirtles.domain.datasource.remote.firebase.FirebasePickDataSource -import com.squirtles.domain.datasource.remote.firebase.FirebaseUserDataSource -import com.squirtles.domain.repository.local.LocalLocationRepository -import com.squirtles.domain.repository.local.LocalPickListOrderRepository -import com.squirtles.domain.repository.local.LocalRepository -import com.squirtles.domain.repository.local.LocalUserRepository -import com.squirtles.domain.repository.remote.applemusic.AppleMusicRepository -import com.squirtles.domain.repository.remote.firebase.FirebaseFavoriteRepository -import com.squirtles.domain.repository.remote.firebase.FirebasePickRepository -import com.squirtles.domain.repository.remote.firebase.FirebaseUserRepository -import dagger.Module -import dagger.Provides -import dagger.hilt.InstallIn -import dagger.hilt.android.qualifiers.ApplicationContext -import dagger.hilt.components.SingletonComponent -import javax.inject.Singleton - -@Module -@InstallIn(SingletonComponent::class) -internal object DataModule { - - // LOCAL REPOSITORY - - @Provides - @Singleton - fun provideLocalPickListOrderRepository(): LocalPickListOrderRepository = - LocalPickListOrderRepositoryImpl() - - @Provides - @Singleton - fun provideLocalLocationRepository(): LocalLocationRepository = - LocalLocationRepositoryImpl() - - @Provides - @Singleton - fun provideLocalUserRepository(userDataSource: LocalUserDataSource): LocalUserRepository = - LocalUserRepositoryImpl(userDataSource) - - // LOCAL DATA SOURCE - - @Provides - @Singleton - fun provideLocalUserDataSource(@ApplicationContext context: Context): LocalUserDataSource = - LocalUserDataSourceImpl(context) - - // FIREBASE REPOSITORY - - @Provides - @Singleton - fun provideFirebaseFavoriteRepository(firebaseFavoriteDataSource: FirebaseFavoriteDataSource): FirebaseFavoriteRepository = - FirebaseFavoriteRepositoryImpl(firebaseFavoriteDataSource) - - @Provides - @Singleton - fun provideFirebaseUserRepository(firebaseUserDataSource: FirebaseUserDataSource): FirebaseUserRepository = - FirebaseUserRepositoryImpl(firebaseUserDataSource) - - @Provides - @Singleton - fun provideFirebasePickRepository(firebasePickDataSource: FirebasePickDataSource): FirebasePickRepository = - FirebasePickRepositoryImpl(firebasePickDataSource) - - - // FIREBASE DATA SOURCE - - @Provides - @Singleton - fun provideFirebasePickDataSource(db: FirebaseFirestore): FirebasePickDataSource = - FirebasePickDataSourceImpl(db) - - @Provides - @Singleton - fun provideFirebaseFavoriteDataSource(db: FirebaseFirestore, cloudFunctionHelper: CloudFunctionHelper): FirebaseFavoriteDataSource = - FirebaseFavoriteDataSourceImpl(db, cloudFunctionHelper) - - @Provides - @Singleton - fun provideFirebaseUserDataSource(db: FirebaseFirestore): FirebaseUserDataSource = - FirebaseUserDataSourceImpl(db) - - @Provides - @Singleton - fun provideCloudFunctionHelper(): CloudFunctionHelper = CloudFunctionHelper() - - // APPLE MUSIC - - @Provides - @Singleton - fun provideAppleMusicRepository(appleMusicDataSource: AppleMusicRemoteDataSource): AppleMusicRepository = - AppleMusicRepositoryImpl(appleMusicDataSource) - - @Provides - @Singleton - fun provideAppleMusicDataSource(api: AppleMusicApi): AppleMusicRemoteDataSource = - AppleMusicDataSourceImpl(api) -} diff --git a/data/src/main/java/com/squirtles/data/datasource/remote/firebase/CloudFunctionHelper.kt b/data/src/main/java/com/squirtles/data/favorite/CloudFunctionHelper.kt similarity index 95% rename from data/src/main/java/com/squirtles/data/datasource/remote/firebase/CloudFunctionHelper.kt rename to data/src/main/java/com/squirtles/data/favorite/CloudFunctionHelper.kt index 7cccf7f5..788f725a 100644 --- a/data/src/main/java/com/squirtles/data/datasource/remote/firebase/CloudFunctionHelper.kt +++ b/data/src/main/java/com/squirtles/data/favorite/CloudFunctionHelper.kt @@ -1,4 +1,4 @@ -package com.squirtles.data.datasource.remote.firebase +package com.squirtles.data.favorite import com.google.firebase.functions.FirebaseFunctions import com.google.firebase.functions.ktx.functions diff --git a/data/src/main/java/com/squirtles/data/datasource/remote/firebase/FirebaseFavoriteDataSourceImpl.kt b/data/src/main/java/com/squirtles/data/favorite/FirebaseFavoriteDataSourceImpl.kt similarity index 89% rename from data/src/main/java/com/squirtles/data/datasource/remote/firebase/FirebaseFavoriteDataSourceImpl.kt rename to data/src/main/java/com/squirtles/data/favorite/FirebaseFavoriteDataSourceImpl.kt index 216681e9..faa56f2c 100644 --- a/data/src/main/java/com/squirtles/data/datasource/remote/firebase/FirebaseFavoriteDataSourceImpl.kt +++ b/data/src/main/java/com/squirtles/data/favorite/FirebaseFavoriteDataSourceImpl.kt @@ -1,4 +1,4 @@ -package com.squirtles.data.datasource.remote.firebase +package com.squirtles.data.favorite import android.util.Log import com.google.android.gms.tasks.Task @@ -8,15 +8,15 @@ import com.google.firebase.firestore.FirebaseFirestore import com.google.firebase.firestore.Query import com.google.firebase.firestore.QuerySnapshot import com.google.firebase.firestore.toObject -import com.squirtles.data.datasource.remote.firebase.model.FirebaseFavorite -import com.squirtles.data.datasource.remote.firebase.model.FirebasePick -import com.squirtles.data.mapper.toPick -import com.squirtles.domain.datasource.remote.firebase.FirebaseDataSourceConstants.COLLECTION_FAVORITES -import com.squirtles.domain.datasource.remote.firebase.FirebaseDataSourceConstants.COLLECTION_PICKS -import com.squirtles.domain.datasource.remote.firebase.FirebaseDataSourceConstants.FIELD_ADDED_AT -import com.squirtles.domain.datasource.remote.firebase.FirebaseDataSourceConstants.FIELD_PICK_ID -import com.squirtles.domain.datasource.remote.firebase.FirebaseDataSourceConstants.FIELD_USER_ID -import com.squirtles.domain.datasource.remote.firebase.FirebaseFavoriteDataSource +import com.squirtles.data.favorite.model.FirebaseFavorite +import com.squirtles.data.firebase.FirebaseDataSourceConstants.COLLECTION_FAVORITES +import com.squirtles.data.firebase.FirebaseDataSourceConstants.COLLECTION_PICKS +import com.squirtles.data.firebase.FirebaseDataSourceConstants.FIELD_ADDED_AT +import com.squirtles.data.firebase.FirebaseDataSourceConstants.FIELD_PICK_ID +import com.squirtles.data.firebase.FirebaseDataSourceConstants.FIELD_USER_ID +import com.squirtles.data.pick.model.FirebasePick +import com.squirtles.data.pick.model.toPick +import com.squirtles.domain.favorite.FirebaseFavoriteDataSource import com.squirtles.domain.model.Pick import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers diff --git a/data/src/main/java/com/squirtles/data/repository/remote/firebase/FirebaseFavoriteRepositoryImpl.kt b/data/src/main/java/com/squirtles/data/favorite/FirebaseFavoriteRepositoryImpl.kt similarity index 75% rename from data/src/main/java/com/squirtles/data/repository/remote/firebase/FirebaseFavoriteRepositoryImpl.kt rename to data/src/main/java/com/squirtles/data/favorite/FirebaseFavoriteRepositoryImpl.kt index e2f9a30d..81ee41bd 100644 --- a/data/src/main/java/com/squirtles/data/repository/remote/firebase/FirebaseFavoriteRepositoryImpl.kt +++ b/data/src/main/java/com/squirtles/data/favorite/FirebaseFavoriteRepositoryImpl.kt @@ -1,16 +1,15 @@ -package com.squirtles.data.repository.remote.firebase +package com.squirtles.data.favorite -import com.squirtles.domain.datasource.remote.firebase.FirebaseFavoriteDataSource +import com.squirtles.domain.favorite.FirebaseFavoriteDataSource import com.squirtles.domain.model.Pick -import com.squirtles.domain.repository.remote.RemoteRepository -import com.squirtles.domain.repository.remote.firebase.FirebaseFavoriteRepository +import com.squirtles.domain.favorite.FirebaseFavoriteRepository import javax.inject.Inject import javax.inject.Singleton @Singleton class FirebaseFavoriteRepositoryImpl @Inject constructor( private val favoriteDataSource: FirebaseFavoriteDataSource -) : FirebaseFavoriteRepository, RemoteRepository() { +) : FirebaseFavoriteRepository { override suspend fun fetchIsFavorite(pickId: String, userId: String): Result { return handleResult { diff --git a/data/src/main/java/com/squirtles/data/favorite/di/FirebaseFavoriteDi.kt b/data/src/main/java/com/squirtles/data/favorite/di/FirebaseFavoriteDi.kt new file mode 100644 index 00000000..e31420bd --- /dev/null +++ b/data/src/main/java/com/squirtles/data/favorite/di/FirebaseFavoriteDi.kt @@ -0,0 +1,32 @@ +package com.squirtles.data.favorite.di + +import com.google.firebase.firestore.FirebaseFirestore +import com.squirtles.data.favorite.FirebaseFavoriteDataSourceImpl +import com.squirtles.data.favorite.FirebaseFavoriteRepositoryImpl +import com.squirtles.data.favorite.CloudFunctionHelper +import com.squirtles.domain.favorite.FirebaseFavoriteDataSource +import com.squirtles.domain.favorite.FirebaseFavoriteRepository +import dagger.Module +import dagger.Provides +import dagger.hilt.InstallIn +import dagger.hilt.components.SingletonComponent +import javax.inject.Singleton + +@Module +@InstallIn(SingletonComponent::class) +object FirebaseFavoriteDi { + + @Provides + @Singleton + fun provideFirebaseFavoriteRepository(firebaseFavoriteDataSource: FirebaseFavoriteDataSource): FirebaseFavoriteRepository = + FirebaseFavoriteRepositoryImpl(firebaseFavoriteDataSource) + + @Provides + @Singleton + fun provideFirebaseFavoriteDataSource(db: FirebaseFirestore, cloudFunctionHelper: CloudFunctionHelper): FirebaseFavoriteDataSource = + FirebaseFavoriteDataSourceImpl(db, cloudFunctionHelper) + + @Provides + @Singleton + fun provideCloudFunctionHelper(): CloudFunctionHelper = CloudFunctionHelper() +} diff --git a/data/src/main/java/com/squirtles/data/datasource/remote/firebase/model/FirebaseFavorite.kt b/data/src/main/java/com/squirtles/data/favorite/model/FirebaseFavorite.kt similarity index 80% rename from data/src/main/java/com/squirtles/data/datasource/remote/firebase/model/FirebaseFavorite.kt rename to data/src/main/java/com/squirtles/data/favorite/model/FirebaseFavorite.kt index 06498bd9..85c11d87 100644 --- a/data/src/main/java/com/squirtles/data/datasource/remote/firebase/model/FirebaseFavorite.kt +++ b/data/src/main/java/com/squirtles/data/favorite/model/FirebaseFavorite.kt @@ -1,4 +1,4 @@ -package com.squirtles.data.datasource.remote.firebase.model +package com.squirtles.data.favorite.model import com.google.firebase.Timestamp import com.google.firebase.firestore.ServerTimestamp diff --git a/domain/src/main/java/com/squirtles/domain/datasource/remote/firebase/FirebaseDataSourceConstants.kt b/data/src/main/java/com/squirtles/data/firebase/FirebaseDataSourceConstants.kt similarity index 87% rename from domain/src/main/java/com/squirtles/domain/datasource/remote/firebase/FirebaseDataSourceConstants.kt rename to data/src/main/java/com/squirtles/data/firebase/FirebaseDataSourceConstants.kt index 865afede..2d86d3b3 100644 --- a/domain/src/main/java/com/squirtles/domain/datasource/remote/firebase/FirebaseDataSourceConstants.kt +++ b/data/src/main/java/com/squirtles/data/firebase/FirebaseDataSourceConstants.kt @@ -1,4 +1,4 @@ -package com.squirtles.domain.datasource.remote.firebase +package com.squirtles.data.firebase object FirebaseDataSourceConstants { const val TAG_LOG = "FirebaseDataSourceImpl" diff --git a/data/src/main/java/com/squirtles/data/di/FirebaseModule.kt b/data/src/main/java/com/squirtles/data/firebase/FirebaseModule.kt similarity index 93% rename from data/src/main/java/com/squirtles/data/di/FirebaseModule.kt rename to data/src/main/java/com/squirtles/data/firebase/FirebaseModule.kt index 53eb74cc..782cfa71 100644 --- a/data/src/main/java/com/squirtles/data/di/FirebaseModule.kt +++ b/data/src/main/java/com/squirtles/data/firebase/FirebaseModule.kt @@ -1,4 +1,4 @@ -package com.squirtles.data.di +package com.squirtles.data.firebase import com.google.firebase.firestore.FirebaseFirestore import com.squirtles.data.BuildConfig diff --git a/data/src/main/java/com/squirtles/data/repository/local/LocalLocationRepositoryImpl.kt b/data/src/main/java/com/squirtles/data/location/LocalLocationRepositoryImpl.kt similarity index 83% rename from data/src/main/java/com/squirtles/data/repository/local/LocalLocationRepositoryImpl.kt rename to data/src/main/java/com/squirtles/data/location/LocalLocationRepositoryImpl.kt index 48a2983a..deb240a5 100644 --- a/data/src/main/java/com/squirtles/data/repository/local/LocalLocationRepositoryImpl.kt +++ b/data/src/main/java/com/squirtles/data/location/LocalLocationRepositoryImpl.kt @@ -1,7 +1,7 @@ -package com.squirtles.data.repository.local +package com.squirtles.data.location import android.location.Location -import com.squirtles.domain.repository.local.LocalLocationRepository +import com.squirtles.domain.location.LocalLocationRepository import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.asStateFlow diff --git a/data/src/main/java/com/squirtles/data/location/di/LocationDi.kt b/data/src/main/java/com/squirtles/data/location/di/LocationDi.kt new file mode 100644 index 00000000..cc10a744 --- /dev/null +++ b/data/src/main/java/com/squirtles/data/location/di/LocationDi.kt @@ -0,0 +1,18 @@ +package com.squirtles.data.location.di + +import com.squirtles.data.location.LocalLocationRepositoryImpl +import com.squirtles.domain.location.LocalLocationRepository +import dagger.Module +import dagger.Provides +import dagger.hilt.InstallIn +import dagger.hilt.components.SingletonComponent +import javax.inject.Singleton + +@Module +@InstallIn(SingletonComponent::class) +object LocationDi{ + @Provides + @Singleton + fun provideLocalLocationRepository(): LocalLocationRepository = + LocalLocationRepositoryImpl() +} diff --git a/data/src/main/java/com/squirtles/data/di/ApiModule.kt b/data/src/main/java/com/squirtles/data/network/NetworkModule.kt similarity index 67% rename from data/src/main/java/com/squirtles/data/di/ApiModule.kt rename to data/src/main/java/com/squirtles/data/network/NetworkModule.kt index 75e98ec8..3acc6dd6 100644 --- a/data/src/main/java/com/squirtles/data/di/ApiModule.kt +++ b/data/src/main/java/com/squirtles/data/network/NetworkModule.kt @@ -1,7 +1,6 @@ -package com.squirtles.data.di +package com.squirtles.data.network import com.squirtles.data.BuildConfig -import com.squirtles.data.datasource.remote.applemusic.api.AppleMusicApi import dagger.Module import dagger.Provides import dagger.hilt.InstallIn @@ -11,16 +10,12 @@ import okhttp3.MediaType.Companion.toMediaType import okhttp3.OkHttpClient import okhttp3.logging.HttpLoggingInterceptor import retrofit2.Converter -import retrofit2.Retrofit import retrofit2.converter.kotlinx.serialization.asConverterFactory import javax.inject.Singleton @Module @InstallIn(SingletonComponent::class) -internal object ApiModule { - - private const val BASE_URL = "" - private const val BASE_APPLE_MUSIC_URL = "https://api.music.apple.com/" +object NetworkModule { @Provides @Singleton @@ -46,20 +41,6 @@ internal object ApiModule { return json.asConverterFactory("application/json".toMediaType()) } - @Provides - @Singleton - fun provideAppleMusicApi( - appleOkHttpClient: OkHttpClient, - converterFactory: Converter.Factory, - ): AppleMusicApi { - return Retrofit.Builder() - .baseUrl(BASE_APPLE_MUSIC_URL) - .addConverterFactory(converterFactory) - .client(appleOkHttpClient) - .build() - .create(AppleMusicApi::class.java) - } - @Provides @Singleton fun provideJson(): Json = Json { diff --git a/data/src/main/java/com/squirtles/data/repository/local/LocalPickListOrderRepositoryImpl.kt b/data/src/main/java/com/squirtles/data/order/LocalPickListOrderRepositoryImpl.kt similarity index 81% rename from data/src/main/java/com/squirtles/data/repository/local/LocalPickListOrderRepositoryImpl.kt rename to data/src/main/java/com/squirtles/data/order/LocalPickListOrderRepositoryImpl.kt index 532bdc47..1bab1717 100644 --- a/data/src/main/java/com/squirtles/data/repository/local/LocalPickListOrderRepositoryImpl.kt +++ b/data/src/main/java/com/squirtles/data/order/LocalPickListOrderRepositoryImpl.kt @@ -1,11 +1,11 @@ -package com.squirtles.data.repository.local +package com.squirtles.data.order import com.squirtles.domain.model.Order -import com.squirtles.domain.repository.local.LocalPickListOrderRepository +import com.squirtles.domain.order.LocalPickListOrderRepository import javax.inject.Singleton @Singleton -class LocalPickListOrderRepositoryImpl: LocalPickListOrderRepository{ +class LocalPickListOrderRepositoryImpl: LocalPickListOrderRepository { private var _favoriteListOrder = Order.LATEST override val favoriteListOrder get() = _favoriteListOrder diff --git a/data/src/main/java/com/squirtles/data/order/di/OrderDi.kt b/data/src/main/java/com/squirtles/data/order/di/OrderDi.kt new file mode 100644 index 00000000..3b3534d3 --- /dev/null +++ b/data/src/main/java/com/squirtles/data/order/di/OrderDi.kt @@ -0,0 +1,18 @@ +package com.squirtles.data.order.di + +import com.squirtles.data.order.LocalPickListOrderRepositoryImpl +import com.squirtles.domain.order.LocalPickListOrderRepository +import dagger.Module +import dagger.Provides +import dagger.hilt.InstallIn +import dagger.hilt.components.SingletonComponent +import javax.inject.Singleton + +@Module +@InstallIn(SingletonComponent::class) +object OrderDi { + @Provides + @Singleton + fun provideLocalPickListOrderRepository(): LocalPickListOrderRepository = + LocalPickListOrderRepositoryImpl() +} diff --git a/data/src/main/java/com/squirtles/data/datasource/remote/firebase/FirebasePickDataSourceImpl.kt b/data/src/main/java/com/squirtles/data/pick/FirebasePickDataSourceImpl.kt similarity index 90% rename from data/src/main/java/com/squirtles/data/datasource/remote/firebase/FirebasePickDataSourceImpl.kt rename to data/src/main/java/com/squirtles/data/pick/FirebasePickDataSourceImpl.kt index 720017a4..11910478 100644 --- a/data/src/main/java/com/squirtles/data/datasource/remote/firebase/FirebasePickDataSourceImpl.kt +++ b/data/src/main/java/com/squirtles/data/pick/FirebasePickDataSourceImpl.kt @@ -1,4 +1,4 @@ -package com.squirtles.data.datasource.remote.firebase +package com.squirtles.data.pick import android.util.Log import com.firebase.geofire.GeoFireUtils @@ -12,18 +12,18 @@ import com.google.firebase.firestore.FirebaseFirestore import com.google.firebase.firestore.Query import com.google.firebase.firestore.QuerySnapshot import com.google.firebase.firestore.toObject -import com.squirtles.data.datasource.remote.firebase.model.FirebasePick -import com.squirtles.data.datasource.remote.firebase.model.FirebaseUser -import com.squirtles.data.mapper.toFirebasePick -import com.squirtles.data.mapper.toPick -import com.squirtles.domain.datasource.remote.firebase.FirebaseDataSourceConstants.COLLECTION_FAVORITES -import com.squirtles.domain.datasource.remote.firebase.FirebaseDataSourceConstants.COLLECTION_PICKS -import com.squirtles.domain.datasource.remote.firebase.FirebaseDataSourceConstants.COLLECTION_USERS -import com.squirtles.domain.datasource.remote.firebase.FirebaseDataSourceConstants.FIELD_MY_PICKS -import com.squirtles.domain.datasource.remote.firebase.FirebaseDataSourceConstants.FIELD_PICK_ID -import com.squirtles.domain.datasource.remote.firebase.FirebaseDataSourceConstants.TAG_LOG -import com.squirtles.domain.datasource.remote.firebase.FirebasePickDataSource +import com.squirtles.data.firebase.FirebaseDataSourceConstants.COLLECTION_FAVORITES +import com.squirtles.data.firebase.FirebaseDataSourceConstants.COLLECTION_PICKS +import com.squirtles.data.firebase.FirebaseDataSourceConstants.COLLECTION_USERS +import com.squirtles.data.firebase.FirebaseDataSourceConstants.FIELD_MY_PICKS +import com.squirtles.data.firebase.FirebaseDataSourceConstants.FIELD_PICK_ID +import com.squirtles.data.firebase.FirebaseDataSourceConstants.TAG_LOG +import com.squirtles.data.pick.model.FirebasePick +import com.squirtles.data.user.model.FirebaseUser +import com.squirtles.data.pick.model.toFirebasePick +import com.squirtles.data.pick.model.toPick import com.squirtles.domain.model.Pick +import com.squirtles.domain.pick.FirebasePickDataSource import kotlinx.coroutines.suspendCancellableCoroutine import kotlinx.coroutines.tasks.await import javax.inject.Inject diff --git a/data/src/main/java/com/squirtles/data/repository/remote/firebase/FirebasePickRepositoryImpl.kt b/data/src/main/java/com/squirtles/data/pick/FirebasePickRepositoryImpl.kt similarity index 76% rename from data/src/main/java/com/squirtles/data/repository/remote/firebase/FirebasePickRepositoryImpl.kt rename to data/src/main/java/com/squirtles/data/pick/FirebasePickRepositoryImpl.kt index ae2a486a..38ae0bd7 100644 --- a/data/src/main/java/com/squirtles/data/repository/remote/firebase/FirebasePickRepositoryImpl.kt +++ b/data/src/main/java/com/squirtles/data/pick/FirebasePickRepositoryImpl.kt @@ -1,17 +1,16 @@ -package com.squirtles.data.repository.remote.firebase +package com.squirtles.data.pick -import com.squirtles.domain.datasource.remote.firebase.FirebasePickDataSource +import com.squirtles.domain.pick.FirebasePickDataSource import com.squirtles.domain.model.Pick -import com.squirtles.domain.repository.remote.RemoteRepository -import com.squirtles.domain.repository.remote.firebase.FirebaseException -import com.squirtles.domain.repository.remote.firebase.FirebasePickRepository +import com.squirtles.domain.firebase.FirebaseException +import com.squirtles.domain.pick.FirebasePickRepository import javax.inject.Inject import javax.inject.Singleton @Singleton class FirebasePickRepositoryImpl @Inject constructor( private val pickDataSource: FirebasePickDataSource -) : FirebasePickRepository, RemoteRepository() { +) : FirebasePickRepository { override suspend fun createPick(pick: Pick): Result { return handleResult { diff --git a/data/src/main/java/com/squirtles/data/pick/di/PickDi.kt b/data/src/main/java/com/squirtles/data/pick/di/PickDi.kt new file mode 100644 index 00000000..b06e8cde --- /dev/null +++ b/data/src/main/java/com/squirtles/data/pick/di/PickDi.kt @@ -0,0 +1,27 @@ +package com.squirtles.data.pick.di + +import com.google.firebase.firestore.FirebaseFirestore +import com.squirtles.data.pick.FirebasePickDataSourceImpl +import com.squirtles.data.pick.FirebasePickRepositoryImpl +import com.squirtles.domain.pick.FirebasePickDataSource +import com.squirtles.domain.pick.FirebasePickRepository +import dagger.Module +import dagger.Provides +import dagger.hilt.InstallIn +import dagger.hilt.components.SingletonComponent +import javax.inject.Singleton + +@Module +@InstallIn(SingletonComponent::class) +object PickDi{ + + @Provides + @Singleton + fun provideFirebasePickRepository(firebasePickDataSource: FirebasePickDataSource): FirebasePickRepository = + FirebasePickRepositoryImpl(firebasePickDataSource) + + @Provides + @Singleton + fun provideFirebasePickDataSource(db: FirebaseFirestore): FirebasePickDataSource = + FirebasePickDataSourceImpl(db) +} diff --git a/data/src/main/java/com/squirtles/data/datasource/remote/firebase/model/FirebasePick.kt b/data/src/main/java/com/squirtles/data/pick/model/FirebasePick.kt similarity index 70% rename from data/src/main/java/com/squirtles/data/datasource/remote/firebase/model/FirebasePick.kt rename to data/src/main/java/com/squirtles/data/pick/model/FirebasePick.kt index 3fb9aab0..2e236527 100644 --- a/data/src/main/java/com/squirtles/data/datasource/remote/firebase/model/FirebasePick.kt +++ b/data/src/main/java/com/squirtles/data/pick/model/FirebasePick.kt @@ -1,8 +1,18 @@ -package com.squirtles.data.datasource.remote.firebase.model +package com.squirtles.data.pick.model +import androidx.core.graphics.toColorInt +import com.firebase.geofire.GeoFireUtils +import com.firebase.geofire.GeoLocation import com.google.firebase.Timestamp import com.google.firebase.firestore.GeoPoint import com.google.firebase.firestore.ServerTimestamp +import com.squirtles.domain.model.Creator +import com.squirtles.domain.model.LocationPoint +import com.squirtles.domain.model.Pick +import com.squirtles.domain.model.Song +import java.text.SimpleDateFormat +import java.util.Date +import java.util.Locale /** * Firestore에 저장된 pick document를 불러와 변환하기위한 데이터 클래스 diff --git a/data/src/main/java/com/squirtles/data/mapper/FirebaseMapper.kt b/data/src/main/java/com/squirtles/data/pick/model/Mapper.kt similarity index 76% rename from data/src/main/java/com/squirtles/data/mapper/FirebaseMapper.kt rename to data/src/main/java/com/squirtles/data/pick/model/Mapper.kt index 238e4689..2721bbd5 100644 --- a/data/src/main/java/com/squirtles/data/mapper/FirebaseMapper.kt +++ b/data/src/main/java/com/squirtles/data/pick/model/Mapper.kt @@ -1,25 +1,36 @@ -package com.squirtles.data.mapper +package com.squirtles.data.pick.model import androidx.core.graphics.toColorInt import com.firebase.geofire.GeoFireUtils import com.firebase.geofire.GeoLocation -import com.google.firebase.Timestamp -import com.google.firebase.firestore.FieldValue import com.google.firebase.firestore.GeoPoint -import com.squirtles.data.datasource.remote.firebase.model.FirebasePick -import com.squirtles.data.datasource.remote.firebase.model.FirebaseUser import com.squirtles.domain.model.Creator import com.squirtles.domain.model.LocationPoint import com.squirtles.domain.model.Pick import com.squirtles.domain.model.Song -import com.squirtles.domain.model.User import java.text.SimpleDateFormat import java.util.Date import java.util.Locale -/** - * using when get pick from firebase and convert to domain data - */ +internal fun Pick.toFirebasePick(): FirebasePick = FirebasePick( + id = id, + albumName = song.albumName, + artistName = song.artistName, + artwork = mapOf("url" to song.imageUrl, "bgColor" to song.bgColor.toRgbString()), + comment = comment, + createdBy = mapOf("userId" to createdBy.userId, "userName" to createdBy.userName), + externalUrl = song.externalUrl, + favoriteCount = favoriteCount, + genreNames = song.genreNames, + geoHash = location.toGeoHash(), + location = GeoPoint(location.latitude, location.longitude), + previewUrl = song.previewUrl, + musicVideoUrl = musicVideoUrl, + musicVideoThumbnail = musicVideoThumbnailUrl, + songId = song.id, + songName = song.songName, +) + internal fun FirebasePick.toPick(): Pick = Pick( id = id.toString(), song = Song( @@ -50,35 +61,6 @@ internal fun FirebasePick.toPick(): Pick = Pick( musicVideoThumbnailUrl = musicVideoThumbnail ?: "" ) -/** - * using when create pick in firebase - */ -internal fun Pick.toFirebasePick(): FirebasePick = FirebasePick( - id = id, - albumName = song.albumName, - artistName = song.artistName, - artwork = mapOf("url" to song.imageUrl, "bgColor" to song.bgColor.toRgbString()), - comment = comment, - createdBy = mapOf("userId" to createdBy.userId, "userName" to createdBy.userName), - externalUrl = song.externalUrl, - favoriteCount = favoriteCount, - genreNames = song.genreNames, - geoHash = location.toGeoHash(), - location = GeoPoint(location.latitude, location.longitude), - previewUrl = song.previewUrl, - musicVideoUrl = musicVideoUrl, - musicVideoThumbnail = musicVideoThumbnailUrl, - songId = song.id, - songName = song.songName, -) - -internal fun FirebaseUser.toUser(): User = User( - userId = "", - userName = name ?: "", - userProfileImage = profileImage, - myPicks = myPicks -) - private fun Int.toRgbString(): String { return String.format("%06X", 0xFFFFFF and this) } @@ -92,11 +74,3 @@ private fun Date.formatTimestamp(): String { val dateFormat = SimpleDateFormat("yyyy.MM.dd", Locale.getDefault()) return dateFormat.format(this) } - -private fun String.toTimeStamp(): Timestamp { - val dateFormat = SimpleDateFormat("yyyy.MM.dd", Locale.getDefault()) - val date = dateFormat.parse(this) ?: Date() - - val time = FieldValue.serverTimestamp() - return Timestamp(date) -} diff --git a/data/src/main/java/com/squirtles/data/repository/local/LocalRepositoryImpl.kt b/data/src/main/java/com/squirtles/data/repository/local/LocalRepositoryImpl.kt deleted file mode 100644 index fea18ee1..00000000 --- a/data/src/main/java/com/squirtles/data/repository/local/LocalRepositoryImpl.kt +++ /dev/null @@ -1,53 +0,0 @@ -package com.squirtles.data.repository.local - -import android.location.Location -import com.squirtles.domain.datasource.local.LocalDataSource -import com.squirtles.domain.model.Order -import com.squirtles.domain.model.User -import com.squirtles.domain.repository.local.LocalRepository -import kotlinx.coroutines.flow.Flow -import javax.inject.Inject -import javax.inject.Singleton - -@Singleton -class LocalRepositoryImpl @Inject constructor( - private val localDataSource: LocalDataSource, -) : LocalRepository { -// override val currentUser get() = localDataSource.currentUser -// override val lastLocation get() = localDataSource.lastLocation -// override val favoriteListOrder get() = localDataSource.favoriteListOrder -// override val myListOrder get() = localDataSource.myListOrder -// -// override fun readUserIdDataStore(): Flow { -// return localDataSource.readUserIdDataStore() -// } -// -// override suspend fun saveUserIdDataStore(userId: String) { -// localDataSource.saveUserIdDataStore(userId) -// } -// -// override suspend fun saveCurrentUser(user: User) { -// localDataSource.saveCurrentUser(user) -// } -// -// override suspend fun clearUser(): Result { -// return try { -// localDataSource.clearUser() -// Result.success(Unit) -// } catch (e: Exception) { -// Result.failure(e) -// } -// } -// -// override suspend fun saveCurrentLocation(location: Location) { -// localDataSource.saveCurrentLocation(location) -// } -// -// override suspend fun saveFavoriteListOrder(order: Order) { -// localDataSource.saveFavoriteListOrder(order) -// } -// -// override suspend fun saveMyListOrder(order: Order) { -// localDataSource.saveMyListOrder(order) -// } -} diff --git a/data/src/main/java/com/squirtles/data/datasource/remote/firebase/FirebaseUserDataSourceImpl.kt b/data/src/main/java/com/squirtles/data/user/FirebaseUserDataSourceImpl.kt similarity index 92% rename from data/src/main/java/com/squirtles/data/datasource/remote/firebase/FirebaseUserDataSourceImpl.kt rename to data/src/main/java/com/squirtles/data/user/FirebaseUserDataSourceImpl.kt index 25e51a20..31b8a778 100644 --- a/data/src/main/java/com/squirtles/data/datasource/remote/firebase/FirebaseUserDataSourceImpl.kt +++ b/data/src/main/java/com/squirtles/data/user/FirebaseUserDataSourceImpl.kt @@ -1,12 +1,12 @@ -package com.squirtles.data.datasource.remote.firebase +package com.squirtles.data.user import android.util.Log import com.google.firebase.firestore.FirebaseFirestore import com.google.firebase.firestore.toObject -import com.squirtles.data.datasource.remote.firebase.model.FirebaseUser -import com.squirtles.data.mapper.toUser -import com.squirtles.domain.datasource.remote.firebase.FirebaseUserDataSource +import com.squirtles.data.user.model.FirebaseUser +import com.squirtles.data.user.model.toUser import com.squirtles.domain.model.User +import com.squirtles.domain.user.FirebaseUserDataSource import kotlinx.coroutines.suspendCancellableCoroutine import javax.inject.Inject import javax.inject.Singleton diff --git a/data/src/main/java/com/squirtles/data/repository/remote/firebase/FirebaseUserRepositoryImpl.kt b/data/src/main/java/com/squirtles/data/user/FirebaseUserRepositoryImpl.kt similarity index 69% rename from data/src/main/java/com/squirtles/data/repository/remote/firebase/FirebaseUserRepositoryImpl.kt rename to data/src/main/java/com/squirtles/data/user/FirebaseUserRepositoryImpl.kt index 5eb0eabb..1d0e23b8 100644 --- a/data/src/main/java/com/squirtles/data/repository/remote/firebase/FirebaseUserRepositoryImpl.kt +++ b/data/src/main/java/com/squirtles/data/user/FirebaseUserRepositoryImpl.kt @@ -1,16 +1,15 @@ -package com.squirtles.data.repository.remote.firebase +package com.squirtles.data.user -import com.squirtles.domain.datasource.remote.firebase.FirebaseUserDataSource +import com.squirtles.domain.user.FirebaseUserDataSource import com.squirtles.domain.model.User -import com.squirtles.domain.repository.remote.RemoteRepository -import com.squirtles.domain.repository.remote.firebase.FirebaseException -import com.squirtles.domain.repository.remote.firebase.FirebaseUserRepository +import com.squirtles.domain.firebase.FirebaseException +import com.squirtles.domain.user.FirebaseUserRepository import javax.inject.Singleton @Singleton class FirebaseUserRepositoryImpl( private val userDataSource: FirebaseUserDataSource -) : FirebaseUserRepository, RemoteRepository() { +) : FirebaseUserRepository { override suspend fun createGoogleIdUser( userId: String, diff --git a/data/src/main/java/com/squirtles/data/datasource/local/LocalUserDataSourceImpl.kt b/data/src/main/java/com/squirtles/data/user/LocalUserDataSourceImpl.kt similarity index 93% rename from data/src/main/java/com/squirtles/data/datasource/local/LocalUserDataSourceImpl.kt rename to data/src/main/java/com/squirtles/data/user/LocalUserDataSourceImpl.kt index 972d67a0..100ae931 100644 --- a/data/src/main/java/com/squirtles/data/datasource/local/LocalUserDataSourceImpl.kt +++ b/data/src/main/java/com/squirtles/data/user/LocalUserDataSourceImpl.kt @@ -1,10 +1,10 @@ -package com.squirtles.data.datasource.local +package com.squirtles.data.user import android.content.Context import androidx.datastore.preferences.core.edit import androidx.datastore.preferences.core.stringPreferencesKey import androidx.datastore.preferences.preferencesDataStore -import com.squirtles.domain.datasource.local.LocalUserDataSource +import com.squirtles.domain.user.LocalUserDataSource import com.squirtles.domain.model.User import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.map diff --git a/data/src/main/java/com/squirtles/data/repository/local/LocalUserRepositoryImpl.kt b/data/src/main/java/com/squirtles/data/user/LocalUserRepositoryImpl.kt similarity index 83% rename from data/src/main/java/com/squirtles/data/repository/local/LocalUserRepositoryImpl.kt rename to data/src/main/java/com/squirtles/data/user/LocalUserRepositoryImpl.kt index ef497239..49a96707 100644 --- a/data/src/main/java/com/squirtles/data/repository/local/LocalUserRepositoryImpl.kt +++ b/data/src/main/java/com/squirtles/data/user/LocalUserRepositoryImpl.kt @@ -1,8 +1,8 @@ -package com.squirtles.data.repository.local +package com.squirtles.data.user -import com.squirtles.domain.datasource.local.LocalUserDataSource +import com.squirtles.domain.user.LocalUserDataSource import com.squirtles.domain.model.User -import com.squirtles.domain.repository.local.LocalUserRepository +import com.squirtles.domain.user.LocalUserRepository import kotlinx.coroutines.flow.Flow import javax.inject.Inject import javax.inject.Singleton diff --git a/data/src/main/java/com/squirtles/data/user/di/UserDi.kt b/data/src/main/java/com/squirtles/data/user/di/UserDi.kt new file mode 100644 index 00000000..89f6a080 --- /dev/null +++ b/data/src/main/java/com/squirtles/data/user/di/UserDi.kt @@ -0,0 +1,42 @@ +package com.squirtles.data.user.di + +import android.content.Context +import com.google.firebase.firestore.FirebaseFirestore +import com.squirtles.data.user.FirebaseUserDataSourceImpl +import com.squirtles.data.user.FirebaseUserRepositoryImpl +import com.squirtles.data.user.LocalUserDataSourceImpl +import com.squirtles.data.user.LocalUserRepositoryImpl +import com.squirtles.domain.user.FirebaseUserDataSource +import com.squirtles.domain.user.FirebaseUserRepository +import com.squirtles.domain.user.LocalUserDataSource +import com.squirtles.domain.user.LocalUserRepository +import dagger.Module +import dagger.Provides +import dagger.hilt.InstallIn +import dagger.hilt.android.qualifiers.ApplicationContext +import dagger.hilt.components.SingletonComponent +import javax.inject.Singleton + +@Module +@InstallIn(SingletonComponent::class) +object UserDi { + @Provides + @Singleton + fun provideLocalUserRepository(localUserDataSource: LocalUserDataSource): LocalUserRepository = + LocalUserRepositoryImpl(localUserDataSource) + + @Provides + @Singleton + fun provideLocalUserDataSource(@ApplicationContext context: Context): LocalUserDataSource = + LocalUserDataSourceImpl(context) + + @Provides + @Singleton + fun provideFirebaseUserRepository(firebaseUserDataSource: FirebaseUserDataSource): FirebaseUserRepository = + FirebaseUserRepositoryImpl(firebaseUserDataSource) + + @Provides + @Singleton + fun provideFirebaseUserDataSource(db: FirebaseFirestore): FirebaseUserDataSource = + FirebaseUserDataSourceImpl(db) +} diff --git a/data/src/main/java/com/squirtles/data/datasource/remote/firebase/model/FirebaseUser.kt b/data/src/main/java/com/squirtles/data/user/model/FirebaseUser.kt similarity index 64% rename from data/src/main/java/com/squirtles/data/datasource/remote/firebase/model/FirebaseUser.kt rename to data/src/main/java/com/squirtles/data/user/model/FirebaseUser.kt index da1d9bbd..ff351294 100644 --- a/data/src/main/java/com/squirtles/data/datasource/remote/firebase/model/FirebaseUser.kt +++ b/data/src/main/java/com/squirtles/data/user/model/FirebaseUser.kt @@ -1,4 +1,6 @@ -package com.squirtles.data.datasource.remote.firebase.model +package com.squirtles.data.user.model + +import com.squirtles.domain.model.User data class FirebaseUser( val name: String? = null, diff --git a/data/src/main/java/com/squirtles/data/user/model/Mapper.kt b/data/src/main/java/com/squirtles/data/user/model/Mapper.kt new file mode 100644 index 00000000..18267513 --- /dev/null +++ b/data/src/main/java/com/squirtles/data/user/model/Mapper.kt @@ -0,0 +1,10 @@ +package com.squirtles.data.user.model + +import com.squirtles.domain.model.User + +internal fun FirebaseUser.toUser(): User = User( + userId = "", + userName = name ?: "", + userProfileImage = profileImage, + myPicks = myPicks +) diff --git a/domain/src/main/java/com/squirtles/domain/repository/remote/applemusic/AppleMusicException.kt b/domain/src/main/java/com/squirtles/domain/applemusic/AppleMusicException.kt similarity index 87% rename from domain/src/main/java/com/squirtles/domain/repository/remote/applemusic/AppleMusicException.kt rename to domain/src/main/java/com/squirtles/domain/applemusic/AppleMusicException.kt index d0db3f8a..779ea832 100644 --- a/domain/src/main/java/com/squirtles/domain/repository/remote/applemusic/AppleMusicException.kt +++ b/domain/src/main/java/com/squirtles/domain/applemusic/AppleMusicException.kt @@ -1,4 +1,4 @@ -package com.squirtles.domain.repository.remote.applemusic +package com.squirtles.domain.applemusic /** * 400 에러가 여러 종류가 있는데 이를 구분할 용도로 만든 예외 클래스 diff --git a/domain/src/main/java/com/squirtles/domain/datasource/remote/applemusic/AppleMusicRemoteDataSource.kt b/domain/src/main/java/com/squirtles/domain/applemusic/AppleMusicRemoteDataSource.kt similarity index 75% rename from domain/src/main/java/com/squirtles/domain/datasource/remote/applemusic/AppleMusicRemoteDataSource.kt rename to domain/src/main/java/com/squirtles/domain/applemusic/AppleMusicRemoteDataSource.kt index 2eadd934..4e09600a 100644 --- a/domain/src/main/java/com/squirtles/domain/datasource/remote/applemusic/AppleMusicRemoteDataSource.kt +++ b/domain/src/main/java/com/squirtles/domain/applemusic/AppleMusicRemoteDataSource.kt @@ -1,4 +1,4 @@ -package com.squirtles.domain.datasource.remote.applemusic +package com.squirtles.domain.applemusic import androidx.paging.PagingData import com.squirtles.domain.model.MusicVideo @@ -7,6 +7,5 @@ import kotlinx.coroutines.flow.Flow interface AppleMusicRemoteDataSource { fun searchSongs(searchText: String): Flow> - suspend fun searchSongById(songId: String): Song suspend fun searchMusicVideos(searchText: String): List } diff --git a/domain/src/main/java/com/squirtles/domain/repository/remote/applemusic/AppleMusicRepository.kt b/domain/src/main/java/com/squirtles/domain/applemusic/AppleMusicRepository.kt similarity index 87% rename from domain/src/main/java/com/squirtles/domain/repository/remote/applemusic/AppleMusicRepository.kt rename to domain/src/main/java/com/squirtles/domain/applemusic/AppleMusicRepository.kt index c159c1b8..4df96e51 100644 --- a/domain/src/main/java/com/squirtles/domain/repository/remote/applemusic/AppleMusicRepository.kt +++ b/domain/src/main/java/com/squirtles/domain/applemusic/AppleMusicRepository.kt @@ -1,4 +1,4 @@ -package com.squirtles.domain.repository.remote.applemusic +package com.squirtles.domain.applemusic import androidx.paging.PagingData import com.squirtles.domain.model.MusicVideo diff --git a/domain/src/main/java/com/squirtles/domain/usecase/music/FetchMusicVideoUseCase.kt b/domain/src/main/java/com/squirtles/domain/applemusic/usecase/FetchMusicVideoUseCase.kt similarity index 83% rename from domain/src/main/java/com/squirtles/domain/usecase/music/FetchMusicVideoUseCase.kt rename to domain/src/main/java/com/squirtles/domain/applemusic/usecase/FetchMusicVideoUseCase.kt index 8ed50e29..7dbff84f 100644 --- a/domain/src/main/java/com/squirtles/domain/usecase/music/FetchMusicVideoUseCase.kt +++ b/domain/src/main/java/com/squirtles/domain/applemusic/usecase/FetchMusicVideoUseCase.kt @@ -1,8 +1,8 @@ -package com.squirtles.domain.usecase.music +package com.squirtles.domain.applemusic.usecase import com.squirtles.domain.model.MusicVideo import com.squirtles.domain.model.Song -import com.squirtles.domain.repository.remote.applemusic.AppleMusicRepository +import com.squirtles.domain.applemusic.AppleMusicRepository import javax.inject.Inject class FetchMusicVideoUseCase @Inject constructor( diff --git a/domain/src/main/java/com/squirtles/domain/usecase/music/FetchSongsUseCase.kt b/domain/src/main/java/com/squirtles/domain/applemusic/usecase/FetchSongsUseCase.kt similarity index 65% rename from domain/src/main/java/com/squirtles/domain/usecase/music/FetchSongsUseCase.kt rename to domain/src/main/java/com/squirtles/domain/applemusic/usecase/FetchSongsUseCase.kt index d822bc92..3f7e87fa 100644 --- a/domain/src/main/java/com/squirtles/domain/usecase/music/FetchSongsUseCase.kt +++ b/domain/src/main/java/com/squirtles/domain/applemusic/usecase/FetchSongsUseCase.kt @@ -1,6 +1,6 @@ -package com.squirtles.domain.usecase.music +package com.squirtles.domain.applemusic.usecase -import com.squirtles.domain.repository.remote.applemusic.AppleMusicRepository +import com.squirtles.domain.applemusic.AppleMusicRepository import javax.inject.Inject class FetchSongsUseCase @Inject constructor( diff --git a/domain/src/main/java/com/squirtles/domain/datasource/local/LocalDataSource.kt b/domain/src/main/java/com/squirtles/domain/datasource/local/LocalDataSource.kt deleted file mode 100644 index 8beb1872..00000000 --- a/domain/src/main/java/com/squirtles/domain/datasource/local/LocalDataSource.kt +++ /dev/null @@ -1,22 +0,0 @@ -package com.squirtles.domain.datasource.local - -import android.location.Location -import com.squirtles.domain.model.Order -import com.squirtles.domain.model.User -import kotlinx.coroutines.flow.Flow -import kotlinx.coroutines.flow.StateFlow - -interface LocalDataSource { - val currentUser: User? - val lastLocation: StateFlow - val favoriteListOrder: Order - val myListOrder: Order - - fun readUserIdDataStore(): Flow - suspend fun saveUserIdDataStore(userId: String) - suspend fun saveCurrentUser(user: User) - suspend fun clearUser() - suspend fun saveCurrentLocation(location: Location) - suspend fun saveFavoriteListOrder(order: Order) - suspend fun saveMyListOrder(order: Order) -} diff --git a/domain/src/main/java/com/squirtles/domain/datasource/remote/firebase/FirebaseFavoriteDataSource.kt b/domain/src/main/java/com/squirtles/domain/favorite/FirebaseFavoriteDataSource.kt similarity index 86% rename from domain/src/main/java/com/squirtles/domain/datasource/remote/firebase/FirebaseFavoriteDataSource.kt rename to domain/src/main/java/com/squirtles/domain/favorite/FirebaseFavoriteDataSource.kt index a64210d9..5b26030c 100644 --- a/domain/src/main/java/com/squirtles/domain/datasource/remote/firebase/FirebaseFavoriteDataSource.kt +++ b/domain/src/main/java/com/squirtles/domain/favorite/FirebaseFavoriteDataSource.kt @@ -1,4 +1,4 @@ -package com.squirtles.domain.datasource.remote.firebase +package com.squirtles.domain.favorite import com.squirtles.domain.model.Pick diff --git a/domain/src/main/java/com/squirtles/domain/repository/remote/firebase/FirebaseFavoriteRepository.kt b/domain/src/main/java/com/squirtles/domain/favorite/FirebaseFavoriteRepository.kt similarity index 70% rename from domain/src/main/java/com/squirtles/domain/repository/remote/firebase/FirebaseFavoriteRepository.kt rename to domain/src/main/java/com/squirtles/domain/favorite/FirebaseFavoriteRepository.kt index 015b6dbd..2f0e0d2c 100644 --- a/domain/src/main/java/com/squirtles/domain/repository/remote/firebase/FirebaseFavoriteRepository.kt +++ b/domain/src/main/java/com/squirtles/domain/favorite/FirebaseFavoriteRepository.kt @@ -1,8 +1,9 @@ -package com.squirtles.domain.repository.remote.firebase +package com.squirtles.domain.favorite import com.squirtles.domain.model.Pick +import com.squirtles.domain.firebase.FirebaseRepository -interface FirebaseFavoriteRepository { +interface FirebaseFavoriteRepository : FirebaseRepository { // Favorite suspend fun fetchIsFavorite(pickId: String, userId: String): Result suspend fun createFavorite(pickId: String, userId: String): Result diff --git a/domain/src/main/java/com/squirtles/domain/usecase/favorite/CreateFavoriteUseCase.kt b/domain/src/main/java/com/squirtles/domain/favorite/usecase/CreateFavoriteUseCase.kt similarity index 67% rename from domain/src/main/java/com/squirtles/domain/usecase/favorite/CreateFavoriteUseCase.kt rename to domain/src/main/java/com/squirtles/domain/favorite/usecase/CreateFavoriteUseCase.kt index 45df3822..6846475c 100644 --- a/domain/src/main/java/com/squirtles/domain/usecase/favorite/CreateFavoriteUseCase.kt +++ b/domain/src/main/java/com/squirtles/domain/favorite/usecase/CreateFavoriteUseCase.kt @@ -1,6 +1,6 @@ -package com.squirtles.domain.usecase.favorite +package com.squirtles.domain.favorite.usecase -import com.squirtles.domain.repository.remote.firebase.FirebaseFavoriteRepository +import com.squirtles.domain.favorite.FirebaseFavoriteRepository import javax.inject.Inject class CreateFavoriteUseCase @Inject constructor( diff --git a/domain/src/main/java/com/squirtles/domain/usecase/favorite/DeleteFavoriteUseCase.kt b/domain/src/main/java/com/squirtles/domain/favorite/usecase/DeleteFavoriteUseCase.kt similarity index 60% rename from domain/src/main/java/com/squirtles/domain/usecase/favorite/DeleteFavoriteUseCase.kt rename to domain/src/main/java/com/squirtles/domain/favorite/usecase/DeleteFavoriteUseCase.kt index 126f74c9..0acf6631 100644 --- a/domain/src/main/java/com/squirtles/domain/usecase/favorite/DeleteFavoriteUseCase.kt +++ b/domain/src/main/java/com/squirtles/domain/favorite/usecase/DeleteFavoriteUseCase.kt @@ -1,7 +1,7 @@ -package com.squirtles.domain.usecase.favorite +package com.squirtles.domain.favorite.usecase -import com.squirtles.domain.repository.remote.firebase.FirebaseFavoriteRepository -import com.squirtles.domain.usecase.picklist.RemovePickUseCaseInterface +import com.squirtles.domain.favorite.FirebaseFavoriteRepository +import com.squirtles.domain.picklist.RemovePickUseCaseInterface import javax.inject.Inject class DeleteFavoriteUseCase @Inject constructor( diff --git a/domain/src/main/java/com/squirtles/domain/usecase/favorite/FetchFavoritePicksUseCase.kt b/domain/src/main/java/com/squirtles/domain/favorite/usecase/FetchFavoritePicksUseCase.kt similarity index 58% rename from domain/src/main/java/com/squirtles/domain/usecase/favorite/FetchFavoritePicksUseCase.kt rename to domain/src/main/java/com/squirtles/domain/favorite/usecase/FetchFavoritePicksUseCase.kt index cc5c3c45..faa17705 100644 --- a/domain/src/main/java/com/squirtles/domain/usecase/favorite/FetchFavoritePicksUseCase.kt +++ b/domain/src/main/java/com/squirtles/domain/favorite/usecase/FetchFavoritePicksUseCase.kt @@ -1,7 +1,7 @@ -package com.squirtles.domain.usecase.favorite +package com.squirtles.domain.favorite.usecase -import com.squirtles.domain.repository.remote.firebase.FirebaseFavoriteRepository -import com.squirtles.domain.usecase.picklist.FetchPickListUseCaseInterface +import com.squirtles.domain.favorite.FirebaseFavoriteRepository +import com.squirtles.domain.picklist.FetchPickListUseCaseInterface import javax.inject.Inject class FetchFavoritePicksUseCase @Inject constructor( diff --git a/domain/src/main/java/com/squirtles/domain/usecase/favorite/FetchIsFavoriteUseCase.kt b/domain/src/main/java/com/squirtles/domain/favorite/usecase/FetchIsFavoriteUseCase.kt similarity index 68% rename from domain/src/main/java/com/squirtles/domain/usecase/favorite/FetchIsFavoriteUseCase.kt rename to domain/src/main/java/com/squirtles/domain/favorite/usecase/FetchIsFavoriteUseCase.kt index c75d3d30..9a30d0cc 100644 --- a/domain/src/main/java/com/squirtles/domain/usecase/favorite/FetchIsFavoriteUseCase.kt +++ b/domain/src/main/java/com/squirtles/domain/favorite/usecase/FetchIsFavoriteUseCase.kt @@ -1,6 +1,6 @@ -package com.squirtles.domain.usecase.favorite +package com.squirtles.domain.favorite.usecase -import com.squirtles.domain.repository.remote.firebase.FirebaseFavoriteRepository +import com.squirtles.domain.favorite.FirebaseFavoriteRepository import javax.inject.Inject class FetchIsFavoriteUseCase @Inject constructor( diff --git a/domain/src/main/java/com/squirtles/domain/repository/remote/firebase/FirebaseException.kt b/domain/src/main/java/com/squirtles/domain/firebase/FirebaseException.kt similarity index 91% rename from domain/src/main/java/com/squirtles/domain/repository/remote/firebase/FirebaseException.kt rename to domain/src/main/java/com/squirtles/domain/firebase/FirebaseException.kt index 49d82486..4fccebe1 100644 --- a/domain/src/main/java/com/squirtles/domain/repository/remote/firebase/FirebaseException.kt +++ b/domain/src/main/java/com/squirtles/domain/firebase/FirebaseException.kt @@ -1,4 +1,4 @@ -package com.squirtles.domain.repository.remote.firebase +package com.squirtles.domain.firebase sealed class FirebaseException(override val message: String) : Exception() { data class CreatedUserFailedException(override val message: String = "Failed to create a user") : diff --git a/domain/src/main/java/com/squirtles/domain/firebase/FirebaseRepository.kt b/domain/src/main/java/com/squirtles/domain/firebase/FirebaseRepository.kt new file mode 100644 index 00000000..0b771702 --- /dev/null +++ b/domain/src/main/java/com/squirtles/domain/firebase/FirebaseRepository.kt @@ -0,0 +1,20 @@ +package com.squirtles.domain.firebase + +interface FirebaseRepository { + suspend fun handleResult( + firebaseRepositoryException: FirebaseException, + call: suspend () -> T? + ): Result { + return runCatching { + call() ?: throw firebaseRepositoryException + } + } + + suspend fun handleResult( + call: suspend () -> T + ): Result { + return runCatching { + call() + } + } +} diff --git a/domain/src/main/java/com/squirtles/domain/repository/local/LocalLocationRepository.kt b/domain/src/main/java/com/squirtles/domain/location/LocalLocationRepository.kt similarity index 82% rename from domain/src/main/java/com/squirtles/domain/repository/local/LocalLocationRepository.kt rename to domain/src/main/java/com/squirtles/domain/location/LocalLocationRepository.kt index 9a27eaec..0011fcc6 100644 --- a/domain/src/main/java/com/squirtles/domain/repository/local/LocalLocationRepository.kt +++ b/domain/src/main/java/com/squirtles/domain/location/LocalLocationRepository.kt @@ -1,4 +1,4 @@ -package com.squirtles.domain.repository.local +package com.squirtles.domain.location import android.location.Location import kotlinx.coroutines.flow.StateFlow diff --git a/domain/src/main/java/com/squirtles/domain/usecase/location/GetLastLocationUseCase.kt b/domain/src/main/java/com/squirtles/domain/location/usecase/GetLastLocationUseCase.kt similarity index 54% rename from domain/src/main/java/com/squirtles/domain/usecase/location/GetLastLocationUseCase.kt rename to domain/src/main/java/com/squirtles/domain/location/usecase/GetLastLocationUseCase.kt index 5070c35d..d4a29cfc 100644 --- a/domain/src/main/java/com/squirtles/domain/usecase/location/GetLastLocationUseCase.kt +++ b/domain/src/main/java/com/squirtles/domain/location/usecase/GetLastLocationUseCase.kt @@ -1,7 +1,6 @@ -package com.squirtles.domain.usecase.location +package com.squirtles.domain.location.usecase -import com.squirtles.domain.repository.local.LocalLocationRepository -import com.squirtles.domain.repository.local.LocalRepository +import com.squirtles.domain.location.LocalLocationRepository import javax.inject.Inject class GetLastLocationUseCase @Inject constructor( diff --git a/domain/src/main/java/com/squirtles/domain/usecase/location/SaveLastLocationUseCase.kt b/domain/src/main/java/com/squirtles/domain/location/usecase/SaveLastLocationUseCase.kt similarity index 62% rename from domain/src/main/java/com/squirtles/domain/usecase/location/SaveLastLocationUseCase.kt rename to domain/src/main/java/com/squirtles/domain/location/usecase/SaveLastLocationUseCase.kt index 3b98e2cb..62e0226b 100644 --- a/domain/src/main/java/com/squirtles/domain/usecase/location/SaveLastLocationUseCase.kt +++ b/domain/src/main/java/com/squirtles/domain/location/usecase/SaveLastLocationUseCase.kt @@ -1,8 +1,7 @@ -package com.squirtles.domain.usecase.location +package com.squirtles.domain.location.usecase import android.location.Location -import com.squirtles.domain.repository.local.LocalLocationRepository -import com.squirtles.domain.repository.local.LocalRepository +import com.squirtles.domain.location.LocalLocationRepository import javax.inject.Inject class SaveLastLocationUseCase @Inject constructor( diff --git a/domain/src/main/java/com/squirtles/domain/repository/local/LocalPickListOrderRepository.kt b/domain/src/main/java/com/squirtles/domain/order/LocalPickListOrderRepository.kt similarity index 86% rename from domain/src/main/java/com/squirtles/domain/repository/local/LocalPickListOrderRepository.kt rename to domain/src/main/java/com/squirtles/domain/order/LocalPickListOrderRepository.kt index df1c67f1..fe9a8a16 100644 --- a/domain/src/main/java/com/squirtles/domain/repository/local/LocalPickListOrderRepository.kt +++ b/domain/src/main/java/com/squirtles/domain/order/LocalPickListOrderRepository.kt @@ -1,4 +1,4 @@ -package com.squirtles.domain.repository.local +package com.squirtles.domain.order import com.squirtles.domain.model.Order diff --git a/domain/src/main/java/com/squirtles/domain/usecase/order/GetFavoriteListOrderUseCase.kt b/domain/src/main/java/com/squirtles/domain/order/usecase/GetFavoriteListOrderUseCase.kt similarity index 53% rename from domain/src/main/java/com/squirtles/domain/usecase/order/GetFavoriteListOrderUseCase.kt rename to domain/src/main/java/com/squirtles/domain/order/usecase/GetFavoriteListOrderUseCase.kt index 9e4024ea..b7d9b0db 100644 --- a/domain/src/main/java/com/squirtles/domain/usecase/order/GetFavoriteListOrderUseCase.kt +++ b/domain/src/main/java/com/squirtles/domain/order/usecase/GetFavoriteListOrderUseCase.kt @@ -1,8 +1,7 @@ -package com.squirtles.domain.usecase.order +package com.squirtles.domain.order.usecase -import com.squirtles.domain.repository.local.LocalPickListOrderRepository -import com.squirtles.domain.repository.local.LocalRepository -import com.squirtles.domain.usecase.picklist.GetPickListOrderUseCaseInterface +import com.squirtles.domain.order.LocalPickListOrderRepository +import com.squirtles.domain.picklist.GetPickListOrderUseCaseInterface import javax.inject.Inject class GetFavoriteListOrderUseCase @Inject constructor( diff --git a/domain/src/main/java/com/squirtles/domain/usecase/order/GetMyPickListOrderUseCase.kt b/domain/src/main/java/com/squirtles/domain/order/usecase/GetMyPickListOrderUseCase.kt similarity index 52% rename from domain/src/main/java/com/squirtles/domain/usecase/order/GetMyPickListOrderUseCase.kt rename to domain/src/main/java/com/squirtles/domain/order/usecase/GetMyPickListOrderUseCase.kt index 682193f7..ab8b72e6 100644 --- a/domain/src/main/java/com/squirtles/domain/usecase/order/GetMyPickListOrderUseCase.kt +++ b/domain/src/main/java/com/squirtles/domain/order/usecase/GetMyPickListOrderUseCase.kt @@ -1,8 +1,7 @@ -package com.squirtles.domain.usecase.order +package com.squirtles.domain.order.usecase -import com.squirtles.domain.repository.local.LocalPickListOrderRepository -import com.squirtles.domain.repository.local.LocalRepository -import com.squirtles.domain.usecase.picklist.GetPickListOrderUseCaseInterface +import com.squirtles.domain.order.LocalPickListOrderRepository +import com.squirtles.domain.picklist.GetPickListOrderUseCaseInterface import javax.inject.Inject class GetMyPickListOrderUseCase @Inject constructor( diff --git a/domain/src/main/java/com/squirtles/domain/usecase/order/SaveFavoriteListOrderUseCase.kt b/domain/src/main/java/com/squirtles/domain/order/usecase/SaveFavoriteListOrderUseCase.kt similarity index 58% rename from domain/src/main/java/com/squirtles/domain/usecase/order/SaveFavoriteListOrderUseCase.kt rename to domain/src/main/java/com/squirtles/domain/order/usecase/SaveFavoriteListOrderUseCase.kt index 73c56a04..1ea94ee2 100644 --- a/domain/src/main/java/com/squirtles/domain/usecase/order/SaveFavoriteListOrderUseCase.kt +++ b/domain/src/main/java/com/squirtles/domain/order/usecase/SaveFavoriteListOrderUseCase.kt @@ -1,9 +1,8 @@ -package com.squirtles.domain.usecase.order +package com.squirtles.domain.order.usecase import com.squirtles.domain.model.Order -import com.squirtles.domain.repository.local.LocalPickListOrderRepository -import com.squirtles.domain.repository.local.LocalRepository -import com.squirtles.domain.usecase.picklist.SavePickListOrderUseCaseInterface +import com.squirtles.domain.order.LocalPickListOrderRepository +import com.squirtles.domain.picklist.SavePickListOrderUseCaseInterface import javax.inject.Inject class SaveFavoriteListOrderUseCase @Inject constructor( diff --git a/domain/src/main/java/com/squirtles/domain/usecase/order/SaveMyPickListOrderUseCase.kt b/domain/src/main/java/com/squirtles/domain/order/usecase/SaveMyPickListOrderUseCase.kt similarity index 57% rename from domain/src/main/java/com/squirtles/domain/usecase/order/SaveMyPickListOrderUseCase.kt rename to domain/src/main/java/com/squirtles/domain/order/usecase/SaveMyPickListOrderUseCase.kt index 73a97c4a..6d06fa5d 100644 --- a/domain/src/main/java/com/squirtles/domain/usecase/order/SaveMyPickListOrderUseCase.kt +++ b/domain/src/main/java/com/squirtles/domain/order/usecase/SaveMyPickListOrderUseCase.kt @@ -1,9 +1,8 @@ -package com.squirtles.domain.usecase.order +package com.squirtles.domain.order.usecase import com.squirtles.domain.model.Order -import com.squirtles.domain.repository.local.LocalPickListOrderRepository -import com.squirtles.domain.repository.local.LocalRepository -import com.squirtles.domain.usecase.picklist.SavePickListOrderUseCaseInterface +import com.squirtles.domain.order.LocalPickListOrderRepository +import com.squirtles.domain.picklist.SavePickListOrderUseCaseInterface import javax.inject.Inject class SaveMyPickListOrderUseCase @Inject constructor( diff --git a/domain/src/main/java/com/squirtles/domain/datasource/remote/firebase/FirebasePickDataSource.kt b/domain/src/main/java/com/squirtles/domain/pick/FirebasePickDataSource.kt similarity index 87% rename from domain/src/main/java/com/squirtles/domain/datasource/remote/firebase/FirebasePickDataSource.kt rename to domain/src/main/java/com/squirtles/domain/pick/FirebasePickDataSource.kt index 02752290..fcd0616d 100644 --- a/domain/src/main/java/com/squirtles/domain/datasource/remote/firebase/FirebasePickDataSource.kt +++ b/domain/src/main/java/com/squirtles/domain/pick/FirebasePickDataSource.kt @@ -1,4 +1,4 @@ -package com.squirtles.domain.datasource.remote.firebase +package com.squirtles.domain.pick import com.squirtles.domain.model.Pick diff --git a/domain/src/main/java/com/squirtles/domain/repository/remote/firebase/FirebasePickRepository.kt b/domain/src/main/java/com/squirtles/domain/pick/FirebasePickRepository.kt similarity index 54% rename from domain/src/main/java/com/squirtles/domain/repository/remote/firebase/FirebasePickRepository.kt rename to domain/src/main/java/com/squirtles/domain/pick/FirebasePickRepository.kt index 5aee47ee..63721d0d 100644 --- a/domain/src/main/java/com/squirtles/domain/repository/remote/firebase/FirebasePickRepository.kt +++ b/domain/src/main/java/com/squirtles/domain/pick/FirebasePickRepository.kt @@ -1,20 +1,12 @@ -package com.squirtles.domain.repository.remote.firebase +package com.squirtles.domain.pick import com.squirtles.domain.model.Pick +import com.squirtles.domain.firebase.FirebaseRepository -interface FirebasePickRepository { +interface FirebasePickRepository : FirebaseRepository { suspend fun createPick(pick: Pick): Result suspend fun deletePick(pickId: String, userId: String): Result suspend fun fetchPick(pickID: String): Result suspend fun fetchPicksInArea(lat: Double, lng: Double, radiusInM: Double): Result> suspend fun fetchMyPicks(userId: String): Result> - - suspend fun handleResult( - firebaseRepositoryException: FirebaseException, - call: suspend () -> T? - ): Result { - return runCatching { - call() ?: throw firebaseRepositoryException - } - } } diff --git a/domain/src/main/java/com/squirtles/domain/usecase/pick/CreatePickUseCase.kt b/domain/src/main/java/com/squirtles/domain/pick/usecase/CreatePickUseCase.kt similarity index 69% rename from domain/src/main/java/com/squirtles/domain/usecase/pick/CreatePickUseCase.kt rename to domain/src/main/java/com/squirtles/domain/pick/usecase/CreatePickUseCase.kt index b32e034e..33805f7e 100644 --- a/domain/src/main/java/com/squirtles/domain/usecase/pick/CreatePickUseCase.kt +++ b/domain/src/main/java/com/squirtles/domain/pick/usecase/CreatePickUseCase.kt @@ -1,7 +1,7 @@ -package com.squirtles.domain.usecase.pick +package com.squirtles.domain.pick.usecase import com.squirtles.domain.model.Pick -import com.squirtles.domain.repository.remote.firebase.FirebasePickRepository +import com.squirtles.domain.pick.FirebasePickRepository import javax.inject.Inject class CreatePickUseCase @Inject constructor( diff --git a/domain/src/main/java/com/squirtles/domain/usecase/pick/DeletePickUseCase.kt b/domain/src/main/java/com/squirtles/domain/pick/usecase/DeletePickUseCase.kt similarity index 61% rename from domain/src/main/java/com/squirtles/domain/usecase/pick/DeletePickUseCase.kt rename to domain/src/main/java/com/squirtles/domain/pick/usecase/DeletePickUseCase.kt index 61e51a44..b2bcbbc0 100644 --- a/domain/src/main/java/com/squirtles/domain/usecase/pick/DeletePickUseCase.kt +++ b/domain/src/main/java/com/squirtles/domain/pick/usecase/DeletePickUseCase.kt @@ -1,7 +1,7 @@ -package com.squirtles.domain.usecase.pick +package com.squirtles.domain.pick.usecase -import com.squirtles.domain.repository.remote.firebase.FirebasePickRepository -import com.squirtles.domain.usecase.picklist.RemovePickUseCaseInterface +import com.squirtles.domain.pick.FirebasePickRepository +import com.squirtles.domain.picklist.RemovePickUseCaseInterface import javax.inject.Inject class DeletePickUseCase @Inject constructor( diff --git a/domain/src/main/java/com/squirtles/domain/usecase/pick/FetchMyPicksUseCase.kt b/domain/src/main/java/com/squirtles/domain/pick/usecase/FetchMyPicksUseCase.kt similarity index 58% rename from domain/src/main/java/com/squirtles/domain/usecase/pick/FetchMyPicksUseCase.kt rename to domain/src/main/java/com/squirtles/domain/pick/usecase/FetchMyPicksUseCase.kt index 9907dbc4..4507acab 100644 --- a/domain/src/main/java/com/squirtles/domain/usecase/pick/FetchMyPicksUseCase.kt +++ b/domain/src/main/java/com/squirtles/domain/pick/usecase/FetchMyPicksUseCase.kt @@ -1,7 +1,7 @@ -package com.squirtles.domain.usecase.pick +package com.squirtles.domain.pick.usecase -import com.squirtles.domain.repository.remote.firebase.FirebasePickRepository -import com.squirtles.domain.usecase.picklist.FetchPickListUseCaseInterface +import com.squirtles.domain.pick.FirebasePickRepository +import com.squirtles.domain.picklist.FetchPickListUseCaseInterface import javax.inject.Inject class FetchMyPicksUseCase @Inject constructor( diff --git a/domain/src/main/java/com/squirtles/domain/usecase/pick/FetchPickUseCase.kt b/domain/src/main/java/com/squirtles/domain/pick/usecase/FetchPickUseCase.kt similarity index 75% rename from domain/src/main/java/com/squirtles/domain/usecase/pick/FetchPickUseCase.kt rename to domain/src/main/java/com/squirtles/domain/pick/usecase/FetchPickUseCase.kt index 9be6e135..6a95b697 100644 --- a/domain/src/main/java/com/squirtles/domain/usecase/pick/FetchPickUseCase.kt +++ b/domain/src/main/java/com/squirtles/domain/pick/usecase/FetchPickUseCase.kt @@ -1,6 +1,6 @@ -package com.squirtles.domain.usecase.pick +package com.squirtles.domain.pick.usecase -import com.squirtles.domain.repository.remote.firebase.FirebasePickRepository +import com.squirtles.domain.pick.FirebasePickRepository import javax.inject.Inject class FetchPickUseCase @Inject constructor( diff --git a/domain/src/main/java/com/squirtles/domain/usecase/picklist/FetchPickListUseCaseInterface.kt b/domain/src/main/java/com/squirtles/domain/picklist/FetchPickListUseCaseInterface.kt similarity index 76% rename from domain/src/main/java/com/squirtles/domain/usecase/picklist/FetchPickListUseCaseInterface.kt rename to domain/src/main/java/com/squirtles/domain/picklist/FetchPickListUseCaseInterface.kt index 5b61faf7..a87e33b4 100644 --- a/domain/src/main/java/com/squirtles/domain/usecase/picklist/FetchPickListUseCaseInterface.kt +++ b/domain/src/main/java/com/squirtles/domain/picklist/FetchPickListUseCaseInterface.kt @@ -1,4 +1,4 @@ -package com.squirtles.domain.usecase.picklist +package com.squirtles.domain.picklist import com.squirtles.domain.model.Pick diff --git a/domain/src/main/java/com/squirtles/domain/usecase/picklist/GetPickListOrderUseCaseInterface.kt b/domain/src/main/java/com/squirtles/domain/picklist/GetPickListOrderUseCaseInterface.kt similarity index 73% rename from domain/src/main/java/com/squirtles/domain/usecase/picklist/GetPickListOrderUseCaseInterface.kt rename to domain/src/main/java/com/squirtles/domain/picklist/GetPickListOrderUseCaseInterface.kt index ad98b854..d2e3350c 100644 --- a/domain/src/main/java/com/squirtles/domain/usecase/picklist/GetPickListOrderUseCaseInterface.kt +++ b/domain/src/main/java/com/squirtles/domain/picklist/GetPickListOrderUseCaseInterface.kt @@ -1,4 +1,4 @@ -package com.squirtles.domain.usecase.picklist +package com.squirtles.domain.picklist import com.squirtles.domain.model.Order diff --git a/domain/src/main/java/com/squirtles/domain/usecase/picklist/RemovePickUseCaseInterface.kt b/domain/src/main/java/com/squirtles/domain/picklist/RemovePickUseCaseInterface.kt similarity index 72% rename from domain/src/main/java/com/squirtles/domain/usecase/picklist/RemovePickUseCaseInterface.kt rename to domain/src/main/java/com/squirtles/domain/picklist/RemovePickUseCaseInterface.kt index 06c90a4f..2e3037a4 100644 --- a/domain/src/main/java/com/squirtles/domain/usecase/picklist/RemovePickUseCaseInterface.kt +++ b/domain/src/main/java/com/squirtles/domain/picklist/RemovePickUseCaseInterface.kt @@ -1,4 +1,4 @@ -package com.squirtles.domain.usecase.picklist +package com.squirtles.domain.picklist interface RemovePickUseCaseInterface { suspend operator fun invoke(pickId: String, userId: String): Result diff --git a/domain/src/main/java/com/squirtles/domain/usecase/picklist/SavePickListOrderUseCaseInterface.kt b/domain/src/main/java/com/squirtles/domain/picklist/SavePickListOrderUseCaseInterface.kt similarity index 74% rename from domain/src/main/java/com/squirtles/domain/usecase/picklist/SavePickListOrderUseCaseInterface.kt rename to domain/src/main/java/com/squirtles/domain/picklist/SavePickListOrderUseCaseInterface.kt index eb04b50e..d7664764 100644 --- a/domain/src/main/java/com/squirtles/domain/usecase/picklist/SavePickListOrderUseCaseInterface.kt +++ b/domain/src/main/java/com/squirtles/domain/picklist/SavePickListOrderUseCaseInterface.kt @@ -1,4 +1,4 @@ -package com.squirtles.domain.usecase.picklist +package com.squirtles.domain.picklist import com.squirtles.domain.model.Order diff --git a/domain/src/main/java/com/squirtles/domain/usecase/player/MediaPlayerListenerUseCase.kt b/domain/src/main/java/com/squirtles/domain/player/MediaPlayerListenerUseCase.kt similarity index 99% rename from domain/src/main/java/com/squirtles/domain/usecase/player/MediaPlayerListenerUseCase.kt rename to domain/src/main/java/com/squirtles/domain/player/MediaPlayerListenerUseCase.kt index 82f57e80..1a395da4 100644 --- a/domain/src/main/java/com/squirtles/domain/usecase/player/MediaPlayerListenerUseCase.kt +++ b/domain/src/main/java/com/squirtles/domain/player/MediaPlayerListenerUseCase.kt @@ -1,4 +1,4 @@ -package com.squirtles.domain.usecase.player +package com.squirtles.domain.player import androidx.media3.common.Player import androidx.media3.common.Tracks diff --git a/domain/src/main/java/com/squirtles/domain/usecase/player/MediaPlayerUseCase.kt b/domain/src/main/java/com/squirtles/domain/player/MediaPlayerUseCase.kt similarity index 98% rename from domain/src/main/java/com/squirtles/domain/usecase/player/MediaPlayerUseCase.kt rename to domain/src/main/java/com/squirtles/domain/player/MediaPlayerUseCase.kt index 218e6388..41878c52 100644 --- a/domain/src/main/java/com/squirtles/domain/usecase/player/MediaPlayerUseCase.kt +++ b/domain/src/main/java/com/squirtles/domain/player/MediaPlayerUseCase.kt @@ -1,4 +1,4 @@ -package com.squirtles.domain.usecase.player +package com.squirtles.domain.player import androidx.media3.common.MediaItem import androidx.media3.common.MediaMetadata diff --git a/domain/src/main/java/com/squirtles/domain/repository/local/LocalRepository.kt b/domain/src/main/java/com/squirtles/domain/repository/local/LocalRepository.kt deleted file mode 100644 index 4c58cc25..00000000 --- a/domain/src/main/java/com/squirtles/domain/repository/local/LocalRepository.kt +++ /dev/null @@ -1,24 +0,0 @@ -package com.squirtles.domain.repository.local - -import android.location.Location -import com.squirtles.domain.model.Order -import com.squirtles.domain.model.User -import kotlinx.coroutines.flow.Flow -import kotlinx.coroutines.flow.StateFlow - -interface LocalRepository { -// val currentUser: User? -// val lastLocation: StateFlow -// val favoriteListOrder: Order // 픽 보관함 정렬 순서 -// val myListOrder: Order // 등록한 픽 정렬 순서 -// -// fun readUserIdDataStore(): Flow -// suspend fun saveUserIdDataStore(userId: String) -// suspend fun saveCurrentUser(user: User) -// suspend fun clearUser(): Result -// -// suspend fun saveCurrentLocation(location: Location) -// -// suspend fun saveFavoriteListOrder(order: Order) -// suspend fun saveMyListOrder(order: Order) -} diff --git a/domain/src/main/java/com/squirtles/domain/repository/remote/RemoteRepository.kt b/domain/src/main/java/com/squirtles/domain/repository/remote/RemoteRepository.kt deleted file mode 100644 index 6fd8c1fd..00000000 --- a/domain/src/main/java/com/squirtles/domain/repository/remote/RemoteRepository.kt +++ /dev/null @@ -1,11 +0,0 @@ -package com.squirtles.domain.repository.remote - -abstract class RemoteRepository { - suspend fun handleResult( - call: suspend () -> T - ): Result { - return runCatching { - call() - } - } -} diff --git a/domain/src/main/java/com/squirtles/domain/repository/remote/firebase/FirebaseUserRepository.kt b/domain/src/main/java/com/squirtles/domain/repository/remote/firebase/FirebaseUserRepository.kt deleted file mode 100644 index 21869909..00000000 --- a/domain/src/main/java/com/squirtles/domain/repository/remote/firebase/FirebaseUserRepository.kt +++ /dev/null @@ -1,19 +0,0 @@ -package com.squirtles.domain.repository.remote.firebase - -import com.squirtles.domain.model.User - -interface FirebaseUserRepository { - // user - suspend fun createGoogleIdUser(userId: String, userName: String?, userProfileImage: String?): Result - suspend fun fetchUser(userId: String): Result - suspend fun updateUserName(userId: String, newUserName: String): Result - - suspend fun handleResult( - firebaseRepositoryException: FirebaseException, - call: suspend () -> T? - ): Result { - return runCatching { - call() ?: throw firebaseRepositoryException - } - } -} diff --git a/domain/src/main/java/com/squirtles/domain/datasource/remote/firebase/FirebaseUserDataSource.kt b/domain/src/main/java/com/squirtles/domain/user/FirebaseUserDataSource.kt similarity index 84% rename from domain/src/main/java/com/squirtles/domain/datasource/remote/firebase/FirebaseUserDataSource.kt rename to domain/src/main/java/com/squirtles/domain/user/FirebaseUserDataSource.kt index 58db5377..a15dba82 100644 --- a/domain/src/main/java/com/squirtles/domain/datasource/remote/firebase/FirebaseUserDataSource.kt +++ b/domain/src/main/java/com/squirtles/domain/user/FirebaseUserDataSource.kt @@ -1,4 +1,4 @@ -package com.squirtles.domain.datasource.remote.firebase +package com.squirtles.domain.user import com.squirtles.domain.model.User diff --git a/domain/src/main/java/com/squirtles/domain/user/FirebaseUserRepository.kt b/domain/src/main/java/com/squirtles/domain/user/FirebaseUserRepository.kt new file mode 100644 index 00000000..c64a5548 --- /dev/null +++ b/domain/src/main/java/com/squirtles/domain/user/FirebaseUserRepository.kt @@ -0,0 +1,11 @@ +package com.squirtles.domain.user + +import com.squirtles.domain.firebase.FirebaseRepository +import com.squirtles.domain.model.User + +interface FirebaseUserRepository : FirebaseRepository { + // user + suspend fun createGoogleIdUser(userId: String, userName: String?, userProfileImage: String?): Result + suspend fun fetchUser(userId: String): Result + suspend fun updateUserName(userId: String, newUserName: String): Result +} diff --git a/domain/src/main/java/com/squirtles/domain/datasource/local/LocalUserDataSource.kt b/domain/src/main/java/com/squirtles/domain/user/LocalUserDataSource.kt similarity index 87% rename from domain/src/main/java/com/squirtles/domain/datasource/local/LocalUserDataSource.kt rename to domain/src/main/java/com/squirtles/domain/user/LocalUserDataSource.kt index 3ed20efb..ebc7c0ca 100644 --- a/domain/src/main/java/com/squirtles/domain/datasource/local/LocalUserDataSource.kt +++ b/domain/src/main/java/com/squirtles/domain/user/LocalUserDataSource.kt @@ -1,4 +1,4 @@ -package com.squirtles.domain.datasource.local +package com.squirtles.domain.user import com.squirtles.domain.model.User import kotlinx.coroutines.flow.Flow diff --git a/domain/src/main/java/com/squirtles/domain/repository/local/LocalUserRepository.kt b/domain/src/main/java/com/squirtles/domain/user/LocalUserRepository.kt similarity index 87% rename from domain/src/main/java/com/squirtles/domain/repository/local/LocalUserRepository.kt rename to domain/src/main/java/com/squirtles/domain/user/LocalUserRepository.kt index 3bfff38d..243f9226 100644 --- a/domain/src/main/java/com/squirtles/domain/repository/local/LocalUserRepository.kt +++ b/domain/src/main/java/com/squirtles/domain/user/LocalUserRepository.kt @@ -1,4 +1,4 @@ -package com.squirtles.domain.repository.local +package com.squirtles.domain.user import com.squirtles.domain.model.User import kotlinx.coroutines.flow.Flow diff --git a/domain/src/main/java/com/squirtles/domain/usecase/user/ClearUserUseCase.kt b/domain/src/main/java/com/squirtles/domain/user/usecase/ClearUserUseCase.kt similarity index 54% rename from domain/src/main/java/com/squirtles/domain/usecase/user/ClearUserUseCase.kt rename to domain/src/main/java/com/squirtles/domain/user/usecase/ClearUserUseCase.kt index 9340a567..c42eb854 100644 --- a/domain/src/main/java/com/squirtles/domain/usecase/user/ClearUserUseCase.kt +++ b/domain/src/main/java/com/squirtles/domain/user/usecase/ClearUserUseCase.kt @@ -1,7 +1,6 @@ -package com.squirtles.domain.usecase.user +package com.squirtles.domain.user.usecase -import com.squirtles.domain.repository.local.LocalRepository -import com.squirtles.domain.repository.local.LocalUserRepository +import com.squirtles.domain.user.LocalUserRepository import javax.inject.Inject class ClearUserUseCase @Inject constructor( diff --git a/domain/src/main/java/com/squirtles/domain/usecase/user/CreateGoogleIdUserUseCase.kt b/domain/src/main/java/com/squirtles/domain/user/usecase/CreateGoogleIdUserUseCase.kt similarity index 80% rename from domain/src/main/java/com/squirtles/domain/usecase/user/CreateGoogleIdUserUseCase.kt rename to domain/src/main/java/com/squirtles/domain/user/usecase/CreateGoogleIdUserUseCase.kt index 6eb4b435..07cdf223 100644 --- a/domain/src/main/java/com/squirtles/domain/usecase/user/CreateGoogleIdUserUseCase.kt +++ b/domain/src/main/java/com/squirtles/domain/user/usecase/CreateGoogleIdUserUseCase.kt @@ -1,8 +1,8 @@ -package com.squirtles.domain.usecase.user +package com.squirtles.domain.user.usecase import com.squirtles.domain.model.User -import com.squirtles.domain.repository.local.LocalUserRepository -import com.squirtles.domain.repository.remote.firebase.FirebaseUserRepository +import com.squirtles.domain.user.LocalUserRepository +import com.squirtles.domain.user.FirebaseUserRepository import javax.inject.Inject class CreateGoogleIdUserUseCase @Inject constructor( diff --git a/domain/src/main/java/com/squirtles/domain/usecase/user/FetchUserByIdUseCase.kt b/domain/src/main/java/com/squirtles/domain/user/usecase/FetchUserByIdUseCase.kt similarity index 67% rename from domain/src/main/java/com/squirtles/domain/usecase/user/FetchUserByIdUseCase.kt rename to domain/src/main/java/com/squirtles/domain/user/usecase/FetchUserByIdUseCase.kt index 333197a6..f5295d0b 100644 --- a/domain/src/main/java/com/squirtles/domain/usecase/user/FetchUserByIdUseCase.kt +++ b/domain/src/main/java/com/squirtles/domain/user/usecase/FetchUserByIdUseCase.kt @@ -1,6 +1,6 @@ -package com.squirtles.domain.usecase.user +package com.squirtles.domain.user.usecase -import com.squirtles.domain.repository.remote.firebase.FirebaseUserRepository +import com.squirtles.domain.user.FirebaseUserRepository import javax.inject.Inject class FetchUserByIdUseCase @Inject constructor( diff --git a/domain/src/main/java/com/squirtles/domain/usecase/user/FetchUserUseCase.kt b/domain/src/main/java/com/squirtles/domain/user/usecase/FetchUserUseCase.kt similarity index 86% rename from domain/src/main/java/com/squirtles/domain/usecase/user/FetchUserUseCase.kt rename to domain/src/main/java/com/squirtles/domain/user/usecase/FetchUserUseCase.kt index c703199c..a586f560 100644 --- a/domain/src/main/java/com/squirtles/domain/usecase/user/FetchUserUseCase.kt +++ b/domain/src/main/java/com/squirtles/domain/user/usecase/FetchUserUseCase.kt @@ -1,7 +1,7 @@ -package com.squirtles.domain.usecase.user +package com.squirtles.domain.user.usecase import com.squirtles.domain.model.User -import com.squirtles.domain.repository.local.LocalUserRepository +import com.squirtles.domain.user.LocalUserRepository import javax.inject.Inject class FetchUserUseCase @Inject constructor( diff --git a/domain/src/main/java/com/squirtles/domain/usecase/user/GetCurrentUserUseCase.kt b/domain/src/main/java/com/squirtles/domain/user/usecase/GetCurrentUserUseCase.kt similarity index 54% rename from domain/src/main/java/com/squirtles/domain/usecase/user/GetCurrentUserUseCase.kt rename to domain/src/main/java/com/squirtles/domain/user/usecase/GetCurrentUserUseCase.kt index 5394350d..a3d9c432 100644 --- a/domain/src/main/java/com/squirtles/domain/usecase/user/GetCurrentUserUseCase.kt +++ b/domain/src/main/java/com/squirtles/domain/user/usecase/GetCurrentUserUseCase.kt @@ -1,7 +1,6 @@ -package com.squirtles.domain.usecase.user +package com.squirtles.domain.user.usecase -import com.squirtles.domain.repository.local.LocalRepository -import com.squirtles.domain.repository.local.LocalUserRepository +import com.squirtles.domain.user.LocalUserRepository import javax.inject.Inject class GetCurrentUserUseCase @Inject constructor( diff --git a/domain/src/main/java/com/squirtles/domain/usecase/user/GetUserIdFromDataStoreUseCase.kt b/domain/src/main/java/com/squirtles/domain/user/usecase/GetUserIdFromDataStoreUseCase.kt similarity index 67% rename from domain/src/main/java/com/squirtles/domain/usecase/user/GetUserIdFromDataStoreUseCase.kt rename to domain/src/main/java/com/squirtles/domain/user/usecase/GetUserIdFromDataStoreUseCase.kt index 6948ce15..c882d41a 100644 --- a/domain/src/main/java/com/squirtles/domain/usecase/user/GetUserIdFromDataStoreUseCase.kt +++ b/domain/src/main/java/com/squirtles/domain/user/usecase/GetUserIdFromDataStoreUseCase.kt @@ -1,6 +1,6 @@ -package com.squirtles.domain.usecase.user +package com.squirtles.domain.user.usecase -import com.squirtles.domain.repository.local.LocalUserRepository +import com.squirtles.domain.user.LocalUserRepository import javax.inject.Inject class GetUserIdFromDataStoreUseCase @Inject constructor( diff --git a/domain/src/main/java/com/squirtles/domain/usecase/user/UpdateUserNameUseCase.kt b/domain/src/main/java/com/squirtles/domain/user/usecase/UpdateUserNameUseCase.kt similarity index 70% rename from domain/src/main/java/com/squirtles/domain/usecase/user/UpdateUserNameUseCase.kt rename to domain/src/main/java/com/squirtles/domain/user/usecase/UpdateUserNameUseCase.kt index fb984671..b79f4b99 100644 --- a/domain/src/main/java/com/squirtles/domain/usecase/user/UpdateUserNameUseCase.kt +++ b/domain/src/main/java/com/squirtles/domain/user/usecase/UpdateUserNameUseCase.kt @@ -1,6 +1,6 @@ -package com.squirtles.domain.usecase.user +package com.squirtles.domain.user.usecase -import com.squirtles.domain.repository.remote.firebase.FirebaseUserRepository +import com.squirtles.domain.user.FirebaseUserRepository import javax.inject.Inject class UpdateUserNameUseCase @Inject constructor( From a330dc20d2f3c72bccfe639d89ff8921dc69d452 Mon Sep 17 00:00:00 2001 From: miller198 Date: Fri, 14 Feb 2025 21:02:10 +0900 Subject: [PATCH 10/62] =?UTF-8?q?[feature]=20core.firebase=20=EB=AA=A8?= =?UTF-8?q?=EB=93=88=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- core/firebase/build.gradle.kts | 65 +++++++++++++++++++ .../firebase/FirebaseDataSourceConstants.kt | 12 ++++ .../com/example/firebase/FirebaseException.kt | 13 ++++ .../com/example/firebase/FirebaseModule.kt | 19 ++++++ .../example/firebase/FirebaseRepository.kt | 20 ++++++ settings.gradle.kts | 1 + 6 files changed, 130 insertions(+) create mode 100644 core/firebase/build.gradle.kts create mode 100644 core/firebase/src/main/java/com/example/firebase/FirebaseDataSourceConstants.kt create mode 100644 core/firebase/src/main/java/com/example/firebase/FirebaseException.kt create mode 100644 core/firebase/src/main/java/com/example/firebase/FirebaseModule.kt create mode 100644 core/firebase/src/main/java/com/example/firebase/FirebaseRepository.kt diff --git a/core/firebase/build.gradle.kts b/core/firebase/build.gradle.kts new file mode 100644 index 00000000..09c62a38 --- /dev/null +++ b/core/firebase/build.gradle.kts @@ -0,0 +1,65 @@ +import java.io.FileInputStream +import java.util.Properties + +var properties = Properties() +properties.load(FileInputStream("local.properties")) + +plugins { + alias(libs.plugins.android.library) + alias(libs.plugins.jetbrains.kotlin.android) + alias(libs.plugins.ksp) + alias(libs.plugins.hilt) +} + +android { + namespace = "com.example.firebase" + compileSdk = 34 + + defaultConfig { + minSdk = 26 + } + + compileOptions { + sourceCompatibility = JavaVersion.VERSION_1_8 + targetCompatibility = JavaVersion.VERSION_1_8 + } + kotlinOptions { + jvmTarget = "1.8" + } + buildFeatures { + buildConfig = true + } + buildTypes { + debug { + isMinifyEnabled = false + + buildConfigField( + "String", + "FIRESTORE_DB_ID", + "\"${properties.getProperty("FIRESTORE_DB_ID_DEBUG")}\"" + ) + } + + release { + isMinifyEnabled = false + + buildConfigField( + "String", + "FIRESTORE_DB_ID", + "\"${properties.getProperty("FIRESTORE_DB_ID_RELEASE")}\"" + ) + } + } +} + +dependencies { + // Firebase + implementation(libs.firebase.firestore.ktx) + implementation(libs.firebase.functions.ktx) + implementation(libs.geofire.android.common) + implementation(libs.kotlinx.coroutines.play.services) + + // hilt + implementation(libs.hilt.android) + ksp(libs.hilt.android.compiler) +} diff --git a/core/firebase/src/main/java/com/example/firebase/FirebaseDataSourceConstants.kt b/core/firebase/src/main/java/com/example/firebase/FirebaseDataSourceConstants.kt new file mode 100644 index 00000000..1ee8af66 --- /dev/null +++ b/core/firebase/src/main/java/com/example/firebase/FirebaseDataSourceConstants.kt @@ -0,0 +1,12 @@ +package com.example.firebase + +object FirebaseDataSourceConstants { + const val TAG_LOG = "FirebaseDataSourceImpl" + const val COLLECTION_FAVORITES = "favorites" + const val COLLECTION_PICKS = "picks" + const val COLLECTION_USERS = "users" + const val FIELD_PICK_ID = "pickId" + const val FIELD_USER_ID = "userId" + const val FIELD_ADDED_AT = "addedAt" + const val FIELD_MY_PICKS = "myPicks" +} diff --git a/core/firebase/src/main/java/com/example/firebase/FirebaseException.kt b/core/firebase/src/main/java/com/example/firebase/FirebaseException.kt new file mode 100644 index 00000000..bb5781c5 --- /dev/null +++ b/core/firebase/src/main/java/com/example/firebase/FirebaseException.kt @@ -0,0 +1,13 @@ +package com.example.firebase + +sealed class FirebaseException(override val message: String) : Exception() { + data class CreatedUserFailedException(override val message: String = "Failed to create a user") : + FirebaseException(message) + + data class UserNotFoundException(override val message: String = "Failed to fetch a user") : + FirebaseException(message) + + data class NoSuchPickException(override val message: String = "No such pick") : FirebaseException(message) + data class NoSuchPickInRadiusException(override val message: String = "No such pick in area") : + FirebaseException(message) +} diff --git a/core/firebase/src/main/java/com/example/firebase/FirebaseModule.kt b/core/firebase/src/main/java/com/example/firebase/FirebaseModule.kt new file mode 100644 index 00000000..28f4bc65 --- /dev/null +++ b/core/firebase/src/main/java/com/example/firebase/FirebaseModule.kt @@ -0,0 +1,19 @@ +package com.example.firebase + +import com.google.firebase.firestore.FirebaseFirestore +import dagger.Module +import dagger.Provides +import dagger.hilt.InstallIn +import dagger.hilt.components.SingletonComponent +import javax.inject.Singleton + +@Module +@InstallIn(SingletonComponent::class) // SingletonComponent에 등록 +object FirebaseModule { + + @Provides + @Singleton + fun provideFirebaseFirestore(): FirebaseFirestore { + return FirebaseFirestore.getInstance(BuildConfig.FIRESTORE_DB_ID) + } +} diff --git a/core/firebase/src/main/java/com/example/firebase/FirebaseRepository.kt b/core/firebase/src/main/java/com/example/firebase/FirebaseRepository.kt new file mode 100644 index 00000000..e4fa659d --- /dev/null +++ b/core/firebase/src/main/java/com/example/firebase/FirebaseRepository.kt @@ -0,0 +1,20 @@ +package com.example.firebase + +interface FirebaseRepository { + suspend fun handleResult( + firebaseRepositoryException: FirebaseException, + call: suspend () -> T? + ): Result { + return runCatching { + call() ?: throw firebaseRepositoryException + } + } + + suspend fun handleResult( + call: suspend () -> T + ): Result { + return runCatching { + call() + } + } +} diff --git a/settings.gradle.kts b/settings.gradle.kts index 9ccb47a5..2aec6aad 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -32,3 +32,4 @@ include(":core:util") include(":core:common") include(":core:picklist") include(":core:mediaservice") +include(":core:firebase") From 55a3f5ad243ae306b96df1f4a51c43204ca51288 Mon Sep 17 00:00:00 2001 From: miller198 Date: Fri, 14 Feb 2025 21:07:42 +0900 Subject: [PATCH 11/62] =?UTF-8?q?[chore]=20=ED=95=84=EC=9A=94=EC=97=86?= =?UTF-8?q?=EB=8A=94=20=ED=8C=8C=EC=9D=BC=20=EC=82=AD=EC=A0=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- core/common/consumer-rules.pro | 0 core/common/proguard-rules.pro | 21 ---------------- .../example/common/ExampleInstrumentedTest.kt | 24 ------------------- core/common/src/main/AndroidManifest.xml | 4 ---- .../com/example/common/ExampleUnitTest.kt | 17 ------------- 5 files changed, 66 deletions(-) delete mode 100644 core/common/consumer-rules.pro delete mode 100644 core/common/proguard-rules.pro delete mode 100644 core/common/src/androidTest/java/com/example/common/ExampleInstrumentedTest.kt delete mode 100644 core/common/src/main/AndroidManifest.xml delete mode 100644 core/common/src/test/java/com/example/common/ExampleUnitTest.kt diff --git a/core/common/consumer-rules.pro b/core/common/consumer-rules.pro deleted file mode 100644 index e69de29b..00000000 diff --git a/core/common/proguard-rules.pro b/core/common/proguard-rules.pro deleted file mode 100644 index 481bb434..00000000 --- a/core/common/proguard-rules.pro +++ /dev/null @@ -1,21 +0,0 @@ -# Add project specific ProGuard rules here. -# You can control the set of applied configuration files using the -# proguardFiles setting in build.gradle. -# -# For more details, see -# http://developer.android.com/guide/developing/tools/proguard.html - -# If your project uses WebView with JS, uncomment the following -# and specify the fully qualified class name to the JavaScript interface -# class: -#-keepclassmembers class fqcn.of.javascript.interface.for.webview { -# public *; -#} - -# Uncomment this to preserve the line number information for -# debugging stack traces. -#-keepattributes SourceFile,LineNumberTable - -# If you keep the line number information, uncomment this to -# hide the original source file name. -#-renamesourcefileattribute SourceFile \ No newline at end of file diff --git a/core/common/src/androidTest/java/com/example/common/ExampleInstrumentedTest.kt b/core/common/src/androidTest/java/com/example/common/ExampleInstrumentedTest.kt deleted file mode 100644 index 800c7dcc..00000000 --- a/core/common/src/androidTest/java/com/example/common/ExampleInstrumentedTest.kt +++ /dev/null @@ -1,24 +0,0 @@ -package com.example.common - -import androidx.test.platform.app.InstrumentationRegistry -import androidx.test.ext.junit.runners.AndroidJUnit4 - -import org.junit.Test -import org.junit.runner.RunWith - -import org.junit.Assert.* - -/** - * Instrumented test, which will execute on an Android device. - * - * See [testing documentation](http://d.android.com/tools/testing). - */ -@RunWith(AndroidJUnit4::class) -class ExampleInstrumentedTest { - @Test - fun useAppContext() { - // Context of the app under test. - val appContext = InstrumentationRegistry.getInstrumentation().targetContext - assertEquals("com.example.common.test", appContext.packageName) - } -} diff --git a/core/common/src/main/AndroidManifest.xml b/core/common/src/main/AndroidManifest.xml deleted file mode 100644 index 8bdb7e14..00000000 --- a/core/common/src/main/AndroidManifest.xml +++ /dev/null @@ -1,4 +0,0 @@ - - - - diff --git a/core/common/src/test/java/com/example/common/ExampleUnitTest.kt b/core/common/src/test/java/com/example/common/ExampleUnitTest.kt deleted file mode 100644 index a55853e0..00000000 --- a/core/common/src/test/java/com/example/common/ExampleUnitTest.kt +++ /dev/null @@ -1,17 +0,0 @@ -package com.example.common - -import org.junit.Test - -import org.junit.Assert.* - -/** - * Example local unit test, which will execute on the development machine (host). - * - * See [testing documentation](http://d.android.com/tools/testing). - */ -class ExampleUnitTest { - @Test - fun addition_isCorrect() { - assertEquals(4, 2 + 2) - } -} From f6045e7338318e72dadd84ba8e80e6948f8f90f7 Mon Sep 17 00:00:00 2001 From: miller198 Date: Tue, 18 Feb 2025 18:30:28 +0900 Subject: [PATCH 12/62] =?UTF-8?q?[chore]=20data,=20domain=20=EB=AA=A8?= =?UTF-8?q?=EB=93=88=20=EB=B6=84=EB=A6=AC=20=EC=A4=91?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../favorite/FavoriteListViewModel.kt | 2 +- .../example/firebase/FirebaseRepository.kt | 20 --- data/applemusic/build.gradle.kts | 68 +++++++++ data/applemusic/consumer-rules.pro | 0 data/applemusic/proguard-rules.pro | 21 +++ .../applemusic/AppleMusicDataSourceImpl.kt | 61 ++++++++ .../applemusic/AppleMusicRepositoryImpl.kt | 34 +++++ .../applemusic/SearchSongsPagingSource.kt | 61 ++++++++ .../example/applemusic/api/AppleMusicApi.kt | 18 +++ .../example/applemusic/api/NetworkModule.kt | 50 +++++++ .../com/example/applemusic/di/AppleMusicDi.kt | 46 ++++++ .../applemusic/model/AppleMusicMapper.kt | 36 +++++ .../com/example/applemusic/model/Artwork.kt | 11 ++ .../example/applemusic/model/Attributes.kt | 16 ++ .../java/com/example/applemusic/model/Data.kt | 9 ++ .../applemusic/model/MusicVideoResponse.kt | 9 ++ .../com/example/applemusic/model/Preview.kt | 10 ++ .../com/example/applemusic/model/Results.kt | 10 ++ .../applemusic/model/SearchResponse.kt | 8 + .../com/example/applemusic/model/Songs.kt | 9 ++ data/favorite/build.gradle.kts | 80 ++++++++++ data/favorite/consumer-rules.pro | 0 data/favorite/proguard-rules.pro | 21 +++ data/favorite/src/main/AndroidManifest.xml | 4 + .../example/favorite/CloudFunctionHelper.kt | 31 ++++ .../FirebaseFavoriteDataSourceImpl.kt | 120 +++++++++++++++ .../FirebaseFavoriteRepositoryImpl.kt | 29 ++++ .../example/favorite/di/FirebaseFavoriteDi.kt | 32 ++++ .../favorite/model/FirebaseFavorite.kt | 10 ++ {core => data}/firebase/build.gradle.kts | 39 +++-- data/firebase/consumer-rules.pro | 0 data/firebase/proguard-rules.pro | 21 +++ .../firebase/FirebaseDataSourceConstants.kt | 0 .../com/example/firebase/FirebaseException.kt | 0 .../com/example/firebase/FirebaseModule.kt | 0 .../firebase/FirebaseRepositoryUtils.kt | 19 +++ data/location/build.gradle.kts | 40 +++++ data/location/consumer-rules.pro | 0 data/location/proguard-rules.pro | 21 +++ .../data/applemusic/di/AppleMusicDi.kt | 1 - .../FirebaseFavoriteDataSourceImpl.kt | 49 ------ .../FirebaseFavoriteRepositoryImpl.kt | 6 - .../location/LocalLocationRepositoryImpl.kt | 4 +- .../data/pick/FirebasePickDataSourceImpl.kt | 51 +++++++ .../data/pick/FirebasePickRepositoryImpl.kt | 10 +- .../data/user/FirebaseUserRepositoryImpl.kt | 4 +- domain/applemusic/build.gradle.kts | 26 ++++ .../example/applemusic/AppleMusicException.kt | 12 ++ .../applemusic/AppleMusicRemoteDataSource.kt | 11 ++ .../applemusic/AppleMusicRepository.kt | 12 ++ .../usecase/FetchMusicVideoUseCase.kt | 21 +++ .../applemusic/usecase/FetchSongsUseCase.kt | 10 ++ domain/favorite/build.gradle.kts | 15 ++ .../favorite/FirebaseFavoriteDataSource.kt | 9 ++ .../favorite/FirebaseFavoriteRepository.kt | 7 + .../favorite/usecase/CreateFavoriteUseCase.kt | 11 ++ .../favorite/usecase/DeleteFavoriteUseCase.kt | 12 ++ .../usecase/FetchIsFavoriteUseCase.kt | 11 ++ domain/location/build.gradle.kts | 25 ++++ .../location/LocalLocationRepository.kt | 10 ++ .../usecase/GetLastLocationUseCase.kt | 10 ++ .../usecase/SaveLastLocationUseCase.kt | 11 ++ domain/order/build.gradle.kts | 14 ++ .../order/LocalPickListOrderRepository.kt | 11 ++ .../usecase/GetFavoriteListOrderUseCase.kt | 11 ++ .../usecase/GetMyPickListOrderUseCase.kt | 11 ++ .../usecase/SaveFavoriteListOrderUseCase.kt | 12 ++ .../usecase/SaveMyPickListOrderUseCase.kt | 12 ++ domain/pick/build.gradle.kts | 15 ++ .../example/pick/FirebasePickDataSource.kt | 12 ++ .../example/pick/FirebasePickRepository.kt | 12 ++ .../example/pick/usecase/CreatePickUseCase.kt | 11 ++ .../example/pick/usecase/DeletePickUseCase.kt | 12 ++ .../pick/usecase/FetchMyPicksUseCase.kt | 12 ++ .../example/pick/usecase/FetchPickUseCase.kt | 14 ++ domain/picklist/build.gradle.kts | 13 ++ .../picklist/FetchPickListUseCaseInterface.kt | 7 + .../GetPickListOrderUseCaseInterface.kt | 7 + .../picklist/RemovePickUseCaseInterface.kt | 5 + .../SavePickListOrderUseCaseInterface.kt | 7 + domain/player/build.gradle.kts | 29 ++++ .../player/MediaPlayerListenerUseCase.kt | 141 ++++++++++++++++++ .../com/example/player/MediaPlayerUseCase.kt | 133 +++++++++++++++++ .../favorite/FirebaseFavoriteDataSource.kt | 1 - .../favorite/FirebaseFavoriteRepository.kt | 3 +- .../usecase/FetchFavoritePicksUseCase.kt | 11 -- .../location/LocalLocationRepository.kt | 2 +- .../com/squirtles/domain/model/GeoLocation.kt | 11 ++ .../domain/pick/FirebasePickDataSource.kt | 1 + .../domain/pick/FirebasePickRepository.kt | 1 + .../pick/usecase/FetchFavoritePicksUseCase.kt | 11 ++ domain/user/build.gradle.kts | 15 ++ .../example/user/FirebaseUserDataSource.kt | 9 ++ .../example/user/FirebaseUserRepository.kt | 10 ++ .../com/example/user/LocalUserDataSource.kt | 13 ++ .../com/example/user/LocalUserRepository.kt | 13 ++ .../example/user/usecase/ClearUserUseCase.kt | 10 ++ .../user/usecase/CreateGoogleIdUserUseCase.kt | 25 ++++ .../user/usecase/FetchUserByIdUseCase.kt | 11 ++ .../example/user/usecase/FetchUserUseCase.kt | 19 +++ .../user/usecase/GetCurrentUserUseCase.kt | 10 ++ .../usecase/GetUserIdFromDataStoreUseCase.kt | 10 ++ .../user/usecase/UpdateUserNameUseCase.kt | 11 ++ gradle/libs.versions.toml | 2 + settings.gradle.kts | 13 +- 105 files changed, 1902 insertions(+), 115 deletions(-) delete mode 100644 core/firebase/src/main/java/com/example/firebase/FirebaseRepository.kt create mode 100644 data/applemusic/build.gradle.kts create mode 100644 data/applemusic/consumer-rules.pro create mode 100644 data/applemusic/proguard-rules.pro create mode 100644 data/applemusic/src/main/java/com/example/applemusic/AppleMusicDataSourceImpl.kt create mode 100644 data/applemusic/src/main/java/com/example/applemusic/AppleMusicRepositoryImpl.kt create mode 100644 data/applemusic/src/main/java/com/example/applemusic/SearchSongsPagingSource.kt create mode 100644 data/applemusic/src/main/java/com/example/applemusic/api/AppleMusicApi.kt create mode 100644 data/applemusic/src/main/java/com/example/applemusic/api/NetworkModule.kt create mode 100644 data/applemusic/src/main/java/com/example/applemusic/di/AppleMusicDi.kt create mode 100644 data/applemusic/src/main/java/com/example/applemusic/model/AppleMusicMapper.kt create mode 100644 data/applemusic/src/main/java/com/example/applemusic/model/Artwork.kt create mode 100644 data/applemusic/src/main/java/com/example/applemusic/model/Attributes.kt create mode 100644 data/applemusic/src/main/java/com/example/applemusic/model/Data.kt create mode 100644 data/applemusic/src/main/java/com/example/applemusic/model/MusicVideoResponse.kt create mode 100644 data/applemusic/src/main/java/com/example/applemusic/model/Preview.kt create mode 100644 data/applemusic/src/main/java/com/example/applemusic/model/Results.kt create mode 100644 data/applemusic/src/main/java/com/example/applemusic/model/SearchResponse.kt create mode 100644 data/applemusic/src/main/java/com/example/applemusic/model/Songs.kt create mode 100644 data/favorite/build.gradle.kts create mode 100644 data/favorite/consumer-rules.pro create mode 100644 data/favorite/proguard-rules.pro create mode 100644 data/favorite/src/main/AndroidManifest.xml create mode 100644 data/favorite/src/main/java/com/example/favorite/CloudFunctionHelper.kt create mode 100644 data/favorite/src/main/java/com/example/favorite/FirebaseFavoriteDataSourceImpl.kt create mode 100644 data/favorite/src/main/java/com/example/favorite/FirebaseFavoriteRepositoryImpl.kt create mode 100644 data/favorite/src/main/java/com/example/favorite/di/FirebaseFavoriteDi.kt create mode 100644 data/favorite/src/main/java/com/example/favorite/model/FirebaseFavorite.kt rename {core => data}/firebase/build.gradle.kts (78%) create mode 100644 data/firebase/consumer-rules.pro create mode 100644 data/firebase/proguard-rules.pro rename {core => data}/firebase/src/main/java/com/example/firebase/FirebaseDataSourceConstants.kt (100%) rename {core => data}/firebase/src/main/java/com/example/firebase/FirebaseException.kt (100%) rename {core => data}/firebase/src/main/java/com/example/firebase/FirebaseModule.kt (100%) create mode 100644 data/firebase/src/main/java/com/example/firebase/FirebaseRepositoryUtils.kt create mode 100644 data/location/build.gradle.kts create mode 100644 data/location/consumer-rules.pro create mode 100644 data/location/proguard-rules.pro create mode 100644 domain/applemusic/build.gradle.kts create mode 100644 domain/applemusic/src/main/java/com/example/applemusic/AppleMusicException.kt create mode 100644 domain/applemusic/src/main/java/com/example/applemusic/AppleMusicRemoteDataSource.kt create mode 100644 domain/applemusic/src/main/java/com/example/applemusic/AppleMusicRepository.kt create mode 100644 domain/applemusic/src/main/java/com/example/applemusic/usecase/FetchMusicVideoUseCase.kt create mode 100644 domain/applemusic/src/main/java/com/example/applemusic/usecase/FetchSongsUseCase.kt create mode 100644 domain/favorite/build.gradle.kts create mode 100644 domain/favorite/src/main/java/com/example/favorite/FirebaseFavoriteDataSource.kt create mode 100644 domain/favorite/src/main/java/com/example/favorite/FirebaseFavoriteRepository.kt create mode 100644 domain/favorite/src/main/java/com/example/favorite/usecase/CreateFavoriteUseCase.kt create mode 100644 domain/favorite/src/main/java/com/example/favorite/usecase/DeleteFavoriteUseCase.kt create mode 100644 domain/favorite/src/main/java/com/example/favorite/usecase/FetchIsFavoriteUseCase.kt create mode 100644 domain/location/build.gradle.kts create mode 100644 domain/location/src/main/java/com/example/location/LocalLocationRepository.kt create mode 100644 domain/location/src/main/java/com/example/location/usecase/GetLastLocationUseCase.kt create mode 100644 domain/location/src/main/java/com/example/location/usecase/SaveLastLocationUseCase.kt create mode 100644 domain/order/build.gradle.kts create mode 100644 domain/order/src/main/java/com/example/order/LocalPickListOrderRepository.kt create mode 100644 domain/order/src/main/java/com/example/order/usecase/GetFavoriteListOrderUseCase.kt create mode 100644 domain/order/src/main/java/com/example/order/usecase/GetMyPickListOrderUseCase.kt create mode 100644 domain/order/src/main/java/com/example/order/usecase/SaveFavoriteListOrderUseCase.kt create mode 100644 domain/order/src/main/java/com/example/order/usecase/SaveMyPickListOrderUseCase.kt create mode 100644 domain/pick/build.gradle.kts create mode 100644 domain/pick/src/main/java/com/example/pick/FirebasePickDataSource.kt create mode 100644 domain/pick/src/main/java/com/example/pick/FirebasePickRepository.kt create mode 100644 domain/pick/src/main/java/com/example/pick/usecase/CreatePickUseCase.kt create mode 100644 domain/pick/src/main/java/com/example/pick/usecase/DeletePickUseCase.kt create mode 100644 domain/pick/src/main/java/com/example/pick/usecase/FetchMyPicksUseCase.kt create mode 100644 domain/pick/src/main/java/com/example/pick/usecase/FetchPickUseCase.kt create mode 100644 domain/picklist/build.gradle.kts create mode 100644 domain/picklist/src/main/java/com/example/picklist/FetchPickListUseCaseInterface.kt create mode 100644 domain/picklist/src/main/java/com/example/picklist/GetPickListOrderUseCaseInterface.kt create mode 100644 domain/picklist/src/main/java/com/example/picklist/RemovePickUseCaseInterface.kt create mode 100644 domain/picklist/src/main/java/com/example/picklist/SavePickListOrderUseCaseInterface.kt create mode 100644 domain/player/build.gradle.kts create mode 100644 domain/player/src/main/java/com/example/player/MediaPlayerListenerUseCase.kt create mode 100644 domain/player/src/main/java/com/example/player/MediaPlayerUseCase.kt delete mode 100644 domain/src/main/java/com/squirtles/domain/favorite/usecase/FetchFavoritePicksUseCase.kt create mode 100644 domain/src/main/java/com/squirtles/domain/model/GeoLocation.kt create mode 100644 domain/src/main/java/com/squirtles/domain/pick/usecase/FetchFavoritePicksUseCase.kt create mode 100644 domain/user/build.gradle.kts create mode 100644 domain/user/src/main/java/com/example/user/FirebaseUserDataSource.kt create mode 100644 domain/user/src/main/java/com/example/user/FirebaseUserRepository.kt create mode 100644 domain/user/src/main/java/com/example/user/LocalUserDataSource.kt create mode 100644 domain/user/src/main/java/com/example/user/LocalUserRepository.kt create mode 100644 domain/user/src/main/java/com/example/user/usecase/ClearUserUseCase.kt create mode 100644 domain/user/src/main/java/com/example/user/usecase/CreateGoogleIdUserUseCase.kt create mode 100644 domain/user/src/main/java/com/example/user/usecase/FetchUserByIdUseCase.kt create mode 100644 domain/user/src/main/java/com/example/user/usecase/FetchUserUseCase.kt create mode 100644 domain/user/src/main/java/com/example/user/usecase/GetCurrentUserUseCase.kt create mode 100644 domain/user/src/main/java/com/example/user/usecase/GetUserIdFromDataStoreUseCase.kt create mode 100644 domain/user/src/main/java/com/example/user/usecase/UpdateUserNameUseCase.kt diff --git a/app/src/main/java/com/squirtles/musicroad/favorite/FavoriteListViewModel.kt b/app/src/main/java/com/squirtles/musicroad/favorite/FavoriteListViewModel.kt index a4e2b562..21b679ba 100644 --- a/app/src/main/java/com/squirtles/musicroad/favorite/FavoriteListViewModel.kt +++ b/app/src/main/java/com/squirtles/musicroad/favorite/FavoriteListViewModel.kt @@ -1,7 +1,7 @@ package com.squirtles.musicroad.favorite import com.squirtles.domain.favorite.usecase.DeleteFavoriteUseCase -import com.squirtles.domain.favorite.usecase.FetchFavoritePicksUseCase +import com.squirtles.domain.pick.usecase.FetchFavoritePicksUseCase import com.squirtles.domain.order.usecase.GetFavoriteListOrderUseCase import com.squirtles.domain.order.usecase.SaveFavoriteListOrderUseCase import com.squirtles.domain.user.usecase.GetCurrentUserUseCase diff --git a/core/firebase/src/main/java/com/example/firebase/FirebaseRepository.kt b/core/firebase/src/main/java/com/example/firebase/FirebaseRepository.kt deleted file mode 100644 index e4fa659d..00000000 --- a/core/firebase/src/main/java/com/example/firebase/FirebaseRepository.kt +++ /dev/null @@ -1,20 +0,0 @@ -package com.example.firebase - -interface FirebaseRepository { - suspend fun handleResult( - firebaseRepositoryException: FirebaseException, - call: suspend () -> T? - ): Result { - return runCatching { - call() ?: throw firebaseRepositoryException - } - } - - suspend fun handleResult( - call: suspend () -> T - ): Result { - return runCatching { - call() - } - } -} diff --git a/data/applemusic/build.gradle.kts b/data/applemusic/build.gradle.kts new file mode 100644 index 00000000..6cfc0e97 --- /dev/null +++ b/data/applemusic/build.gradle.kts @@ -0,0 +1,68 @@ +import java.io.FileInputStream +import java.util.Properties + +var properties = Properties() +properties.load(FileInputStream("local.properties")) + +plugins { + alias(libs.plugins.android.library) + alias(libs.plugins.jetbrains.kotlin.android) + alias(libs.plugins.ksp) + alias(libs.plugins.hilt) + alias(libs.plugins.kotlin.serialization) +} + +android { + namespace = "com.example.applemusic" + compileSdk = 34 + + defaultConfig { + minSdk = 26 + + testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner" + consumerProguardFiles("consumer-rules.pro") + + buildConfigField( + "String", + "APPLE_MUSIC_API_TOKEN", + "\"${properties.getProperty("APPLE_MUSIC_API_TOKEN")}\"" + ) + } + + buildTypes { + release { + isMinifyEnabled = false + proguardFiles(getDefaultProguardFile("proguard-android-optimize.txt"), "proguard-rules.pro") + } + } + compileOptions { + sourceCompatibility = JavaVersion.VERSION_1_8 + targetCompatibility = JavaVersion.VERSION_1_8 + } + kotlinOptions { + jvmTarget = "1.8" + } + buildFeatures { + buildConfig = true + } +} + +dependencies { + implementation(projects.domain.applemusic) + implementation(projects.core.model) + implementation(libs.androidx.paging.runtime) + // Kotlinx Serialization + implementation(libs.kotlinx.serialization.json) + + // hilt + implementation(libs.hilt.android) + ksp(libs.hilt.android.compiler) + implementation(libs.inject) + + // OkHttp + implementation(libs.okhttp.logging) + + // Retrofit + implementation(libs.retrofit.core) + implementation(libs.retrofit.kotlin.serialization) +} diff --git a/data/applemusic/consumer-rules.pro b/data/applemusic/consumer-rules.pro new file mode 100644 index 00000000..e69de29b diff --git a/data/applemusic/proguard-rules.pro b/data/applemusic/proguard-rules.pro new file mode 100644 index 00000000..481bb434 --- /dev/null +++ b/data/applemusic/proguard-rules.pro @@ -0,0 +1,21 @@ +# Add project specific ProGuard rules here. +# You can control the set of applied configuration files using the +# proguardFiles setting in build.gradle. +# +# For more details, see +# http://developer.android.com/guide/developing/tools/proguard.html + +# If your project uses WebView with JS, uncomment the following +# and specify the fully qualified class name to the JavaScript interface +# class: +#-keepclassmembers class fqcn.of.javascript.interface.for.webview { +# public *; +#} + +# Uncomment this to preserve the line number information for +# debugging stack traces. +#-keepattributes SourceFile,LineNumberTable + +# If you keep the line number information, uncomment this to +# hide the original source file name. +#-renamesourcefileattribute SourceFile \ No newline at end of file diff --git a/data/applemusic/src/main/java/com/example/applemusic/AppleMusicDataSourceImpl.kt b/data/applemusic/src/main/java/com/example/applemusic/AppleMusicDataSourceImpl.kt new file mode 100644 index 00000000..9926f9a3 --- /dev/null +++ b/data/applemusic/src/main/java/com/example/applemusic/AppleMusicDataSourceImpl.kt @@ -0,0 +1,61 @@ +package com.example.applemusic + +import androidx.paging.Pager +import androidx.paging.PagingConfig +import androidx.paging.PagingData +import com.example.applemusic.SearchSongsPagingSource.Companion.SEARCH_PAGE_SIZE +import com.example.applemusic.api.AppleMusicApi +import com.example.applemusic.model.SearchResponse +import com.example.applemusic.model.toMusicVideo +import com.example.model.MusicVideo +import com.example.model.Song +import kotlinx.coroutines.flow.Flow +import retrofit2.Response +import javax.inject.Inject + +class AppleMusicDataSourceImpl @Inject constructor( + private val appleMusicApi: AppleMusicApi +) : AppleMusicRemoteDataSource { + + override fun searchSongs(searchText: String): Flow> { + return Pager( + config = PagingConfig( + pageSize = SEARCH_PAGE_SIZE, + enablePlaceholders = false + ), + pagingSourceFactory = { SearchSongsPagingSource(appleMusicApi, searchText) } + ).flow + } + + override suspend fun searchMusicVideos(searchText: String): List { + val searchResult = requestSearchApi(searchText, "music-videos") + return searchResult.results.musicVideos?.data?.map { + it.toMusicVideo() + } ?: emptyList() + } + + private suspend fun requestSearchApi(searchText: String, types: String): SearchResponse { + return checkResponse( + appleMusicApi.searchSongs( + storefront = DEFAULT_STOREFRONT, + types = types, + term = searchText, + limit = 10, + offset = "0", + ) + ) + } + + private fun checkResponse(response: Response): T { + if (response.isSuccessful) { + return requireNotNull(response.body()) + } else { + val errorBody = requireNotNull(response.errorBody()?.string()) + throw Exception(errorBody) + } + } + + companion object { + const val DEFAULT_STOREFRONT = "kr" + } +} diff --git a/data/applemusic/src/main/java/com/example/applemusic/AppleMusicRepositoryImpl.kt b/data/applemusic/src/main/java/com/example/applemusic/AppleMusicRepositoryImpl.kt new file mode 100644 index 00000000..92eb18aa --- /dev/null +++ b/data/applemusic/src/main/java/com/example/applemusic/AppleMusicRepositoryImpl.kt @@ -0,0 +1,34 @@ +package com.example.applemusic + +import androidx.paging.PagingData +import com.example.model.MusicVideo +import com.example.model.Song +import kotlinx.coroutines.flow.Flow +import javax.inject.Inject + +class AppleMusicRepositoryImpl @Inject constructor( + private val appleMusicDataSource: AppleMusicRemoteDataSource +) : AppleMusicRepository { + + override fun searchSongs(searchText: String): Flow> = + appleMusicDataSource.searchSongs(searchText) + + override suspend fun searchSongById(songId: String): Result { + TODO("Not yet implemented") + } + + override suspend fun searchMusicVideos(searchText: String): Result> { + return handleResult(AppleMusicException.NotFoundException()) { + appleMusicDataSource.searchMusicVideos(searchText).ifEmpty { null } + } + } + + private suspend fun handleResult( + appleMusicException: AppleMusicException, + call: suspend () -> T? + ): Result { + return runCatching { + call() ?: throw appleMusicException + } + } +} diff --git a/data/applemusic/src/main/java/com/example/applemusic/SearchSongsPagingSource.kt b/data/applemusic/src/main/java/com/example/applemusic/SearchSongsPagingSource.kt new file mode 100644 index 00000000..0d4b80e9 --- /dev/null +++ b/data/applemusic/src/main/java/com/example/applemusic/SearchSongsPagingSource.kt @@ -0,0 +1,61 @@ +package com.example.applemusic + +import androidx.paging.PagingSource +import androidx.paging.PagingState +import com.example.applemusic.api.AppleMusicApi +import com.example.applemusic.model.toSong +import com.example.model.Song +import retrofit2.HttpException +import java.io.IOException + +class SearchSongsPagingSource( + private val appleMusicApi: AppleMusicApi, + private val searchText: String +) : PagingSource() { + + override suspend fun load(params: LoadParams): LoadResult { + val pageIndex = params.key ?: 0 + return try { + val response = appleMusicApi.searchSongs( + storefront = DEFAULT_STOREFRONT, + types = SEARCH_TYPES, + term = searchText, + limit = SEARCH_PAGE_SIZE, + offset = (pageIndex * SEARCH_PAGE_SIZE).toString() + ) + + if (response.isSuccessful) { + val songs = response.body()?.results?.songs?.data + ?.map { it.toSong() } + ?: emptyList() + + val nextKey = if (response.body()?.results?.songs?.next == null) null else pageIndex + 1 + LoadResult.Page( + data = songs, + prevKey = if (pageIndex == 0) null else pageIndex - 1, + nextKey = nextKey + ) + } else { + val errorBody = response.errorBody()?.string() + throw Exception(errorBody ?: "Unknown error occurred") + } + } catch (exception: IOException) { + LoadResult.Error(exception) + } catch (exception: HttpException) { + LoadResult.Error(exception) + } + } + + override fun getRefreshKey(state: PagingState): Int? { + return state.anchorPosition?.let { anchorPosition -> + state.closestPageToPosition(anchorPosition)?.prevKey?.plus(1) + ?: state.closestPageToPosition(anchorPosition)?.nextKey?.minus(1) + } + } + + companion object { + const val DEFAULT_STOREFRONT = "kr" + const val SEARCH_TYPES = "songs" + const val SEARCH_PAGE_SIZE = 10 + } +} diff --git a/data/applemusic/src/main/java/com/example/applemusic/api/AppleMusicApi.kt b/data/applemusic/src/main/java/com/example/applemusic/api/AppleMusicApi.kt new file mode 100644 index 00000000..6330d147 --- /dev/null +++ b/data/applemusic/src/main/java/com/example/applemusic/api/AppleMusicApi.kt @@ -0,0 +1,18 @@ +package com.example.applemusic.api + +import com.example.applemusic.model.SearchResponse +import retrofit2.Response +import retrofit2.http.GET +import retrofit2.http.Path +import retrofit2.http.Query + +interface AppleMusicApi { + @GET("v1/catalog/{storefront}/search") + suspend fun searchSongs( + @Path("storefront") storefront: String, + @Query("types") types: String, + @Query("term") term: String, + @Query("limit") limit: Int, + @Query("offset") offset: String + ): Response +} diff --git a/data/applemusic/src/main/java/com/example/applemusic/api/NetworkModule.kt b/data/applemusic/src/main/java/com/example/applemusic/api/NetworkModule.kt new file mode 100644 index 00000000..99ebda89 --- /dev/null +++ b/data/applemusic/src/main/java/com/example/applemusic/api/NetworkModule.kt @@ -0,0 +1,50 @@ +package com.example.applemusic.api + +import com.example.applemusic.BuildConfig +import dagger.Module +import dagger.Provides +import dagger.hilt.InstallIn +import dagger.hilt.components.SingletonComponent +import kotlinx.serialization.json.Json +import okhttp3.MediaType.Companion.toMediaType +import okhttp3.OkHttpClient +import okhttp3.logging.HttpLoggingInterceptor +import retrofit2.Converter +import retrofit2.converter.kotlinx.serialization.asConverterFactory +import javax.inject.Singleton + +@Module +@InstallIn(SingletonComponent::class) +object NetworkModule { + + @Provides + @Singleton + fun provideAppleOkhttpClient(): OkHttpClient { + val loggingInterceptor = HttpLoggingInterceptor() + loggingInterceptor.setLevel(HttpLoggingInterceptor.Level.BODY) + return OkHttpClient.Builder() + .addInterceptor { chain -> + val newRequest = chain.request().newBuilder() + .addHeader("Authorization", "Bearer ${BuildConfig.APPLE_MUSIC_API_TOKEN}") + .build() + chain.proceed(newRequest) + } + .addInterceptor(loggingInterceptor) + .build() + } + + @Provides + @Singleton + fun provideConverterFactory( + json: Json, + ): Converter.Factory { + return json.asConverterFactory("application/json".toMediaType()) + } + + @Provides + @Singleton + fun provideJson(): Json = Json { + ignoreUnknownKeys = true + coerceInputValues = true + } +} diff --git a/data/applemusic/src/main/java/com/example/applemusic/di/AppleMusicDi.kt b/data/applemusic/src/main/java/com/example/applemusic/di/AppleMusicDi.kt new file mode 100644 index 00000000..ed0ac533 --- /dev/null +++ b/data/applemusic/src/main/java/com/example/applemusic/di/AppleMusicDi.kt @@ -0,0 +1,46 @@ +package com.example.applemusic.di + +import com.example.applemusic.AppleMusicDataSourceImpl +import com.example.applemusic.AppleMusicRemoteDataSource +import com.example.applemusic.AppleMusicRepository +import com.example.applemusic.AppleMusicRepositoryImpl +import com.example.applemusic.api.AppleMusicApi +import dagger.Module +import dagger.Provides +import dagger.hilt.InstallIn +import dagger.hilt.components.SingletonComponent +import okhttp3.OkHttpClient +import retrofit2.Converter +import retrofit2.Retrofit +import javax.inject.Singleton + +@Module +@InstallIn(SingletonComponent::class) +object AppleMusicModule{ + + private const val BASE_APPLE_MUSIC_URL = "https://api.music.apple.com/" + + @Provides + @Singleton + fun provideAppleMusicApi( + appleOkHttpClient: OkHttpClient, + converterFactory: Converter.Factory, + ): AppleMusicApi { + return Retrofit.Builder() + .baseUrl(BASE_APPLE_MUSIC_URL) + .addConverterFactory(converterFactory) + .client(appleOkHttpClient) + .build() + .create(AppleMusicApi::class.java) + } + + @Provides + @Singleton + fun provideAppleMusicRepository(appleMusicDataSource: AppleMusicRemoteDataSource): AppleMusicRepository = + AppleMusicRepositoryImpl(appleMusicDataSource) + + @Provides + @Singleton + fun provideAppleMusicDataSource(api: AppleMusicApi): AppleMusicRemoteDataSource = + AppleMusicDataSourceImpl(api) +} diff --git a/data/applemusic/src/main/java/com/example/applemusic/model/AppleMusicMapper.kt b/data/applemusic/src/main/java/com/example/applemusic/model/AppleMusicMapper.kt new file mode 100644 index 00000000..983ac6e7 --- /dev/null +++ b/data/applemusic/src/main/java/com/example/applemusic/model/AppleMusicMapper.kt @@ -0,0 +1,36 @@ +package com.example.applemusic.model + +import androidx.core.graphics.toColorInt +import com.example.model.MusicVideo +import com.example.model.Song +import java.time.LocalDate +import java.time.format.DateTimeFormatter + +internal fun Data.toSong(): Song = Song( + id = id, + songName = this.attributes.songName, + artistName = this.attributes.artistName, + albumName = this.attributes.albumName.toString(), + imageUrl = this.attributes.artwork.url, + genreNames = this.attributes.genreNames, + bgColor = "#${this.attributes.artwork.bgColor}".toColorInt(), + externalUrl = this.attributes.externalUrl, + previewUrl = this.attributes.previews[0].url.toString(), +) + +internal fun Data.toMusicVideo(): MusicVideo = MusicVideo( + id = id, + songName = this.attributes.songName, + artistName = this.attributes.artistName, + albumName = this.attributes.albumName.toString(), + releaseDate = this.attributes.releaseDate?.toLocalDate() ?: DEFAULT_DATE, + previewUrl = this.attributes.previews[0].url.toString(), + thumbnailUrl = this.attributes.previews[0].artwork?.url.toString() +) + +private fun String.toLocalDate(): LocalDate { + val formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd") + return LocalDate.parse(this, formatter) +} + +private val DEFAULT_DATE = LocalDate.of(2000, 1, 1) diff --git a/data/applemusic/src/main/java/com/example/applemusic/model/Artwork.kt b/data/applemusic/src/main/java/com/example/applemusic/model/Artwork.kt new file mode 100644 index 00000000..04990f93 --- /dev/null +++ b/data/applemusic/src/main/java/com/example/applemusic/model/Artwork.kt @@ -0,0 +1,11 @@ +package com.example.applemusic.model + +import kotlinx.serialization.Serializable + +@Serializable +data class Artwork( + val width: Int, + val height: Int, + val url: String, + val bgColor: String? = null +) diff --git a/data/applemusic/src/main/java/com/example/applemusic/model/Attributes.kt b/data/applemusic/src/main/java/com/example/applemusic/model/Attributes.kt new file mode 100644 index 00000000..95e75010 --- /dev/null +++ b/data/applemusic/src/main/java/com/example/applemusic/model/Attributes.kt @@ -0,0 +1,16 @@ +package com.example.applemusic.model + +import kotlinx.serialization.SerialName +import kotlinx.serialization.Serializable + +@Serializable +data class Attributes( + @SerialName("name") val songName: String, + @SerialName("artistName") val artistName: String, + @SerialName("albumName") val albumName: String? = null, + @SerialName("releaseDate") val releaseDate: String? = null, + @SerialName("genreNames") val genreNames: List, + @SerialName("artwork") val artwork: Artwork, + @SerialName("url") val externalUrl: String, + @SerialName("previews") val previews: List +) diff --git a/data/applemusic/src/main/java/com/example/applemusic/model/Data.kt b/data/applemusic/src/main/java/com/example/applemusic/model/Data.kt new file mode 100644 index 00000000..a92da6c5 --- /dev/null +++ b/data/applemusic/src/main/java/com/example/applemusic/model/Data.kt @@ -0,0 +1,9 @@ +package com.example.applemusic.model + +import kotlinx.serialization.Serializable + +@Serializable +data class Data( + val id: String, + val attributes: Attributes, +) diff --git a/data/applemusic/src/main/java/com/example/applemusic/model/MusicVideoResponse.kt b/data/applemusic/src/main/java/com/example/applemusic/model/MusicVideoResponse.kt new file mode 100644 index 00000000..783f9049 --- /dev/null +++ b/data/applemusic/src/main/java/com/example/applemusic/model/MusicVideoResponse.kt @@ -0,0 +1,9 @@ +package com.example.applemusic.model + +import kotlinx.serialization.Serializable + +@Serializable +data class MusicVideoResponse( + val data: List, +) + diff --git a/data/applemusic/src/main/java/com/example/applemusic/model/Preview.kt b/data/applemusic/src/main/java/com/example/applemusic/model/Preview.kt new file mode 100644 index 00000000..507ea01a --- /dev/null +++ b/data/applemusic/src/main/java/com/example/applemusic/model/Preview.kt @@ -0,0 +1,10 @@ +package com.example.applemusic.model + +import kotlinx.serialization.Serializable + +@Serializable +data class Preview( + val url: String? = null, + val hlsUrl: String? = null, + val artwork: Artwork? = null, +) diff --git a/data/applemusic/src/main/java/com/example/applemusic/model/Results.kt b/data/applemusic/src/main/java/com/example/applemusic/model/Results.kt new file mode 100644 index 00000000..4674e0b7 --- /dev/null +++ b/data/applemusic/src/main/java/com/example/applemusic/model/Results.kt @@ -0,0 +1,10 @@ +package com.example.applemusic.model + +import kotlinx.serialization.SerialName +import kotlinx.serialization.Serializable + +@Serializable +data class Results( + @SerialName("songs") val songs: Songs? = null, + @SerialName("music-videos") val musicVideos: MusicVideoResponse? = null +) diff --git a/data/applemusic/src/main/java/com/example/applemusic/model/SearchResponse.kt b/data/applemusic/src/main/java/com/example/applemusic/model/SearchResponse.kt new file mode 100644 index 00000000..10c99668 --- /dev/null +++ b/data/applemusic/src/main/java/com/example/applemusic/model/SearchResponse.kt @@ -0,0 +1,8 @@ +package com.example.applemusic.model + +import kotlinx.serialization.Serializable + +@Serializable +data class SearchResponse( + val results: Results, +) diff --git a/data/applemusic/src/main/java/com/example/applemusic/model/Songs.kt b/data/applemusic/src/main/java/com/example/applemusic/model/Songs.kt new file mode 100644 index 00000000..14b85769 --- /dev/null +++ b/data/applemusic/src/main/java/com/example/applemusic/model/Songs.kt @@ -0,0 +1,9 @@ +package com.example.applemusic.model + +import kotlinx.serialization.Serializable + +@Serializable +data class Songs( + val next: String? = null, + val data: List, +) diff --git a/data/favorite/build.gradle.kts b/data/favorite/build.gradle.kts new file mode 100644 index 00000000..6db85a60 --- /dev/null +++ b/data/favorite/build.gradle.kts @@ -0,0 +1,80 @@ +import java.io.FileInputStream +import java.util.Properties + +var properties = Properties() +properties.load(FileInputStream("local.properties")) + +plugins { + alias(libs.plugins.android.library) + alias(libs.plugins.jetbrains.kotlin.android) + alias(libs.plugins.ksp) + alias(libs.plugins.hilt) + alias(libs.plugins.kotlin.serialization) +} + +android { + namespace = "com.example.favorite" + compileSdk = 34 + + defaultConfig { + minSdk = 26 + + testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner" + consumerProguardFiles("consumer-rules.pro") + } + + buildTypes { + debug { + isMinifyEnabled = false + + buildConfigField( + "String", + "HTTPS_CALLABLE", + "\"${properties.getProperty("HTTPS_CALLABLE_DEBUG")}\"" + ) + } + + release { + isMinifyEnabled = false + proguardFiles( + getDefaultProguardFile("proguard-android-optimize.txt"), + "proguard-rules.pro" + ) + + buildConfigField( + "String", + "HTTPS_CALLABLE", + "\"${properties.getProperty("HTTPS_CALLABLE_RELEASE")}\"" + ) + } + } + compileOptions { + sourceCompatibility = JavaVersion.VERSION_1_8 + targetCompatibility = JavaVersion.VERSION_1_8 + } + kotlinOptions { + jvmTarget = "1.8" + } + buildFeatures { + buildConfig = true + } +} + +dependencies { + implementation(projects.data.firebase) + implementation(projects.domain.favorite) + // Kotlinx Serialization + implementation(libs.kotlinx.serialization.json) + + // retrofit + implementation(libs.retrofit.core) + + // hilt + implementation(libs.hilt.android) + implementation(libs.firebase.functions.ktx) + ksp(libs.hilt.android.compiler) + implementation(libs.inject) + + // Firebase + implementation(libs.google.firebase.firestore.ktx) +} diff --git a/data/favorite/consumer-rules.pro b/data/favorite/consumer-rules.pro new file mode 100644 index 00000000..e69de29b diff --git a/data/favorite/proguard-rules.pro b/data/favorite/proguard-rules.pro new file mode 100644 index 00000000..481bb434 --- /dev/null +++ b/data/favorite/proguard-rules.pro @@ -0,0 +1,21 @@ +# Add project specific ProGuard rules here. +# You can control the set of applied configuration files using the +# proguardFiles setting in build.gradle. +# +# For more details, see +# http://developer.android.com/guide/developing/tools/proguard.html + +# If your project uses WebView with JS, uncomment the following +# and specify the fully qualified class name to the JavaScript interface +# class: +#-keepclassmembers class fqcn.of.javascript.interface.for.webview { +# public *; +#} + +# Uncomment this to preserve the line number information for +# debugging stack traces. +#-keepattributes SourceFile,LineNumberTable + +# If you keep the line number information, uncomment this to +# hide the original source file name. +#-renamesourcefileattribute SourceFile \ No newline at end of file diff --git a/data/favorite/src/main/AndroidManifest.xml b/data/favorite/src/main/AndroidManifest.xml new file mode 100644 index 00000000..8bdb7e14 --- /dev/null +++ b/data/favorite/src/main/AndroidManifest.xml @@ -0,0 +1,4 @@ + + + + diff --git a/data/favorite/src/main/java/com/example/favorite/CloudFunctionHelper.kt b/data/favorite/src/main/java/com/example/favorite/CloudFunctionHelper.kt new file mode 100644 index 00000000..39848b9d --- /dev/null +++ b/data/favorite/src/main/java/com/example/favorite/CloudFunctionHelper.kt @@ -0,0 +1,31 @@ +package com.example.favorite + +import com.google.firebase.functions.FirebaseFunctions +import com.google.firebase.functions.ktx.functions +import com.google.firebase.ktx.Firebase +import kotlinx.coroutines.tasks.await +import javax.inject.Singleton + +@Singleton +class CloudFunctionHelper { + private val functions: FirebaseFunctions = Firebase.functions + + suspend fun updateFavoriteCount(pickId: String): Result { + return try { + val data = hashMapOf("pickId" to pickId) + val result = functions + .getHttpsCallable(BuildConfig.HTTPS_CALLABLE) + .call(data) + .await() + + // 성공 메시지 반환 + val message = result.getData()?.let { + (it as? Map<*, *>)?.get("message") as? String ?: "Function executed successfully" + } ?: "No message in response" + Result.success(message) + } catch (e: Exception) { + // 에러 처리 + Result.failure(e) + } + } +} diff --git a/data/favorite/src/main/java/com/example/favorite/FirebaseFavoriteDataSourceImpl.kt b/data/favorite/src/main/java/com/example/favorite/FirebaseFavoriteDataSourceImpl.kt new file mode 100644 index 00000000..9dba1cc3 --- /dev/null +++ b/data/favorite/src/main/java/com/example/favorite/FirebaseFavoriteDataSourceImpl.kt @@ -0,0 +1,120 @@ +package com.example.favorite + +import android.util.Log +import com.example.favorite.model.FirebaseFavorite +import com.example.firebase.FirebaseDataSourceConstants.COLLECTION_FAVORITES +import com.example.firebase.FirebaseDataSourceConstants.FIELD_PICK_ID +import com.example.firebase.FirebaseDataSourceConstants.FIELD_USER_ID +import com.google.firebase.firestore.FirebaseFirestore +import com.google.firebase.firestore.QuerySnapshot +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.launch +import kotlinx.coroutines.suspendCancellableCoroutine +import javax.inject.Inject +import javax.inject.Singleton +import kotlin.coroutines.resume +import kotlin.coroutines.resumeWithException + +@Singleton +class FirebaseFavoriteDataSourceImpl @Inject constructor( + private val db: FirebaseFirestore, + private val cloudFunctionHelper: CloudFunctionHelper +) : FirebaseFavoriteDataSource { + + override suspend fun fetchIsFavorite(pickId: String, userId: String): Boolean { + val favoriteDocument = fetchFavoriteByPickIdAndUserId(pickId, userId) + return favoriteDocument.isEmpty.not() + } + + override suspend fun createFavorite(pickId: String, userId: String): Boolean { + return suspendCancellableCoroutine { continuation -> + val firebaseFavorite = FirebaseFavorite( + pickId = pickId, + userId = userId + ) + + db.collection(COLLECTION_FAVORITES) + .add(firebaseFavorite) + .addOnSuccessListener { + // favorites에 문서 생성 후 클라우드 함수가 완료됐을 때 담기 완료 + CoroutineScope(Dispatchers.IO).launch { + try { + updateFavoriteCount(pickId) // 클라우드 함수 호출 + continuation.resume(true) + } catch (e: Exception) { + continuation.resumeWithException(e) + } + } + } + .addOnFailureListener { exception -> + Log.e("FirebaseDataSourceImpl", "Failed to create favorite", exception) + continuation.resumeWithException(exception) + } + } + } + + override suspend fun deleteFavorite(pickId: String, userId: String): Boolean { + val favoriteDocument = fetchFavoriteByPickIdAndUserId(pickId, userId) + return suspendCancellableCoroutine { continuation -> + favoriteDocument.forEach { document -> + db.collection(COLLECTION_FAVORITES).document(document.id) + .delete() + .addOnSuccessListener { + // favorites에 문서 삭제 후 클라우드 함수가 완료됐을 때 담기 해제 완료 + CoroutineScope(Dispatchers.IO).launch { + try { + updateFavoriteCount(pickId) // 클라우드 함수 호출 + continuation.resume(true) + } catch (e: Exception) { + continuation.resumeWithException(e) + } + } + } + .addOnFailureListener { exception -> + Log.w( + "FirebaseDataSourceImpl", + "Error deleting favorite document", + exception + ) + continuation.resumeWithException(exception) + } + } + } + } + + private suspend fun fetchFavoriteByPickIdAndUserId(pickId: String, userId: String): QuerySnapshot { + return suspendCancellableCoroutine { continuation -> + db.collection(COLLECTION_FAVORITES) + .whereEqualTo(FIELD_PICK_ID, pickId) + .whereEqualTo(FIELD_USER_ID, userId) + .get() + .addOnSuccessListener { result -> + continuation.resume(result) + } + .addOnFailureListener { exception -> + Log.w( + "FirebaseDataSourceImpl", + "Error at fetching favorite document", + exception + ) + continuation.resumeWithException(exception) + } + } + } + + private suspend fun updateFavoriteCount(pickId: String) { + try { + val result = cloudFunctionHelper.updateFavoriteCount(pickId) + result.onSuccess { + Log.d("FirebaseDataSourceImpl", "Success to update favorite count") + }.onFailure { exception -> + Log.e("FirebaseDataSourceImpl", "Failed to update favorite count", exception) + throw exception + } + } catch (e: Exception) { + Log.e("FirebaseDataSourceImpl", "Exception occurred while updating favorite count", e) + throw e + } + } +} diff --git a/data/favorite/src/main/java/com/example/favorite/FirebaseFavoriteRepositoryImpl.kt b/data/favorite/src/main/java/com/example/favorite/FirebaseFavoriteRepositoryImpl.kt new file mode 100644 index 00000000..be2582c4 --- /dev/null +++ b/data/favorite/src/main/java/com/example/favorite/FirebaseFavoriteRepositoryImpl.kt @@ -0,0 +1,29 @@ +package com.example.favorite + +import com.example.firebase.handleResult +import javax.inject.Inject +import javax.inject.Singleton + +@Singleton +class FirebaseFavoriteRepositoryImpl @Inject constructor( + private val favoriteDataSource: FirebaseFavoriteDataSource +) : FirebaseFavoriteRepository { + + override suspend fun fetchIsFavorite(pickId: String, userId: String): Result { + return handleResult { + favoriteDataSource.fetchIsFavorite(pickId, userId) + } + } + + override suspend fun createFavorite(pickId: String, userId: String): Result { + return handleResult { + favoriteDataSource.createFavorite(pickId, userId) + } + } + + override suspend fun deleteFavorite(pickId: String, userId: String): Result { + return handleResult { + favoriteDataSource.deleteFavorite(pickId, userId) + } + } +} diff --git a/data/favorite/src/main/java/com/example/favorite/di/FirebaseFavoriteDi.kt b/data/favorite/src/main/java/com/example/favorite/di/FirebaseFavoriteDi.kt new file mode 100644 index 00000000..f588ffbf --- /dev/null +++ b/data/favorite/src/main/java/com/example/favorite/di/FirebaseFavoriteDi.kt @@ -0,0 +1,32 @@ +package com.example.favorite.di + +import com.example.favorite.CloudFunctionHelper +import com.example.favorite.FirebaseFavoriteDataSource +import com.example.favorite.FirebaseFavoriteDataSourceImpl +import com.example.favorite.FirebaseFavoriteRepository +import com.example.favorite.FirebaseFavoriteRepositoryImpl +import com.google.firebase.firestore.FirebaseFirestore +import dagger.Module +import dagger.Provides +import dagger.hilt.InstallIn +import dagger.hilt.components.SingletonComponent +import javax.inject.Singleton + +@Module +@InstallIn(SingletonComponent::class) +object FirebaseFavoriteDi { + + @Provides + @Singleton + fun provideFirebaseFavoriteRepository(firebaseFavoriteDataSource: FirebaseFavoriteDataSource): FirebaseFavoriteRepository = + FirebaseFavoriteRepositoryImpl(firebaseFavoriteDataSource) + + @Provides + @Singleton + fun provideFirebaseFavoriteDataSource(db: FirebaseFirestore, cloudFunctionHelper: CloudFunctionHelper): FirebaseFavoriteDataSource = + FirebaseFavoriteDataSourceImpl(db, cloudFunctionHelper) + + @Provides + @Singleton + fun provideCloudFunctionHelper(): CloudFunctionHelper = CloudFunctionHelper() +} diff --git a/data/favorite/src/main/java/com/example/favorite/model/FirebaseFavorite.kt b/data/favorite/src/main/java/com/example/favorite/model/FirebaseFavorite.kt new file mode 100644 index 00000000..59b06f9e --- /dev/null +++ b/data/favorite/src/main/java/com/example/favorite/model/FirebaseFavorite.kt @@ -0,0 +1,10 @@ +package com.example.favorite.model + +import com.google.firebase.Timestamp +import com.google.firebase.firestore.ServerTimestamp + +data class FirebaseFavorite( + val pickId: String? = null, + val userId: String? = null, + @ServerTimestamp val addedAt: Timestamp? = null, +) diff --git a/core/firebase/build.gradle.kts b/data/firebase/build.gradle.kts similarity index 78% rename from core/firebase/build.gradle.kts rename to data/firebase/build.gradle.kts index 09c62a38..010b3f7b 100644 --- a/core/firebase/build.gradle.kts +++ b/data/firebase/build.gradle.kts @@ -17,18 +17,11 @@ android { defaultConfig { minSdk = 26 - } - compileOptions { - sourceCompatibility = JavaVersion.VERSION_1_8 - targetCompatibility = JavaVersion.VERSION_1_8 - } - kotlinOptions { - jvmTarget = "1.8" - } - buildFeatures { - buildConfig = true + testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner" + consumerProguardFiles("consumer-rules.pro") } + buildTypes { debug { isMinifyEnabled = false @@ -43,6 +36,11 @@ android { release { isMinifyEnabled = false + proguardFiles( + getDefaultProguardFile("proguard-android-optimize.txt"), + "proguard-rules.pro" + ) + buildConfigField( "String", "FIRESTORE_DB_ID", @@ -50,16 +48,25 @@ android { ) } } + compileOptions { + sourceCompatibility = JavaVersion.VERSION_1_8 + targetCompatibility = JavaVersion.VERSION_1_8 + } + kotlinOptions { + jvmTarget = "1.8" + } + buildFeatures { + buildConfig = true + } } dependencies { - // Firebase - implementation(libs.firebase.firestore.ktx) - implementation(libs.firebase.functions.ktx) - implementation(libs.geofire.android.common) - implementation(libs.kotlinx.coroutines.play.services) - // hilt implementation(libs.hilt.android) + implementation(libs.firebase.functions.ktx) ksp(libs.hilt.android.compiler) + implementation(libs.inject) + + // Firebase + implementation(libs.google.firebase.firestore.ktx) } diff --git a/data/firebase/consumer-rules.pro b/data/firebase/consumer-rules.pro new file mode 100644 index 00000000..e69de29b diff --git a/data/firebase/proguard-rules.pro b/data/firebase/proguard-rules.pro new file mode 100644 index 00000000..481bb434 --- /dev/null +++ b/data/firebase/proguard-rules.pro @@ -0,0 +1,21 @@ +# Add project specific ProGuard rules here. +# You can control the set of applied configuration files using the +# proguardFiles setting in build.gradle. +# +# For more details, see +# http://developer.android.com/guide/developing/tools/proguard.html + +# If your project uses WebView with JS, uncomment the following +# and specify the fully qualified class name to the JavaScript interface +# class: +#-keepclassmembers class fqcn.of.javascript.interface.for.webview { +# public *; +#} + +# Uncomment this to preserve the line number information for +# debugging stack traces. +#-keepattributes SourceFile,LineNumberTable + +# If you keep the line number information, uncomment this to +# hide the original source file name. +#-renamesourcefileattribute SourceFile \ No newline at end of file diff --git a/core/firebase/src/main/java/com/example/firebase/FirebaseDataSourceConstants.kt b/data/firebase/src/main/java/com/example/firebase/FirebaseDataSourceConstants.kt similarity index 100% rename from core/firebase/src/main/java/com/example/firebase/FirebaseDataSourceConstants.kt rename to data/firebase/src/main/java/com/example/firebase/FirebaseDataSourceConstants.kt diff --git a/core/firebase/src/main/java/com/example/firebase/FirebaseException.kt b/data/firebase/src/main/java/com/example/firebase/FirebaseException.kt similarity index 100% rename from core/firebase/src/main/java/com/example/firebase/FirebaseException.kt rename to data/firebase/src/main/java/com/example/firebase/FirebaseException.kt diff --git a/core/firebase/src/main/java/com/example/firebase/FirebaseModule.kt b/data/firebase/src/main/java/com/example/firebase/FirebaseModule.kt similarity index 100% rename from core/firebase/src/main/java/com/example/firebase/FirebaseModule.kt rename to data/firebase/src/main/java/com/example/firebase/FirebaseModule.kt diff --git a/data/firebase/src/main/java/com/example/firebase/FirebaseRepositoryUtils.kt b/data/firebase/src/main/java/com/example/firebase/FirebaseRepositoryUtils.kt new file mode 100644 index 00000000..80ca0a84 --- /dev/null +++ b/data/firebase/src/main/java/com/example/firebase/FirebaseRepositoryUtils.kt @@ -0,0 +1,19 @@ +package com.example.firebase + +suspend fun handleResult( + firebaseRepositoryException: FirebaseException, + call: suspend () -> T? +): Result { + return runCatching { + call() ?: throw firebaseRepositoryException + } +} + +suspend fun handleResult( + call: suspend () -> T +): Result { + return runCatching { + call() + } +} + diff --git a/data/location/build.gradle.kts b/data/location/build.gradle.kts new file mode 100644 index 00000000..83a77750 --- /dev/null +++ b/data/location/build.gradle.kts @@ -0,0 +1,40 @@ +plugins { + alias(libs.plugins.android.library) + alias(libs.plugins.jetbrains.kotlin.android) +} + +android { + namespace = "com.example.location" + compileSdk = 34 + + defaultConfig { + minSdk = 26 + + testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner" + consumerProguardFiles("consumer-rules.pro") + } + + buildTypes { + release { + isMinifyEnabled = false + proguardFiles(getDefaultProguardFile("proguard-android-optimize.txt"), "proguard-rules.pro") + } + } + compileOptions { + sourceCompatibility = JavaVersion.VERSION_1_8 + targetCompatibility = JavaVersion.VERSION_1_8 + } + kotlinOptions { + jvmTarget = "1.8" + } +} + +dependencies { + + implementation(libs.androidx.core.ktx) + implementation(libs.androidx.appcompat) + implementation(libs.material) + testImplementation(libs.junit) + androidTestImplementation(libs.androidx.junit) + androidTestImplementation(libs.androidx.espresso.core) +} diff --git a/data/location/consumer-rules.pro b/data/location/consumer-rules.pro new file mode 100644 index 00000000..e69de29b diff --git a/data/location/proguard-rules.pro b/data/location/proguard-rules.pro new file mode 100644 index 00000000..481bb434 --- /dev/null +++ b/data/location/proguard-rules.pro @@ -0,0 +1,21 @@ +# Add project specific ProGuard rules here. +# You can control the set of applied configuration files using the +# proguardFiles setting in build.gradle. +# +# For more details, see +# http://developer.android.com/guide/developing/tools/proguard.html + +# If your project uses WebView with JS, uncomment the following +# and specify the fully qualified class name to the JavaScript interface +# class: +#-keepclassmembers class fqcn.of.javascript.interface.for.webview { +# public *; +#} + +# Uncomment this to preserve the line number information for +# debugging stack traces. +#-keepattributes SourceFile,LineNumberTable + +# If you keep the line number information, uncomment this to +# hide the original source file name. +#-renamesourcefileattribute SourceFile \ No newline at end of file diff --git a/data/src/main/java/com/squirtles/data/applemusic/di/AppleMusicDi.kt b/data/src/main/java/com/squirtles/data/applemusic/di/AppleMusicDi.kt index 66350d78..b824fef5 100644 --- a/data/src/main/java/com/squirtles/data/applemusic/di/AppleMusicDi.kt +++ b/data/src/main/java/com/squirtles/data/applemusic/di/AppleMusicDi.kt @@ -14,7 +14,6 @@ import retrofit2.Converter import retrofit2.Retrofit import javax.inject.Singleton - @Module @InstallIn(SingletonComponent::class) object AppleMusicModule{ diff --git a/data/src/main/java/com/squirtles/data/favorite/FirebaseFavoriteDataSourceImpl.kt b/data/src/main/java/com/squirtles/data/favorite/FirebaseFavoriteDataSourceImpl.kt index faa56f2c..9b619bdf 100644 --- a/data/src/main/java/com/squirtles/data/favorite/FirebaseFavoriteDataSourceImpl.kt +++ b/data/src/main/java/com/squirtles/data/favorite/FirebaseFavoriteDataSourceImpl.kt @@ -34,34 +34,6 @@ class FirebaseFavoriteDataSourceImpl @Inject constructor( private val cloudFunctionHelper: CloudFunctionHelper ) : FirebaseFavoriteDataSource { - override suspend fun fetchFavoritePicks(userId: String): List { - val favoriteDocuments = fetchFavoritesByUserId(userId) - - val tasks = mutableListOf>() - val favorites = mutableListOf() - - try { - favoriteDocuments.forEach { doc -> - tasks.add( - db.collection(COLLECTION_PICKS) - .document(doc.data[FIELD_PICK_ID].toString()) - .get() - ) - } - Tasks.whenAllComplete(tasks).await() - } catch (exception: Exception) { - Log.e("FirebaseDataSourceImpl", "Failed to get favorite picks", exception) - throw exception - } - tasks.forEach { task -> - task.result.toObject()?.run { - favorites.add(this.toPick().copy(id = task.result.id)) - } - } - - return favorites - } - override suspend fun fetchIsFavorite(pickId: String, userId: String): Boolean { val favoriteDocument = fetchFavoriteByPickIdAndUserId(pickId, userId) return favoriteDocument.isEmpty.not() @@ -143,27 +115,6 @@ class FirebaseFavoriteDataSourceImpl @Inject constructor( } } - private suspend fun fetchFavoritesByUserId(userId: String): QuerySnapshot { - val query = db.collection(COLLECTION_FAVORITES) - .whereEqualTo(FIELD_USER_ID, userId) - .orderBy(FIELD_ADDED_AT, Query.Direction.DESCENDING) - - return executeQuery(query) - } - - private suspend fun executeQuery(query: Query): QuerySnapshot { - return suspendCancellableCoroutine { continuation -> - query.get() - .addOnSuccessListener { result -> - continuation.resume(result) - } - .addOnFailureListener { exception -> - Log.w("FirebaseDataSourceImpl", "Error fetching favorite documents", exception) - continuation.resumeWithException(exception) - } - } - } - private suspend fun updateFavoriteCount(pickId: String) { try { val result = cloudFunctionHelper.updateFavoriteCount(pickId) diff --git a/data/src/main/java/com/squirtles/data/favorite/FirebaseFavoriteRepositoryImpl.kt b/data/src/main/java/com/squirtles/data/favorite/FirebaseFavoriteRepositoryImpl.kt index 81ee41bd..7e44e389 100644 --- a/data/src/main/java/com/squirtles/data/favorite/FirebaseFavoriteRepositoryImpl.kt +++ b/data/src/main/java/com/squirtles/data/favorite/FirebaseFavoriteRepositoryImpl.kt @@ -28,10 +28,4 @@ class FirebaseFavoriteRepositoryImpl @Inject constructor( favoriteDataSource.deleteFavorite(pickId, userId) } } - - override suspend fun fetchFavoritePicks(userId: String): Result> { - return handleResult { - favoriteDataSource.fetchFavoritePicks(userId) - } - } } diff --git a/data/src/main/java/com/squirtles/data/location/LocalLocationRepositoryImpl.kt b/data/src/main/java/com/squirtles/data/location/LocalLocationRepositoryImpl.kt index deb240a5..1f15b086 100644 --- a/data/src/main/java/com/squirtles/data/location/LocalLocationRepositoryImpl.kt +++ b/data/src/main/java/com/squirtles/data/location/LocalLocationRepositoryImpl.kt @@ -12,7 +12,7 @@ class LocalLocationRepositoryImpl: LocalLocationRepository { private var _currentLocation: MutableStateFlow = MutableStateFlow(null) override val lastLocation: StateFlow = _currentLocation.asStateFlow() - override suspend fun saveCurrentLocation(location: Location) { - _currentLocation.emit(location) + override suspend fun saveCurrentLocation(geoLocation: Location) { + _currentLocation.emit(geoLocation) } } diff --git a/data/src/main/java/com/squirtles/data/pick/FirebasePickDataSourceImpl.kt b/data/src/main/java/com/squirtles/data/pick/FirebasePickDataSourceImpl.kt index 11910478..70979e5c 100644 --- a/data/src/main/java/com/squirtles/data/pick/FirebasePickDataSourceImpl.kt +++ b/data/src/main/java/com/squirtles/data/pick/FirebasePickDataSourceImpl.kt @@ -15,8 +15,10 @@ import com.google.firebase.firestore.toObject import com.squirtles.data.firebase.FirebaseDataSourceConstants.COLLECTION_FAVORITES import com.squirtles.data.firebase.FirebaseDataSourceConstants.COLLECTION_PICKS import com.squirtles.data.firebase.FirebaseDataSourceConstants.COLLECTION_USERS +import com.squirtles.data.firebase.FirebaseDataSourceConstants.FIELD_ADDED_AT import com.squirtles.data.firebase.FirebaseDataSourceConstants.FIELD_MY_PICKS import com.squirtles.data.firebase.FirebaseDataSourceConstants.FIELD_PICK_ID +import com.squirtles.data.firebase.FirebaseDataSourceConstants.FIELD_USER_ID import com.squirtles.data.firebase.FirebaseDataSourceConstants.TAG_LOG import com.squirtles.data.pick.model.FirebasePick import com.squirtles.data.user.model.FirebaseUser @@ -216,6 +218,34 @@ class FirebasePickDataSourceImpl @Inject constructor( } } + override suspend fun fetchFavoritePicks(userId: String): List { + val favoriteDocuments = fetchFavoritesByUserId(userId) + + val tasks = mutableListOf>() + val favorites = mutableListOf() + + try { + favoriteDocuments.forEach { doc -> + tasks.add( + db.collection(COLLECTION_PICKS) + .document(doc.data[FIELD_PICK_ID].toString()) + .get() + ) + } + Tasks.whenAllComplete(tasks).await() + } catch (exception: Exception) { + Log.e("FirebaseDataSourceImpl", "Failed to get favorite picks", exception) + throw exception + } + tasks.forEach { task -> + task.result.toObject()?.run { + favorites.add(this.toPick().copy(id = task.result.id)) + } + } + + return favorites + } + /** * GeoHash의 FP 문제 - Geohash의 쿼리가 정확하지 않으며 클라이언트 측에서 거짓양성 결과를 필터링해야 합니다. * 이러한 추가 읽기로 인해 앱에 비용과 지연 시간이 추가됩니다. @@ -228,4 +258,25 @@ class FirebasePickDataSourceImpl @Inject constructor( return distanceInM <= radiusInM } + + private suspend fun fetchFavoritesByUserId(userId: String): QuerySnapshot { + val query = db.collection(COLLECTION_FAVORITES) + .whereEqualTo(FIELD_USER_ID, userId) + .orderBy(FIELD_ADDED_AT, Query.Direction.DESCENDING) + + return executeQuery(query) + } + + private suspend fun executeQuery(query: Query): QuerySnapshot { + return suspendCancellableCoroutine { continuation -> + query.get() + .addOnSuccessListener { result -> + continuation.resume(result) + } + .addOnFailureListener { exception -> + Log.w("FirebaseDataSourceImpl", "Error fetching favorite documents", exception) + continuation.resumeWithException(exception) + } + } + } } diff --git a/data/src/main/java/com/squirtles/data/pick/FirebasePickRepositoryImpl.kt b/data/src/main/java/com/squirtles/data/pick/FirebasePickRepositoryImpl.kt index 38ae0bd7..bc56974a 100644 --- a/data/src/main/java/com/squirtles/data/pick/FirebasePickRepositoryImpl.kt +++ b/data/src/main/java/com/squirtles/data/pick/FirebasePickRepositoryImpl.kt @@ -1,8 +1,8 @@ package com.squirtles.data.pick -import com.squirtles.domain.pick.FirebasePickDataSource -import com.squirtles.domain.model.Pick import com.squirtles.domain.firebase.FirebaseException +import com.squirtles.domain.model.Pick +import com.squirtles.domain.pick.FirebasePickDataSource import com.squirtles.domain.pick.FirebasePickRepository import javax.inject.Inject import javax.inject.Singleton @@ -46,4 +46,10 @@ class FirebasePickRepositoryImpl @Inject constructor( pickList.ifEmpty { null } } } + + override suspend fun fetchFavoritePicks(userId: String): Result> { + return handleResult { + pickDataSource.fetchFavoritePicks(userId) + } + } } diff --git a/data/src/main/java/com/squirtles/data/user/FirebaseUserRepositoryImpl.kt b/data/src/main/java/com/squirtles/data/user/FirebaseUserRepositoryImpl.kt index 1d0e23b8..d096fbca 100644 --- a/data/src/main/java/com/squirtles/data/user/FirebaseUserRepositoryImpl.kt +++ b/data/src/main/java/com/squirtles/data/user/FirebaseUserRepositoryImpl.kt @@ -1,8 +1,8 @@ package com.squirtles.data.user -import com.squirtles.domain.user.FirebaseUserDataSource -import com.squirtles.domain.model.User import com.squirtles.domain.firebase.FirebaseException +import com.squirtles.domain.model.User +import com.squirtles.domain.user.FirebaseUserDataSource import com.squirtles.domain.user.FirebaseUserRepository import javax.inject.Singleton diff --git a/domain/applemusic/build.gradle.kts b/domain/applemusic/build.gradle.kts new file mode 100644 index 00000000..3b013582 --- /dev/null +++ b/domain/applemusic/build.gradle.kts @@ -0,0 +1,26 @@ +plugins { + alias(libs.plugins.android.library) + alias(libs.plugins.jetbrains.kotlin.android) +} + +android { + namespace = "com.example.applemusic" + compileSdk = 34 + + defaultConfig { + minSdk = 26 + } + compileOptions { + sourceCompatibility = JavaVersion.VERSION_1_8 + targetCompatibility = JavaVersion.VERSION_1_8 + } + kotlinOptions { + jvmTarget = "1.8" + } +} + +dependencies { + implementation(projects.core.model) + implementation(libs.androidx.paging.runtime) + implementation(libs.inject) +} diff --git a/domain/applemusic/src/main/java/com/example/applemusic/AppleMusicException.kt b/domain/applemusic/src/main/java/com/example/applemusic/AppleMusicException.kt new file mode 100644 index 00000000..ed060391 --- /dev/null +++ b/domain/applemusic/src/main/java/com/example/applemusic/AppleMusicException.kt @@ -0,0 +1,12 @@ +package com.example.applemusic + +/** + * 400 에러가 여러 종류가 있는데 이를 구분할 용도로 만든 예외 클래스 + */ +sealed class AppleMusicException(override val message: String) : Exception() { + data class InvalidParameterException(override val message: String) : + AppleMusicException(message) + + data class NotFoundException(override val message: String = "No such resource") : + AppleMusicException(message) +} diff --git a/domain/applemusic/src/main/java/com/example/applemusic/AppleMusicRemoteDataSource.kt b/domain/applemusic/src/main/java/com/example/applemusic/AppleMusicRemoteDataSource.kt new file mode 100644 index 00000000..fb22083b --- /dev/null +++ b/domain/applemusic/src/main/java/com/example/applemusic/AppleMusicRemoteDataSource.kt @@ -0,0 +1,11 @@ +package com.example.applemusic + +import androidx.paging.PagingData +import com.example.model.MusicVideo +import com.example.model.Song +import kotlinx.coroutines.flow.Flow + +interface AppleMusicRemoteDataSource { + fun searchSongs(searchText: String): Flow> + suspend fun searchMusicVideos(searchText: String): List +} diff --git a/domain/applemusic/src/main/java/com/example/applemusic/AppleMusicRepository.kt b/domain/applemusic/src/main/java/com/example/applemusic/AppleMusicRepository.kt new file mode 100644 index 00000000..ba66663e --- /dev/null +++ b/domain/applemusic/src/main/java/com/example/applemusic/AppleMusicRepository.kt @@ -0,0 +1,12 @@ +package com.example.applemusic + +import androidx.paging.PagingData +import com.example.model.MusicVideo +import com.example.model.Song +import kotlinx.coroutines.flow.Flow + +interface AppleMusicRepository { + fun searchSongs(searchText: String): Flow> + suspend fun searchSongById(songId: String): Result + suspend fun searchMusicVideos(searchText: String): Result> +} diff --git a/domain/applemusic/src/main/java/com/example/applemusic/usecase/FetchMusicVideoUseCase.kt b/domain/applemusic/src/main/java/com/example/applemusic/usecase/FetchMusicVideoUseCase.kt new file mode 100644 index 00000000..c84b4857 --- /dev/null +++ b/domain/applemusic/src/main/java/com/example/applemusic/usecase/FetchMusicVideoUseCase.kt @@ -0,0 +1,21 @@ +package com.example.applemusic.usecase + +import com.example.applemusic.AppleMusicRepository +import com.example.model.MusicVideo +import com.example.model.Song +import javax.inject.Inject + + +class FetchMusicVideoUseCase @Inject constructor( + private val appleMusicRepository: AppleMusicRepository +) { + suspend operator fun invoke(song: Song): MusicVideo? { + val keyword = "${song.songName}-${song.artistName}" + appleMusicRepository.searchMusicVideos(keyword).onSuccess { musicVideos -> + return musicVideos.find { + it.artistName == song.artistName && song.songName in it.songName + } + } + return null + } +} diff --git a/domain/applemusic/src/main/java/com/example/applemusic/usecase/FetchSongsUseCase.kt b/domain/applemusic/src/main/java/com/example/applemusic/usecase/FetchSongsUseCase.kt new file mode 100644 index 00000000..8d95072d --- /dev/null +++ b/domain/applemusic/src/main/java/com/example/applemusic/usecase/FetchSongsUseCase.kt @@ -0,0 +1,10 @@ +package com.example.applemusic.usecase + +import com.example.applemusic.AppleMusicRepository +import javax.inject.Inject + +class FetchSongsUseCase @Inject constructor( + private val appleMusicRepository: AppleMusicRepository +) { + operator fun invoke(searchText: String) = appleMusicRepository.searchSongs(searchText) +} diff --git a/domain/favorite/build.gradle.kts b/domain/favorite/build.gradle.kts new file mode 100644 index 00000000..a54401f8 --- /dev/null +++ b/domain/favorite/build.gradle.kts @@ -0,0 +1,15 @@ +plugins { + id("java-library") + alias(libs.plugins.jetbrains.kotlin.jvm) +} + +java { + sourceCompatibility = JavaVersion.VERSION_21 + targetCompatibility = JavaVersion.VERSION_21 +} + +dependencies { + implementation(projects.core.model) + implementation(projects.domain.picklist) + implementation(libs.inject) +} diff --git a/domain/favorite/src/main/java/com/example/favorite/FirebaseFavoriteDataSource.kt b/domain/favorite/src/main/java/com/example/favorite/FirebaseFavoriteDataSource.kt new file mode 100644 index 00000000..ec113547 --- /dev/null +++ b/domain/favorite/src/main/java/com/example/favorite/FirebaseFavoriteDataSource.kt @@ -0,0 +1,9 @@ +package com.example.favorite + +import com.example.model.Pick + +interface FirebaseFavoriteDataSource { + suspend fun fetchIsFavorite(pickId: String, userId: String): Boolean + suspend fun createFavorite(pickId: String, userId: String): Boolean + suspend fun deleteFavorite(pickId: String, userId: String): Boolean +} diff --git a/domain/favorite/src/main/java/com/example/favorite/FirebaseFavoriteRepository.kt b/domain/favorite/src/main/java/com/example/favorite/FirebaseFavoriteRepository.kt new file mode 100644 index 00000000..27d46a8e --- /dev/null +++ b/domain/favorite/src/main/java/com/example/favorite/FirebaseFavoriteRepository.kt @@ -0,0 +1,7 @@ +package com.example.favorite + +interface FirebaseFavoriteRepository { + suspend fun fetchIsFavorite(pickId: String, userId: String): Result + suspend fun createFavorite(pickId: String, userId: String): Result + suspend fun deleteFavorite(pickId: String, userId: String): Result +} diff --git a/domain/favorite/src/main/java/com/example/favorite/usecase/CreateFavoriteUseCase.kt b/domain/favorite/src/main/java/com/example/favorite/usecase/CreateFavoriteUseCase.kt new file mode 100644 index 00000000..0a9b881e --- /dev/null +++ b/domain/favorite/src/main/java/com/example/favorite/usecase/CreateFavoriteUseCase.kt @@ -0,0 +1,11 @@ +package com.example.favorite.usecase + +import com.example.favorite.FirebaseFavoriteRepository +import javax.inject.Inject + +class CreateFavoriteUseCase @Inject constructor( + private val favoriteRepository: FirebaseFavoriteRepository +) { + suspend operator fun invoke(pickId: String, userId: String) = + favoriteRepository.createFavorite(pickId, userId) +} diff --git a/domain/favorite/src/main/java/com/example/favorite/usecase/DeleteFavoriteUseCase.kt b/domain/favorite/src/main/java/com/example/favorite/usecase/DeleteFavoriteUseCase.kt new file mode 100644 index 00000000..2dce1262 --- /dev/null +++ b/domain/favorite/src/main/java/com/example/favorite/usecase/DeleteFavoriteUseCase.kt @@ -0,0 +1,12 @@ +package com.example.favorite.usecase + +import com.example.favorite.FirebaseFavoriteRepository +import com.example.picklist.RemovePickUseCaseInterface +import javax.inject.Inject + +class DeleteFavoriteUseCase @Inject constructor( + private val favoriteRepository: FirebaseFavoriteRepository +) : RemovePickUseCaseInterface { + override suspend operator fun invoke(pickId: String, userId: String) = + favoriteRepository.deleteFavorite(pickId, userId) +} diff --git a/domain/favorite/src/main/java/com/example/favorite/usecase/FetchIsFavoriteUseCase.kt b/domain/favorite/src/main/java/com/example/favorite/usecase/FetchIsFavoriteUseCase.kt new file mode 100644 index 00000000..3f300231 --- /dev/null +++ b/domain/favorite/src/main/java/com/example/favorite/usecase/FetchIsFavoriteUseCase.kt @@ -0,0 +1,11 @@ +package com.example.favorite.usecase + +import com.example.favorite.FirebaseFavoriteRepository +import javax.inject.Inject + +class FetchIsFavoriteUseCase @Inject constructor( + private val favoriteRepository: FirebaseFavoriteRepository +) { + suspend operator fun invoke(pickId: String, userId: String) = + favoriteRepository.fetchIsFavorite(pickId, userId) +} diff --git a/domain/location/build.gradle.kts b/domain/location/build.gradle.kts new file mode 100644 index 00000000..b7417737 --- /dev/null +++ b/domain/location/build.gradle.kts @@ -0,0 +1,25 @@ +plugins { + alias(libs.plugins.android.library) + alias(libs.plugins.jetbrains.kotlin.android) +} + +android { + namespace = "com.example.location" + compileSdk = 34 + + defaultConfig { + minSdk = 26 + } + compileOptions { + sourceCompatibility = JavaVersion.VERSION_1_8 + targetCompatibility = JavaVersion.VERSION_1_8 + } + kotlinOptions { + jvmTarget = "1.8" + } +} + +dependencies { + implementation(libs.androidx.core.ktx) + implementation(libs.inject) +} diff --git a/domain/location/src/main/java/com/example/location/LocalLocationRepository.kt b/domain/location/src/main/java/com/example/location/LocalLocationRepository.kt new file mode 100644 index 00000000..d0112c9c --- /dev/null +++ b/domain/location/src/main/java/com/example/location/LocalLocationRepository.kt @@ -0,0 +1,10 @@ +package com.example.location + +import android.location.Location +import kotlinx.coroutines.flow.StateFlow + +interface LocalLocationRepository { + val lastLocation: StateFlow + + suspend fun saveCurrentLocation(geoLocation: Location) +} diff --git a/domain/location/src/main/java/com/example/location/usecase/GetLastLocationUseCase.kt b/domain/location/src/main/java/com/example/location/usecase/GetLastLocationUseCase.kt new file mode 100644 index 00000000..d5e8d21e --- /dev/null +++ b/domain/location/src/main/java/com/example/location/usecase/GetLastLocationUseCase.kt @@ -0,0 +1,10 @@ +package com.example.location.usecase + +import com.example.location.LocalLocationRepository +import javax.inject.Inject + +class GetLastLocationUseCase @Inject constructor( + private val localLocationRepository: LocalLocationRepository +) { + operator fun invoke() = localLocationRepository.lastLocation +} diff --git a/domain/location/src/main/java/com/example/location/usecase/SaveLastLocationUseCase.kt b/domain/location/src/main/java/com/example/location/usecase/SaveLastLocationUseCase.kt new file mode 100644 index 00000000..ac376f51 --- /dev/null +++ b/domain/location/src/main/java/com/example/location/usecase/SaveLastLocationUseCase.kt @@ -0,0 +1,11 @@ +package com.example.location.usecase + +import android.location.Location +import com.example.location.LocalLocationRepository +import javax.inject.Inject + +class SaveLastLocationUseCase @Inject constructor( + private val localLocationRepository: LocalLocationRepository +) { + suspend operator fun invoke(location: Location) = localLocationRepository.saveCurrentLocation(location) +} diff --git a/domain/order/build.gradle.kts b/domain/order/build.gradle.kts new file mode 100644 index 00000000..41bb4029 --- /dev/null +++ b/domain/order/build.gradle.kts @@ -0,0 +1,14 @@ +plugins { + id("java-library") + alias(libs.plugins.jetbrains.kotlin.jvm) +} + +java { + sourceCompatibility = JavaVersion.VERSION_21 + targetCompatibility = JavaVersion.VERSION_21 +} +dependencies { + implementation(projects.core.model) + implementation(projects.domain.picklist) + implementation(libs.inject) +} diff --git a/domain/order/src/main/java/com/example/order/LocalPickListOrderRepository.kt b/domain/order/src/main/java/com/example/order/LocalPickListOrderRepository.kt new file mode 100644 index 00000000..e75053f7 --- /dev/null +++ b/domain/order/src/main/java/com/example/order/LocalPickListOrderRepository.kt @@ -0,0 +1,11 @@ +package com.example.order + +import com.example.model.Order + +interface LocalPickListOrderRepository { + val favoriteListOrder: Order // 픽 보관함 정렬 순서 + val myListOrder: Order // 등록한 픽 정렬 순서 + + suspend fun saveFavoriteListOrder(order: Order) + suspend fun saveMyListOrder(order: Order) +} diff --git a/domain/order/src/main/java/com/example/order/usecase/GetFavoriteListOrderUseCase.kt b/domain/order/src/main/java/com/example/order/usecase/GetFavoriteListOrderUseCase.kt new file mode 100644 index 00000000..337605bf --- /dev/null +++ b/domain/order/src/main/java/com/example/order/usecase/GetFavoriteListOrderUseCase.kt @@ -0,0 +1,11 @@ +package com.example.order.usecase + +import com.example.order.LocalPickListOrderRepository +import com.example.picklist.GetPickListOrderUseCaseInterface +import javax.inject.Inject + +class GetFavoriteListOrderUseCase @Inject constructor( + private val localPickListOrderRepository: LocalPickListOrderRepository +) : GetPickListOrderUseCaseInterface { + override suspend operator fun invoke() = localPickListOrderRepository.favoriteListOrder +} diff --git a/domain/order/src/main/java/com/example/order/usecase/GetMyPickListOrderUseCase.kt b/domain/order/src/main/java/com/example/order/usecase/GetMyPickListOrderUseCase.kt new file mode 100644 index 00000000..aab74652 --- /dev/null +++ b/domain/order/src/main/java/com/example/order/usecase/GetMyPickListOrderUseCase.kt @@ -0,0 +1,11 @@ +package com.example.order.usecase + +import com.example.order.LocalPickListOrderRepository +import com.example.picklist.GetPickListOrderUseCaseInterface +import javax.inject.Inject + +class GetMyPickListOrderUseCase @Inject constructor( + private val localPickListOrderRepository: LocalPickListOrderRepository +) : GetPickListOrderUseCaseInterface { + override suspend operator fun invoke() = localPickListOrderRepository.myListOrder +} diff --git a/domain/order/src/main/java/com/example/order/usecase/SaveFavoriteListOrderUseCase.kt b/domain/order/src/main/java/com/example/order/usecase/SaveFavoriteListOrderUseCase.kt new file mode 100644 index 00000000..cfca34ef --- /dev/null +++ b/domain/order/src/main/java/com/example/order/usecase/SaveFavoriteListOrderUseCase.kt @@ -0,0 +1,12 @@ +package com.example.order.usecase + +import com.example.model.Order +import com.example.order.LocalPickListOrderRepository +import com.example.picklist.SavePickListOrderUseCaseInterface +import javax.inject.Inject + +class SaveFavoriteListOrderUseCase @Inject constructor( + private val localPickListOrderRepository: LocalPickListOrderRepository +) : SavePickListOrderUseCaseInterface { + override suspend operator fun invoke(order: Order) = localPickListOrderRepository.saveFavoriteListOrder(order) +} diff --git a/domain/order/src/main/java/com/example/order/usecase/SaveMyPickListOrderUseCase.kt b/domain/order/src/main/java/com/example/order/usecase/SaveMyPickListOrderUseCase.kt new file mode 100644 index 00000000..e2d73319 --- /dev/null +++ b/domain/order/src/main/java/com/example/order/usecase/SaveMyPickListOrderUseCase.kt @@ -0,0 +1,12 @@ +package com.example.order.usecase + +import com.example.model.Order +import com.example.order.LocalPickListOrderRepository +import com.example.picklist.SavePickListOrderUseCaseInterface +import javax.inject.Inject + +class SaveMyPickListOrderUseCase @Inject constructor( + private val localPickListOrderRepository: LocalPickListOrderRepository +) : SavePickListOrderUseCaseInterface { + override suspend operator fun invoke(order: Order) = localPickListOrderRepository.saveMyListOrder(order) +} diff --git a/domain/pick/build.gradle.kts b/domain/pick/build.gradle.kts new file mode 100644 index 00000000..a54401f8 --- /dev/null +++ b/domain/pick/build.gradle.kts @@ -0,0 +1,15 @@ +plugins { + id("java-library") + alias(libs.plugins.jetbrains.kotlin.jvm) +} + +java { + sourceCompatibility = JavaVersion.VERSION_21 + targetCompatibility = JavaVersion.VERSION_21 +} + +dependencies { + implementation(projects.core.model) + implementation(projects.domain.picklist) + implementation(libs.inject) +} diff --git a/domain/pick/src/main/java/com/example/pick/FirebasePickDataSource.kt b/domain/pick/src/main/java/com/example/pick/FirebasePickDataSource.kt new file mode 100644 index 00000000..2e6ba46a --- /dev/null +++ b/domain/pick/src/main/java/com/example/pick/FirebasePickDataSource.kt @@ -0,0 +1,12 @@ +package com.example.pick + +import com.example.model.Pick + +interface FirebasePickDataSource { + suspend fun createPick(pick: Pick): String + suspend fun deletePick(pickId: String, userId: String): Boolean + suspend fun fetchPick(pickID: String): Pick? + suspend fun fetchPicksInArea(lat: Double, lng: Double, radiusInM: Double): List + suspend fun fetchMyPicks(userId: String): List + suspend fun fetchFavoritePicks(userId: String): Result> +} diff --git a/domain/pick/src/main/java/com/example/pick/FirebasePickRepository.kt b/domain/pick/src/main/java/com/example/pick/FirebasePickRepository.kt new file mode 100644 index 00000000..589a6019 --- /dev/null +++ b/domain/pick/src/main/java/com/example/pick/FirebasePickRepository.kt @@ -0,0 +1,12 @@ +package com.example.pick + +import com.example.model.Pick + +interface FirebasePickRepository { + suspend fun createPick(pick: Pick): Result + suspend fun deletePick(pickId: String, userId: String): Result + suspend fun fetchPick(pickID: String): Result + suspend fun fetchPicksInArea(lat: Double, lng: Double, radiusInM: Double): Result> + suspend fun fetchMyPicks(userId: String): Result> + suspend fun fetchFavoritePicks(userId: String): Result> +} diff --git a/domain/pick/src/main/java/com/example/pick/usecase/CreatePickUseCase.kt b/domain/pick/src/main/java/com/example/pick/usecase/CreatePickUseCase.kt new file mode 100644 index 00000000..8c2a4b42 --- /dev/null +++ b/domain/pick/src/main/java/com/example/pick/usecase/CreatePickUseCase.kt @@ -0,0 +1,11 @@ +package com.example.pick.usecase + +import com.example.model.Pick +import com.example.pick.FirebasePickRepository +import javax.inject.Inject + +class CreatePickUseCase @Inject constructor( + private val pickRepository: FirebasePickRepository +) { + suspend operator fun invoke(pick: Pick): Result = pickRepository.createPick(pick) +} diff --git a/domain/pick/src/main/java/com/example/pick/usecase/DeletePickUseCase.kt b/domain/pick/src/main/java/com/example/pick/usecase/DeletePickUseCase.kt new file mode 100644 index 00000000..bde4761a --- /dev/null +++ b/domain/pick/src/main/java/com/example/pick/usecase/DeletePickUseCase.kt @@ -0,0 +1,12 @@ +package com.example.pick.usecase + +import com.example.pick.FirebasePickRepository +import com.example.picklist.RemovePickUseCaseInterface +import javax.inject.Inject + +class DeletePickUseCase @Inject constructor( + private val pickRepository: FirebasePickRepository +) : RemovePickUseCaseInterface { + override suspend operator fun invoke(pickId: String, userId: String): Result = + pickRepository.deletePick(pickId, userId) +} diff --git a/domain/pick/src/main/java/com/example/pick/usecase/FetchMyPicksUseCase.kt b/domain/pick/src/main/java/com/example/pick/usecase/FetchMyPicksUseCase.kt new file mode 100644 index 00000000..26e2cf18 --- /dev/null +++ b/domain/pick/src/main/java/com/example/pick/usecase/FetchMyPicksUseCase.kt @@ -0,0 +1,12 @@ +package com.example.pick.usecase + +import com.example.pick.FirebasePickRepository +import com.example.picklist.FetchPickListUseCaseInterface +import javax.inject.Inject + +class FetchMyPicksUseCase @Inject constructor( + private val pickRepository: FirebasePickRepository +) : FetchPickListUseCaseInterface { + override suspend operator fun invoke(userId: String) = + pickRepository.fetchMyPicks(userId) +} diff --git a/domain/pick/src/main/java/com/example/pick/usecase/FetchPickUseCase.kt b/domain/pick/src/main/java/com/example/pick/usecase/FetchPickUseCase.kt new file mode 100644 index 00000000..15307d2c --- /dev/null +++ b/domain/pick/src/main/java/com/example/pick/usecase/FetchPickUseCase.kt @@ -0,0 +1,14 @@ +package com.example.pick.usecase + +import com.example.pick.FirebasePickRepository +import javax.inject.Inject + +class FetchPickUseCase @Inject constructor( + private val pickRepository: FirebasePickRepository +) { + suspend operator fun invoke(pickId: String) = + pickRepository.fetchPick(pickId) + + suspend operator fun invoke(lat: Double, lng: Double, radiusInM: Double) = + pickRepository.fetchPicksInArea(lat, lng, radiusInM) +} diff --git a/domain/picklist/build.gradle.kts b/domain/picklist/build.gradle.kts new file mode 100644 index 00000000..2d8b61e1 --- /dev/null +++ b/domain/picklist/build.gradle.kts @@ -0,0 +1,13 @@ +plugins { + id("java-library") + alias(libs.plugins.jetbrains.kotlin.jvm) +} + +java { + sourceCompatibility = JavaVersion.VERSION_21 + targetCompatibility = JavaVersion.VERSION_21 +} + +dependencies { + implementation(projects.core.model) +} diff --git a/domain/picklist/src/main/java/com/example/picklist/FetchPickListUseCaseInterface.kt b/domain/picklist/src/main/java/com/example/picklist/FetchPickListUseCaseInterface.kt new file mode 100644 index 00000000..febb6887 --- /dev/null +++ b/domain/picklist/src/main/java/com/example/picklist/FetchPickListUseCaseInterface.kt @@ -0,0 +1,7 @@ +package com.example.picklist + +import com.example.model.Pick + +interface FetchPickListUseCaseInterface { + suspend operator fun invoke(userId: String): Result> +} diff --git a/domain/picklist/src/main/java/com/example/picklist/GetPickListOrderUseCaseInterface.kt b/domain/picklist/src/main/java/com/example/picklist/GetPickListOrderUseCaseInterface.kt new file mode 100644 index 00000000..f07dd755 --- /dev/null +++ b/domain/picklist/src/main/java/com/example/picklist/GetPickListOrderUseCaseInterface.kt @@ -0,0 +1,7 @@ +package com.example.picklist + +import com.example.model.Order + +interface GetPickListOrderUseCaseInterface { + suspend operator fun invoke(): Order +} diff --git a/domain/picklist/src/main/java/com/example/picklist/RemovePickUseCaseInterface.kt b/domain/picklist/src/main/java/com/example/picklist/RemovePickUseCaseInterface.kt new file mode 100644 index 00000000..b67e8fa7 --- /dev/null +++ b/domain/picklist/src/main/java/com/example/picklist/RemovePickUseCaseInterface.kt @@ -0,0 +1,5 @@ +package com.example.picklist + +interface RemovePickUseCaseInterface { + suspend operator fun invoke(pickId: String, userId: String): Result +} diff --git a/domain/picklist/src/main/java/com/example/picklist/SavePickListOrderUseCaseInterface.kt b/domain/picklist/src/main/java/com/example/picklist/SavePickListOrderUseCaseInterface.kt new file mode 100644 index 00000000..08dd5c0b --- /dev/null +++ b/domain/picklist/src/main/java/com/example/picklist/SavePickListOrderUseCaseInterface.kt @@ -0,0 +1,7 @@ +package com.example.picklist + +import com.example.model.Order + +interface SavePickListOrderUseCaseInterface { + suspend operator fun invoke(order: Order) +} diff --git a/domain/player/build.gradle.kts b/domain/player/build.gradle.kts new file mode 100644 index 00000000..2b7a42ec --- /dev/null +++ b/domain/player/build.gradle.kts @@ -0,0 +1,29 @@ +plugins { + alias(libs.plugins.android.library) + alias(libs.plugins.jetbrains.kotlin.android) +} + +android { + namespace = "com.example.player" + compileSdk = 34 + + defaultConfig { + minSdk = 26 + } + compileOptions { + sourceCompatibility = JavaVersion.VERSION_1_8 + targetCompatibility = JavaVersion.VERSION_1_8 + } + kotlinOptions { + jvmTarget = "1.8" + } +} + +dependencies { + implementation(projects.core.model) + implementation(projects.core.mediaservice) + implementation(libs.inject) + implementation(libs.kotlinx.coroutines.core) + implementation(libs.androidx.media3.common) + implementation(libs.androidx.media3.session) +} diff --git a/domain/player/src/main/java/com/example/player/MediaPlayerListenerUseCase.kt b/domain/player/src/main/java/com/example/player/MediaPlayerListenerUseCase.kt new file mode 100644 index 00000000..7630d006 --- /dev/null +++ b/domain/player/src/main/java/com/example/player/MediaPlayerListenerUseCase.kt @@ -0,0 +1,141 @@ +package com.example.player + +import androidx.media3.common.Player +import androidx.media3.common.Tracks +import com.example.mediaservice.MediaControllerProvider +import com.example.model.PlayerState +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.Job +import kotlinx.coroutines.cancel +import kotlinx.coroutines.channels.awaitClose +import kotlinx.coroutines.delay +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.callbackFlow +import kotlinx.coroutines.flow.collect +import kotlinx.coroutines.flow.distinctUntilChanged +import kotlinx.coroutines.flow.first +import kotlinx.coroutines.flow.map +import kotlinx.coroutines.flow.onEach +import kotlinx.coroutines.flow.update +import kotlinx.coroutines.isActive +import kotlinx.coroutines.launch +import javax.inject.Inject + +/* 현재 플레이어에 이벤트 리스너 등록 -> 로딩,재생,seekbar 탐색, 재생시간 등 데이터를 UI데이터로 변환 */ +class MediaPlayerListenerUseCase @Inject constructor( + private val mediaControllerProvider: MediaControllerProvider +) { + private val coroutineScope = CoroutineScope(Dispatchers.Main) + private var timerJob: Job? = null + + fun playerStateFlow() = callbackFlow { + val mediaController = mediaControllerProvider.mediaControllerFlow.first() + + val currentPlayerState = MutableStateFlow( + if (mediaController.currentMediaItem != null) { + PlayerState( + id = mediaController.currentMediaItem!!.mediaId, + isLoading = mediaController.isLoading, + isPlaying = mediaController.isPlaying, + hasNext = mediaController.hasNextMediaItem(), + currentPosition = mediaController.currentPosition, + duration = mediaController.duration, + bufferPercentage = mediaController.bufferedPercentage + ) + } else { + PlayerState() + } + ) + + val playerListener = object : Player.Listener { + override fun onIsPlayingChanged(isPlaying: Boolean) { + super.onIsPlayingChanged(isPlaying) + currentPlayerState.update { + it.copy(isPlaying = isPlaying) + } + } + + override fun onIsLoadingChanged(isLoading: Boolean) { + super.onIsLoadingChanged(isLoading) +// currentPlayerState.update { +// it.copy(id = mediaController.currentMediaItem?.mediaId ?: "", isLoading = isLoading) +// } + } + + override fun onPositionDiscontinuity( + oldPosition: Player.PositionInfo, + newPosition: Player.PositionInfo, + reason: Int + ) { + super.onPositionDiscontinuity(oldPosition, newPosition, reason) + if (reason == Player.DISCONTINUITY_REASON_SEEK) { + currentPlayerState.update { + it.copy(currentPosition = mediaController.currentPosition) + } + } + } + + override fun onPlaybackStateChanged(playbackState: Int) { + super.onPlaybackStateChanged(playbackState) + when (playbackState) { + Player.STATE_ENDED -> { + mediaController.seekTo(0) + mediaController.pause() + } + + Player.STATE_READY -> { + currentPlayerState.update { + it.copy(id = mediaController.currentMediaItem?.mediaId ?: "") + } + } + } + } + + override fun onTracksChanged(tracks: Tracks) { + super.onTracksChanged(tracks) + currentPlayerState.value = PlayerState() + } + } + + coroutineScope.launch { + currentPlayerState + .map { it.isLoading.not() && it.isPlaying } + .distinctUntilChanged() + .collect { isPlaying -> + if (isPlaying) { + timerJob = coroutineScope.launch { + val startDuration = mediaController.currentPosition + val maxDuration = mediaController.contentDuration + + while (isActive && startDuration <= maxDuration) { + currentPlayerState.update { + it.copy( + currentPosition = mediaController.currentPosition, + bufferPercentage = mediaController.bufferedPercentage + ) + } + delay(1000L) + } + } + } else { + timerJob?.cancel() + timerJob = null + } + } + } + + coroutineScope.launch { + currentPlayerState + .onEach { send(it) } + .collect() + } + + mediaController.addListener(playerListener) + + awaitClose { + mediaController.removeListener(playerListener) + coroutineScope.cancel() + } + } +} diff --git a/domain/player/src/main/java/com/example/player/MediaPlayerUseCase.kt b/domain/player/src/main/java/com/example/player/MediaPlayerUseCase.kt new file mode 100644 index 00000000..f5a0790d --- /dev/null +++ b/domain/player/src/main/java/com/example/player/MediaPlayerUseCase.kt @@ -0,0 +1,133 @@ +package com.example.player + +import androidx.media3.common.MediaItem +import androidx.media3.common.MediaMetadata +import androidx.media3.common.Player +import androidx.media3.session.MediaController +import com.example.mediaservice.MediaControllerProvider +import com.example.mediaservice.SEEK_TO_DURATION +import com.example.model.Pick +import kotlinx.coroutines.flow.first +import kotlinx.coroutines.flow.flow +import javax.inject.Inject + +/* */ +class MediaPlayerUseCase @Inject constructor( + private val mediaControllerProvider: MediaControllerProvider, +) { + private var mediaController: MediaController? = null + + val audioSessionId = flow { + emit(mediaControllerProvider.audioSessionFlow.first()) + } + + suspend fun readyPlayer() { + mediaController = mediaControllerProvider.mediaControllerFlow.first() + } + + fun addMediaItem(pick: Pick) { + val mediaItem = pick.toMediaItem() + mediaController?.addMediaItem(mediaItem) + } + + fun addMediaItems(picks: List) { + mediaController?.run { + addMediaItems(picks.map { it.toMediaItem() }) + } + } + + fun setMediaItem(pick: Pick) { + mediaController?.run { + clearMediaItems() + pause() + setMediaItem(pick.toMediaItem()) + prepare() + repeatMode = Player.REPEAT_MODE_OFF + volume = 0.5f + } + } + + fun setMediaItems(picks: List) { + mediaController?.run { + setMediaItems(picks.map { + it.toMediaItem() + }) + prepare() + playWhenReady = false + repeatMode = Player.REPEAT_MODE_ALL + volume = 0.5f + } + } + + fun changeRepeatMode(repeatable: Boolean) { + mediaController?.repeatMode = if (repeatable) { + Player.REPEAT_MODE_ALL + } else { + Player.REPEAT_MODE_OFF + } + } + + fun play() { + mediaController?.play() + } + + fun pause() { + mediaController?.pause() + } + + fun stop() { + mediaController?.stop() + } + + fun release() { + mediaController?.release() + } + + fun next() { + if (mediaController?.hasNextMediaItem() == true) { + mediaController?.seekToNextMediaItem() + } + } + + fun previous() { + if (mediaController?.hasPreviousMediaItem() == true) { + mediaController?.seekToPreviousMediaItem() + } else { + mediaController?.seekToDefaultPosition() + } + } + + fun advanceBy() { + mediaController?.apply { + seekTo(currentPosition + SEEK_TO_DURATION) + } + } + + fun rewindBy() { + mediaController?.apply { + seekTo(currentPosition - SEEK_TO_DURATION) + } + } + + fun onSeekingStarted() { + mediaController?.seekToDefaultPosition() + } + + fun onSeekingFinished(time: Long) { + mediaController?.seekTo(time) + } + + private fun Pick.toMediaItem(): MediaItem { + return MediaItem.Builder() + .setMediaId(this.id) + .setMediaMetadata( + MediaMetadata.Builder() + .setTitle(song.songName) + .setArtist(song.artistName) + .setAlbumTitle(song.albumName) + .build() + ) + .setUri(song.previewUrl) + .build() + } +} diff --git a/domain/src/main/java/com/squirtles/domain/favorite/FirebaseFavoriteDataSource.kt b/domain/src/main/java/com/squirtles/domain/favorite/FirebaseFavoriteDataSource.kt index 5b26030c..34425956 100644 --- a/domain/src/main/java/com/squirtles/domain/favorite/FirebaseFavoriteDataSource.kt +++ b/domain/src/main/java/com/squirtles/domain/favorite/FirebaseFavoriteDataSource.kt @@ -6,5 +6,4 @@ interface FirebaseFavoriteDataSource { suspend fun fetchIsFavorite(pickId: String, userId: String): Boolean suspend fun createFavorite(pickId: String, userId: String): Boolean suspend fun deleteFavorite(pickId: String, userId: String): Boolean - suspend fun fetchFavoritePicks(userId: String): List } diff --git a/domain/src/main/java/com/squirtles/domain/favorite/FirebaseFavoriteRepository.kt b/domain/src/main/java/com/squirtles/domain/favorite/FirebaseFavoriteRepository.kt index 2f0e0d2c..a3af14b3 100644 --- a/domain/src/main/java/com/squirtles/domain/favorite/FirebaseFavoriteRepository.kt +++ b/domain/src/main/java/com/squirtles/domain/favorite/FirebaseFavoriteRepository.kt @@ -1,12 +1,11 @@ package com.squirtles.domain.favorite -import com.squirtles.domain.model.Pick import com.squirtles.domain.firebase.FirebaseRepository +import com.squirtles.domain.model.Pick interface FirebaseFavoriteRepository : FirebaseRepository { // Favorite suspend fun fetchIsFavorite(pickId: String, userId: String): Result suspend fun createFavorite(pickId: String, userId: String): Result suspend fun deleteFavorite(pickId: String, userId: String): Result - suspend fun fetchFavoritePicks(userId: String): Result> } diff --git a/domain/src/main/java/com/squirtles/domain/favorite/usecase/FetchFavoritePicksUseCase.kt b/domain/src/main/java/com/squirtles/domain/favorite/usecase/FetchFavoritePicksUseCase.kt deleted file mode 100644 index faa17705..00000000 --- a/domain/src/main/java/com/squirtles/domain/favorite/usecase/FetchFavoritePicksUseCase.kt +++ /dev/null @@ -1,11 +0,0 @@ -package com.squirtles.domain.favorite.usecase - -import com.squirtles.domain.favorite.FirebaseFavoriteRepository -import com.squirtles.domain.picklist.FetchPickListUseCaseInterface -import javax.inject.Inject - -class FetchFavoritePicksUseCase @Inject constructor( - private val favoriteRepository: FirebaseFavoriteRepository -) : FetchPickListUseCaseInterface { - override suspend operator fun invoke(userId: String) = favoriteRepository.fetchFavoritePicks(userId) -} diff --git a/domain/src/main/java/com/squirtles/domain/location/LocalLocationRepository.kt b/domain/src/main/java/com/squirtles/domain/location/LocalLocationRepository.kt index 0011fcc6..943b5b00 100644 --- a/domain/src/main/java/com/squirtles/domain/location/LocalLocationRepository.kt +++ b/domain/src/main/java/com/squirtles/domain/location/LocalLocationRepository.kt @@ -6,5 +6,5 @@ import kotlinx.coroutines.flow.StateFlow interface LocalLocationRepository { val lastLocation: StateFlow - suspend fun saveCurrentLocation(location: Location) + suspend fun saveCurrentLocation(geoLocation: Location) } diff --git a/domain/src/main/java/com/squirtles/domain/model/GeoLocation.kt b/domain/src/main/java/com/squirtles/domain/model/GeoLocation.kt new file mode 100644 index 00000000..e2a1a16f --- /dev/null +++ b/domain/src/main/java/com/squirtles/domain/model/GeoLocation.kt @@ -0,0 +1,11 @@ +package com.squirtles.domain.model + +data class GeoLocation( + val latitude: Double, + val longitude: Double, +) { + init { + require(latitude in -90.0..90.0) { "latitude must be in -90.0 ~ 90.0 range" } + require(longitude in -180.0..180.0) { "longitude must be in -180.0 ~ 180.0 range" } + } +} diff --git a/domain/src/main/java/com/squirtles/domain/pick/FirebasePickDataSource.kt b/domain/src/main/java/com/squirtles/domain/pick/FirebasePickDataSource.kt index fcd0616d..326c9393 100644 --- a/domain/src/main/java/com/squirtles/domain/pick/FirebasePickDataSource.kt +++ b/domain/src/main/java/com/squirtles/domain/pick/FirebasePickDataSource.kt @@ -8,4 +8,5 @@ interface FirebasePickDataSource { suspend fun fetchPick(pickID: String): Pick? suspend fun fetchPicksInArea(lat: Double, lng: Double, radiusInM: Double): List suspend fun fetchMyPicks(userId: String): List + suspend fun fetchFavoritePicks(userId: String): List } diff --git a/domain/src/main/java/com/squirtles/domain/pick/FirebasePickRepository.kt b/domain/src/main/java/com/squirtles/domain/pick/FirebasePickRepository.kt index 63721d0d..f8181254 100644 --- a/domain/src/main/java/com/squirtles/domain/pick/FirebasePickRepository.kt +++ b/domain/src/main/java/com/squirtles/domain/pick/FirebasePickRepository.kt @@ -9,4 +9,5 @@ interface FirebasePickRepository : FirebaseRepository { suspend fun fetchPick(pickID: String): Result suspend fun fetchPicksInArea(lat: Double, lng: Double, radiusInM: Double): Result> suspend fun fetchMyPicks(userId: String): Result> + suspend fun fetchFavoritePicks(userId: String): Result> } diff --git a/domain/src/main/java/com/squirtles/domain/pick/usecase/FetchFavoritePicksUseCase.kt b/domain/src/main/java/com/squirtles/domain/pick/usecase/FetchFavoritePicksUseCase.kt new file mode 100644 index 00000000..9ce36467 --- /dev/null +++ b/domain/src/main/java/com/squirtles/domain/pick/usecase/FetchFavoritePicksUseCase.kt @@ -0,0 +1,11 @@ +package com.squirtles.domain.pick.usecase + +import com.squirtles.domain.pick.FirebasePickRepository +import com.squirtles.domain.picklist.FetchPickListUseCaseInterface +import javax.inject.Inject + +class FetchFavoritePicksUseCase @Inject constructor( + private val pickRepository: FirebasePickRepository +) : FetchPickListUseCaseInterface { + override suspend operator fun invoke(userId: String) = pickRepository.fetchFavoritePicks(userId) +} diff --git a/domain/user/build.gradle.kts b/domain/user/build.gradle.kts new file mode 100644 index 00000000..2142e940 --- /dev/null +++ b/domain/user/build.gradle.kts @@ -0,0 +1,15 @@ +plugins { + id("java-library") + alias(libs.plugins.jetbrains.kotlin.jvm) +} + +java { + sourceCompatibility = JavaVersion.VERSION_21 + targetCompatibility = JavaVersion.VERSION_21 +} + +dependencies { + implementation(projects.core.model) + implementation(libs.inject) + implementation(libs.kotlinx.coroutines.core) +} diff --git a/domain/user/src/main/java/com/example/user/FirebaseUserDataSource.kt b/domain/user/src/main/java/com/example/user/FirebaseUserDataSource.kt new file mode 100644 index 00000000..e4639a55 --- /dev/null +++ b/domain/user/src/main/java/com/example/user/FirebaseUserDataSource.kt @@ -0,0 +1,9 @@ +package com.example.user + +import com.example.model.User + +interface FirebaseUserDataSource { + suspend fun fetchUser(userId: String): User? + suspend fun createGoogleIdUser(userId: String, userName: String?, userProfileImage: String?): User? + suspend fun updateUserName(userId: String, newUserName: String): Boolean +} diff --git a/domain/user/src/main/java/com/example/user/FirebaseUserRepository.kt b/domain/user/src/main/java/com/example/user/FirebaseUserRepository.kt new file mode 100644 index 00000000..110de5af --- /dev/null +++ b/domain/user/src/main/java/com/example/user/FirebaseUserRepository.kt @@ -0,0 +1,10 @@ +package com.example.user + +import com.example.model.User + +interface FirebaseUserRepository { + // user + suspend fun createGoogleIdUser(userId: String, userName: String?, userProfileImage: String?): Result + suspend fun fetchUser(userId: String): Result + suspend fun updateUserName(userId: String, newUserName: String): Result +} diff --git a/domain/user/src/main/java/com/example/user/LocalUserDataSource.kt b/domain/user/src/main/java/com/example/user/LocalUserDataSource.kt new file mode 100644 index 00000000..34433601 --- /dev/null +++ b/domain/user/src/main/java/com/example/user/LocalUserDataSource.kt @@ -0,0 +1,13 @@ +package com.example.user + +import com.example.model.User +import kotlinx.coroutines.flow.Flow + +interface LocalUserDataSource { + val currentUser: User? + + fun readUserIdDataStore(): Flow + suspend fun saveUserIdDataStore(userId: String) + suspend fun saveCurrentUser(user: User) + suspend fun clearUser() +} diff --git a/domain/user/src/main/java/com/example/user/LocalUserRepository.kt b/domain/user/src/main/java/com/example/user/LocalUserRepository.kt new file mode 100644 index 00000000..80ee5e86 --- /dev/null +++ b/domain/user/src/main/java/com/example/user/LocalUserRepository.kt @@ -0,0 +1,13 @@ +package com.example.user + +import com.example.model.User +import kotlinx.coroutines.flow.Flow + +interface LocalUserRepository { + val currentUser: User? + + fun readUserIdDataStore(): Flow + suspend fun saveUserIdDataStore(userId: String) + suspend fun saveCurrentUser(user: User) + suspend fun clearUser(): Result +} diff --git a/domain/user/src/main/java/com/example/user/usecase/ClearUserUseCase.kt b/domain/user/src/main/java/com/example/user/usecase/ClearUserUseCase.kt new file mode 100644 index 00000000..d19ec554 --- /dev/null +++ b/domain/user/src/main/java/com/example/user/usecase/ClearUserUseCase.kt @@ -0,0 +1,10 @@ +package com.example.user.usecase + +import com.example.user.LocalUserRepository +import javax.inject.Inject + +class ClearUserUseCase @Inject constructor( + private val localUserRepository: LocalUserRepository +) { + suspend operator fun invoke() = localUserRepository.clearUser() +} diff --git a/domain/user/src/main/java/com/example/user/usecase/CreateGoogleIdUserUseCase.kt b/domain/user/src/main/java/com/example/user/usecase/CreateGoogleIdUserUseCase.kt new file mode 100644 index 00000000..7918cc56 --- /dev/null +++ b/domain/user/src/main/java/com/example/user/usecase/CreateGoogleIdUserUseCase.kt @@ -0,0 +1,25 @@ +package com.example.user.usecase + +import com.example.model.User +import com.example.user.FirebaseUserRepository +import com.example.user.LocalUserRepository +import javax.inject.Inject + +class CreateGoogleIdUserUseCase @Inject constructor( + private val localUserRepository: LocalUserRepository, + private val firebaseUserRepository: FirebaseUserRepository +) { + suspend operator fun invoke( + userId: String, + userName: String? = null, + userProfileImage: String? = null + ): Result { + val createdUser = firebaseUserRepository.createGoogleIdUser(userId, userName, userProfileImage) + .onSuccess { user -> + // 생성된 유저의 userId 저장 후 user 반환 + localUserRepository.saveUserIdDataStore(user.userId) + localUserRepository.saveCurrentUser(user) + } + return createdUser + } +} diff --git a/domain/user/src/main/java/com/example/user/usecase/FetchUserByIdUseCase.kt b/domain/user/src/main/java/com/example/user/usecase/FetchUserByIdUseCase.kt new file mode 100644 index 00000000..f51b24d7 --- /dev/null +++ b/domain/user/src/main/java/com/example/user/usecase/FetchUserByIdUseCase.kt @@ -0,0 +1,11 @@ +package com.example.user.usecase + +import com.example.user.FirebaseUserRepository +import javax.inject.Inject + +class FetchUserByIdUseCase @Inject constructor( + private val firebaseUserRepository: FirebaseUserRepository +) { + suspend operator fun invoke(userId: String) = + firebaseUserRepository.fetchUser(userId) +} diff --git a/domain/user/src/main/java/com/example/user/usecase/FetchUserUseCase.kt b/domain/user/src/main/java/com/example/user/usecase/FetchUserUseCase.kt new file mode 100644 index 00000000..4e0793e5 --- /dev/null +++ b/domain/user/src/main/java/com/example/user/usecase/FetchUserUseCase.kt @@ -0,0 +1,19 @@ +package com.example.user.usecase + +import com.example.model.User +import com.example.user.LocalUserRepository +import javax.inject.Inject + +class FetchUserUseCase @Inject constructor( + private val localUserRepository: LocalUserRepository, + private val fetchUserByIdUseCase: FetchUserByIdUseCase +) { + suspend operator fun invoke(userId: String): Result { + val user = fetchUserByIdUseCase(userId) // userId가 있으면 Firestore에서 유저 가져오기 + .onSuccess { user -> + localUserRepository.saveUserIdDataStore(user.userId) + localUserRepository.saveCurrentUser(user) // Firestore에서 가져온 user를 LocalDataSource에 저장 + } + return user + } +} diff --git a/domain/user/src/main/java/com/example/user/usecase/GetCurrentUserUseCase.kt b/domain/user/src/main/java/com/example/user/usecase/GetCurrentUserUseCase.kt new file mode 100644 index 00000000..def59a5e --- /dev/null +++ b/domain/user/src/main/java/com/example/user/usecase/GetCurrentUserUseCase.kt @@ -0,0 +1,10 @@ +package com.example.user.usecase + +import com.example.user.LocalUserRepository +import javax.inject.Inject + +class GetCurrentUserUseCase @Inject constructor( + private val localUserRepository: LocalUserRepository +) { + operator fun invoke() = localUserRepository.currentUser +} diff --git a/domain/user/src/main/java/com/example/user/usecase/GetUserIdFromDataStoreUseCase.kt b/domain/user/src/main/java/com/example/user/usecase/GetUserIdFromDataStoreUseCase.kt new file mode 100644 index 00000000..5b8ee41e --- /dev/null +++ b/domain/user/src/main/java/com/example/user/usecase/GetUserIdFromDataStoreUseCase.kt @@ -0,0 +1,10 @@ +package com.example.user.usecase + +import com.example.user.LocalUserRepository +import javax.inject.Inject + +class GetUserIdFromDataStoreUseCase @Inject constructor( + private val localUserRepository: LocalUserRepository +) { + operator fun invoke() = localUserRepository.readUserIdDataStore() +} diff --git a/domain/user/src/main/java/com/example/user/usecase/UpdateUserNameUseCase.kt b/domain/user/src/main/java/com/example/user/usecase/UpdateUserNameUseCase.kt new file mode 100644 index 00000000..7d3c9c96 --- /dev/null +++ b/domain/user/src/main/java/com/example/user/usecase/UpdateUserNameUseCase.kt @@ -0,0 +1,11 @@ +package com.example.user.usecase + +import com.example.user.FirebaseUserRepository +import javax.inject.Inject + +class UpdateUserNameUseCase @Inject constructor( + private val firebaseUserRepository: FirebaseUserRepository +) { + suspend operator fun invoke(userId: String, newUserName: String) = + firebaseUserRepository.updateUserName(userId, newUserName) +} diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 81ca80e6..47edae0b 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -45,6 +45,7 @@ kotlinxImmutable = "0.3.7" jetbrainsKotlinJvm = "1.9.22" navigationCommonKtx = "2.8.5" runtimeAndroid = "1.7.6" +firebaseFirestoreKtxVersion = "25.1.2" [libraries] # AndroidX @@ -131,6 +132,7 @@ coil-network-okhttp = { module = "io.coil-kt.coil3:coil-network-okhttp", version androidx-paging-compose-android = { group = "androidx.paging", name = "paging-compose-android", version.ref = "pagingComposeAndroid" } androidx-navigation-common-ktx = { group = "androidx.navigation", name = "navigation-common-ktx", version.ref = "navigationCommonKtx" } androidx-runtime-android = { group = "androidx.compose.runtime", name = "runtime-android", version.ref = "runtimeAndroid" } +google-firebase-firestore-ktx = { group = "com.google.firebase", name = "firebase-firestore-ktx", version.ref = "firebaseFirestoreKtxVersion" } # Plugins [plugins] diff --git a/settings.gradle.kts b/settings.gradle.kts index 2aec6aad..0c14b9c2 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -32,4 +32,15 @@ include(":core:util") include(":core:common") include(":core:picklist") include(":core:mediaservice") -include(":core:firebase") +include(":domain:picklist") +include(":domain:applemusic") +include(":domain:favorite") +include(":domain:location") +include(":domain:order") +include(":domain:pick") +include(":domain:user") +include(":domain:player") +include(":data:applemusic") +include(":data:favorite") +include(":data:firebase") +include(":data:location") From 5aac499b705871e64375ba97df2b4dcac321b9f5 Mon Sep 17 00:00:00 2001 From: miller198 Date: Tue, 18 Feb 2025 20:01:23 +0900 Subject: [PATCH 13/62] =?UTF-8?q?[chore]=20data=20=EB=AA=A8=EB=93=88=20?= =?UTF-8?q?=EB=B6=84=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/main/java/com/example/model/User.kt | 3 +- .../{AppleMusicDi.kt => AppleMusicModule.kt} | 0 data/favorite/src/main/AndroidManifest.xml | 4 - .../FirebaseFavoriteDataSourceImpl.kt | 2 +- ...avoriteDi.kt => FirebaseFavoriteModule.kt} | 7 +- data/firebase/build.gradle.kts | 3 + .../firebase}/model/FirebaseFavorite.kt | 2 +- .../example/firebase/model/FirebasePick.kt | 28 ++ .../example/firebase/model/FirebaseUser.kt | 7 + .../java/com/example/firebase/model/Mapper.kt | 84 ++++++ data/location/build.gradle.kts | 16 +- .../order/LocalLocationRepositoryImpl.kt | 18 ++ .../com/example/order/di/LocationModule.kt | 18 ++ data/order/build.gradle.kts | 43 +++ data/order/consumer-rules.pro | 0 data/order/proguard-rules.pro | 21 ++ .../order/LocalPickListOrderRepositoryImpl.kt | 21 ++ .../java/com/example/order/di/OrderModule.kt | 18 ++ data/pick/build.gradle.kts | 48 +++ data/pick/consumer-rules.pro | 0 data/pick/proguard-rules.pro | 21 ++ .../pick/FirebasePickDataSourceImpl.kt | 281 ++++++++++++++++++ .../pick/FirebasePickRepositoryImpl.kt | 54 ++++ .../java/com/example/pick/di/PickModule.kt | 27 ++ data/user/build.gradle.kts | 50 ++++ data/user/consumer-rules.pro | 0 data/user/proguard-rules.pro | 21 ++ .../user/FirebaseUserDataSourceImpl.kt | 76 +++++ .../user/FirebaseUserRepositoryImpl.kt | 34 +++ .../example/user/LocalUserDataSourceImpl.kt | 53 ++++ .../example/user/LocalUserRepositoryImpl.kt | 34 +++ .../main/java/com/example/user/di/UserDi.kt | 42 +++ .../example/pick/FirebasePickDataSource.kt | 2 +- settings.gradle.kts | 3 + 34 files changed, 1024 insertions(+), 17 deletions(-) rename data/applemusic/src/main/java/com/example/applemusic/di/{AppleMusicDi.kt => AppleMusicModule.kt} (100%) delete mode 100644 data/favorite/src/main/AndroidManifest.xml rename data/favorite/src/main/java/com/example/favorite/di/{FirebaseFavoriteDi.kt => FirebaseFavoriteModule.kt} (83%) rename data/{favorite/src/main/java/com/example/favorite => firebase/src/main/java/com/example/firebase}/model/FirebaseFavorite.kt (87%) create mode 100644 data/firebase/src/main/java/com/example/firebase/model/FirebasePick.kt create mode 100644 data/firebase/src/main/java/com/example/firebase/model/FirebaseUser.kt create mode 100644 data/firebase/src/main/java/com/example/firebase/model/Mapper.kt create mode 100644 data/location/src/main/java/com/example/order/LocalLocationRepositoryImpl.kt create mode 100644 data/location/src/main/java/com/example/order/di/LocationModule.kt create mode 100644 data/order/build.gradle.kts create mode 100644 data/order/consumer-rules.pro create mode 100644 data/order/proguard-rules.pro create mode 100644 data/order/src/main/java/com/example/order/LocalPickListOrderRepositoryImpl.kt create mode 100644 data/order/src/main/java/com/example/order/di/OrderModule.kt create mode 100644 data/pick/build.gradle.kts create mode 100644 data/pick/consumer-rules.pro create mode 100644 data/pick/proguard-rules.pro create mode 100644 data/pick/src/main/java/com/example/pick/FirebasePickDataSourceImpl.kt create mode 100644 data/pick/src/main/java/com/example/pick/FirebasePickRepositoryImpl.kt create mode 100644 data/pick/src/main/java/com/example/pick/di/PickModule.kt create mode 100644 data/user/build.gradle.kts create mode 100644 data/user/consumer-rules.pro create mode 100644 data/user/proguard-rules.pro create mode 100644 data/user/src/main/java/com/example/user/FirebaseUserDataSourceImpl.kt create mode 100644 data/user/src/main/java/com/example/user/FirebaseUserRepositoryImpl.kt create mode 100644 data/user/src/main/java/com/example/user/LocalUserDataSourceImpl.kt create mode 100644 data/user/src/main/java/com/example/user/LocalUserRepositoryImpl.kt create mode 100644 data/user/src/main/java/com/example/user/di/UserDi.kt diff --git a/core/model/src/main/java/com/example/model/User.kt b/core/model/src/main/java/com/example/model/User.kt index ca29f4f3..22eb9bb4 100644 --- a/core/model/src/main/java/com/example/model/User.kt +++ b/core/model/src/main/java/com/example/model/User.kt @@ -3,5 +3,6 @@ package com.example.model data class User( val userId: String, val userName: String, - val myPicks: List + val userProfileImage: String?, + val myPicks: List, ) diff --git a/data/applemusic/src/main/java/com/example/applemusic/di/AppleMusicDi.kt b/data/applemusic/src/main/java/com/example/applemusic/di/AppleMusicModule.kt similarity index 100% rename from data/applemusic/src/main/java/com/example/applemusic/di/AppleMusicDi.kt rename to data/applemusic/src/main/java/com/example/applemusic/di/AppleMusicModule.kt diff --git a/data/favorite/src/main/AndroidManifest.xml b/data/favorite/src/main/AndroidManifest.xml deleted file mode 100644 index 8bdb7e14..00000000 --- a/data/favorite/src/main/AndroidManifest.xml +++ /dev/null @@ -1,4 +0,0 @@ - - - - diff --git a/data/favorite/src/main/java/com/example/favorite/FirebaseFavoriteDataSourceImpl.kt b/data/favorite/src/main/java/com/example/favorite/FirebaseFavoriteDataSourceImpl.kt index 9dba1cc3..a62df7dc 100644 --- a/data/favorite/src/main/java/com/example/favorite/FirebaseFavoriteDataSourceImpl.kt +++ b/data/favorite/src/main/java/com/example/favorite/FirebaseFavoriteDataSourceImpl.kt @@ -1,10 +1,10 @@ package com.example.favorite import android.util.Log -import com.example.favorite.model.FirebaseFavorite import com.example.firebase.FirebaseDataSourceConstants.COLLECTION_FAVORITES import com.example.firebase.FirebaseDataSourceConstants.FIELD_PICK_ID import com.example.firebase.FirebaseDataSourceConstants.FIELD_USER_ID +import com.example.firebase.model.FirebaseFavorite import com.google.firebase.firestore.FirebaseFirestore import com.google.firebase.firestore.QuerySnapshot import kotlinx.coroutines.CoroutineScope diff --git a/data/favorite/src/main/java/com/example/favorite/di/FirebaseFavoriteDi.kt b/data/favorite/src/main/java/com/example/favorite/di/FirebaseFavoriteModule.kt similarity index 83% rename from data/favorite/src/main/java/com/example/favorite/di/FirebaseFavoriteDi.kt rename to data/favorite/src/main/java/com/example/favorite/di/FirebaseFavoriteModule.kt index f588ffbf..0f3e0900 100644 --- a/data/favorite/src/main/java/com/example/favorite/di/FirebaseFavoriteDi.kt +++ b/data/favorite/src/main/java/com/example/favorite/di/FirebaseFavoriteModule.kt @@ -14,7 +14,7 @@ import javax.inject.Singleton @Module @InstallIn(SingletonComponent::class) -object FirebaseFavoriteDi { +object FirebaseFavoriteModule { @Provides @Singleton @@ -23,7 +23,10 @@ object FirebaseFavoriteDi { @Provides @Singleton - fun provideFirebaseFavoriteDataSource(db: FirebaseFirestore, cloudFunctionHelper: CloudFunctionHelper): FirebaseFavoriteDataSource = + fun provideFirebaseFavoriteDataSource( + db: FirebaseFirestore, + cloudFunctionHelper: CloudFunctionHelper + ): FirebaseFavoriteDataSource = FirebaseFavoriteDataSourceImpl(db, cloudFunctionHelper) @Provides diff --git a/data/firebase/build.gradle.kts b/data/firebase/build.gradle.kts index 010b3f7b..5dfff9d4 100644 --- a/data/firebase/build.gradle.kts +++ b/data/firebase/build.gradle.kts @@ -61,6 +61,8 @@ android { } dependencies { + implementation(projects.core.model) + // hilt implementation(libs.hilt.android) implementation(libs.firebase.functions.ktx) @@ -69,4 +71,5 @@ dependencies { // Firebase implementation(libs.google.firebase.firestore.ktx) + implementation(libs.geofire.android.common) } diff --git a/data/favorite/src/main/java/com/example/favorite/model/FirebaseFavorite.kt b/data/firebase/src/main/java/com/example/firebase/model/FirebaseFavorite.kt similarity index 87% rename from data/favorite/src/main/java/com/example/favorite/model/FirebaseFavorite.kt rename to data/firebase/src/main/java/com/example/firebase/model/FirebaseFavorite.kt index 59b06f9e..926c8189 100644 --- a/data/favorite/src/main/java/com/example/favorite/model/FirebaseFavorite.kt +++ b/data/firebase/src/main/java/com/example/firebase/model/FirebaseFavorite.kt @@ -1,4 +1,4 @@ -package com.example.favorite.model +package com.example.firebase.model import com.google.firebase.Timestamp import com.google.firebase.firestore.ServerTimestamp diff --git a/data/firebase/src/main/java/com/example/firebase/model/FirebasePick.kt b/data/firebase/src/main/java/com/example/firebase/model/FirebasePick.kt new file mode 100644 index 00000000..297cbeb3 --- /dev/null +++ b/data/firebase/src/main/java/com/example/firebase/model/FirebasePick.kt @@ -0,0 +1,28 @@ +package com.example.firebase.model + +import com.google.firebase.Timestamp +import com.google.firebase.firestore.GeoPoint +import com.google.firebase.firestore.ServerTimestamp + +/** + * Firestore에 저장된 pick document를 불러와 변환하기위한 데이터 클래스 + */ +data class FirebasePick( + val id: String? = null, + val albumName: String? = null, + val artistName: String? = null, + val artwork: Map? = null, + val comment: String? = null, + @ServerTimestamp val createdAt: Timestamp? = null, // 등록 시 자동으로 서버 시간으로 설정되도록 합니다 + val createdBy: Map? = null, + val externalUrl: String? = null, + val favoriteCount: Int = 0, + val genreNames: List = emptyList(), + val geoHash: String? = null, + val location: GeoPoint? = null, + val previewUrl: String? = null, + val musicVideoUrl: String? = null, + val musicVideoThumbnail: String? = null, + val songId: String? = null, + val songName: String? = null, +) diff --git a/data/firebase/src/main/java/com/example/firebase/model/FirebaseUser.kt b/data/firebase/src/main/java/com/example/firebase/model/FirebaseUser.kt new file mode 100644 index 00000000..cfa64956 --- /dev/null +++ b/data/firebase/src/main/java/com/example/firebase/model/FirebaseUser.kt @@ -0,0 +1,7 @@ +package com.example.firebase.model + +data class FirebaseUser( + val name: String? = null, + val profileImage: String? = null, + val myPicks: List = emptyList() +) diff --git a/data/firebase/src/main/java/com/example/firebase/model/Mapper.kt b/data/firebase/src/main/java/com/example/firebase/model/Mapper.kt new file mode 100644 index 00000000..0aec15cc --- /dev/null +++ b/data/firebase/src/main/java/com/example/firebase/model/Mapper.kt @@ -0,0 +1,84 @@ +package com.example.firebase.model + +import androidx.core.graphics.toColorInt +import com.example.model.Creator +import com.example.model.LocationPoint +import com.example.model.Pick +import com.example.model.Song +import com.example.model.User +import com.firebase.geofire.GeoFireUtils +import com.firebase.geofire.GeoLocation +import com.google.firebase.firestore.GeoPoint +import java.text.SimpleDateFormat +import java.util.Date +import java.util.Locale + +fun Pick.toFirebasePick(): FirebasePick = FirebasePick( + id = id, + albumName = song.albumName, + artistName = song.artistName, + artwork = mapOf("url" to song.imageUrl, "bgColor" to song.bgColor.toRgbString()), + comment = comment, + createdBy = mapOf("userId" to createdBy.userId, "userName" to createdBy.userName), + externalUrl = song.externalUrl, + favoriteCount = favoriteCount, + genreNames = song.genreNames, + geoHash = location.toGeoHash(), + location = GeoPoint(location.latitude, location.longitude), + previewUrl = song.previewUrl, + musicVideoUrl = musicVideoUrl, + musicVideoThumbnail = musicVideoThumbnailUrl, + songId = song.id, + songName = song.songName, +) + +fun FirebasePick.toPick(): Pick = Pick( + id = id.toString(), + song = Song( + id = songId.toString(), + songName = songName.toString(), + artistName = artistName.toString(), + albumName = albumName.toString(), + imageUrl = artwork?.get("url") ?: "", + genreNames = genreNames, + bgColor = artwork?.get("bgColor")?.let { + "#${it}".toColorInt() + } ?: "#000000".toColorInt(), + externalUrl = externalUrl.toString(), + previewUrl = previewUrl.toString(), + ), + comment = comment.toString(), + favoriteCount = favoriteCount, + createdBy = Creator( + userId = createdBy?.get("userId") ?: "", + userName = createdBy?.get("userName") ?: "" + ), + createdAt = createdAt?.toDate()?.formatTimestamp() ?: "", + location = LocationPoint( + latitude = location?.latitude ?: 0.0, + longitude = location?.longitude ?: 0.0 + ), + musicVideoUrl = musicVideoUrl ?: "", + musicVideoThumbnailUrl = musicVideoThumbnail ?: "" +) + +private fun Int.toRgbString(): String { + return String.format("%06X", 0xFFFFFF and this) +} + +private fun LocationPoint.toGeoHash(): String { + val geoLocation = GeoLocation(this.latitude, this.longitude) + return GeoFireUtils.getGeoHashForLocation(geoLocation) +} + +private fun Date.formatTimestamp(): String { + val dateFormat = SimpleDateFormat("yyyy.MM.dd", Locale.getDefault()) + return dateFormat.format(this) +} + +fun FirebaseUser.toUser(): User = User( + userId = "", + userName = name ?: "", + userProfileImage = profileImage, + myPicks = myPicks +) diff --git a/data/location/build.gradle.kts b/data/location/build.gradle.kts index 83a77750..6a34658a 100644 --- a/data/location/build.gradle.kts +++ b/data/location/build.gradle.kts @@ -1,6 +1,8 @@ plugins { alias(libs.plugins.android.library) alias(libs.plugins.jetbrains.kotlin.android) + alias(libs.plugins.ksp) + alias(libs.plugins.hilt) } android { @@ -30,11 +32,11 @@ android { } dependencies { - - implementation(libs.androidx.core.ktx) - implementation(libs.androidx.appcompat) - implementation(libs.material) - testImplementation(libs.junit) - androidTestImplementation(libs.androidx.junit) - androidTestImplementation(libs.androidx.espresso.core) + implementation(projects.domain.location) + + // hilt + implementation(libs.hilt.android) + implementation(libs.firebase.functions.ktx) + ksp(libs.hilt.android.compiler) + implementation(libs.inject) } diff --git a/data/location/src/main/java/com/example/order/LocalLocationRepositoryImpl.kt b/data/location/src/main/java/com/example/order/LocalLocationRepositoryImpl.kt new file mode 100644 index 00000000..ae164412 --- /dev/null +++ b/data/location/src/main/java/com/example/order/LocalLocationRepositoryImpl.kt @@ -0,0 +1,18 @@ +package com.example.order + +import android.location.Location +import com.example.location.LocalLocationRepository +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.StateFlow +import kotlinx.coroutines.flow.asStateFlow +import javax.inject.Singleton + +@Singleton +class LocalLocationRepositoryImpl : LocalLocationRepository { + private var _currentLocation: MutableStateFlow = MutableStateFlow(null) + override val lastLocation: StateFlow = _currentLocation.asStateFlow() + + override suspend fun saveCurrentLocation(geoLocation: Location) { + _currentLocation.emit(geoLocation) + } +} diff --git a/data/location/src/main/java/com/example/order/di/LocationModule.kt b/data/location/src/main/java/com/example/order/di/LocationModule.kt new file mode 100644 index 00000000..9bb10b6e --- /dev/null +++ b/data/location/src/main/java/com/example/order/di/LocationModule.kt @@ -0,0 +1,18 @@ +package com.example.order.di + +import com.example.location.LocalLocationRepository +import com.example.order.LocalLocationRepositoryImpl +import dagger.Module +import dagger.Provides +import dagger.hilt.InstallIn +import dagger.hilt.components.SingletonComponent +import javax.inject.Singleton + +@Module +@InstallIn(SingletonComponent::class) +object LocationModule { + @Provides + @Singleton + fun provideLocalLocationRepository(): LocalLocationRepository = + LocalLocationRepositoryImpl() +} diff --git a/data/order/build.gradle.kts b/data/order/build.gradle.kts new file mode 100644 index 00000000..f4df2cac --- /dev/null +++ b/data/order/build.gradle.kts @@ -0,0 +1,43 @@ +plugins { + alias(libs.plugins.android.library) + alias(libs.plugins.jetbrains.kotlin.android) + alias(libs.plugins.ksp) + alias(libs.plugins.hilt) +} + +android { + namespace = "com.example.order" + compileSdk = 34 + + defaultConfig { + minSdk = 26 + + testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner" + consumerProguardFiles("consumer-rules.pro") + } + + buildTypes { + release { + isMinifyEnabled = false + proguardFiles(getDefaultProguardFile("proguard-android-optimize.txt"), "proguard-rules.pro") + } + } + compileOptions { + sourceCompatibility = JavaVersion.VERSION_1_8 + targetCompatibility = JavaVersion.VERSION_1_8 + } + kotlinOptions { + jvmTarget = "1.8" + } +} + +dependencies { + implementation(projects.core.model) + implementation(projects.domain.order) + + // hilt + implementation(libs.hilt.android) + implementation(libs.firebase.functions.ktx) + ksp(libs.hilt.android.compiler) + implementation(libs.inject) +} diff --git a/data/order/consumer-rules.pro b/data/order/consumer-rules.pro new file mode 100644 index 00000000..e69de29b diff --git a/data/order/proguard-rules.pro b/data/order/proguard-rules.pro new file mode 100644 index 00000000..481bb434 --- /dev/null +++ b/data/order/proguard-rules.pro @@ -0,0 +1,21 @@ +# Add project specific ProGuard rules here. +# You can control the set of applied configuration files using the +# proguardFiles setting in build.gradle. +# +# For more details, see +# http://developer.android.com/guide/developing/tools/proguard.html + +# If your project uses WebView with JS, uncomment the following +# and specify the fully qualified class name to the JavaScript interface +# class: +#-keepclassmembers class fqcn.of.javascript.interface.for.webview { +# public *; +#} + +# Uncomment this to preserve the line number information for +# debugging stack traces. +#-keepattributes SourceFile,LineNumberTable + +# If you keep the line number information, uncomment this to +# hide the original source file name. +#-renamesourcefileattribute SourceFile \ No newline at end of file diff --git a/data/order/src/main/java/com/example/order/LocalPickListOrderRepositoryImpl.kt b/data/order/src/main/java/com/example/order/LocalPickListOrderRepositoryImpl.kt new file mode 100644 index 00000000..3e714887 --- /dev/null +++ b/data/order/src/main/java/com/example/order/LocalPickListOrderRepositoryImpl.kt @@ -0,0 +1,21 @@ +package com.example.order + +import com.example.model.Order +import javax.inject.Singleton + +@Singleton +class LocalPickListOrderRepositoryImpl : LocalPickListOrderRepository { + private var _favoriteListOrder = Order.LATEST + override val favoriteListOrder get() = _favoriteListOrder + + private var _myListOrder = Order.LATEST + override val myListOrder get() = _myListOrder + + override suspend fun saveFavoriteListOrder(order: Order) { + _favoriteListOrder = order + } + + override suspend fun saveMyListOrder(order: Order) { + _myListOrder = order + } +} diff --git a/data/order/src/main/java/com/example/order/di/OrderModule.kt b/data/order/src/main/java/com/example/order/di/OrderModule.kt new file mode 100644 index 00000000..7b66f651 --- /dev/null +++ b/data/order/src/main/java/com/example/order/di/OrderModule.kt @@ -0,0 +1,18 @@ +package com.example.order.di + +import com.example.order.LocalPickListOrderRepository +import com.example.order.LocalPickListOrderRepositoryImpl +import dagger.Module +import dagger.Provides +import dagger.hilt.InstallIn +import dagger.hilt.components.SingletonComponent +import javax.inject.Singleton + +@Module +@InstallIn(SingletonComponent::class) +object OrderModule { + @Provides + @Singleton + fun provideLocalPickListOrderRepository(): LocalPickListOrderRepository = + LocalPickListOrderRepositoryImpl() +} diff --git a/data/pick/build.gradle.kts b/data/pick/build.gradle.kts new file mode 100644 index 00000000..829de00f --- /dev/null +++ b/data/pick/build.gradle.kts @@ -0,0 +1,48 @@ +plugins { + alias(libs.plugins.android.library) + alias(libs.plugins.jetbrains.kotlin.android) + alias(libs.plugins.ksp) + alias(libs.plugins.hilt) +} + +android { + namespace = "com.example.pick" + compileSdk = 34 + + defaultConfig { + minSdk = 26 + + testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner" + consumerProguardFiles("consumer-rules.pro") + } + + buildTypes { + release { + isMinifyEnabled = false + proguardFiles(getDefaultProguardFile("proguard-android-optimize.txt"), "proguard-rules.pro") + } + } + compileOptions { + sourceCompatibility = JavaVersion.VERSION_1_8 + targetCompatibility = JavaVersion.VERSION_1_8 + } + kotlinOptions { + jvmTarget = "1.8" + } +} + +dependencies { + implementation(projects.core.model) + implementation(projects.domain.pick) + implementation(projects.data.firebase) + + // hilt + implementation(libs.hilt.android) + implementation(libs.firebase.functions.ktx) + ksp(libs.hilt.android.compiler) + implementation(libs.inject) + + // Firebase + implementation(libs.google.firebase.firestore.ktx) + implementation(libs.geofire.android.common) +} diff --git a/data/pick/consumer-rules.pro b/data/pick/consumer-rules.pro new file mode 100644 index 00000000..e69de29b diff --git a/data/pick/proguard-rules.pro b/data/pick/proguard-rules.pro new file mode 100644 index 00000000..481bb434 --- /dev/null +++ b/data/pick/proguard-rules.pro @@ -0,0 +1,21 @@ +# Add project specific ProGuard rules here. +# You can control the set of applied configuration files using the +# proguardFiles setting in build.gradle. +# +# For more details, see +# http://developer.android.com/guide/developing/tools/proguard.html + +# If your project uses WebView with JS, uncomment the following +# and specify the fully qualified class name to the JavaScript interface +# class: +#-keepclassmembers class fqcn.of.javascript.interface.for.webview { +# public *; +#} + +# Uncomment this to preserve the line number information for +# debugging stack traces. +#-keepattributes SourceFile,LineNumberTable + +# If you keep the line number information, uncomment this to +# hide the original source file name. +#-renamesourcefileattribute SourceFile \ No newline at end of file diff --git a/data/pick/src/main/java/com/example/pick/FirebasePickDataSourceImpl.kt b/data/pick/src/main/java/com/example/pick/FirebasePickDataSourceImpl.kt new file mode 100644 index 00000000..0e1a1894 --- /dev/null +++ b/data/pick/src/main/java/com/example/pick/FirebasePickDataSourceImpl.kt @@ -0,0 +1,281 @@ +package com.example.pick + +import android.util.Log +import com.example.firebase.FirebaseDataSourceConstants.COLLECTION_FAVORITES +import com.example.firebase.FirebaseDataSourceConstants.COLLECTION_PICKS +import com.example.firebase.FirebaseDataSourceConstants.COLLECTION_USERS +import com.example.firebase.FirebaseDataSourceConstants.FIELD_ADDED_AT +import com.example.firebase.FirebaseDataSourceConstants.FIELD_MY_PICKS +import com.example.firebase.FirebaseDataSourceConstants.FIELD_PICK_ID +import com.example.firebase.FirebaseDataSourceConstants.FIELD_USER_ID +import com.example.firebase.FirebaseDataSourceConstants.TAG_LOG +import com.example.firebase.model.FirebasePick +import com.example.firebase.model.FirebaseUser +import com.example.firebase.model.toFirebasePick +import com.example.firebase.model.toPick +import com.example.model.Pick +import com.firebase.geofire.GeoFireUtils +import com.firebase.geofire.GeoLocation +import com.google.android.gms.tasks.Task +import com.google.android.gms.tasks.Tasks +import com.google.firebase.firestore.DocumentReference +import com.google.firebase.firestore.DocumentSnapshot +import com.google.firebase.firestore.FieldValue +import com.google.firebase.firestore.FirebaseFirestore +import com.google.firebase.firestore.Query +import com.google.firebase.firestore.QuerySnapshot +import com.google.firebase.firestore.toObject +import kotlinx.coroutines.suspendCancellableCoroutine +import kotlinx.coroutines.tasks.await +import javax.inject.Inject +import javax.inject.Singleton +import kotlin.coroutines.resume +import kotlin.coroutines.resumeWithException + +@Singleton +class FirebasePickDataSourceImpl @Inject constructor( + private val db: FirebaseFirestore +) : FirebasePickDataSource { + + /* Fetches a pick by ID from Firestore */ + override suspend fun fetchPick(pickID: String): Pick? { + return suspendCancellableCoroutine { continuation -> + db.collection("picks").document(pickID).get() + .addOnSuccessListener { document -> + val firestorePick = document.toObject()?.copy(id = pickID) + val resultPick = firestorePick?.toPick() + continuation.resume(resultPick) + } + .addOnFailureListener { exception -> + Log.e("FirebaseDataSourceImpl", "Failed to fetch a pick", exception) + continuation.resumeWithException(exception) + } + } + } + + /* Fetches picks within a given radius from Firestore */ + override suspend fun fetchPicksInArea( + lat: Double, + lng: Double, + radiusInM: Double + ): List { + val center = GeoLocation(lat, lng) + val bounds = GeoFireUtils.getGeoHashQueryBounds(center, radiusInM) + + val queries: MutableList = ArrayList() + val tasks: MutableList> = ArrayList() + val matchingPicks: MutableList = ArrayList() + + bounds.forEach { bound -> + val query = db.collection("picks") + .orderBy("geoHash") + .startAt(bound.startHash) + .endAt(bound.endHash) + queries.add(query) + } + + try { + queries.forEach { query -> + tasks.add(query.get()) + } + Tasks.whenAllComplete(tasks).await() + } catch (exception: Exception) { + Log.e("FirebaseDataSourceImpl", "Failed to fetch picks", exception) + throw exception + } + + tasks.forEach { task -> + val snap = task.result + snap.documents.forEach { doc -> + if (isAccurate(doc, center, radiusInM)) { + doc.toObject()?.run { + matchingPicks.add(this.toPick().copy(id = doc.id)) + } + } + } + } + + return matchingPicks + } + + /* Creates a new pick in Firestore */ + override suspend fun createPick(pick: Pick): String = + suspendCancellableCoroutine { continuation -> + val firebasePick = pick.toFirebasePick() + + // add() 메소드는 Cloud Firestore에서 ID를 자동으로 생성 + db.collection("picks").add(firebasePick) + .addOnSuccessListener { documentReference -> + val pickId = documentReference.id + // 유저의 픽 정보 업데이트 + updateCurrentUserPick(pick.createdBy.userId, pickId) + .addOnCompleteListener { task -> + if (task.isSuccessful) { + continuation.resume(pickId) + } else { + continuation.resumeWithException( + task.exception ?: Exception("Failed to updating user pick info") + ) + } + } + } + .addOnFailureListener { exception -> + Log.e("FirebaseDataSourceImpl", "Failed to create a pick", exception) + continuation.resumeWithException(exception) + } + } + + override suspend fun deletePick(pickId: String, userId: String): Boolean { + val pickDocument = db.collection(COLLECTION_PICKS).document(pickId) + val userDocument = db.collection(COLLECTION_USERS).document(userId) + val favoriteDocuments = fetchFavoriteDocuments(pickId) + + return suspendCancellableCoroutine { continuation -> + db.runTransaction { transaction -> + transaction.delete(pickDocument) + + favoriteDocuments.forEach { document -> + transaction.delete(document) + } + + transaction.update(userDocument, FIELD_MY_PICKS, FieldValue.arrayRemove(pickId)) + }.addOnSuccessListener { _ -> + continuation.resume(true) + }.addOnFailureListener { e -> + Log.w(TAG_LOG, "Transaction failure.", e) + continuation.resumeWithException(e) + } + } + } + + override suspend fun fetchMyPicks(userId: String): List { + val userDocument = fetchUserDocument(userId) + if (userDocument.exists().not()) throw Exception("No user info in database") + + val tasks = mutableListOf>() + val myPicks = mutableListOf() + + try { + userDocument.toObject()?.myPicks?.forEach { pickId -> + tasks.add( + db.collection(COLLECTION_PICKS) + .document(pickId) + .get() + ) + } + Tasks.whenAllComplete(tasks).await() + } catch (exception: Exception) { + Log.e("FirebaseDataSourceImpl", "Failed to fetch my picks", exception) + throw exception + } + + tasks.forEach { task -> + task.result.toObject()?.run { + myPicks.add(this.toPick().copy(id = task.result.id)) + } + } + + return myPicks.reversed() + } + + private fun updateCurrentUserPick(userId: String, pickId: String): Task { + val userDoc = db.collection("users").document(userId) + return userDoc.update("myPicks", FieldValue.arrayUnion(pickId)) + } + + private suspend fun fetchFavoriteDocuments(pickId: String): List { + return suspendCancellableCoroutine { continuation -> + db.collection(COLLECTION_FAVORITES) + .whereEqualTo(FIELD_PICK_ID, pickId) + .get() + .addOnSuccessListener { querySnapShot -> + val documentIds = querySnapShot.documents.map { it.id } + val documentRefs = mutableListOf() + documentIds.forEach { id -> + documentRefs.add(db.collection(COLLECTION_FAVORITES).document(id)) + } + continuation.resume(documentRefs) + } + .addOnFailureListener { e -> + Log.w(TAG_LOG, "Failed to fetch favorite documents id", e) + continuation.resumeWithException(e) + } + } + } + + private suspend fun fetchUserDocument(userId: String): DocumentSnapshot { + return suspendCancellableCoroutine { continuation -> + db.collection(COLLECTION_USERS).document(userId) + .get() + .addOnSuccessListener { document -> + continuation.resume(document) + } + .addOnFailureListener { exception -> + Log.e("FirebaseDataSourceImpl", "Failed to get user document", exception) + continuation.resumeWithException(exception) + } + } + } + + override suspend fun fetchFavoritePicks(userId: String): List { + val favoriteDocuments = fetchFavoritesByUserId(userId) + + val tasks = mutableListOf>() + val favorites = mutableListOf() + + try { + favoriteDocuments.forEach { doc -> + tasks.add( + db.collection(COLLECTION_PICKS) + .document(doc.data[FIELD_PICK_ID].toString()) + .get() + ) + } + Tasks.whenAllComplete(tasks).await() + } catch (exception: Exception) { + Log.e("FirebaseDataSourceImpl", "Failed to get favorite picks", exception) + throw exception + } + tasks.forEach { task -> + task.result.toObject()?.run { + favorites.add(this.toPick().copy(id = task.result.id)) + } + } + + return favorites + } + + /** + * GeoHash의 FP 문제 - Geohash의 쿼리가 정확하지 않으며 클라이언트 측에서 거짓양성 결과를 필터링해야 합니다. + * 이러한 추가 읽기로 인해 앱에 비용과 지연 시간이 추가됩니다. + */ + private fun isAccurate(doc: DocumentSnapshot, center: GeoLocation, radiusInM: Double): Boolean { + val location = doc.getGeoPoint("location") ?: return false + + val docLocation = GeoLocation(location.latitude, location.longitude) + val distanceInM = GeoFireUtils.getDistanceBetween(docLocation, center) + + return distanceInM <= radiusInM + } + + private suspend fun fetchFavoritesByUserId(userId: String): QuerySnapshot { + val query = db.collection(COLLECTION_FAVORITES) + .whereEqualTo(FIELD_USER_ID, userId) + .orderBy(FIELD_ADDED_AT, Query.Direction.DESCENDING) + + return executeQuery(query) + } + + private suspend fun executeQuery(query: Query): QuerySnapshot { + return suspendCancellableCoroutine { continuation -> + query.get() + .addOnSuccessListener { result -> + continuation.resume(result) + } + .addOnFailureListener { exception -> + Log.w("FirebaseDataSourceImpl", "Error fetching favorite documents", exception) + continuation.resumeWithException(exception) + } + } + } +} diff --git a/data/pick/src/main/java/com/example/pick/FirebasePickRepositoryImpl.kt b/data/pick/src/main/java/com/example/pick/FirebasePickRepositoryImpl.kt new file mode 100644 index 00000000..d91b1dc8 --- /dev/null +++ b/data/pick/src/main/java/com/example/pick/FirebasePickRepositoryImpl.kt @@ -0,0 +1,54 @@ +package com.example.pick + +import com.example.firebase.FirebaseException +import com.example.firebase.handleResult +import com.example.model.Pick +import javax.inject.Inject +import javax.inject.Singleton + +@Singleton +class FirebasePickRepositoryImpl @Inject constructor( + private val pickDataSource: FirebasePickDataSource +) : FirebasePickRepository { + + override suspend fun createPick(pick: Pick): Result { + return handleResult { + pickDataSource.createPick(pick) + } + } + + override suspend fun deletePick(pickId: String, userId: String): Result { + return handleResult { + pickDataSource.deletePick(pickId, userId) + } + } + + override suspend fun fetchPick(pickID: String): Result { + return handleResult(FirebaseException.NoSuchPickException()) { + pickDataSource.fetchPick(pickID) + } + } + + override suspend fun fetchMyPicks(userId: String): Result> { + return handleResult { + pickDataSource.fetchMyPicks(userId) + } + } + + override suspend fun fetchPicksInArea( + lat: Double, + lng: Double, + radiusInM: Double + ): Result> { + val pickList = pickDataSource.fetchPicksInArea(lat, lng, radiusInM) + return handleResult(FirebaseException.NoSuchPickInRadiusException()) { + pickList.ifEmpty { null } + } + } + + override suspend fun fetchFavoritePicks(userId: String): Result> { + return handleResult { + pickDataSource.fetchFavoritePicks(userId) + } + } +} diff --git a/data/pick/src/main/java/com/example/pick/di/PickModule.kt b/data/pick/src/main/java/com/example/pick/di/PickModule.kt new file mode 100644 index 00000000..dbe42d44 --- /dev/null +++ b/data/pick/src/main/java/com/example/pick/di/PickModule.kt @@ -0,0 +1,27 @@ +package com.example.pick.di + +import com.example.pick.FirebasePickDataSource +import com.example.pick.FirebasePickDataSourceImpl +import com.example.pick.FirebasePickRepository +import com.example.pick.FirebasePickRepositoryImpl +import com.google.firebase.firestore.FirebaseFirestore +import dagger.Module +import dagger.Provides +import dagger.hilt.InstallIn +import dagger.hilt.components.SingletonComponent +import javax.inject.Singleton + +@Module +@InstallIn(SingletonComponent::class) +object PickModule { + + @Provides + @Singleton + fun provideFirebasePickRepository(firebasePickDataSource: FirebasePickDataSource): FirebasePickRepository = + FirebasePickRepositoryImpl(firebasePickDataSource) + + @Provides + @Singleton + fun provideFirebasePickDataSource(db: FirebaseFirestore): FirebasePickDataSource = + FirebasePickDataSourceImpl(db) +} diff --git a/data/user/build.gradle.kts b/data/user/build.gradle.kts new file mode 100644 index 00000000..42e5cbd3 --- /dev/null +++ b/data/user/build.gradle.kts @@ -0,0 +1,50 @@ +plugins { + alias(libs.plugins.android.library) + alias(libs.plugins.jetbrains.kotlin.android) + alias(libs.plugins.ksp) + alias(libs.plugins.hilt) +} + +android { + namespace = "com.example.user" + compileSdk = 34 + + defaultConfig { + minSdk = 26 + + testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner" + consumerProguardFiles("consumer-rules.pro") + } + + buildTypes { + release { + isMinifyEnabled = false + proguardFiles(getDefaultProguardFile("proguard-android-optimize.txt"), "proguard-rules.pro") + } + } + compileOptions { + sourceCompatibility = JavaVersion.VERSION_1_8 + targetCompatibility = JavaVersion.VERSION_1_8 + } + kotlinOptions { + jvmTarget = "1.8" + } +} + +dependencies { + implementation(projects.core.model) + implementation(projects.domain.user) + implementation(projects.data.firebase) + + // Datastore + implementation(libs.androidx.datastore.preferences) + + // hilt + implementation(libs.hilt.android) + implementation(libs.firebase.functions.ktx) + ksp(libs.hilt.android.compiler) + implementation(libs.inject) + + // firebase + implementation(libs.google.firebase.firestore.ktx) +} diff --git a/data/user/consumer-rules.pro b/data/user/consumer-rules.pro new file mode 100644 index 00000000..e69de29b diff --git a/data/user/proguard-rules.pro b/data/user/proguard-rules.pro new file mode 100644 index 00000000..481bb434 --- /dev/null +++ b/data/user/proguard-rules.pro @@ -0,0 +1,21 @@ +# Add project specific ProGuard rules here. +# You can control the set of applied configuration files using the +# proguardFiles setting in build.gradle. +# +# For more details, see +# http://developer.android.com/guide/developing/tools/proguard.html + +# If your project uses WebView with JS, uncomment the following +# and specify the fully qualified class name to the JavaScript interface +# class: +#-keepclassmembers class fqcn.of.javascript.interface.for.webview { +# public *; +#} + +# Uncomment this to preserve the line number information for +# debugging stack traces. +#-keepattributes SourceFile,LineNumberTable + +# If you keep the line number information, uncomment this to +# hide the original source file name. +#-renamesourcefileattribute SourceFile \ No newline at end of file diff --git a/data/user/src/main/java/com/example/user/FirebaseUserDataSourceImpl.kt b/data/user/src/main/java/com/example/user/FirebaseUserDataSourceImpl.kt new file mode 100644 index 00000000..8f73f9c7 --- /dev/null +++ b/data/user/src/main/java/com/example/user/FirebaseUserDataSourceImpl.kt @@ -0,0 +1,76 @@ +package com.example.user + +import android.util.Log +import com.example.firebase.model.FirebaseUser +import com.example.firebase.model.toUser +import com.example.model.User +import com.google.firebase.firestore.FirebaseFirestore +import com.google.firebase.firestore.toObject +import kotlinx.coroutines.suspendCancellableCoroutine +import javax.inject.Inject +import javax.inject.Singleton +import kotlin.coroutines.resume +import kotlin.coroutines.resumeWithException + +@Singleton +class FirebaseUserDataSourceImpl @Inject constructor( + private val db: FirebaseFirestore +) : FirebaseUserDataSource { + + override suspend fun createGoogleIdUser(userId: String, userName: String?, userProfileImage: String?): User? { + return suspendCancellableCoroutine { continuation -> + val documentReference = db.collection("users").document(userId) + documentReference.set(FirebaseUser(name = userName, profileImage = userProfileImage)) + .addOnSuccessListener { + documentReference.get() + .addOnSuccessListener { documentSnapshot -> + val savedUser = documentSnapshot.toObject() + continuation.resume( + savedUser?.toUser()?.copy(userId = documentReference.id) + ) + } + .addOnFailureListener { exception -> + continuation.resumeWithException(exception) + } + } + .addOnFailureListener { exception -> + Log.e("FirebaseDataSourceImpl", exception.message.toString()) + continuation.resumeWithException(exception) + } + } + } + + override suspend fun fetchUser(userId: String): User? { + return suspendCancellableCoroutine { continuation -> + db.collection("users").document(userId).get() + .addOnSuccessListener { document -> + val firebaseUser = document.toObject() + continuation.resume(firebaseUser?.toUser()?.copy(userId = userId)) + } + .addOnFailureListener { exception -> + continuation.resumeWithException(exception) + } + } + } + + override suspend fun updateUserName(userId: String, newUserName: String): Boolean { + return suspendCancellableCoroutine { continuation -> + db.runTransaction { transaction -> + val userRef = db.collection("users").document(userId) + val userDocument = transaction.get(userRef) + transaction.update(userRef, "name", newUserName) + + val myPicks = userDocument.get("myPicks")?.let { it as List } ?: emptyList() + myPicks.forEach { pickId -> + val pickRef = db.collection("picks").document(pickId) + transaction.update(pickRef, "createdBy.userName", newUserName) + } + }.addOnSuccessListener { + continuation.resume(true) + }.addOnFailureListener { exception -> + continuation.resumeWithException(exception) + } + } + } + +} diff --git a/data/user/src/main/java/com/example/user/FirebaseUserRepositoryImpl.kt b/data/user/src/main/java/com/example/user/FirebaseUserRepositoryImpl.kt new file mode 100644 index 00000000..95584647 --- /dev/null +++ b/data/user/src/main/java/com/example/user/FirebaseUserRepositoryImpl.kt @@ -0,0 +1,34 @@ +package com.example.user + +import com.example.firebase.FirebaseException +import com.example.firebase.handleResult +import com.example.model.User +import javax.inject.Singleton + +@Singleton +class FirebaseUserRepositoryImpl( + private val userDataSource: FirebaseUserDataSource +) : FirebaseUserRepository { + + override suspend fun createGoogleIdUser( + userId: String, + userName: String?, + userProfileImage: String? + ): Result { + return handleResult(FirebaseException.CreatedUserFailedException()) { + userDataSource.createGoogleIdUser(userId, userName, userProfileImage) + } + } + + override suspend fun fetchUser(userId: String): Result { + return handleResult(FirebaseException.UserNotFoundException()) { + userDataSource.fetchUser(userId) + } + } + + override suspend fun updateUserName(userId: String, newUserName: String): Result { + return handleResult { + userDataSource.updateUserName(userId, newUserName) + } + } +} diff --git a/data/user/src/main/java/com/example/user/LocalUserDataSourceImpl.kt b/data/user/src/main/java/com/example/user/LocalUserDataSourceImpl.kt new file mode 100644 index 00000000..1edc1cfa --- /dev/null +++ b/data/user/src/main/java/com/example/user/LocalUserDataSourceImpl.kt @@ -0,0 +1,53 @@ +package com.example.user + +import android.content.Context +import androidx.datastore.preferences.core.edit +import androidx.datastore.preferences.core.stringPreferencesKey +import androidx.datastore.preferences.preferencesDataStore +import com.example.model.User +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.map +import javax.inject.Inject +import javax.inject.Singleton + +@Singleton +class LocalUserDataSourceImpl @Inject constructor( + private val context: Context, +) : LocalUserDataSource { + private val Context.dataStore by preferencesDataStore(name = USER_PREFERENCES_NAME) + + private var _currentUser: User? = null + override val currentUser: User? + get() = _currentUser + + override fun readUserIdDataStore(): Flow { + val dataStoreKey = stringPreferencesKey(USER_ID_KEY) + return context.dataStore.data.map { preferences -> + preferences[dataStoreKey] + } + } + + override suspend fun saveUserIdDataStore(userId: String) { + val dataStoreKey = stringPreferencesKey(USER_ID_KEY) + context.dataStore.edit { preferences -> + preferences[dataStoreKey] = userId + } + } + + override suspend fun saveCurrentUser(user: User) { + _currentUser = user + } + + override suspend fun clearUser() { + val dataStoreKey = stringPreferencesKey(USER_ID_KEY) + context.dataStore.edit { preferences -> + preferences.remove(dataStoreKey) + } + _currentUser = null + } + + companion object { + private const val USER_PREFERENCES_NAME = "user_preferences" + private const val USER_ID_KEY = "user_id" + } +} diff --git a/data/user/src/main/java/com/example/user/LocalUserRepositoryImpl.kt b/data/user/src/main/java/com/example/user/LocalUserRepositoryImpl.kt new file mode 100644 index 00000000..84e7c625 --- /dev/null +++ b/data/user/src/main/java/com/example/user/LocalUserRepositoryImpl.kt @@ -0,0 +1,34 @@ +package com.example.user + +import com.example.model.User +import kotlinx.coroutines.flow.Flow +import javax.inject.Inject +import javax.inject.Singleton + +@Singleton +class LocalUserRepositoryImpl @Inject constructor( + private val userDataSource: LocalUserDataSource +) : LocalUserRepository { + override val currentUser get() = userDataSource.currentUser + + override fun readUserIdDataStore(): Flow { + return userDataSource.readUserIdDataStore() + } + + override suspend fun saveUserIdDataStore(userId: String) { + userDataSource.saveUserIdDataStore(userId) + } + + override suspend fun saveCurrentUser(user: User) { + userDataSource.saveCurrentUser(user) + } + + override suspend fun clearUser(): Result { + return try { + userDataSource.clearUser() + Result.success(Unit) + } catch (e: Exception) { + Result.failure(e) + } + } +} diff --git a/data/user/src/main/java/com/example/user/di/UserDi.kt b/data/user/src/main/java/com/example/user/di/UserDi.kt new file mode 100644 index 00000000..c209c3d1 --- /dev/null +++ b/data/user/src/main/java/com/example/user/di/UserDi.kt @@ -0,0 +1,42 @@ +package com.example.user.di + +import android.content.Context +import com.example.user.FirebaseUserDataSource +import com.example.user.FirebaseUserDataSourceImpl +import com.example.user.FirebaseUserRepository +import com.example.user.FirebaseUserRepositoryImpl +import com.example.user.LocalUserDataSource +import com.example.user.LocalUserDataSourceImpl +import com.example.user.LocalUserRepository +import com.example.user.LocalUserRepositoryImpl +import com.google.firebase.firestore.FirebaseFirestore +import dagger.Module +import dagger.Provides +import dagger.hilt.InstallIn +import dagger.hilt.android.qualifiers.ApplicationContext +import dagger.hilt.components.SingletonComponent +import javax.inject.Singleton + +@Module +@InstallIn(SingletonComponent::class) +object UserDi { + @Provides + @Singleton + fun provideLocalUserRepository(localUserDataSource: LocalUserDataSource): LocalUserRepository = + LocalUserRepositoryImpl(localUserDataSource) + + @Provides + @Singleton + fun provideLocalUserDataSource(@ApplicationContext context: Context): LocalUserDataSource = + LocalUserDataSourceImpl(context) + + @Provides + @Singleton + fun provideFirebaseUserRepository(firebaseUserDataSource: FirebaseUserDataSource): FirebaseUserRepository = + FirebaseUserRepositoryImpl(firebaseUserDataSource) + + @Provides + @Singleton + fun provideFirebaseUserDataSource(db: FirebaseFirestore): FirebaseUserDataSource = + FirebaseUserDataSourceImpl(db) +} diff --git a/domain/pick/src/main/java/com/example/pick/FirebasePickDataSource.kt b/domain/pick/src/main/java/com/example/pick/FirebasePickDataSource.kt index 2e6ba46a..be55c151 100644 --- a/domain/pick/src/main/java/com/example/pick/FirebasePickDataSource.kt +++ b/domain/pick/src/main/java/com/example/pick/FirebasePickDataSource.kt @@ -8,5 +8,5 @@ interface FirebasePickDataSource { suspend fun fetchPick(pickID: String): Pick? suspend fun fetchPicksInArea(lat: Double, lng: Double, radiusInM: Double): List suspend fun fetchMyPicks(userId: String): List - suspend fun fetchFavoritePicks(userId: String): Result> + suspend fun fetchFavoritePicks(userId: String): List } diff --git a/settings.gradle.kts b/settings.gradle.kts index 0c14b9c2..3dd56d3c 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -44,3 +44,6 @@ include(":data:applemusic") include(":data:favorite") include(":data:firebase") include(":data:location") +include(":data:order") +include(":data:pick") +include(":data:user") From 876c37ab1bd300f99d76dfce183a5739e71893d0 Mon Sep 17 00:00:00 2001 From: miller198 Date: Fri, 21 Feb 2025 12:31:37 +0900 Subject: [PATCH 14/62] =?UTF-8?q?[chore]=20core=20musicvideo,=20=ED=8C=A8?= =?UTF-8?q?=ED=82=A4=EC=A7=80=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../musicroad/detail/PickDetailScreen.kt | 2 +- .../videoplayer/MusicVideoPlayer.kt | 2 +- .../videoplayer/MusicVideoScreen.kt | 2 +- .../videoplayer/VideoPlayerOverlay.kt | 2 +- .../videoplayer/VideoPlayerState.kt | 4 +- .../videoplayer/VideoPlayerViewModel.kt | 2 +- core/mediaservice/.gitignore | 1 - core/mediaservice/build.gradle.kts | 1 - .../mediaservice/src/main/AndroidManifest.xml | 4 - core/musicplayer/build.gradle.kts | 47 ++++ core/musicplayer/consumer-rules.pro | 0 core/musicplayer/proguard-rules.pro | 21 ++ .../musicplayer/PlayerServiceViewModel.kt | 113 +++++++++ .../com/example/musicplayer/PlayerUiState.kt | 12 + .../example/musicplayer/PlayerViewModel.kt | 228 ++++++++++++++++++ settings.gradle.kts | 1 + 16 files changed, 429 insertions(+), 13 deletions(-) rename app/src/main/java/com/squirtles/musicroad/{ => detail}/videoplayer/MusicVideoPlayer.kt (98%) rename app/src/main/java/com/squirtles/musicroad/{ => detail}/videoplayer/MusicVideoScreen.kt (95%) rename app/src/main/java/com/squirtles/musicroad/{ => detail}/videoplayer/VideoPlayerOverlay.kt (99%) rename app/src/main/java/com/squirtles/musicroad/{ => detail}/videoplayer/VideoPlayerState.kt (52%) rename app/src/main/java/com/squirtles/musicroad/{ => detail}/videoplayer/VideoPlayerViewModel.kt (98%) delete mode 100644 core/mediaservice/.gitignore delete mode 100644 core/mediaservice/src/main/AndroidManifest.xml create mode 100644 core/musicplayer/build.gradle.kts create mode 100644 core/musicplayer/consumer-rules.pro create mode 100644 core/musicplayer/proguard-rules.pro create mode 100644 core/musicplayer/src/main/java/com/example/musicplayer/PlayerServiceViewModel.kt create mode 100644 core/musicplayer/src/main/java/com/example/musicplayer/PlayerUiState.kt create mode 100644 core/musicplayer/src/main/java/com/example/musicplayer/PlayerViewModel.kt diff --git a/app/src/main/java/com/squirtles/musicroad/detail/PickDetailScreen.kt b/app/src/main/java/com/squirtles/musicroad/detail/PickDetailScreen.kt index 51515725..f3315300 100644 --- a/app/src/main/java/com/squirtles/musicroad/detail/PickDetailScreen.kt +++ b/app/src/main/java/com/squirtles/musicroad/detail/PickDetailScreen.kt @@ -76,7 +76,7 @@ import com.squirtles.musicroad.media.PlayerServiceViewModel import com.squirtles.musicroad.ui.theme.Black import com.squirtles.musicroad.ui.theme.Primary import com.squirtles.musicroad.ui.theme.White -import com.squirtles.musicroad.videoplayer.MusicVideoScreen +import com.squirtles.musicroad.detail.videoplayer.MusicVideoScreen import kotlinx.coroutines.launch import kotlin.math.absoluteValue diff --git a/app/src/main/java/com/squirtles/musicroad/videoplayer/MusicVideoPlayer.kt b/app/src/main/java/com/squirtles/musicroad/detail/videoplayer/MusicVideoPlayer.kt similarity index 98% rename from app/src/main/java/com/squirtles/musicroad/videoplayer/MusicVideoPlayer.kt rename to app/src/main/java/com/squirtles/musicroad/detail/videoplayer/MusicVideoPlayer.kt index 0146b7d3..82295f3e 100644 --- a/app/src/main/java/com/squirtles/musicroad/videoplayer/MusicVideoPlayer.kt +++ b/app/src/main/java/com/squirtles/musicroad/detail/videoplayer/MusicVideoPlayer.kt @@ -1,4 +1,4 @@ -package com.squirtles.musicroad.videoplayer +package com.squirtles.musicroad.detail.videoplayer import android.graphics.Matrix import android.graphics.SurfaceTexture diff --git a/app/src/main/java/com/squirtles/musicroad/videoplayer/MusicVideoScreen.kt b/app/src/main/java/com/squirtles/musicroad/detail/videoplayer/MusicVideoScreen.kt similarity index 95% rename from app/src/main/java/com/squirtles/musicroad/videoplayer/MusicVideoScreen.kt rename to app/src/main/java/com/squirtles/musicroad/detail/videoplayer/MusicVideoScreen.kt index 3efab07e..75f4073f 100644 --- a/app/src/main/java/com/squirtles/musicroad/videoplayer/MusicVideoScreen.kt +++ b/app/src/main/java/com/squirtles/musicroad/detail/videoplayer/MusicVideoScreen.kt @@ -1,4 +1,4 @@ -package com.squirtles.musicroad.videoplayer +package com.squirtles.musicroad.detail.videoplayer import androidx.activity.compose.BackHandler import androidx.annotation.OptIn diff --git a/app/src/main/java/com/squirtles/musicroad/videoplayer/VideoPlayerOverlay.kt b/app/src/main/java/com/squirtles/musicroad/detail/videoplayer/VideoPlayerOverlay.kt similarity index 99% rename from app/src/main/java/com/squirtles/musicroad/videoplayer/VideoPlayerOverlay.kt rename to app/src/main/java/com/squirtles/musicroad/detail/videoplayer/VideoPlayerOverlay.kt index 5d927a02..c1f325d5 100644 --- a/app/src/main/java/com/squirtles/musicroad/videoplayer/VideoPlayerOverlay.kt +++ b/app/src/main/java/com/squirtles/musicroad/detail/videoplayer/VideoPlayerOverlay.kt @@ -1,4 +1,4 @@ -package com.squirtles.musicroad.videoplayer +package com.squirtles.musicroad.detail.videoplayer import androidx.compose.animation.core.Animatable import androidx.compose.animation.core.tween diff --git a/app/src/main/java/com/squirtles/musicroad/videoplayer/VideoPlayerState.kt b/app/src/main/java/com/squirtles/musicroad/detail/videoplayer/VideoPlayerState.kt similarity index 52% rename from app/src/main/java/com/squirtles/musicroad/videoplayer/VideoPlayerState.kt rename to app/src/main/java/com/squirtles/musicroad/detail/videoplayer/VideoPlayerState.kt index 2036fa3d..5103a1a8 100644 --- a/app/src/main/java/com/squirtles/musicroad/videoplayer/VideoPlayerState.kt +++ b/app/src/main/java/com/squirtles/musicroad/detail/videoplayer/VideoPlayerState.kt @@ -1,5 +1,5 @@ -package com.squirtles.musicroad.videoplayer +package com.squirtles.musicroad.detail.videoplayer enum class VideoPlayerState { Playing, Pause, Replay -} \ No newline at end of file +} diff --git a/app/src/main/java/com/squirtles/musicroad/videoplayer/VideoPlayerViewModel.kt b/app/src/main/java/com/squirtles/musicroad/detail/videoplayer/VideoPlayerViewModel.kt similarity index 98% rename from app/src/main/java/com/squirtles/musicroad/videoplayer/VideoPlayerViewModel.kt rename to app/src/main/java/com/squirtles/musicroad/detail/videoplayer/VideoPlayerViewModel.kt index d9493642..5f4acc52 100644 --- a/app/src/main/java/com/squirtles/musicroad/videoplayer/VideoPlayerViewModel.kt +++ b/app/src/main/java/com/squirtles/musicroad/detail/videoplayer/VideoPlayerViewModel.kt @@ -1,4 +1,4 @@ -package com.squirtles.musicroad.videoplayer +package com.squirtles.musicroad.detail.videoplayer import android.content.Context import android.util.Size diff --git a/core/mediaservice/.gitignore b/core/mediaservice/.gitignore deleted file mode 100644 index 42afabfd..00000000 --- a/core/mediaservice/.gitignore +++ /dev/null @@ -1 +0,0 @@ -/build \ No newline at end of file diff --git a/core/mediaservice/build.gradle.kts b/core/mediaservice/build.gradle.kts index 808de15f..399001d3 100644 --- a/core/mediaservice/build.gradle.kts +++ b/core/mediaservice/build.gradle.kts @@ -34,7 +34,6 @@ android { dependencies { implementation(libs.androidx.core.ktx) implementation(libs.androidx.appcompat) - implementation(libs.material) testImplementation(libs.junit) androidTestImplementation(libs.androidx.junit) androidTestImplementation(libs.androidx.espresso.core) diff --git a/core/mediaservice/src/main/AndroidManifest.xml b/core/mediaservice/src/main/AndroidManifest.xml deleted file mode 100644 index 8bdb7e14..00000000 --- a/core/mediaservice/src/main/AndroidManifest.xml +++ /dev/null @@ -1,4 +0,0 @@ - - - - diff --git a/core/musicplayer/build.gradle.kts b/core/musicplayer/build.gradle.kts new file mode 100644 index 00000000..b2216d58 --- /dev/null +++ b/core/musicplayer/build.gradle.kts @@ -0,0 +1,47 @@ +plugins { + alias(libs.plugins.android.library) + alias(libs.plugins.jetbrains.kotlin.android) + alias(libs.plugins.ksp) + alias(libs.plugins.hilt) +} + +android { + namespace = "com.example.musicplayer" + compileSdk = 34 + + defaultConfig { + minSdk = 26 + + testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner" + consumerProguardFiles("consumer-rules.pro") + } + + buildTypes { + release { + isMinifyEnabled = false + proguardFiles(getDefaultProguardFile("proguard-android-optimize.txt"), "proguard-rules.pro") + } + } + compileOptions { + sourceCompatibility = JavaVersion.VERSION_1_8 + targetCompatibility = JavaVersion.VERSION_1_8 + } + kotlinOptions { + jvmTarget = "1.8" + } +} + +dependencies { + implementation(projects.domain.player) + implementation(projects.core.model) + implementation(libs.androidx.lifecycle.runtime.ktx) + // Hilt + implementation(libs.hilt.android) + ksp(libs.hilt.android.compiler) + implementation(libs.inject) + + // ExoPlayer + implementation(libs.androidx.media3.exoplayer) + implementation(libs.androidx.media3.exoplayer.dash) + implementation(libs.androidx.media3.session) +} diff --git a/core/musicplayer/consumer-rules.pro b/core/musicplayer/consumer-rules.pro new file mode 100644 index 00000000..e69de29b diff --git a/core/musicplayer/proguard-rules.pro b/core/musicplayer/proguard-rules.pro new file mode 100644 index 00000000..481bb434 --- /dev/null +++ b/core/musicplayer/proguard-rules.pro @@ -0,0 +1,21 @@ +# Add project specific ProGuard rules here. +# You can control the set of applied configuration files using the +# proguardFiles setting in build.gradle. +# +# For more details, see +# http://developer.android.com/guide/developing/tools/proguard.html + +# If your project uses WebView with JS, uncomment the following +# and specify the fully qualified class name to the JavaScript interface +# class: +#-keepclassmembers class fqcn.of.javascript.interface.for.webview { +# public *; +#} + +# Uncomment this to preserve the line number information for +# debugging stack traces. +#-keepattributes SourceFile,LineNumberTable + +# If you keep the line number information, uncomment this to +# hide the original source file name. +#-renamesourcefileattribute SourceFile \ No newline at end of file diff --git a/core/musicplayer/src/main/java/com/example/musicplayer/PlayerServiceViewModel.kt b/core/musicplayer/src/main/java/com/example/musicplayer/PlayerServiceViewModel.kt new file mode 100644 index 00000000..768bc29e --- /dev/null +++ b/core/musicplayer/src/main/java/com/example/musicplayer/PlayerServiceViewModel.kt @@ -0,0 +1,113 @@ +package com.example.musicplayer + +import androidx.lifecycle.ViewModel +import androidx.lifecycle.viewModelScope +import com.example.model.Pick +import com.example.model.PlayerState +import com.example.model.Song +import com.example.player.MediaPlayerListenerUseCase +import com.example.player.MediaPlayerUseCase +import dagger.hilt.android.lifecycle.HiltViewModel +import kotlinx.coroutines.async +import kotlinx.coroutines.flow.SharingStarted +import kotlinx.coroutines.flow.StateFlow +import kotlinx.coroutines.flow.stateIn +import kotlinx.coroutines.launch +import javax.inject.Inject + +@HiltViewModel +class PlayerServiceViewModel @Inject constructor( + private val mediaPlayerUseCase: MediaPlayerUseCase, + private val mediaPlayerListenerUseCase: MediaPlayerListenerUseCase, +) : ViewModel() { + + val playerState: StateFlow = mediaPlayerListenerUseCase.playerStateFlow() + .stateIn( + scope = viewModelScope, + started = SharingStarted.WhileSubscribed(5_000), + initialValue = PlayerState() + ) + + val audioSessionId get() = mediaPlayerUseCase.audioSessionId + + suspend fun readyPlayer() { + viewModelScope.async { + mediaPlayerUseCase.readyPlayer() + }.await() + } + + fun setMediaItem(pick: Pick) { + viewModelScope.launch { + if (playerState.value.id == pick.id) { + mediaPlayerUseCase.changeRepeatMode(false) + } else { + mediaPlayerUseCase.setMediaItem(pick) + } + } + } + + fun setMediaItems(picks: List) { + mediaPlayerUseCase.setMediaItems(picks) + } + + private fun onPlay() { + mediaPlayerUseCase.play() + } + + fun onPause() { + mediaPlayerUseCase.pause() + } + + fun onStop() { + mediaPlayerUseCase.stop() + } + + fun onRelease() { + mediaPlayerUseCase.release() + } + + fun onPrevious() { + mediaPlayerUseCase.previous() + } + + fun onNext() { + mediaPlayerUseCase.next() + } + + fun onAdvanceBy() { + mediaPlayerUseCase.advanceBy() + } + + fun shuffleNext(pick: Pick) { + if (playerState.value.isPlaying) { + onPause() + } else { + setMediaItem(pick) + onPlay() + } + } + + fun togglePlayPause(song: Song) { + if (playerState.value.isPlaying) { + onPause() + } else { + onPlay() + } + } + + fun onRewindBy() { + mediaPlayerUseCase.rewindBy() + } + + fun onSeekingStarted() { + mediaPlayerUseCase.onSeekingStarted() + } + + fun onSeekingFinished(time: Long) { + mediaPlayerUseCase.onSeekingFinished(time) + } + + fun onAddToQueue(pick: Pick) { + mediaPlayerUseCase.addMediaItem(pick) + } +} diff --git a/core/musicplayer/src/main/java/com/example/musicplayer/PlayerUiState.kt b/core/musicplayer/src/main/java/com/example/musicplayer/PlayerUiState.kt new file mode 100644 index 00000000..53044f66 --- /dev/null +++ b/core/musicplayer/src/main/java/com/example/musicplayer/PlayerUiState.kt @@ -0,0 +1,12 @@ +package com.example.musicplayer + +data class PlayerUiState( + val isReady: Boolean = true, + val isPlaying: Boolean = false, + val currentPosition: Long = 0L, +) { + companion object { + val PLAYER_STATE_INITIAL = PlayerUiState(isReady = false) + val PLAYER_STATE_STOP = PlayerUiState(isReady = true, isPlaying = false) + } +} diff --git a/core/musicplayer/src/main/java/com/example/musicplayer/PlayerViewModel.kt b/core/musicplayer/src/main/java/com/example/musicplayer/PlayerViewModel.kt new file mode 100644 index 00000000..a2382a4f --- /dev/null +++ b/core/musicplayer/src/main/java/com/example/musicplayer/PlayerViewModel.kt @@ -0,0 +1,228 @@ +package com.example.musicplayer + +import android.content.Context +import android.util.Log +import androidx.annotation.OptIn +import androidx.lifecycle.ViewModel +import androidx.lifecycle.viewModelScope +import androidx.media3.common.C.TIME_UNSET +import androidx.media3.common.MediaItem +import androidx.media3.common.PlaybackException +import androidx.media3.common.Player +import androidx.media3.common.util.UnstableApi +import androidx.media3.exoplayer.ExoPlayer +import com.example.musicplayer.PlayerUiState.Companion.PLAYER_STATE_INITIAL +import com.example.musicplayer.PlayerUiState.Companion.PLAYER_STATE_STOP +import dagger.hilt.android.lifecycle.HiltViewModel +import kotlinx.coroutines.delay +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.StateFlow +import kotlinx.coroutines.launch +import javax.inject.Inject + +@HiltViewModel +class PlayerViewModel @Inject constructor( +) : ViewModel() { + + private var player: ExoPlayer? = null + + private var _audioSessionId = 0 + val audioSessionId get() = _audioSessionId + + private val _playerState = MutableStateFlow(PLAYER_STATE_INITIAL) + val playerUiState: StateFlow = _playerState + + private val _bufferPercentage = MutableStateFlow(0) + val bufferPercentage: StateFlow = _bufferPercentage + + private val _duration = MutableStateFlow(30_000L) + val duration: StateFlow = _duration + + @OptIn(UnstableApi::class) + private fun initializePlayer(context: Context) { + val exoPlayer = ExoPlayer.Builder(context).build().also { + it.addListener(object : Player.Listener { + override fun onPlayerError(error: PlaybackException) { + handleError(error) + } + + override fun onPlaybackStateChanged(playbackState: Int) { + if (playbackState == Player.STATE_ENDED) { + it.seekTo(0) + it.pause() + } + } + }) + it.volume = 0.8f + } + this.player = exoPlayer + _audioSessionId = exoPlayer.audioSessionId + } + + fun readyPlayer(context: Context, sourceUrl: String) { + if (player != null) return + + initializePlayer(context) + + player?.let { + val mediaItem = MediaItem.fromUri(sourceUrl) + it.setMediaItem(mediaItem) + it.prepare() + it.playWhenReady = false + it.seekTo(_playerState.value.currentPosition) + + _playerState.value = + PlayerUiState(isReady = true, currentPosition = _playerState.value.currentPosition) + + _duration.value = + if (it.duration == TIME_UNSET) 30_000L else it.duration + + updatePlayerStatePeriodically(it) + } + } + + fun readyPlayerSetList(context: Context, sourceUrls: List) { + if (player != null) return + + initializePlayer(context) + + player?.let { + it.setMediaItems(sourceUrls.map { url -> + MediaItem.fromUri(url) + }) + it.prepare() + it.playWhenReady = false + it.repeatMode = Player.REPEAT_MODE_ALL + } + } + + private fun updatePlayerStatePeriodically(exoPlayer: ExoPlayer) { + viewModelScope.launch { + while (_playerState.value.isReady) { + _playerState.value = _playerState.value.copy( + isPlaying = exoPlayer.isPlaying, + currentPosition = exoPlayer.currentPosition, + ) + _bufferPercentage.value = exoPlayer.bufferedPercentage + delay(1000) + } + } + } + + fun shuffleNextItem() { + viewModelScope.launch { + player?.let { + if (!it.isPlaying) it.seekToNextMediaItem() + togglePlayPause(it) + } + } + } + + fun replayForward(sec: Long) { + player?.let { + it.seekTo(it.currentPosition + sec) + viewModelScope.launch { + _playerState.value = + _playerState.value.copy(currentPosition = it.currentPosition) + } + } + } + + fun togglePlayPause() { + player?.let { + togglePlayPause(it) + } + } + + private fun togglePlayPause(exoPlayer: ExoPlayer) { + if (exoPlayer.isPlaying) pause(exoPlayer) + else play(exoPlayer) + } + + fun play() { + player?.let { + play(it) + } + } + + private fun play(exoPlayer: ExoPlayer) { + viewModelScope.launch { + _playerState.value = _playerState.value.copy(isPlaying = true) + } + exoPlayer.play() + } + + fun pause() { + player?.let { + pause(it) + } + } + + private fun pause(exoPlayer: ExoPlayer) { + viewModelScope.launch { + _playerState.value = _playerState.value.copy(isPlaying = false) + } + exoPlayer.pause() + } + + fun stop() { + player?.let { + viewModelScope.launch { + _playerState.value = PLAYER_STATE_STOP + } + it.stop() + } + } + + fun playerSeekTo(sec: Long) { + viewModelScope.launch { + player?.let { + _playerState.value = _playerState.value.copy(currentPosition = sec) + it.seekTo(sec) + } + } + } + + fun savePlayerState() { + player?.let { + _playerState.value = _playerState.value.copy( + isReady = false, + isPlaying = false, + currentPosition = it.currentPosition, + ) + } + } + + private fun releasePlayer() { + player?.release() + } + + private fun handleError(error: PlaybackException) { + when (error.errorCode) { + PlaybackException.ERROR_CODE_IO_NETWORK_CONNECTION_FAILED -> { + // TODO: Handle network connection error + Log.d("PlayerViewModel", "Network connection error") + } + + PlaybackException.ERROR_CODE_IO_FILE_NOT_FOUND -> { + // TODO: Handle file not found error + Log.d("PlayerViewModel", "File not found") + } + + PlaybackException.ERROR_CODE_DECODER_INIT_FAILED -> { + // TODO: Handle decoder initialization error + Log.d("PlayerViewModel", "Decoder initialization error") + } + + else -> { + // TODO: Handle other types of errors + Log.d("PlayerViewModel", "${error.message}") + } + } + } + + override fun onCleared() { + releasePlayer() + super.onCleared() + } +} diff --git a/settings.gradle.kts b/settings.gradle.kts index 3dd56d3c..9d40721e 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -47,3 +47,4 @@ include(":data:location") include(":data:order") include(":data:pick") include(":data:user") +include(":core:musicplayer") From da598e3a7d05fe601cd6ec4ab7e862217c25ab42 Mon Sep 17 00:00:00 2001 From: miller198 Date: Sun, 23 Feb 2025 21:21:53 +0900 Subject: [PATCH 15/62] =?UTF-8?q?[build]=20=EB=B2=84=EC=A0=84=20=EC=B9=B4?= =?UTF-8?q?=ED=83=88=EB=A1=9C=EA=B7=B8=20=EC=A0=95=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/build.gradle.kts | 3 - gradle/libs.versions.toml | 180 +++++++++++++++++++++++--------------- 2 files changed, 110 insertions(+), 73 deletions(-) diff --git a/app/build.gradle.kts b/app/build.gradle.kts index 27f7dbc3..644ba87c 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -106,8 +106,6 @@ dependencies { implementation(libs.androidx.lifecycle.runtime.ktx) implementation(libs.androidx.ui) implementation(libs.androidx.ui.graphics) - implementation(libs.androidx.constraintlayout) - implementation(libs.androidx.ui.viewbinding) implementation(libs.androidx.core.splashscreen) implementation(libs.androidx.animation) testImplementation(libs.junit) @@ -134,7 +132,6 @@ dependencies { androidTestImplementation(libs.hilt.android.testing) kspAndroidTest(libs.hilt.android.compiler) implementation(libs.androidx.hilt.navigation.compose) - implementation(libs.androidx.hilt.navigation.fragment) // Firebase implementation(platform(libs.firebase.bom)) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 47edae0b..3518b61c 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -1,85 +1,137 @@ [versions] -activityCompose = "1.9.3" + +# SDK versions +compileSdk = "34" +minSdk = "26" +targetSdk = "34" +jvmTarget = "1.8" + +# Build agp = "8.5.1" -animation = "1.7.5" -appcompat = "1.7.0" -coil = "3.0.0" + +# AndroidX +androidx-core-ktx = "1.13.1" +androidx-core-splashscreen = "1.0.1" +androidx-animation = "1.7.5" +androidx-credentials = "1.3.0" +androidx-lifecycle-runtime-ktx = "2.8.7" +androidx-datastore-preferences = "1.1.1" +androidx-appcompat = "1.7.0" +androidx-navigation-common-ktx = "2.8.5" + + +# Media3 +media3Ui = "1.4.1" + +# Compose +activityCompose = "1.9.3" composeBom = "2024.10.01" composeMaterial = "1.4.0" -constraintlayout = "2.2.0" -coreKtx = "1.13.1" -credentials = "1.3.0" -datastorePreferences = "1.1.1" -espressoCore = "3.6.1" +navigationCompose = "2.8.3" +compose-runtime-android = "1.7.6" + +# Kotlin +kotlin = "1.9.22" +kotlinxImmutable = "0.3.7" +jetbrainsKotlinJvm = "1.9.22" +ksp = "1.9.22-1.0.16" + +# Coroutines +kotlinxCoroutinesPlayServices = "1.9.0" +kotlinxCoroutinesCore = "1.9.0" + +# Coil +coil = "3.0.0" + +# Firebase +firebaseFirestoreKtxVersion = "25.1.2" firebaseBom = "33.5.1" firebaseDynamicModuleSupportVersion = "16.0.0-beta03" firebaseFirestoreKtx = "25.1.1" firebaseFunctionsKtx = "21.1.0" firebaseCrashlytics = "3.0.2" geofireAndroidCommon = "3.2.0" -googleid = "1.1.1" -googleServices = "4.4.2" + +# Hilt hilt = "2.51.1" hiltNavigationCompose = "1.2.0" inject = "1" + +# Test +espressoCore = "3.6.1" junit = "4.13.2" junitVersion = "1.2.1" -kotlin = "1.9.22" -kotlinxCoroutinesPlayServices = "1.9.0" -kotlinxCoroutinesCore = "1.9.0" + +# Google +googleid = "1.1.1" +googleServices = "4.4.2" +playServicesLocation = "21.3.0" + +# Paging +pagingRuntime = "3.3.4" +pagingComposeAndroid = "3.3.4" + +# Serialization kotlinxSerializationJson = "1.6.0" -ksp = "1.9.22-1.0.16" -lifecycleRuntimeKtx = "2.8.7" + +# NaverMap mapSdk = "3.19.1" -material = "1.12.0" -media3Ui = "1.4.1" -navigationCompose = "2.8.3" + +# Network okhttp = "4.12.0" -pagingRuntime = "3.3.4" -playServicesLocation = "21.3.0" retrofit = "2.11.0" -splashscreen = "1.0.1" -uiViewbinding = "1.7.5" -pagingComposeAndroid = "3.3.4" -kotlinxImmutable = "0.3.7" -jetbrainsKotlinJvm = "1.9.22" -navigationCommonKtx = "2.8.5" -runtimeAndroid = "1.7.6" -firebaseFirestoreKtxVersion = "25.1.2" + +material = "1.12.0" [libraries] # AndroidX -androidx-animation = { module = "androidx.compose.animation:animation", version.ref = "animation" } -androidx-core-ktx = { group = "androidx.core", name = "core-ktx", version.ref = "coreKtx" } -androidx-credentials = { module = "androidx.credentials:credentials", version.ref = "credentials" } -androidx-credentials-play-services-auth = { module = "androidx.credentials:credentials-play-services-auth", version.ref = "credentials" } -androidx-datastore-preferences = { module = "androidx.datastore:datastore-preferences", version.ref = "datastorePreferences" } -androidx-hilt-navigation-compose = { module = "androidx.hilt:hilt-navigation-compose", version.ref = "hiltNavigationCompose" } -androidx-hilt-navigation-fragment = { module = "androidx.hilt:hilt-navigation-fragment", version.ref = "hiltNavigationCompose" } -androidx-junit = { group = "androidx.test.ext", name = "junit", version.ref = "junitVersion" } -androidx-espresso-core = { group = "androidx.test.espresso", name = "espresso-core", version.ref = "espressoCore" } -androidx-lifecycle-runtime-ktx = { group = "androidx.lifecycle", name = "lifecycle-runtime-ktx", version.ref = "lifecycleRuntimeKtx" } -androidx-activity-compose = { group = "androidx.activity", name = "activity-compose", version.ref = "activityCompose" } -androidx-compose-bom = { group = "androidx.compose", name = "compose-bom", version.ref = "composeBom" } +androidx-animation = { module = "androidx.compose.animation:animation", version.ref = "androidx-animation" } +androidx-core-ktx = { group = "androidx.core", name = "core-ktx", version.ref = "androidx-core-ktx" } +androidx-credentials = { module = "androidx.credentials:credentials", version.ref = "androidx-credentials" } +androidx-credentials-play-services-auth = { module = "androidx.credentials:credentials-play-services-auth", version.ref = "androidx-credentials" } +androidx-datastore-preferences = { module = "androidx.datastore:datastore-preferences", version.ref = "androidx-datastore-preferences" } +androidx-lifecycle-runtime-ktx = { group = "androidx.lifecycle", name = "lifecycle-runtime-ktx", version.ref = "androidx-lifecycle-runtime-ktx" } +androidx-appcompat = { group = "androidx.appcompat", name = "appcompat", version.ref = "androidx-appcompat" } +androidx-core-splashscreen = { module = "androidx.core:core-splashscreen", version.ref = "androidx-core-splashscreen" } +androidx-navigation-common-ktx = { group = "androidx.navigation", name = "navigation-common-ktx", version.ref = "androidx-navigation-common-ktx" } + +# Media3 androidx-media3-exoplayer = { module = "androidx.media3:media3-exoplayer", version.ref = "media3Ui" } androidx-media3-exoplayer-dash = { module = "androidx.media3:media3-exoplayer-dash", version.ref = "media3Ui" } androidx-media3-ui = { module = "androidx.media3:media3-ui", version.ref = "media3Ui" } -androidx-paging-runtime = { module = "androidx.paging:paging-runtime", version.ref = "pagingRuntime" } androidx-media3-session = { group = "androidx.media3", name = "media3-session", version.ref = "media3Ui" } -androidx-media3-common = { group = "androidx.media3", name = "media3-common", version.ref = "media3Ui" } # for media3-common +androidx-media3-common = { group = "androidx.media3", name = "media3-common", version.ref = "media3Ui" } + +# Hilt +inject = { group = "javax.inject", name = "javax.inject", version.ref = "inject" } +hilt-android = { group = "com.google.dagger", name = "hilt-android", version.ref = "hilt" } +hilt-android-compiler = { group = "com.google.dagger", name = "hilt-android-compiler", version.ref = "hilt" } +hilt-android-testing = { group = "com.google.dagger", name = "hilt-android-testing", version.ref = "hilt" } +androidx-hilt-navigation-compose = { module = "androidx.hilt:hilt-navigation-compose", version.ref = "hiltNavigationCompose" } + +# Compose +androidx-activity-compose = { group = "androidx.activity", name = "activity-compose", version.ref = "activityCompose" } +androidx-compose-bom = { group = "androidx.compose", name = "compose-bom", version.ref = "composeBom" } +androidx-compose-material3 = { group = "androidx.compose.material3", name = "material3" } +androidx-navigation-compose = { module = "androidx.navigation:navigation-compose", version.ref = "navigationCompose" } +androidx-compose-material = { group = "androidx.wear.compose", name = "compose-material", version.ref = "composeMaterial" } androidx-ui = { group = "androidx.compose.ui", name = "ui" } androidx-ui-graphics = { group = "androidx.compose.ui", name = "ui-graphics" } androidx-ui-tooling = { group = "androidx.compose.ui", name = "ui-tooling" } androidx-ui-tooling-preview = { group = "androidx.compose.ui", name = "ui-tooling-preview" } androidx-ui-test-manifest = { group = "androidx.compose.ui", name = "ui-test-manifest" } androidx-ui-test-junit4 = { group = "androidx.compose.ui", name = "ui-test-junit4" } -androidx-compose-material3 = { group = "androidx.compose.material3", name = "material3" } -androidx-appcompat = { group = "androidx.appcompat", name = "appcompat", version.ref = "appcompat" } -androidx-navigation-compose = { module = "androidx.navigation:navigation-compose", version.ref = "navigationCompose" } -androidx-core-splashscreen = { module = "androidx.core:core-splashscreen", version.ref = "splashscreen" } -androidx-compose-material = { group = "androidx.wear.compose", name = "compose-material", version.ref = "composeMaterial" } +androidx-runtime-android = { group = "androidx.compose.runtime", name = "runtime-android", version.ref = "compose-runtime-android" } + +# Paging +androidx-paging-runtime = { module = "androidx.paging:paging-runtime", version.ref = "pagingRuntime" } + +# Google googleid = { module = "com.google.android.libraries.identity.googleid:googleid", version.ref = "googleid" } +play-services-location = { module = "com.google.android.gms:play-services-location", version.ref = "playServicesLocation" } + kotlinx-coroutines-core = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-core", version.ref = "kotlinxCoroutinesCore" } +kotlinx-coroutines-play-services = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-play-services", version.ref = "kotlinxCoroutinesPlayServices" } kotlinx-immutable = { group = "org.jetbrains.kotlinx", name = "kotlinx-collections-immutable", version.ref = "kotlinxImmutable" } # Firebase @@ -87,52 +139,38 @@ firebase-analytics = { module = "com.google.firebase:firebase-analytics" } firebase-bom = { module = "com.google.firebase:firebase-bom", version.ref = "firebaseBom" } firebase-firestore-ktx = { group = "com.google.firebase", name = "firebase-firestore-ktx", version.ref = "firebaseFirestoreKtx" } firebase-functions-ktx = { group = "com.google.firebase", name = "firebase-functions-ktx", version.ref = "firebaseFunctionsKtx" } -kotlinx-coroutines-play-services = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-play-services", version.ref = "kotlinxCoroutinesPlayServices" } firebase-crashlytics = { module = "com.google.firebase:firebase-crashlytics" } - -# Test geofire-android-common = { module = "com.firebase:geofire-android-common", version.ref = "geofireAndroidCommon" } google-firebase-dynamic-module-support = { module = "com.google.firebase:firebase-dynamic-module-support", version.ref = "firebaseDynamicModuleSupportVersion" } +google-firebase-firestore-ktx = { group = "com.google.firebase", name = "firebase-firestore-ktx", version.ref = "firebaseFirestoreKtxVersion" } + +# Test junit = { group = "junit", name = "junit", version.ref = "junit" } +androidx-junit = { group = "androidx.test.ext", name = "junit", version.ref = "junitVersion" } +androidx-espresso-core = { group = "androidx.test.espresso", name = "espresso-core", version.ref = "espressoCore" } # Map map-sdk = { module = "com.naver.maps:map-sdk", version.ref = "mapSdk" } -play-services-location = { module = "com.google.android.gms:play-services-location", version.ref = "playServicesLocation" } # Material Design material = { group = "com.google.android.material", name = "material", version.ref = "material" } androidx-material-icons-extended = { module = "androidx.compose.material:material-icons-extended" } -# Hilt -inject = { group = "javax.inject", name = "javax.inject", version.ref = "inject" } -hilt-android = { group = "com.google.dagger", name = "hilt-android", version.ref = "hilt" } -hilt-android-compiler = { group = "com.google.dagger", name = "hilt-android-compiler", version.ref = "hilt" } -hilt-android-testing = { group = "com.google.dagger", name = "hilt-android-testing", version.ref = "hilt" } - -# OkHttp +# Network okhttp-logging = { group = "com.squareup.okhttp3", name = "logging-interceptor", version.ref = "okhttp" } - -# Retrofit retrofit-core = { group = "com.squareup.retrofit2", name = "retrofit", version.ref = "retrofit" } retrofit-kotlin-serialization = { module = "com.squareup.retrofit2:converter-kotlinx-serialization", version.ref = "retrofit" } # Kotlinx Serialization kotlinx-serialization-json = { group = "org.jetbrains.kotlinx", name = "kotlinx-serialization-json", version.ref = "kotlinxSerializationJson" } -# ConstraintLayout -androidx-constraintlayout = { group = "androidx.constraintlayout", name = "constraintlayout", version.ref = "constraintlayout" } - -# UI ViewBinding -androidx-ui-viewbinding = { group = "androidx.compose.ui", name = "ui-viewbinding", version.ref = "uiViewbinding" } - # Coil coil = { module = "io.coil-kt.coil3:coil", version.ref = "coil" } coil-compose = { module = "io.coil-kt.coil3:coil-compose", version.ref = "coil" } coil-network-okhttp = { module = "io.coil-kt.coil3:coil-network-okhttp", version.ref = "coil" } + +# Paging androidx-paging-compose-android = { group = "androidx.paging", name = "paging-compose-android", version.ref = "pagingComposeAndroid" } -androidx-navigation-common-ktx = { group = "androidx.navigation", name = "navigation-common-ktx", version.ref = "navigationCommonKtx" } -androidx-runtime-android = { group = "androidx.compose.runtime", name = "runtime-android", version.ref = "runtimeAndroid" } -google-firebase-firestore-ktx = { group = "com.google.firebase", name = "firebase-firestore-ktx", version.ref = "firebaseFirestoreKtxVersion" } # Plugins [plugins] @@ -145,3 +183,5 @@ kotlin-serialization = { id = "org.jetbrains.kotlin.plugin.serialization", versi google-services = { id = "com.google.gms.google-services", version.ref = "googleServices" } firebase-crashlytics = { id = "com.google.firebase.crashlytics", version.ref = "firebaseCrashlytics" } jetbrains-kotlin-jvm = { id = "org.jetbrains.kotlin.jvm", version.ref = "jetbrainsKotlinJvm" } + +[bundles] From 7ce538eeaa38a807a2bb3f0e723af43982c0e9f0 Mon Sep 17 00:00:00 2001 From: miller198 Date: Sun, 23 Feb 2025 21:22:12 +0900 Subject: [PATCH 16/62] =?UTF-8?q?[chore]=20account=20=EB=AA=A8=EB=93=88,?= =?UTF-8?q?=20feature=20=EB=AA=A8=EB=93=88?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- core/account/.gitignore | 1 + core/account/build.gradle.kts | 64 ++++ core/account/consumer-rules.pro | 0 core/account/proguard-rules.pro | 21 ++ .../com/example/account/AccountViewModel.kt | 64 ++++ .../main/java/com/example/account/GoogleId.kt | 70 ++++ core/account/src/main/res/values/strings.xml | 5 + .../java/com/example/util/ThrottleFirst.kt | 2 +- feature/create/.gitignore | 1 + feature/create/build.gradle.kts | 68 ++++ feature/create/consumer-rules.pro | 0 feature/create/proguard-rules.pro | 21 ++ .../example/create/ExampleInstrumentedTest.kt | 24 ++ feature/create/src/main/AndroidManifest.xml | 4 + .../com/example/create/CreatePickScreen.kt | 351 ++++++++++++++++++ .../com/example/create/CreatePickUiState.kt | 12 + .../com/example/create/CreatePickViewModel.kt | 120 ++++++ .../create/src/main/res/values/strings.xml | 9 + .../com/example/create/ExampleUnitTest.kt | 17 + settings.gradle.kts | 2 + 20 files changed, 855 insertions(+), 1 deletion(-) create mode 100644 core/account/.gitignore create mode 100644 core/account/build.gradle.kts create mode 100644 core/account/consumer-rules.pro create mode 100644 core/account/proguard-rules.pro create mode 100644 core/account/src/main/java/com/example/account/AccountViewModel.kt create mode 100644 core/account/src/main/java/com/example/account/GoogleId.kt create mode 100644 core/account/src/main/res/values/strings.xml create mode 100644 feature/create/.gitignore create mode 100644 feature/create/build.gradle.kts create mode 100644 feature/create/consumer-rules.pro create mode 100644 feature/create/proguard-rules.pro create mode 100644 feature/create/src/androidTest/java/com/example/create/ExampleInstrumentedTest.kt create mode 100644 feature/create/src/main/AndroidManifest.xml create mode 100644 feature/create/src/main/java/com/example/create/CreatePickScreen.kt create mode 100644 feature/create/src/main/java/com/example/create/CreatePickUiState.kt create mode 100644 feature/create/src/main/java/com/example/create/CreatePickViewModel.kt create mode 100644 feature/create/src/main/res/values/strings.xml create mode 100644 feature/create/src/test/java/com/example/create/ExampleUnitTest.kt diff --git a/core/account/.gitignore b/core/account/.gitignore new file mode 100644 index 00000000..42afabfd --- /dev/null +++ b/core/account/.gitignore @@ -0,0 +1 @@ +/build \ No newline at end of file diff --git a/core/account/build.gradle.kts b/core/account/build.gradle.kts new file mode 100644 index 00000000..6a35f488 --- /dev/null +++ b/core/account/build.gradle.kts @@ -0,0 +1,64 @@ +import java.io.FileInputStream +import java.util.Properties + +val properties = Properties().apply { + load(FileInputStream(rootProject.file("local.properties"))) +} + +plugins { + alias(libs.plugins.android.library) + alias(libs.plugins.jetbrains.kotlin.android) + alias(libs.plugins.ksp) + alias(libs.plugins.hilt) +} + +android { + namespace = "com.example.account" + compileSdk = 34 + + defaultConfig { + minSdk = 26 + + testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner" + consumerProguardFiles("consumer-rules.pro") + + buildConfigField( + "String", + "GOOGLE_CLIENT_ID", + "\"${properties.getProperty("GOOGLE_CLIENT_ID")}\"" + ) + } + + buildTypes { + release { + isMinifyEnabled = false + proguardFiles(getDefaultProguardFile("proguard-android-optimize.txt"), "proguard-rules.pro") + } + } + compileOptions { + sourceCompatibility = JavaVersion.VERSION_1_8 + targetCompatibility = JavaVersion.VERSION_1_8 + } + kotlinOptions { + jvmTarget = "1.8" + } + buildFeatures { + buildConfig = true + } +} + +dependencies { + implementation(projects.domain.user) + implementation(projects.core.model) + implementation(libs.androidx.lifecycle.runtime.ktx) + + // Hilt + implementation(libs.hilt.android) + ksp(libs.hilt.android.compiler) + implementation(libs.inject) + + // Credentials + implementation(libs.androidx.credentials) + implementation(libs.androidx.credentials.play.services.auth) + implementation(libs.googleid) +} diff --git a/core/account/consumer-rules.pro b/core/account/consumer-rules.pro new file mode 100644 index 00000000..e69de29b diff --git a/core/account/proguard-rules.pro b/core/account/proguard-rules.pro new file mode 100644 index 00000000..481bb434 --- /dev/null +++ b/core/account/proguard-rules.pro @@ -0,0 +1,21 @@ +# Add project specific ProGuard rules here. +# You can control the set of applied configuration files using the +# proguardFiles setting in build.gradle. +# +# For more details, see +# http://developer.android.com/guide/developing/tools/proguard.html + +# If your project uses WebView with JS, uncomment the following +# and specify the fully qualified class name to the JavaScript interface +# class: +#-keepclassmembers class fqcn.of.javascript.interface.for.webview { +# public *; +#} + +# Uncomment this to preserve the line number information for +# debugging stack traces. +#-keepattributes SourceFile,LineNumberTable + +# If you keep the line number information, uncomment this to +# hide the original source file name. +#-renamesourcefileattribute SourceFile \ No newline at end of file diff --git a/core/account/src/main/java/com/example/account/AccountViewModel.kt b/core/account/src/main/java/com/example/account/AccountViewModel.kt new file mode 100644 index 00000000..112e50d5 --- /dev/null +++ b/core/account/src/main/java/com/example/account/AccountViewModel.kt @@ -0,0 +1,64 @@ +package com.example.account + +import android.util.Log +import androidx.lifecycle.ViewModel +import androidx.lifecycle.viewModelScope +import com.example.user.usecase.ClearUserUseCase +import com.example.user.usecase.CreateGoogleIdUserUseCase +import com.example.user.usecase.FetchUserUseCase +import com.google.android.libraries.identity.googleid.GoogleIdTokenCredential +import dagger.hilt.android.lifecycle.HiltViewModel +import kotlinx.coroutines.flow.MutableSharedFlow +import kotlinx.coroutines.flow.asSharedFlow +import kotlinx.coroutines.launch +import javax.inject.Inject + +@HiltViewModel +class AccountViewModel @Inject constructor( + private val fetchUserUseCase: FetchUserUseCase, + private val createGoogleIdUserUseCase: CreateGoogleIdUserUseCase, + private val clearUserUseCase: ClearUserUseCase, +) : ViewModel() { + + private val _signInSuccess = MutableSharedFlow() + val signInSuccess = _signInSuccess.asSharedFlow() + + private val _signOutSuccess = MutableSharedFlow() + val signOutSuccess = _signOutSuccess.asSharedFlow() + + fun signIn(credential: GoogleIdTokenCredential) { + viewModelScope.launch { + fetchUserUseCase(credential.id) + .onSuccess { + Log.d("SignIn", "기존 계정 ${it.userId} 로그인") + _signInSuccess.emit(true) + } + .onFailure { + createGoogleIdUser(credential) + } + } + } + + private fun createGoogleIdUser(credential: GoogleIdTokenCredential) { + viewModelScope.launch { + createGoogleIdUserUseCase( + userId = credential.id, + userName = credential.displayName, + userProfileImage = credential.profilePictureUri.toString() + ).onSuccess { + Log.d("SignIn", "새로운 계정 ${it.userId} 로그인") + _signInSuccess.emit(true) + }.onFailure { + _signInSuccess.emit(false) + } + } + } + + fun signOut() { + viewModelScope.launch { + clearUserUseCase() + .onSuccess { _signOutSuccess.emit(true) } + .onFailure { _signOutSuccess.emit(false) } + } + } +} diff --git a/core/account/src/main/java/com/example/account/GoogleId.kt b/core/account/src/main/java/com/example/account/GoogleId.kt new file mode 100644 index 00000000..7d65a4cf --- /dev/null +++ b/core/account/src/main/java/com/example/account/GoogleId.kt @@ -0,0 +1,70 @@ +package com.example.account + +import android.content.Context +import android.util.Log +import android.widget.Toast +import androidx.credentials.ClearCredentialStateRequest +import androidx.credentials.CredentialManager +import androidx.credentials.CustomCredential +import androidx.credentials.GetCredentialRequest +import androidx.credentials.GetCredentialResponse +import androidx.credentials.exceptions.NoCredentialException +import com.google.android.libraries.identity.googleid.GetGoogleIdOption +import com.google.android.libraries.identity.googleid.GoogleIdTokenCredential +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.launch + +class GoogleId(private val context: Context) { + private val credentialManager = CredentialManager.create(context) + + private val googleIdOption: GetGoogleIdOption = GetGoogleIdOption.Builder() + .setFilterByAuthorizedAccounts(false) + .setServerClientId(BuildConfig.GOOGLE_CLIENT_ID) + .setAutoSelectEnabled(true) + .build() + + private val request = GetCredentialRequest.Builder() + .addCredentialOption(googleIdOption) + .build() + + private fun handleSignIn(result: GetCredentialResponse, onSuccess: (GoogleIdTokenCredential) -> Unit) { + when (val data = result.credential) { + is CustomCredential -> { + if (data.type == GoogleIdTokenCredential.TYPE_GOOGLE_ID_TOKEN_CREDENTIAL) { + val googleIdTokenCredential = GoogleIdTokenCredential.createFrom(data.data) + Log.d("GoogleId", "data.type : ${googleIdTokenCredential.id}") + Log.d("GoogleId", "data.type : ${googleIdTokenCredential.displayName}") + Log.d("GoogleId", "data.type : ${googleIdTokenCredential.profilePictureUri.toString()}") + onSuccess(googleIdTokenCredential) + } + } + } + } + + fun signIn(onSuccess: (GoogleIdTokenCredential) -> Unit) { + CoroutineScope(Dispatchers.Main).launch { + runCatching { + val result = credentialManager.getCredential(context, request) + handleSignIn(result, onSuccess) + }.onFailure { exception -> + when (exception) { + is NoCredentialException -> Toast.makeText( + context, + context.getString(R.string.google_id_no_credential_exception_message), + Toast.LENGTH_SHORT + ).show() + } + Log.e("GoogleId", "Google SignIn Error : $exception") + } + } + } + + fun signOut() { + CoroutineScope(Dispatchers.Main).launch { + credentialManager.clearCredentialState( + request = ClearCredentialStateRequest() + ) + } + } +} diff --git a/core/account/src/main/res/values/strings.xml b/core/account/src/main/res/values/strings.xml new file mode 100644 index 00000000..711c2358 --- /dev/null +++ b/core/account/src/main/res/values/strings.xml @@ -0,0 +1,5 @@ + + + + 기기에 로그인된 구글 계정이 없습니다 + diff --git a/core/util/src/main/java/com/example/util/ThrottleFirst.kt b/core/util/src/main/java/com/example/util/ThrottleFirst.kt index d9b3bb9f..a707d53b 100644 --- a/core/util/src/main/java/com/example/util/ThrottleFirst.kt +++ b/core/util/src/main/java/com/example/util/ThrottleFirst.kt @@ -3,7 +3,7 @@ package com.example.util import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.flow -internal fun Flow.throttleFirst(periodMillis: Long): Flow { +fun Flow.throttleFirst(periodMillis: Long): Flow { require(periodMillis > 0) { "period should be positive" } return flow { var lastTime = 0L diff --git a/feature/create/.gitignore b/feature/create/.gitignore new file mode 100644 index 00000000..42afabfd --- /dev/null +++ b/feature/create/.gitignore @@ -0,0 +1 @@ +/build \ No newline at end of file diff --git a/feature/create/build.gradle.kts b/feature/create/build.gradle.kts new file mode 100644 index 00000000..901245e4 --- /dev/null +++ b/feature/create/build.gradle.kts @@ -0,0 +1,68 @@ +plugins { + alias(libs.plugins.android.library) + alias(libs.plugins.jetbrains.kotlin.android) + alias(libs.plugins.kotlin.serialization) + alias(libs.plugins.ksp) + alias(libs.plugins.hilt) +} + +android { + namespace = "com.example.create" + compileSdk = 34 + + defaultConfig { + minSdk = 26 + + testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner" + consumerProguardFiles("consumer-rules.pro") + } + + buildTypes { + release { + isMinifyEnabled = false + proguardFiles(getDefaultProguardFile("proguard-android-optimize.txt"), "proguard-rules.pro") + } + } + compileOptions { + sourceCompatibility = JavaVersion.VERSION_1_8 + targetCompatibility = JavaVersion.VERSION_1_8 + } + kotlinOptions { + jvmTarget = "1.8" + } +} + +dependencies { + implementation(projects.core.model) + implementation(projects.core.common) + implementation(projects.core.util) + implementation(projects.core.navigation) + implementation(projects.domain.pick) + implementation(projects.domain.user) + implementation(projects.domain.applemusic) + implementation(projects.domain.location) + + implementation(libs.androidx.core.ktx) + implementation(libs.androidx.appcompat) + implementation(libs.material) + testImplementation(libs.junit) + androidTestImplementation(libs.androidx.junit) + androidTestImplementation(libs.androidx.espresso.core) + + // Compose + implementation(platform(libs.androidx.compose.bom)) + implementation(libs.androidx.compose.material) + implementation(libs.androidx.compose.material3) + implementation(libs.androidx.material.icons.extended) + implementation(libs.androidx.ui.tooling.preview) + implementation(libs.androidx.navigation.compose) + + // Hilt + implementation(libs.hilt.android) + ksp(libs.hilt.android.compiler) + implementation(libs.inject) + implementation(libs.androidx.hilt.navigation.compose) + + // Serialization + implementation(libs.kotlinx.serialization.json) +} diff --git a/feature/create/consumer-rules.pro b/feature/create/consumer-rules.pro new file mode 100644 index 00000000..e69de29b diff --git a/feature/create/proguard-rules.pro b/feature/create/proguard-rules.pro new file mode 100644 index 00000000..481bb434 --- /dev/null +++ b/feature/create/proguard-rules.pro @@ -0,0 +1,21 @@ +# Add project specific ProGuard rules here. +# You can control the set of applied configuration files using the +# proguardFiles setting in build.gradle. +# +# For more details, see +# http://developer.android.com/guide/developing/tools/proguard.html + +# If your project uses WebView with JS, uncomment the following +# and specify the fully qualified class name to the JavaScript interface +# class: +#-keepclassmembers class fqcn.of.javascript.interface.for.webview { +# public *; +#} + +# Uncomment this to preserve the line number information for +# debugging stack traces. +#-keepattributes SourceFile,LineNumberTable + +# If you keep the line number information, uncomment this to +# hide the original source file name. +#-renamesourcefileattribute SourceFile \ No newline at end of file diff --git a/feature/create/src/androidTest/java/com/example/create/ExampleInstrumentedTest.kt b/feature/create/src/androidTest/java/com/example/create/ExampleInstrumentedTest.kt new file mode 100644 index 00000000..be3c1df4 --- /dev/null +++ b/feature/create/src/androidTest/java/com/example/create/ExampleInstrumentedTest.kt @@ -0,0 +1,24 @@ +package com.example.create + +import androidx.test.platform.app.InstrumentationRegistry +import androidx.test.ext.junit.runners.AndroidJUnit4 + +import org.junit.Test +import org.junit.runner.RunWith + +import org.junit.Assert.* + +/** + * Instrumented test, which will execute on an Android device. + * + * See [testing documentation](http://d.android.com/tools/testing). + */ +@RunWith(AndroidJUnit4::class) +class ExampleInstrumentedTest { + @Test + fun useAppContext() { + // Context of the app under test. + val appContext = InstrumentationRegistry.getInstrumentation().targetContext + assertEquals("com.example.create.test", appContext.packageName) + } +} diff --git a/feature/create/src/main/AndroidManifest.xml b/feature/create/src/main/AndroidManifest.xml new file mode 100644 index 00000000..8bdb7e14 --- /dev/null +++ b/feature/create/src/main/AndroidManifest.xml @@ -0,0 +1,4 @@ + + + + diff --git a/feature/create/src/main/java/com/example/create/CreatePickScreen.kt b/feature/create/src/main/java/com/example/create/CreatePickScreen.kt new file mode 100644 index 00000000..a48c9685 --- /dev/null +++ b/feature/create/src/main/java/com/example/create/CreatePickScreen.kt @@ -0,0 +1,351 @@ +package com.example.create + +import android.app.Activity +import android.util.Size +import androidx.activity.compose.BackHandler +import androidx.compose.foundation.background +import androidx.compose.foundation.clickable +import androidx.compose.foundation.interaction.MutableInteractionSource +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.aspectRatio +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.statusBarsPadding +import androidx.compose.foundation.rememberScrollState +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.foundation.verticalScroll +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.automirrored.filled.ArrowBack +import androidx.compose.material.icons.filled.Check +import androidx.compose.material3.CenterAlignedTopAppBar +import androidx.compose.material3.CircularProgressIndicator +import androidx.compose.material3.ExperimentalMaterial3Api +import androidx.compose.material3.Icon +import androidx.compose.material3.IconButton +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.OutlinedTextField +import androidx.compose.material3.OutlinedTextFieldDefaults +import androidx.compose.material3.Scaffold +import androidx.compose.material3.Text +import androidx.compose.material3.TopAppBarDefaults +import androidx.compose.runtime.Composable +import androidx.compose.runtime.LaunchedEffect +import androidx.compose.runtime.SideEffect +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.saveable.rememberSaveable +import androidx.compose.runtime.setValue +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.clip +import androidx.compose.ui.graphics.Brush +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.graphics.luminance +import androidx.compose.ui.platform.LocalView +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.text.style.TextAlign +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.dp +import androidx.core.graphics.toColorInt +import androidx.core.view.WindowInsetsControllerCompat +import androidx.hilt.navigation.compose.hiltViewModel +import androidx.lifecycle.compose.collectAsStateWithLifecycle +import com.example.common.ui.AlbumImage +import com.example.common.ui.VerticalSpacer +import com.example.common.ui.theme.Black +import com.example.common.ui.theme.Dark +import com.example.common.ui.theme.Gray +import com.example.common.ui.theme.White +import com.example.model.Song + +@Composable +fun CreatePickScreen( + song: Song, + onBackClick: () -> Unit, + onCreateClick: (String) -> Unit, + createPickViewModel: CreatePickViewModel = hiltViewModel(), +) { + + val comment = createPickViewModel.comment.collectAsStateWithLifecycle() + val uiState by createPickViewModel.createPickUiState.collectAsStateWithLifecycle() + var showCreateIndicator by rememberSaveable { mutableStateOf(false) } + + val dynamicBackgroundColor = Color(song.bgColor) + val dynamicOnBackgroundColor = if (dynamicBackgroundColor.luminance() >= 0.5f) Black else White + val view = LocalView.current + + if (!view.isInEditMode) { + SideEffect { + val window = (view.context as Activity).window + val windowInsetsController = WindowInsetsControllerCompat(window, view) + val isLightStatusBar = dynamicBackgroundColor.luminance() >= 0.5f + windowInsetsController.isAppearanceLightStatusBars = isLightStatusBar + } + } + + // 생성 중 인디케이터가 표시되고 있을 때는 시스템의 뒤로 가기 버튼 클릭을 무시 + BackHandler(enabled = showCreateIndicator) { } + + when (uiState) { + CreateUiState.Default -> { + CreatePickDisplay( + song = song, + comment = comment.value, + dynamicBackgroundColor = dynamicBackgroundColor, + dynamicOnBackgroundColor = dynamicOnBackgroundColor, + onBackClick = { + createPickViewModel.resetComment() + onBackClick() + }, + onCreateClick = { + createPickViewModel.onCreatePickClick() + showCreateIndicator = true + }, + onCommentChange = createPickViewModel::onCommentChange + ) + } + + is CreateUiState.Success -> { + LaunchedEffect(Unit) { + showCreateIndicator = false + val pickId = (uiState as CreateUiState.Success).data + onCreateClick(pickId) + } + } + + CreateUiState.Error -> { + // TODO() + showCreateIndicator = false + Text("생성 오류") + } + + else -> {} + } + + if (showCreateIndicator) { + Box( + modifier = Modifier + .fillMaxSize() + .background(Black.copy(alpha = 0.5F)) + .clickable( // 클릭 효과 제거 및 클릭 이벤트 무시 + interactionSource = remember { MutableInteractionSource() }, + indication = null, + onClick = {} + ), + contentAlignment = Alignment.Center + ) { + CircularProgressIndicator() + } + } +} + +@Composable +private fun CreatePickDisplay( + song: Song, + comment: String, + dynamicBackgroundColor: Color, + dynamicOnBackgroundColor: Color, + onCommentChange: (String) -> Unit, + onBackClick: () -> Unit, + onCreateClick: () -> Unit, +) { + Scaffold( + containerColor = dynamicBackgroundColor, + topBar = { + CreatePickScreenTopBar( + dynamicOnBackgroundColor = dynamicOnBackgroundColor, + onBackClick = onBackClick, + onCreateClick = onCreateClick + ) + } + ) { innerPadding -> + Box( + modifier = Modifier + .padding(innerPadding) + .fillMaxSize() + .background( + brush = Brush.verticalGradient( + colorStops = arrayOf( + 0.0f to dynamicBackgroundColor, + 0.47f to Black + ) + ) + ) + ) { + CreatePickContent( + song = song, + comment = comment, + onValueChange = onCommentChange, + dynamicOnBackgroundColor = dynamicOnBackgroundColor, + ) + } + } +} + +@Composable +private fun CreatePickContent( + song: Song, + comment: String, + onValueChange: (String) -> Unit, + dynamicOnBackgroundColor: Color, +) { + val scrollState = rememberScrollState() + + Column( + modifier = Modifier + .fillMaxSize() + .verticalScroll(scrollState) + .padding(vertical = 10.dp), + horizontalAlignment = Alignment.CenterHorizontally, + verticalArrangement = Arrangement.spacedBy(10.dp), + ) { + Text( + text = song.songName, + color = dynamicOnBackgroundColor, + textAlign = TextAlign.Center, + style = MaterialTheme.typography.titleLarge.copy(fontWeight = FontWeight.Bold) + ) + + Text( + text = song.artistName, + color = dynamicOnBackgroundColor, + textAlign = TextAlign.Center, + style = MaterialTheme.typography.bodyLarge + ) + + AlbumImage( + imageUrl = song.getImageUrlWithSize(RequestImageSize.width, RequestImageSize.height), + modifier = Modifier + .fillMaxWidth() + .padding(horizontal = 30.dp) + .aspectRatio(1f) + .clip(RoundedCornerShape(20.dp)), + contentDescription = song.albumName + stringResource(id = R.string.pick_album_description) + ) + + VerticalSpacer(40) + + CommentTextBox( + comment = comment, + onValueChange = onValueChange, + ) + } +} + +@Composable +private fun CommentTextBox( + comment: String, + onValueChange: (String) -> Unit, +) { + OutlinedTextField( + value = comment, + onValueChange = { textValue -> + onValueChange(textValue) + }, + modifier = Modifier + .fillMaxWidth() + .height(100.dp) + .padding(horizontal = 30.dp), + textStyle = MaterialTheme.typography.bodyLarge.copy(White), + placeholder = { + Text( + text = stringResource(id = R.string.pick_comment_placeholder), + style = MaterialTheme.typography.bodyLarge.copy(Gray) + ) + }, + shape = RoundedCornerShape(10.dp), + colors = OutlinedTextFieldDefaults.colors( + unfocusedContainerColor = Dark, + focusedContainerColor = Dark, + focusedBorderColor = Color.Transparent, + unfocusedBorderColor = Color.Transparent + ) + ) +} + +@OptIn(ExperimentalMaterial3Api::class) +@Composable +private fun CreatePickScreenTopBar( + dynamicOnBackgroundColor: Color, + onBackClick: () -> Unit, + onCreateClick: () -> Unit +) { + CenterAlignedTopAppBar( + modifier = Modifier.statusBarsPadding(), + title = { + Text( + text = stringResource(id = R.string.pick_app_bar_title), + color = dynamicOnBackgroundColor, + style = MaterialTheme.typography.titleLarge.copy(fontWeight = FontWeight.Bold) + ) + }, + navigationIcon = { + IconButton(onClick = onBackClick) { + Icon( + imageVector = Icons.AutoMirrored.Filled.ArrowBack, + contentDescription = stringResource(id = R.string.top_app_bar_back_description), + tint = dynamicOnBackgroundColor + ) + } + }, + actions = { + IconButton(onClick = onCreateClick) { + Icon( + imageVector = Icons.Filled.Check, + contentDescription = stringResource(id = R.string.pick_app_bar_upload_description), + tint = dynamicOnBackgroundColor + ) + } + }, + colors = TopAppBarDefaults.centerAlignedTopAppBarColors( + containerColor = Color.Transparent + ) + ) +} + +@Preview +@Composable +private fun CreatePickScreenPreview() { + CreatePickDisplay( + song = Song( + id = "1778132734", + songName = "Super Shy", + artistName = "뉴진스", + albumName = "NewJeans 'Super Shy' - Single", + imageUrl = "https://i.scdn.co/image/ab67616d0000b2733d98a0ae7c78a3a9babaf8af", + genreNames = listOf("K-Pop"), + bgColor = "#8FC1E2".toColorInt(), + externalUrl = "", + previewUrl = "" + ), + onBackClick = {}, + comment = "TEST COMMENT", + dynamicBackgroundColor = Color.White, + dynamicOnBackgroundColor = Color.Gray, + onCommentChange = { + + }, + onCreateClick = { + + } + ) +} + +private val RequestImageSize = Size(720, 720) +private val DEFAULT_SONG = Song( + id = "1778132734", + songName = "", + artistName = "", + albumName = "", + imageUrl = "", + genreNames = listOf("K-Pop"), + bgColor = "#8FC1E2".toColorInt(), + externalUrl = "", + previewUrl = "" +) diff --git a/feature/create/src/main/java/com/example/create/CreatePickUiState.kt b/feature/create/src/main/java/com/example/create/CreatePickUiState.kt new file mode 100644 index 00000000..f9f7da26 --- /dev/null +++ b/feature/create/src/main/java/com/example/create/CreatePickUiState.kt @@ -0,0 +1,12 @@ +package com.example.create + +sealed class SearchUiState { + data object HotResult : SearchUiState() + data object SearchResult : SearchUiState() +} + +sealed class CreateUiState { + data object Default : CreateUiState() + data class Success(val data: T) : CreateUiState() + data object Error : CreateUiState() +} diff --git a/feature/create/src/main/java/com/example/create/CreatePickViewModel.kt b/feature/create/src/main/java/com/example/create/CreatePickViewModel.kt new file mode 100644 index 00000000..50ee67ef --- /dev/null +++ b/feature/create/src/main/java/com/example/create/CreatePickViewModel.kt @@ -0,0 +1,120 @@ +package com.example.create + +import android.location.Location +import android.util.Log +import androidx.lifecycle.SavedStateHandle +import androidx.lifecycle.ViewModel +import androidx.lifecycle.viewModelScope +import androidx.navigation.toRoute +import com.example.applemusic.usecase.FetchMusicVideoUseCase +import com.example.location.usecase.GetLastLocationUseCase +import com.example.model.Creator +import com.example.model.LocationPoint +import com.example.model.Pick +import com.example.model.Song +import com.example.navigation.SearchRoute +import com.example.pick.usecase.CreatePickUseCase +import com.example.user.usecase.GetCurrentUserUseCase +import com.example.util.serializableType +import com.example.util.throttleFirst +import dagger.hilt.android.lifecycle.HiltViewModel +import kotlinx.coroutines.FlowPreview +import kotlinx.coroutines.flow.MutableSharedFlow +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.asStateFlow +import kotlinx.coroutines.launch +import javax.inject.Inject +import kotlin.reflect.typeOf + +@OptIn(FlowPreview::class) +@HiltViewModel +class CreatePickViewModel @Inject constructor( + savedStateHandle: SavedStateHandle, + getLastLocationUseCase: GetLastLocationUseCase, + private val fetchMusicVideoUseCase: FetchMusicVideoUseCase, + private val createPickUseCase: CreatePickUseCase, + private val getCurrentUserUseCase: GetCurrentUserUseCase +) : ViewModel() { + + private val song = + savedStateHandle.toRoute(mapOf(typeOf() to serializableType())).song + + private val _createPickUiState = MutableStateFlow>(CreateUiState.Default) + val createPickUiState = _createPickUiState.asStateFlow() + + private val _comment = MutableStateFlow("") + val comment get() = _comment + + private var lastLocation: Location? = null + private val createPickClick = MutableSharedFlow() + + init { + // 데이터소스의 위치값을 계속 collect하며 curLocation 변수에 저장 + viewModelScope.launch { + getLastLocationUseCase().collect { location -> + lastLocation = location + } + } + + // 등록 버튼 클릭 후 3초 이내의 클릭은 무시하고 픽 생성하기 + viewModelScope.launch { + createPickClick + .throttleFirst(3000) + .collect { + createPick(song) + } + } + } + + fun onCommentChange(text: String) { + _comment.value = text + } + + fun resetComment() { + _comment.value = "" + } + + fun onCreatePickClick() { + viewModelScope.launch { + createPickClick.emit(Unit) + } + } + + private fun createPick(song: Song) { + viewModelScope.launch { + if (lastLocation == null) { + /* TODO: DEFAULT 인 경우 -> LocalDataSource 위치 데이터 못 불러옴 */ + return@launch + } + + val musicVideo = fetchMusicVideoUseCase(song) + + /* 등록 결과 - pick ID 담긴 Result */ + getCurrentUserUseCase()?.let { user -> + val createResult = createPickUseCase( + Pick( + id = "", + song = song, + comment = _comment.value, + createdAt = "", + createdBy = Creator( + userId = user.userId, + userName = user.userName + ), + location = LocationPoint(lastLocation!!.latitude, lastLocation!!.longitude), + musicVideoUrl = musicVideo?.previewUrl ?: "", + musicVideoThumbnailUrl = musicVideo?.thumbnailUrl ?: "" + ) + ) + + createResult.onSuccess { pickId -> + _createPickUiState.emit(CreateUiState.Success(pickId)) + }.onFailure { + /* TODO: Firestore 등록 실패처리 */ + _createPickUiState.emit(CreateUiState.Error) + Log.d("CreatePickViewModel", createResult.exceptionOrNull()?.message.toString()) + } + } + } + } +} diff --git a/feature/create/src/main/res/values/strings.xml b/feature/create/src/main/res/values/strings.xml new file mode 100644 index 00000000..c0ae7751 --- /dev/null +++ b/feature/create/src/main/res/values/strings.xml @@ -0,0 +1,9 @@ + + MusicRoad + + 상단 바 뒤로 가기 버튼 + 앨범 이미지 + 거리에 남길 한마디를 입력하세요. + 픽 등록 + 등록하기 + diff --git a/feature/create/src/test/java/com/example/create/ExampleUnitTest.kt b/feature/create/src/test/java/com/example/create/ExampleUnitTest.kt new file mode 100644 index 00000000..f747d23f --- /dev/null +++ b/feature/create/src/test/java/com/example/create/ExampleUnitTest.kt @@ -0,0 +1,17 @@ +package com.example.create + +import org.junit.Test + +import org.junit.Assert.* + +/** + * Example local unit test, which will execute on the development machine (host). + * + * See [testing documentation](http://d.android.com/tools/testing). + */ +class ExampleUnitTest { + @Test + fun addition_isCorrect() { + assertEquals(4, 2 + 2) + } +} diff --git a/settings.gradle.kts b/settings.gradle.kts index 9d40721e..fd1b10ba 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -48,3 +48,5 @@ include(":data:order") include(":data:pick") include(":data:user") include(":core:musicplayer") +include(":core:account") +include(":feature:create") From 91d5da0b2f7e3a0dc68392457fb10463cf74a4d4 Mon Sep 17 00:00:00 2001 From: miller198 Date: Thu, 27 Feb 2025 20:24:04 +0900 Subject: [PATCH 17/62] =?UTF-8?q?[build]=20libs.version=20=EC=A0=95?= =?UTF-8?q?=EB=A6=AC,=20=EB=B2=88=EB=93=A4=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- gradle/libs.versions.toml | 181 +++++++++++++++++++++++++++++++------- 1 file changed, 147 insertions(+), 34 deletions(-) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 3518b61c..43fd0dcc 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -6,24 +6,30 @@ minSdk = "26" targetSdk = "34" jvmTarget = "1.8" +# App Version +versionCode = "10100" +versionName = "1.1.0" + # Build agp = "8.5.1" +androidTools = "31.2.0" # AndroidX androidx-core-ktx = "1.13.1" -androidx-core-splashscreen = "1.0.1" -androidx-animation = "1.7.5" -androidx-credentials = "1.3.0" +androidx-lifecycle = "2.8.3" androidx-lifecycle-runtime-ktx = "2.8.7" androidx-datastore-preferences = "1.1.1" androidx-appcompat = "1.7.0" androidx-navigation-common-ktx = "2.8.5" +# Splash +androidx-core-splashscreen = "1.0.1" # Media3 media3Ui = "1.4.1" # Compose +androidx-compose-animation = "1.7.5" activityCompose = "1.9.3" composeBom = "2024.10.01" composeMaterial = "1.4.0" @@ -31,10 +37,10 @@ navigationCompose = "2.8.3" compose-runtime-android = "1.7.6" # Kotlin -kotlin = "1.9.22" +kotlin = "1.9.24" kotlinxImmutable = "0.3.7" jetbrainsKotlinJvm = "1.9.22" -ksp = "1.9.22-1.0.16" +ksp = "1.9.24-1.0.20" # Coroutines kotlinxCoroutinesPlayServices = "1.9.0" @@ -44,7 +50,6 @@ kotlinxCoroutinesCore = "1.9.0" coil = "3.0.0" # Firebase -firebaseFirestoreKtxVersion = "25.1.2" firebaseBom = "33.5.1" firebaseDynamicModuleSupportVersion = "16.0.0-beta03" firebaseFirestoreKtx = "25.1.1" @@ -63,9 +68,11 @@ junit = "4.13.2" junitVersion = "1.2.1" # Google -googleid = "1.1.1" googleServices = "4.4.2" -playServicesLocation = "21.3.0" + +# Account +androidx-credentials = "1.3.0" +googleid = "1.1.1" # Paging pagingRuntime = "3.3.4" @@ -74,8 +81,9 @@ pagingComposeAndroid = "3.3.4" # Serialization kotlinxSerializationJson = "1.6.0" -# NaverMap +# Map mapSdk = "3.19.1" +playServicesLocation = "21.3.0" # Network okhttp = "4.12.0" @@ -85,16 +93,15 @@ material = "1.12.0" [libraries] # AndroidX -androidx-animation = { module = "androidx.compose.animation:animation", version.ref = "androidx-animation" } +androidx-appcompat = { group = "androidx.appcompat", name = "appcompat", version.ref = "androidx-appcompat" } androidx-core-ktx = { group = "androidx.core", name = "core-ktx", version.ref = "androidx-core-ktx" } -androidx-credentials = { module = "androidx.credentials:credentials", version.ref = "androidx-credentials" } -androidx-credentials-play-services-auth = { module = "androidx.credentials:credentials-play-services-auth", version.ref = "androidx-credentials" } androidx-datastore-preferences = { module = "androidx.datastore:datastore-preferences", version.ref = "androidx-datastore-preferences" } androidx-lifecycle-runtime-ktx = { group = "androidx.lifecycle", name = "lifecycle-runtime-ktx", version.ref = "androidx-lifecycle-runtime-ktx" } -androidx-appcompat = { group = "androidx.appcompat", name = "appcompat", version.ref = "androidx-appcompat" } -androidx-core-splashscreen = { module = "androidx.core:core-splashscreen", version.ref = "androidx-core-splashscreen" } androidx-navigation-common-ktx = { group = "androidx.navigation", name = "navigation-common-ktx", version.ref = "androidx-navigation-common-ktx" } +# Splash +androidx-core-splashscreen = { module = "androidx.core:core-splashscreen", version.ref = "androidx-core-splashscreen" } + # Media3 androidx-media3-exoplayer = { module = "androidx.media3:media3-exoplayer", version.ref = "media3Ui" } androidx-media3-exoplayer-dash = { module = "androidx.media3:media3-exoplayer-dash", version.ref = "media3Ui" } @@ -107,42 +114,45 @@ inject = { group = "javax.inject", name = "javax.inject", version.ref = "inject" hilt-android = { group = "com.google.dagger", name = "hilt-android", version.ref = "hilt" } hilt-android-compiler = { group = "com.google.dagger", name = "hilt-android-compiler", version.ref = "hilt" } hilt-android-testing = { group = "com.google.dagger", name = "hilt-android-testing", version.ref = "hilt" } -androidx-hilt-navigation-compose = { module = "androidx.hilt:hilt-navigation-compose", version.ref = "hiltNavigationCompose" } +hilt-navigation-compose = { module = "androidx.hilt:hilt-navigation-compose", version.ref = "hiltNavigationCompose" } # Compose +compose-bom = { group = "androidx.compose", name = "compose-bom", version.ref = "composeBom" } androidx-activity-compose = { group = "androidx.activity", name = "activity-compose", version.ref = "activityCompose" } -androidx-compose-bom = { group = "androidx.compose", name = "compose-bom", version.ref = "composeBom" } -androidx-compose-material3 = { group = "androidx.compose.material3", name = "material3" } -androidx-navigation-compose = { module = "androidx.navigation:navigation-compose", version.ref = "navigationCompose" } -androidx-compose-material = { group = "androidx.wear.compose", name = "compose-material", version.ref = "composeMaterial" } -androidx-ui = { group = "androidx.compose.ui", name = "ui" } -androidx-ui-graphics = { group = "androidx.compose.ui", name = "ui-graphics" } -androidx-ui-tooling = { group = "androidx.compose.ui", name = "ui-tooling" } -androidx-ui-tooling-preview = { group = "androidx.compose.ui", name = "ui-tooling-preview" } -androidx-ui-test-manifest = { group = "androidx.compose.ui", name = "ui-test-manifest" } -androidx-ui-test-junit4 = { group = "androidx.compose.ui", name = "ui-test-junit4" } -androidx-runtime-android = { group = "androidx.compose.runtime", name = "runtime-android", version.ref = "compose-runtime-android" } +androidx-lifecycle-viewmodel-compose = { group = "androidx.lifecycle", name = "lifecycle-viewmodel-compose", version.ref = "androidx-lifecycle" } +navigation-compose = { module = "androidx.navigation:navigation-compose", version.ref = "navigationCompose" } +compose-ui = { group = "androidx.compose.ui", name = "ui" } +compose-ui-tooling = { group = "androidx.compose.ui", name = "ui-tooling" } +compose-ui-tooling-preview = { group = "androidx.compose.ui", name = "ui-tooling-preview" } +compose-ui-test-manifest = { group = "androidx.compose.ui", name = "ui-test-manifest" } +compose-ui-test-junit4 = { group = "androidx.compose.ui", name = "ui-test-junit4" } +compose-runtime-android = { group = "androidx.compose.runtime", name = "runtime-android", version.ref = "compose-runtime-android" } # Paging androidx-paging-runtime = { module = "androidx.paging:paging-runtime", version.ref = "pagingRuntime" } -# Google +# Auth +androidx-credentials = { module = "androidx.credentials:credentials", version.ref = "androidx-credentials" } +androidx-credentials-play-services-auth = { module = "androidx.credentials:credentials-play-services-auth", version.ref = "androidx-credentials" } googleid = { module = "com.google.android.libraries.identity.googleid:googleid", version.ref = "googleid" } -play-services-location = { module = "com.google.android.gms:play-services-location", version.ref = "playServicesLocation" } +# Coroutines kotlinx-coroutines-core = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-core", version.ref = "kotlinxCoroutinesCore" } -kotlinx-coroutines-play-services = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-play-services", version.ref = "kotlinxCoroutinesPlayServices" } +kotlinx-coroutines-android = { group = "org.jetbrains.kotlinx", name = "kotlinx-coroutines-android", version.ref = "kotlinxCoroutinesCore" } kotlinx-immutable = { group = "org.jetbrains.kotlinx", name = "kotlinx-collections-immutable", version.ref = "kotlinxImmutable" } # Firebase -firebase-analytics = { module = "com.google.firebase:firebase-analytics" } firebase-bom = { module = "com.google.firebase:firebase-bom", version.ref = "firebaseBom" } firebase-firestore-ktx = { group = "com.google.firebase", name = "firebase-firestore-ktx", version.ref = "firebaseFirestoreKtx" } firebase-functions-ktx = { group = "com.google.firebase", name = "firebase-functions-ktx", version.ref = "firebaseFunctionsKtx" } -firebase-crashlytics = { module = "com.google.firebase:firebase-crashlytics" } + +# goefire geofire-android-common = { module = "com.firebase:geofire-android-common", version.ref = "geofireAndroidCommon" } google-firebase-dynamic-module-support = { module = "com.google.firebase:firebase-dynamic-module-support", version.ref = "firebaseDynamicModuleSupportVersion" } -google-firebase-firestore-ktx = { group = "com.google.firebase", name = "firebase-firestore-ktx", version.ref = "firebaseFirestoreKtxVersion" } + +# Analytics +firebase-crashlytics = { module = "com.google.firebase:firebase-crashlytics" } +firebase-analytics = { module = "com.google.firebase:firebase-analytics" } # Test junit = { group = "junit", name = "junit", version.ref = "junit" } @@ -151,12 +161,16 @@ androidx-espresso-core = { group = "androidx.test.espresso", name = "espresso-co # Map map-sdk = { module = "com.naver.maps:map-sdk", version.ref = "mapSdk" } +play-services-location = { module = "com.google.android.gms:play-services-location", version.ref = "playServicesLocation" } # Material Design material = { group = "com.google.android.material", name = "material", version.ref = "material" } -androidx-material-icons-extended = { module = "androidx.compose.material:material-icons-extended" } +compose-material3 = { group = "androidx.compose.material3", name = "material3" } +compose-material = { group = "androidx.wear.compose", name = "compose-material", version.ref = "composeMaterial" } +compose-material-icons-extended = { module = "androidx.compose.material:material-icons-extended" } # Network +okhttp = { group = "com.squareup.okhttp3", name = "okhttp" } okhttp-logging = { group = "com.squareup.okhttp3", name = "logging-interceptor", version.ref = "okhttp" } retrofit-core = { group = "com.squareup.retrofit2", name = "retrofit", version.ref = "retrofit" } retrofit-kotlin-serialization = { module = "com.squareup.retrofit2:converter-kotlinx-serialization", version.ref = "retrofit" } @@ -172,6 +186,104 @@ coil-network-okhttp = { module = "io.coil-kt.coil3:coil-network-okhttp", version # Paging androidx-paging-compose-android = { group = "androidx.paging", name = "paging-compose-android", version.ref = "pagingComposeAndroid" } +# Build-logic +android-gradlePlugin = { group = "com.android.tools.build", name = "gradle", version.ref = "agp" } +android-tools-common = { group = "com.android.tools", name = "common", version.ref = "androidTools" } +kotlin-gradlePlugin = { group = "org.jetbrains.kotlin", name = "kotlin-gradle-plugin", version.ref = "kotlin" } +ksp-gradlePlugin = { group = "com.google.devtools.ksp", name = "com.google.devtools.ksp.gradle.plugin", version.ref = "ksp" } + +[bundles] +androidx-core = [ + "androidx-core-ktx", + "androidx-appcompat", + "androidx-lifecycle-runtime-ktx", +] + +# Compose +compose = [ + "compose-ui", + "compose-ui-tooling-preview", + "compose-runtime-android", + "androidx-activity-compose", + "androidx-lifecycle-viewmodel-compose", +] +compose-debug = [ + "compose-ui-tooling", + "compose-ui-test-manifest", + "compose-ui-test-junit4", +] +material = [ + "material", + "compose-material", + "compose-material3", + "compose-material-icons-extended", +] + +navigation = [ + "navigation-compose", + "hilt-navigation-compose" +] + +coroutines = [ + "kotlinx-coroutines-core", + "kotlinx-coroutines-android", +] + +datastore = [ + "androidx-datastore-preferences", +] + +auth = [ + "androidx-credentials", + "androidx-credentials-play-services-auth", + "googleid", +] + +# Network +retrofit = [ + "retrofit-core", + "retrofit-kotlin-serialization", +] +okhttp = [ + "okhttp", + "okhttp-logging", +] + +# Firebase +firebase = [ + "firebase-firestore-ktx", + "firebase-functions-ktx", +] +analytics = [ + "firebase-analytics", + "firebase-crashlytics", +] +geofire = [ + "geofire-android-common", + "google-firebase-dynamic-module-support", +] + +# Coil +coil = [ + "coil", + "coil-compose", + "coil-network-okhttp", +] + +media3 = [ + "androidx-media3-exoplayer", + "androidx-media3-exoplayer-dash", + "androidx-media3-ui", + "androidx-media3-session", + "androidx-media3-common", +] + +map = [ + "map-sdk", + "play-services-location", +] + + # Plugins [plugins] android-application = { id = "com.android.application", version.ref = "agp" } @@ -184,4 +296,5 @@ google-services = { id = "com.google.gms.google-services", version.ref = "google firebase-crashlytics = { id = "com.google.firebase.crashlytics", version.ref = "firebaseCrashlytics" } jetbrains-kotlin-jvm = { id = "org.jetbrains.kotlin.jvm", version.ref = "jetbrainsKotlinJvm" } -[bundles] +# Custom Plugins +musicroad-android-application = { id = "musicroad.android.application", version = "unspecified" } From 2d6c69d2facecd6637f9d9ae97d170f9b10cd8b6 Mon Sep 17 00:00:00 2001 From: miller198 Date: Thu, 27 Feb 2025 20:27:56 +0900 Subject: [PATCH 18/62] =?UTF-8?q?[chore]=20=ED=8C=A8=ED=82=A4=EC=A7=80?= =?UTF-8?q?=EB=AA=85=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 모듈 생성시 기본명인 example를 모두 squirtles로 수정 --- app/build.gradle.kts | 29 +- core/account/build.gradle.kts | 2 +- .../com/example/account/AccountViewModel.kt | 64 ---- .../main/java/com/example/account/GoogleId.kt | 70 ---- core/common/build.gradle.kts | 16 +- .../java/com/example/common/ui/AlbumImage.kt | 39 -- .../java/com/example/common/ui/Constants.kt | 17 - .../example/common/ui/CreatedByPickText.kt | 64 ---- .../com/example/common/ui/DefaultTopAppBar.kt | 56 --- .../example/common/ui/MessageAlertDialog.kt | 126 ------- .../com/example/common/ui/PickInfoText.kt | 102 ----- .../main/java/com/example/common/ui/Spacer.kt | 14 - .../java/com/example/common/ui/theme/Color.kt | 21 -- .../java/com/example/common/ui/theme/Theme.kt | 45 --- .../java/com/example/common/ui/theme/Type.kt | 34 -- core/mediaservice/build.gradle.kts | 2 +- .../mediaservice/ExampleInstrumentedTest.kt | 4 +- .../CustomMediaSessionCallback.kt | 2 +- .../mediaservice/MediaControllerProvider.kt | 2 +- .../mediaservice/MediaNotificationProvider.kt | 2 +- .../mediaservice/MediaPlayerService.kt | 2 +- .../mediaservice/PlayerCommands.kt | 2 +- .../mediaservice/di/MediaDiModule.kt | 14 +- .../mediaservice/ExampleUnitTest.kt | 2 +- .../model/MusicVideo.kt | 2 +- .../com/{example => squirtles}/model/Order.kt | 2 +- .../com/{example => squirtles}/model/Pick.kt | 2 +- .../model/PlayerState.kt | 2 +- .../com/{example => squirtles}/model/Song.kt | 2 +- .../com/{example => squirtles}/model/User.kt | 2 +- core/musicplayer/build.gradle.kts | 2 +- .../musicplayer/PlayerServiceViewModel.kt | 12 +- .../musicplayer/PlayerUiState.kt | 2 +- .../musicplayer/PlayerViewModel.kt | 6 +- .../navigation/MainRoute.kt | 2 +- .../navigation/MapRoute.kt | 2 +- .../navigation/ProfileRoute.kt | 2 +- .../navigation/Route.kt | 2 +- .../navigation/SearchRoute.kt | 5 +- core/picklist/build.gradle.kts | 14 +- .../picklist}/ExampleInstrumentedTest.kt | 4 +- .../picklist/Extension.kt | 6 +- .../picklist/OrderBottomSheet.kt | 12 +- .../picklist/PickListContents.kt | 17 +- .../picklist/PickListItem.kt | 22 +- .../picklist/PickListType.kt | 2 +- .../picklist/PickListUiState.kt | 6 +- .../picklist/ExampleUnitTest.kt | 2 +- core/util/build.gradle.kts | 4 +- .../util}/ExampleInstrumentedTest.kt | 4 +- .../util/SerializableType.kt | 2 +- .../util/ThrottleFirst.kt | 2 +- .../util/ExampleUnitTest.kt | 2 +- data/applemusic/build.gradle.kts | 2 +- .../applemusic/AppleMusicDataSourceImpl.kt | 14 +- .../applemusic/AppleMusicRepositoryImpl.kt | 6 +- .../applemusic/SearchSongsPagingSource.kt | 8 +- .../applemusic/api/AppleMusicApi.kt | 4 +- .../applemusic/api/NetworkModule.kt | 4 +- .../applemusic/di/AppleMusicModule.kt | 12 +- .../applemusic/model/AppleMusicMapper.kt | 6 +- .../applemusic/model/Artwork.kt | 2 +- .../applemusic/model/Attributes.kt | 2 +- .../applemusic/model/Data.kt | 2 +- .../applemusic/model/MusicVideoResponse.kt | 2 +- .../applemusic/model/Preview.kt | 2 +- .../applemusic/model/Results.kt | 2 +- .../applemusic/model/SearchResponse.kt | 2 +- .../applemusic/model/Songs.kt | 2 +- data/build.gradle.kts | 2 +- data/favorite/build.gradle.kts | 4 +- .../favorite/CloudFunctionHelper.kt | 2 +- .../FirebaseFavoriteDataSourceImpl.kt | 10 +- .../FirebaseFavoriteRepositoryImpl.kt | 4 +- .../favorite/di/FirebaseFavoriteModule.kt | 12 +- data/firebase/build.gradle.kts | 4 +- .../firebase/FirebaseDataSourceConstants.kt | 2 +- .../firebase/FirebaseException.kt | 2 +- .../firebase/FirebaseModule.kt | 2 +- .../firebase/FirebaseRepositoryUtils.kt | 2 +- .../firebase/model/FirebaseFavorite.kt | 2 +- .../firebase/model/FirebasePick.kt | 2 +- .../firebase/model/FirebaseUser.kt | 2 +- .../firebase/model/Mapper.kt | 12 +- data/location/build.gradle.kts | 2 +- .../order/LocalLocationRepositoryImpl.kt | 4 +- .../order/di/LocationModule.kt | 6 +- data/order/build.gradle.kts | 2 +- .../order/LocalPickListOrderRepositoryImpl.kt | 4 +- .../order/di/OrderModule.kt | 6 +- data/pick/build.gradle.kts | 4 +- .../pick/FirebasePickDataSourceImpl.kt | 28 +- .../pick/FirebasePickRepositoryImpl.kt | 8 +- .../pick/di/PickModule.kt | 10 +- .../applemusic/SearchSongsPagingSource.kt | 3 + data/user/build.gradle.kts | 4 +- .../user/FirebaseUserDataSourceImpl.kt | 8 +- .../user/FirebaseUserRepositoryImpl.kt | 8 +- .../user/LocalUserDataSourceImpl.kt | 4 +- .../user/LocalUserRepositoryImpl.kt | 4 +- .../{example => squirtles}/user/di/UserDi.kt | 18 +- domain/applemusic/build.gradle.kts | 2 +- .../applemusic/AppleMusicException.kt | 2 +- .../applemusic/AppleMusicRemoteDataSource.kt | 6 +- .../applemusic/AppleMusicRepository.kt | 6 +- .../usecase/FetchMusicVideoUseCase.kt | 8 +- .../applemusic/usecase/FetchSongsUseCase.kt | 4 +- .../favorite/FirebaseFavoriteDataSource.kt | 4 +- .../favorite/FirebaseFavoriteRepository.kt | 2 +- .../favorite/usecase/CreateFavoriteUseCase.kt | 4 +- .../favorite/usecase/DeleteFavoriteUseCase.kt | 6 +- .../usecase/FetchIsFavoriteUseCase.kt | 4 +- domain/location/build.gradle.kts | 2 +- .../location/LocalLocationRepository.kt | 2 +- .../usecase/GetLastLocationUseCase.kt | 4 +- .../usecase/SaveLastLocationUseCase.kt | 4 +- .../order/LocalPickListOrderRepository.kt | 4 +- .../usecase/GetFavoriteListOrderUseCase.kt | 6 +- .../usecase/GetMyPickListOrderUseCase.kt | 6 +- .../usecase/SaveFavoriteListOrderUseCase.kt | 8 +- .../usecase/SaveMyPickListOrderUseCase.kt | 8 +- .../pick/FirebasePickDataSource.kt | 4 +- .../pick/FirebasePickRepository.kt | 4 +- .../pick/usecase/CreatePickUseCase.kt | 6 +- .../pick/usecase/DeletePickUseCase.kt | 6 +- .../pick/usecase/FetchMyPicksUseCase.kt | 6 +- .../pick/usecase/FetchPickUseCase.kt | 4 +- .../picklist/FetchPickListUseCaseInterface.kt | 4 +- .../GetPickListOrderUseCaseInterface.kt | 4 +- .../picklist/RemovePickUseCaseInterface.kt | 2 +- .../SavePickListOrderUseCaseInterface.kt | 4 +- domain/player/build.gradle.kts | 2 +- .../player/MediaPlayerListenerUseCase.kt | 6 +- .../player/MediaPlayerUseCase.kt | 8 +- .../applemusic/usecase/FetchSongsUseCase.kt | 3 +- .../user/FirebaseUserDataSource.kt | 4 +- .../user/FirebaseUserRepository.kt | 4 +- .../user/LocalUserDataSource.kt | 4 +- .../user/LocalUserRepository.kt | 4 +- .../user/usecase/ClearUserUseCase.kt | 4 +- .../user/usecase/CreateGoogleIdUserUseCase.kt | 8 +- .../user/usecase/FetchUserByIdUseCase.kt | 4 +- .../user/usecase/FetchUserUseCase.kt | 6 +- .../user/usecase/GetCurrentUserUseCase.kt | 4 +- .../usecase/GetUserIdFromDataStoreUseCase.kt | 4 +- .../user/usecase/UpdateUserNameUseCase.kt | 4 +- feature/create/build.gradle.kts | 22 +- .../create}/ExampleInstrumentedTest.kt | 4 +- .../com/example/create/CreatePickScreen.kt | 351 ------------------ .../com/example/create/CreatePickUiState.kt | 12 - .../com/example/create/CreatePickViewModel.kt | 120 ------ .../create/ExampleUnitTest.kt | 2 +- settings.gradle.kts | 4 + 153 files changed, 365 insertions(+), 1491 deletions(-) delete mode 100644 core/account/src/main/java/com/example/account/AccountViewModel.kt delete mode 100644 core/account/src/main/java/com/example/account/GoogleId.kt delete mode 100644 core/common/src/main/java/com/example/common/ui/AlbumImage.kt delete mode 100644 core/common/src/main/java/com/example/common/ui/Constants.kt delete mode 100644 core/common/src/main/java/com/example/common/ui/CreatedByPickText.kt delete mode 100644 core/common/src/main/java/com/example/common/ui/DefaultTopAppBar.kt delete mode 100644 core/common/src/main/java/com/example/common/ui/MessageAlertDialog.kt delete mode 100644 core/common/src/main/java/com/example/common/ui/PickInfoText.kt delete mode 100644 core/common/src/main/java/com/example/common/ui/Spacer.kt delete mode 100644 core/common/src/main/java/com/example/common/ui/theme/Color.kt delete mode 100644 core/common/src/main/java/com/example/common/ui/theme/Theme.kt delete mode 100644 core/common/src/main/java/com/example/common/ui/theme/Type.kt rename core/mediaservice/src/androidTest/java/com/{example => squirtles}/mediaservice/ExampleInstrumentedTest.kt (83%) rename core/mediaservice/src/main/java/com/{example => squirtles}/mediaservice/CustomMediaSessionCallback.kt (99%) rename core/mediaservice/src/main/java/com/{example => squirtles}/mediaservice/MediaControllerProvider.kt (98%) rename core/mediaservice/src/main/java/com/{example => squirtles}/mediaservice/MediaNotificationProvider.kt (99%) rename core/mediaservice/src/main/java/com/{example => squirtles}/mediaservice/MediaPlayerService.kt (98%) rename core/mediaservice/src/main/java/com/{example => squirtles}/mediaservice/PlayerCommands.kt (97%) rename core/mediaservice/src/main/java/com/{example => squirtles}/mediaservice/di/MediaDiModule.kt (86%) rename core/mediaservice/src/test/java/com/{example => squirtles}/mediaservice/ExampleUnitTest.kt (90%) rename core/model/src/main/java/com/{example => squirtles}/model/MusicVideo.kt (89%) rename core/model/src/main/java/com/{example => squirtles}/model/Order.kt (69%) rename core/model/src/main/java/com/{example => squirtles}/model/Pick.kt (95%) rename core/model/src/main/java/com/{example => squirtles}/model/PlayerState.kt (90%) rename core/model/src/main/java/com/{example => squirtles}/model/Song.kt (95%) rename core/model/src/main/java/com/{example => squirtles}/model/User.kt (82%) rename core/musicplayer/src/main/java/com/{example => squirtles}/musicplayer/PlayerServiceViewModel.kt (91%) rename core/musicplayer/src/main/java/com/{example => squirtles}/musicplayer/PlayerUiState.kt (90%) rename core/musicplayer/src/main/java/com/{example => squirtles}/musicplayer/PlayerViewModel.kt (97%) rename core/navigation/src/main/java/com/{example => squirtles}/navigation/MainRoute.kt (90%) rename core/navigation/src/main/java/com/{example => squirtles}/navigation/MapRoute.kt (83%) rename core/navigation/src/main/java/com/{example => squirtles}/navigation/ProfileRoute.kt (90%) rename core/navigation/src/main/java/com/{example => squirtles}/navigation/Route.kt (79%) rename core/navigation/src/main/java/com/{example => squirtles}/navigation/SearchRoute.kt (65%) rename core/{util/src/androidTest/java/com/example/util => picklist/src/androidTest/java/com/squirtles/picklist}/ExampleInstrumentedTest.kt (84%) rename core/picklist/src/main/java/com/{example => squirtles}/picklist/Extension.kt (78%) rename core/picklist/src/main/java/com/{example => squirtles}/picklist/OrderBottomSheet.kt (95%) rename core/picklist/src/main/java/com/{example => squirtles}/picklist/PickListContents.kt (95%) rename core/picklist/src/main/java/com/{example => squirtles}/picklist/PickListItem.kt (87%) rename core/picklist/src/main/java/com/{example => squirtles}/picklist/PickListType.kt (62%) rename core/picklist/src/main/java/com/{example => squirtles}/picklist/PickListUiState.kt (68%) rename core/picklist/src/test/java/com/{example => squirtles}/picklist/ExampleUnitTest.kt (91%) rename {feature/create/src/androidTest/java/com/example/create => core/util/src/androidTest/java/com/squirtles/util}/ExampleInstrumentedTest.kt (86%) rename core/util/src/main/java/com/{example => squirtles}/util/SerializableType.kt (96%) rename core/util/src/main/java/com/{example => squirtles}/util/ThrottleFirst.kt (94%) rename core/util/src/test/java/com/{example => squirtles}/util/ExampleUnitTest.kt (92%) rename data/applemusic/src/main/java/com/{example => squirtles}/applemusic/AppleMusicDataSourceImpl.kt (83%) rename data/applemusic/src/main/java/com/{example => squirtles}/applemusic/AppleMusicRepositoryImpl.kt (90%) rename data/applemusic/src/main/java/com/{example => squirtles}/applemusic/SearchSongsPagingSource.kt (92%) rename data/applemusic/src/main/java/com/{example => squirtles}/applemusic/api/AppleMusicApi.kt (83%) rename data/applemusic/src/main/java/com/{example => squirtles}/applemusic/api/NetworkModule.kt (94%) rename data/applemusic/src/main/java/com/{example => squirtles}/applemusic/di/AppleMusicModule.kt (78%) rename data/applemusic/src/main/java/com/{example => squirtles}/applemusic/model/AppleMusicMapper.kt (91%) rename data/applemusic/src/main/java/com/{example => squirtles}/applemusic/model/Artwork.kt (81%) rename data/applemusic/src/main/java/com/{example => squirtles}/applemusic/model/Attributes.kt (93%) rename data/applemusic/src/main/java/com/{example => squirtles}/applemusic/model/Data.kt (76%) rename data/applemusic/src/main/java/com/{example => squirtles}/applemusic/model/MusicVideoResponse.kt (75%) rename data/applemusic/src/main/java/com/{example => squirtles}/applemusic/model/Preview.kt (81%) rename data/applemusic/src/main/java/com/{example => squirtles}/applemusic/model/Results.kt (86%) rename data/applemusic/src/main/java/com/{example => squirtles}/applemusic/model/SearchResponse.kt (74%) rename data/applemusic/src/main/java/com/{example => squirtles}/applemusic/model/Songs.kt (77%) rename data/favorite/src/main/java/com/{example => squirtles}/favorite/CloudFunctionHelper.kt (96%) rename data/favorite/src/main/java/com/{example => squirtles}/favorite/FirebaseFavoriteDataSourceImpl.kt (93%) rename data/favorite/src/main/java/com/{example => squirtles}/favorite/FirebaseFavoriteRepositoryImpl.kt (91%) rename data/favorite/src/main/java/com/{example => squirtles}/favorite/di/FirebaseFavoriteModule.kt (73%) rename data/firebase/src/main/java/com/{example => squirtles}/firebase/FirebaseDataSourceConstants.kt (92%) rename data/firebase/src/main/java/com/{example => squirtles}/firebase/FirebaseException.kt (95%) rename data/firebase/src/main/java/com/{example => squirtles}/firebase/FirebaseModule.kt (93%) rename data/firebase/src/main/java/com/{example => squirtles}/firebase/FirebaseRepositoryUtils.kt (91%) rename data/firebase/src/main/java/com/{example => squirtles}/firebase/model/FirebaseFavorite.kt (86%) rename data/firebase/src/main/java/com/{example => squirtles}/firebase/model/FirebasePick.kt (96%) rename data/firebase/src/main/java/com/{example => squirtles}/firebase/model/FirebaseUser.kt (79%) rename data/firebase/src/main/java/com/{example => squirtles}/firebase/model/Mapper.kt (92%) rename data/location/src/main/java/com/{example => squirtles}/order/LocalLocationRepositoryImpl.kt (87%) rename data/location/src/main/java/com/{example => squirtles}/order/di/LocationModule.kt (72%) rename data/order/src/main/java/com/{example => squirtles}/order/LocalPickListOrderRepositoryImpl.kt (89%) rename data/order/src/main/java/com/{example => squirtles}/order/di/OrderModule.kt (71%) rename data/pick/src/main/java/com/{example => squirtles}/pick/FirebasePickDataSourceImpl.kt (92%) rename data/pick/src/main/java/com/{example => squirtles}/pick/FirebasePickRepositoryImpl.kt (90%) rename data/pick/src/main/java/com/{example => squirtles}/pick/di/PickModule.kt (73%) rename data/user/src/main/java/com/{example => squirtles}/user/FirebaseUserDataSourceImpl.kt (95%) rename data/user/src/main/java/com/{example => squirtles}/user/FirebaseUserRepositoryImpl.kt (85%) rename data/user/src/main/java/com/{example => squirtles}/user/LocalUserDataSourceImpl.kt (96%) rename data/user/src/main/java/com/{example => squirtles}/user/LocalUserRepositoryImpl.kt (93%) rename data/user/src/main/java/com/{example => squirtles}/user/di/UserDi.kt (71%) rename domain/applemusic/src/main/java/com/{example => squirtles}/applemusic/AppleMusicException.kt (92%) rename domain/applemusic/src/main/java/com/{example => squirtles}/applemusic/AppleMusicRemoteDataSource.kt (70%) rename domain/applemusic/src/main/java/com/{example => squirtles}/applemusic/AppleMusicRepository.kt (75%) rename domain/applemusic/src/main/java/com/{example => squirtles}/applemusic/usecase/FetchMusicVideoUseCase.kt (75%) rename domain/applemusic/src/main/java/com/{example => squirtles}/applemusic/usecase/FetchSongsUseCase.kt (70%) rename domain/favorite/src/main/java/com/{example => squirtles}/favorite/FirebaseFavoriteDataSource.kt (81%) rename domain/favorite/src/main/java/com/{example => squirtles}/favorite/FirebaseFavoriteRepository.kt (90%) rename domain/favorite/src/main/java/com/{example => squirtles}/favorite/usecase/CreateFavoriteUseCase.kt (73%) rename domain/favorite/src/main/java/com/{example => squirtles}/favorite/usecase/DeleteFavoriteUseCase.kt (66%) rename domain/favorite/src/main/java/com/{example => squirtles}/favorite/usecase/FetchIsFavoriteUseCase.kt (73%) rename domain/location/src/main/java/com/{example => squirtles}/location/LocalLocationRepository.kt (87%) rename domain/location/src/main/java/com/{example => squirtles}/location/usecase/GetLastLocationUseCase.kt (69%) rename domain/location/src/main/java/com/{example => squirtles}/location/usecase/SaveLastLocationUseCase.kt (75%) rename domain/order/src/main/java/com/{example => squirtles}/order/LocalPickListOrderRepository.kt (81%) rename domain/order/src/main/java/com/{example => squirtles}/order/usecase/GetFavoriteListOrderUseCase.kt (65%) rename domain/order/src/main/java/com/{example => squirtles}/order/usecase/GetMyPickListOrderUseCase.kt (64%) rename domain/order/src/main/java/com/{example => squirtles}/order/usecase/SaveFavoriteListOrderUseCase.kt (62%) rename domain/order/src/main/java/com/{example => squirtles}/order/usecase/SaveMyPickListOrderUseCase.kt (62%) rename domain/pick/src/main/java/com/{example => squirtles}/pick/FirebasePickDataSource.kt (87%) rename domain/pick/src/main/java/com/{example => squirtles}/pick/FirebasePickRepository.kt (88%) rename domain/pick/src/main/java/com/{example => squirtles}/pick/usecase/CreatePickUseCase.kt (66%) rename domain/pick/src/main/java/com/{example => squirtles}/pick/usecase/DeletePickUseCase.kt (68%) rename domain/pick/src/main/java/com/{example => squirtles}/pick/usecase/FetchMyPicksUseCase.kt (65%) rename domain/pick/src/main/java/com/{example => squirtles}/pick/usecase/FetchPickUseCase.kt (81%) rename domain/picklist/src/main/java/com/{example => squirtles}/picklist/FetchPickListUseCaseInterface.kt (64%) rename domain/picklist/src/main/java/com/{example => squirtles}/picklist/GetPickListOrderUseCaseInterface.kt (58%) rename domain/picklist/src/main/java/com/{example => squirtles}/picklist/RemovePickUseCaseInterface.kt (79%) rename domain/picklist/src/main/java/com/{example => squirtles}/picklist/SavePickListOrderUseCaseInterface.kt (60%) rename domain/player/src/main/java/com/{example => squirtles}/player/MediaPlayerListenerUseCase.kt (97%) rename domain/player/src/main/java/com/{example => squirtles}/player/MediaPlayerUseCase.kt (94%) rename domain/user/src/main/java/com/{example => squirtles}/user/FirebaseUserDataSource.kt (82%) rename domain/user/src/main/java/com/{example => squirtles}/user/FirebaseUserRepository.kt (83%) rename domain/user/src/main/java/com/{example => squirtles}/user/LocalUserDataSource.kt (82%) rename domain/user/src/main/java/com/{example => squirtles}/user/LocalUserRepository.kt (82%) rename domain/user/src/main/java/com/{example => squirtles}/user/usecase/ClearUserUseCase.kt (71%) rename domain/user/src/main/java/com/{example => squirtles}/user/usecase/CreateGoogleIdUserUseCase.kt (82%) rename domain/user/src/main/java/com/{example => squirtles}/user/usecase/FetchUserByIdUseCase.kt (74%) rename domain/user/src/main/java/com/{example => squirtles}/user/usecase/FetchUserUseCase.kt (84%) rename domain/user/src/main/java/com/{example => squirtles}/user/usecase/GetCurrentUserUseCase.kt (71%) rename domain/user/src/main/java/com/{example => squirtles}/user/usecase/GetUserIdFromDataStoreUseCase.kt (73%) rename domain/user/src/main/java/com/{example => squirtles}/user/usecase/UpdateUserNameUseCase.kt (77%) rename {core/picklist/src/androidTest/java/com/example/picklist => feature/create/src/androidTest/java/com/squirtles/create}/ExampleInstrumentedTest.kt (86%) delete mode 100644 feature/create/src/main/java/com/example/create/CreatePickScreen.kt delete mode 100644 feature/create/src/main/java/com/example/create/CreatePickUiState.kt delete mode 100644 feature/create/src/main/java/com/example/create/CreatePickViewModel.kt rename feature/create/src/test/java/com/{example => squirtles}/create/ExampleUnitTest.kt (91%) diff --git a/app/build.gradle.kts b/app/build.gradle.kts index 644ba87c..681d6521 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -88,7 +88,7 @@ android { compose = true } composeOptions { - kotlinCompilerExtensionVersion = "1.5.10" + kotlinCompilerExtensionVersion = "1.5.14" } packaging { resources { @@ -104,34 +104,33 @@ dependencies { implementation(libs.androidx.core.ktx) implementation(libs.androidx.lifecycle.runtime.ktx) - implementation(libs.androidx.ui) - implementation(libs.androidx.ui.graphics) + implementation(libs.compose.ui) implementation(libs.androidx.core.splashscreen) - implementation(libs.androidx.animation) +// implementation(libs.androidx.compose.animation) testImplementation(libs.junit) androidTestImplementation(libs.androidx.junit) androidTestImplementation(libs.androidx.espresso.core) - androidTestImplementation(libs.androidx.ui.test.junit4) - debugImplementation(libs.androidx.ui.tooling) - debugImplementation(libs.androidx.ui.test.manifest) + androidTestImplementation(libs.compose.ui.test.junit4) + debugImplementation(libs.compose.ui.tooling) + debugImplementation(libs.compose.ui.test.manifest) implementation(libs.kotlinx.immutable) // Compose implementation(libs.androidx.activity.compose) - implementation(platform(libs.androidx.compose.bom)) - implementation(libs.androidx.compose.material) - androidTestImplementation(platform(libs.androidx.compose.bom)) - implementation(libs.androidx.navigation.compose) - implementation(libs.androidx.compose.material3) - implementation(libs.androidx.material.icons.extended) - implementation(libs.androidx.ui.tooling.preview) + implementation(platform(libs.compose.bom)) + implementation(libs.compose.material) + androidTestImplementation(platform(libs.compose.bom)) + implementation(libs.navigation.compose) + implementation(libs.compose.material3) + implementation(libs.compose.material.icons.extended) + implementation(libs.compose.ui.tooling.preview) // Hilt implementation(libs.hilt.android) ksp(libs.hilt.android.compiler) androidTestImplementation(libs.hilt.android.testing) kspAndroidTest(libs.hilt.android.compiler) - implementation(libs.androidx.hilt.navigation.compose) + implementation(libs.hilt.navigation.compose) // Firebase implementation(platform(libs.firebase.bom)) diff --git a/core/account/build.gradle.kts b/core/account/build.gradle.kts index 6a35f488..8cf01839 100644 --- a/core/account/build.gradle.kts +++ b/core/account/build.gradle.kts @@ -13,7 +13,7 @@ plugins { } android { - namespace = "com.example.account" + namespace = "com.squirtles.account" compileSdk = 34 defaultConfig { diff --git a/core/account/src/main/java/com/example/account/AccountViewModel.kt b/core/account/src/main/java/com/example/account/AccountViewModel.kt deleted file mode 100644 index 112e50d5..00000000 --- a/core/account/src/main/java/com/example/account/AccountViewModel.kt +++ /dev/null @@ -1,64 +0,0 @@ -package com.example.account - -import android.util.Log -import androidx.lifecycle.ViewModel -import androidx.lifecycle.viewModelScope -import com.example.user.usecase.ClearUserUseCase -import com.example.user.usecase.CreateGoogleIdUserUseCase -import com.example.user.usecase.FetchUserUseCase -import com.google.android.libraries.identity.googleid.GoogleIdTokenCredential -import dagger.hilt.android.lifecycle.HiltViewModel -import kotlinx.coroutines.flow.MutableSharedFlow -import kotlinx.coroutines.flow.asSharedFlow -import kotlinx.coroutines.launch -import javax.inject.Inject - -@HiltViewModel -class AccountViewModel @Inject constructor( - private val fetchUserUseCase: FetchUserUseCase, - private val createGoogleIdUserUseCase: CreateGoogleIdUserUseCase, - private val clearUserUseCase: ClearUserUseCase, -) : ViewModel() { - - private val _signInSuccess = MutableSharedFlow() - val signInSuccess = _signInSuccess.asSharedFlow() - - private val _signOutSuccess = MutableSharedFlow() - val signOutSuccess = _signOutSuccess.asSharedFlow() - - fun signIn(credential: GoogleIdTokenCredential) { - viewModelScope.launch { - fetchUserUseCase(credential.id) - .onSuccess { - Log.d("SignIn", "기존 계정 ${it.userId} 로그인") - _signInSuccess.emit(true) - } - .onFailure { - createGoogleIdUser(credential) - } - } - } - - private fun createGoogleIdUser(credential: GoogleIdTokenCredential) { - viewModelScope.launch { - createGoogleIdUserUseCase( - userId = credential.id, - userName = credential.displayName, - userProfileImage = credential.profilePictureUri.toString() - ).onSuccess { - Log.d("SignIn", "새로운 계정 ${it.userId} 로그인") - _signInSuccess.emit(true) - }.onFailure { - _signInSuccess.emit(false) - } - } - } - - fun signOut() { - viewModelScope.launch { - clearUserUseCase() - .onSuccess { _signOutSuccess.emit(true) } - .onFailure { _signOutSuccess.emit(false) } - } - } -} diff --git a/core/account/src/main/java/com/example/account/GoogleId.kt b/core/account/src/main/java/com/example/account/GoogleId.kt deleted file mode 100644 index 7d65a4cf..00000000 --- a/core/account/src/main/java/com/example/account/GoogleId.kt +++ /dev/null @@ -1,70 +0,0 @@ -package com.example.account - -import android.content.Context -import android.util.Log -import android.widget.Toast -import androidx.credentials.ClearCredentialStateRequest -import androidx.credentials.CredentialManager -import androidx.credentials.CustomCredential -import androidx.credentials.GetCredentialRequest -import androidx.credentials.GetCredentialResponse -import androidx.credentials.exceptions.NoCredentialException -import com.google.android.libraries.identity.googleid.GetGoogleIdOption -import com.google.android.libraries.identity.googleid.GoogleIdTokenCredential -import kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.launch - -class GoogleId(private val context: Context) { - private val credentialManager = CredentialManager.create(context) - - private val googleIdOption: GetGoogleIdOption = GetGoogleIdOption.Builder() - .setFilterByAuthorizedAccounts(false) - .setServerClientId(BuildConfig.GOOGLE_CLIENT_ID) - .setAutoSelectEnabled(true) - .build() - - private val request = GetCredentialRequest.Builder() - .addCredentialOption(googleIdOption) - .build() - - private fun handleSignIn(result: GetCredentialResponse, onSuccess: (GoogleIdTokenCredential) -> Unit) { - when (val data = result.credential) { - is CustomCredential -> { - if (data.type == GoogleIdTokenCredential.TYPE_GOOGLE_ID_TOKEN_CREDENTIAL) { - val googleIdTokenCredential = GoogleIdTokenCredential.createFrom(data.data) - Log.d("GoogleId", "data.type : ${googleIdTokenCredential.id}") - Log.d("GoogleId", "data.type : ${googleIdTokenCredential.displayName}") - Log.d("GoogleId", "data.type : ${googleIdTokenCredential.profilePictureUri.toString()}") - onSuccess(googleIdTokenCredential) - } - } - } - } - - fun signIn(onSuccess: (GoogleIdTokenCredential) -> Unit) { - CoroutineScope(Dispatchers.Main).launch { - runCatching { - val result = credentialManager.getCredential(context, request) - handleSignIn(result, onSuccess) - }.onFailure { exception -> - when (exception) { - is NoCredentialException -> Toast.makeText( - context, - context.getString(R.string.google_id_no_credential_exception_message), - Toast.LENGTH_SHORT - ).show() - } - Log.e("GoogleId", "Google SignIn Error : $exception") - } - } - } - - fun signOut() { - CoroutineScope(Dispatchers.Main).launch { - credentialManager.clearCredentialState( - request = ClearCredentialStateRequest() - ) - } - } -} diff --git a/core/common/build.gradle.kts b/core/common/build.gradle.kts index 43d977e1..d16e4dcc 100644 --- a/core/common/build.gradle.kts +++ b/core/common/build.gradle.kts @@ -4,7 +4,7 @@ plugins { } android { - namespace = "com.example.common" + namespace = "com.squirtles.common" compileSdk = 34 buildFeatures { @@ -12,7 +12,7 @@ android { } composeOptions { - kotlinCompilerExtensionVersion = "1.5.10" + kotlinCompilerExtensionVersion = "1.5.14" } defaultConfig { @@ -42,17 +42,17 @@ dependencies { implementation(libs.androidx.core.ktx) implementation(libs.androidx.appcompat) implementation(libs.material) - implementation(libs.androidx.runtime.android) + implementation(libs.compose.runtime.android) testImplementation(libs.junit) androidTestImplementation(libs.androidx.junit) androidTestImplementation(libs.androidx.espresso.core) // Compose - implementation(platform(libs.androidx.compose.bom)) - implementation(libs.androidx.compose.material) - implementation(libs.androidx.compose.material3) - implementation(libs.androidx.material.icons.extended) - implementation(libs.androidx.ui.tooling.preview) + implementation(platform(libs.compose.bom)) + implementation(libs.compose.material) + implementation(libs.compose.material3) + implementation(libs.compose.material.icons.extended) + implementation(libs.compose.ui.tooling.preview) // Coil implementation(libs.coil) diff --git a/core/common/src/main/java/com/example/common/ui/AlbumImage.kt b/core/common/src/main/java/com/example/common/ui/AlbumImage.kt deleted file mode 100644 index 411a9696..00000000 --- a/core/common/src/main/java/com/example/common/ui/AlbumImage.kt +++ /dev/null @@ -1,39 +0,0 @@ -package com.example.common.ui - -import android.util.Size -import androidx.compose.runtime.Composable -import androidx.compose.ui.Modifier -import androidx.compose.ui.graphics.painter.ColorPainter -import androidx.compose.ui.layout.ContentScale -import androidx.compose.ui.platform.LocalContext -import androidx.compose.ui.res.stringResource -import coil3.compose.AsyncImage -import coil3.request.ImageRequest -import coil3.request.crossfade -import com.example.common.R -import com.example.common.ui.theme.Gray - -@Composable -fun AlbumImage( - imageUrl: String?, - modifier: Modifier = Modifier, - contentDescription: String = stringResource(R.string.map_album_image_description), -) { - AsyncImage( - model = ImageRequest.Builder(LocalContext.current) - .data(imageUrl) - .crossfade(true) - .build(), - contentDescription = contentDescription, - modifier = modifier, - placeholder = ColorPainter(Gray), - error = ColorPainter(Gray), - contentScale = ContentScale.Crop, - ) -} - -fun String.toImageUrlWithSize(size: Size): String? { - return if (isEmpty()) null - else replace("{w}", size.width.toString()) - .replace("{h}", size.height.toString()) -} diff --git a/core/common/src/main/java/com/example/common/ui/Constants.kt b/core/common/src/main/java/com/example/common/ui/Constants.kt deleted file mode 100644 index 839e2965..00000000 --- a/core/common/src/main/java/com/example/common/ui/Constants.kt +++ /dev/null @@ -1,17 +0,0 @@ -package com.example.common.ui - -import android.util.Size -import androidx.compose.ui.unit.dp -import com.example.common.ui.theme.Black -import com.example.common.ui.theme.Primary - -object Constants { - val DEFAULT_PADDING = 16.dp - - val REQUEST_IMAGE_SIZE_DEFAULT = Size(300, 300) - - val COLOR_STOPS = arrayOf( - 0.0f to Primary, - 0.25f to Black - ) -} diff --git a/core/common/src/main/java/com/example/common/ui/CreatedByPickText.kt b/core/common/src/main/java/com/example/common/ui/CreatedByPickText.kt deleted file mode 100644 index 6500f34f..00000000 --- a/core/common/src/main/java/com/example/common/ui/CreatedByPickText.kt +++ /dev/null @@ -1,64 +0,0 @@ -package com.example.common.ui - -import androidx.compose.material3.MaterialTheme -import androidx.compose.material3.Text -import androidx.compose.runtime.Composable -import androidx.compose.ui.Modifier -import androidx.compose.ui.graphics.Color -import androidx.compose.ui.res.stringResource -import androidx.compose.ui.text.SpanStyle -import androidx.compose.ui.text.TextStyle -import androidx.compose.ui.text.buildAnnotatedString -import androidx.compose.ui.text.style.TextDecoration -import androidx.compose.ui.text.style.TextOverflow -import androidx.compose.ui.text.withStyle -import com.example.common.R - -@Composable -fun CreatedBySelfText( - modifier: Modifier = Modifier, - showUnderline: Boolean = false, - color: Color = MaterialTheme.colorScheme.onSurface, - style: TextStyle = MaterialTheme.typography.bodyMedium -) { - val myPickDescription = buildAnnotatedString { - withStyle(style = SpanStyle(textDecoration = if (showUnderline) TextDecoration.Underline else null)) { - append(stringResource(R.string.pick_created_by_self_1)) - } - append(" ${stringResource(R.string.pick_created_by_self_2)}") - } - - Text( - text = myPickDescription, - modifier = modifier, - color = color, - overflow = TextOverflow.Ellipsis, - maxLines = 1, - style = style - ) -} - -@Composable -fun CreatedByOtherUserText( - userName: String, - modifier: Modifier = Modifier, - showUnderline: Boolean = false, - color: Color = MaterialTheme.colorScheme.onSurface, - style: TextStyle = MaterialTheme.typography.bodyMedium -) { - Text( - text = userName, - modifier = modifier, - color = color, - textDecoration = if (showUnderline) TextDecoration.Underline else null, - overflow = TextOverflow.Ellipsis, - maxLines = 1, - style = style - ) - - Text( - text = stringResource(id = R.string.map_info_window_pick_user), - color = color, - style = style, - ) -} diff --git a/core/common/src/main/java/com/example/common/ui/DefaultTopAppBar.kt b/core/common/src/main/java/com/example/common/ui/DefaultTopAppBar.kt deleted file mode 100644 index f3e93041..00000000 --- a/core/common/src/main/java/com/example/common/ui/DefaultTopAppBar.kt +++ /dev/null @@ -1,56 +0,0 @@ -package com.example.common.ui - -import androidx.compose.foundation.layout.RowScope -import androidx.compose.foundation.layout.displayCutoutPadding -import androidx.compose.material.icons.Icons -import androidx.compose.material.icons.automirrored.filled.ArrowBack -import androidx.compose.material3.CenterAlignedTopAppBar -import androidx.compose.material3.ExperimentalMaterial3Api -import androidx.compose.material3.Icon -import androidx.compose.material3.IconButton -import androidx.compose.material3.MaterialTheme -import androidx.compose.material3.Text -import androidx.compose.material3.TopAppBarDefaults -import androidx.compose.runtime.Composable -import androidx.compose.ui.Modifier -import androidx.compose.ui.graphics.Color -import androidx.compose.ui.res.stringResource -import androidx.compose.ui.text.TextStyle -import androidx.compose.ui.text.font.FontWeight -import com.example.common.R -import com.example.common.ui.theme.White - -@OptIn(ExperimentalMaterial3Api::class) -@Composable -fun DefaultTopAppBar( - title: String, - titleStyle: TextStyle = MaterialTheme.typography.titleLarge.copy(fontWeight = FontWeight.Bold), - onBackClick: () -> Unit, - actions: @Composable RowScope.() -> Unit = {}, -) { - CenterAlignedTopAppBar( - title = { - Text( - text = title, - style = titleStyle - ) - }, - modifier = Modifier.displayCutoutPadding(), - navigationIcon = { - IconButton( - onClick = onBackClick - ) { - Icon( - imageVector = Icons.AutoMirrored.Filled.ArrowBack, - contentDescription = stringResource(R.string.top_app_bar_back_description), - tint = White - ) - } - }, - actions = actions, - colors = TopAppBarDefaults.centerAlignedTopAppBarColors().copy( - containerColor = Color.Transparent, - titleContentColor = White - ) - ) -} diff --git a/core/common/src/main/java/com/example/common/ui/MessageAlertDialog.kt b/core/common/src/main/java/com/example/common/ui/MessageAlertDialog.kt deleted file mode 100644 index 5f15c450..00000000 --- a/core/common/src/main/java/com/example/common/ui/MessageAlertDialog.kt +++ /dev/null @@ -1,126 +0,0 @@ -package com.example.common.ui - -import androidx.compose.foundation.layout.Arrangement -import androidx.compose.foundation.layout.Column -import androidx.compose.foundation.layout.Row -import androidx.compose.foundation.layout.RowScope -import androidx.compose.foundation.layout.fillMaxWidth -import androidx.compose.foundation.layout.padding -import androidx.compose.foundation.shape.RoundedCornerShape -import androidx.compose.material3.BasicAlertDialog -import androidx.compose.material3.ButtonDefaults -import androidx.compose.material3.ExperimentalMaterial3Api -import androidx.compose.material3.MaterialTheme -import androidx.compose.material3.Surface -import androidx.compose.material3.Text -import androidx.compose.material3.TextButton -import androidx.compose.runtime.Composable -import androidx.compose.ui.Alignment -import androidx.compose.ui.Modifier -import androidx.compose.ui.graphics.Color -import androidx.compose.ui.res.stringResource -import androidx.compose.ui.text.font.FontWeight -import androidx.compose.ui.tooling.preview.Preview -import androidx.compose.ui.unit.dp -import com.example.common.R -import com.example.common.ui.theme.Black -import com.example.common.ui.theme.MusicRoadTheme -import com.example.common.ui.theme.Primary -import com.example.common.ui.theme.White - -@OptIn(ExperimentalMaterial3Api::class) -@Composable -internal fun MessageAlertDialog( - onDismissRequest: () -> Unit, - title: String, - body: String, - buttons: @Composable RowScope.() -> Unit, -) { - BasicAlertDialog( - onDismissRequest = { onDismissRequest() }, - ) { - Surface( - modifier = Modifier.fillMaxWidth(), - shape = RoundedCornerShape(16.dp), - color = White - ) { - Column( - modifier = Modifier.padding(24.dp), - verticalArrangement = Arrangement.Center - ) { - Text( - text = title, - color = Black, - fontWeight = FontWeight.Bold, - style = MaterialTheme.typography.bodyLarge - ) - - VerticalSpacer(8) - - Text( - text = body, - color = Black, - style = MaterialTheme.typography.bodyLarge - ) - - VerticalSpacer(24) - - Row( - modifier = Modifier.fillMaxWidth(), - horizontalArrangement = Arrangement.End, - verticalAlignment = Alignment.CenterVertically, - content = buttons - ) - } - } - } -} - -@Composable -internal fun DialogTextButton( - onClick: () -> Unit, - text: String, - textColor: Color = Black, - buttonColor: Color = Color.Transparent, - fontWeight: FontWeight? = null, -) { - TextButton( - onClick = onClick, - colors = ButtonDefaults.buttonColors().copy( - containerColor = buttonColor, - contentColor = textColor - ) - ) { - Text( - text = text, - fontWeight = fontWeight, - ) - } -} - -@Preview(showBackground = true) -@Composable -private fun DeletePickDialogPreview() { - MusicRoadTheme { - MessageAlertDialog( - onDismissRequest = {}, - title = stringResource(R.string.delete_pick_dialog_title), - body = stringResource(R.string.delete_pick_dialog_body), - buttons = { - DialogTextButton( - onClick = {}, - text = "취소" - ) - - HorizontalSpacer(8) - - DialogTextButton( - onClick = {}, - text = "삭제하기", - textColor = Primary, - fontWeight = FontWeight.Bold - ) - } - ) - } -} diff --git a/core/common/src/main/java/com/example/common/ui/PickInfoText.kt b/core/common/src/main/java/com/example/common/ui/PickInfoText.kt deleted file mode 100644 index 6e165d67..00000000 --- a/core/common/src/main/java/com/example/common/ui/PickInfoText.kt +++ /dev/null @@ -1,102 +0,0 @@ -package com.example.common.ui - -import androidx.compose.material3.Icon -import androidx.compose.material3.MaterialTheme -import androidx.compose.material3.Text -import androidx.compose.runtime.Composable -import androidx.compose.ui.Modifier -import androidx.compose.ui.graphics.Color -import androidx.compose.ui.res.painterResource -import androidx.compose.ui.res.stringResource -import androidx.compose.ui.text.SpanStyle -import androidx.compose.ui.text.TextStyle -import androidx.compose.ui.text.buildAnnotatedString -import androidx.compose.ui.text.font.FontWeight -import androidx.compose.ui.text.style.TextOverflow -import androidx.compose.ui.text.withStyle -import com.example.common.R -import com.example.common.ui.theme.Primary - -@Composable -fun SongInfoText( - songInfo: String, - color: Color = MaterialTheme.colorScheme.onSurface -) { - Text( - text = songInfo, - color = color, - overflow = TextOverflow.Ellipsis, - maxLines = 1, - style = MaterialTheme.typography.titleMedium, - ) -} - -@Composable -fun FavoriteCountText( - favoriteCount: Int, - iconTint: Color = Primary, - color: Color = MaterialTheme.colorScheme.onSurface, - style: TextStyle = MaterialTheme.typography.bodyMedium -) { - Icon( - painter = painterResource(id = R.drawable.ic_favorite), - contentDescription = stringResource(R.string.map_info_window_favorite_count_icon_description), - tint = iconTint - ) - - Text( - text = " $favoriteCount", - color = color, - style = style, - ) -} - -@Composable -fun CommentText( - comment: String, - color: Color = MaterialTheme.colorScheme.onSecondary, - overflow: TextOverflow = TextOverflow.Ellipsis, - maxLines: Int = 1, - style: TextStyle = MaterialTheme.typography.bodyMedium -) { - Text( - text = comment, - color = color, - overflow = overflow, - maxLines = maxLines, - style = style, - ) -} - -@Composable -fun CountText( - totalCount: Int, - modifier: Modifier = Modifier, - countLabel: String = stringResource(R.string.total_count_text), - defaultColor: Color = MaterialTheme.colorScheme.onSurface, - pointColor: Color = MaterialTheme.colorScheme.primary, - style: TextStyle = MaterialTheme.typography.titleMedium -) { - Text( - text = buildAnnotatedString { - withStyle( - SpanStyle( - color = defaultColor, - fontWeight = FontWeight.Bold - ) - ) { - append("$countLabel ") - } - withStyle( - SpanStyle( - color = pointColor, - fontWeight = FontWeight.Bold - ) - ) { - append("$totalCount") - } - }, - modifier = modifier, - style = style - ) -} diff --git a/core/common/src/main/java/com/example/common/ui/Spacer.kt b/core/common/src/main/java/com/example/common/ui/Spacer.kt deleted file mode 100644 index 4baa7ecb..00000000 --- a/core/common/src/main/java/com/example/common/ui/Spacer.kt +++ /dev/null @@ -1,14 +0,0 @@ -package com.example.common.ui - -import androidx.compose.foundation.layout.Spacer -import androidx.compose.foundation.layout.height -import androidx.compose.foundation.layout.width -import androidx.compose.runtime.Composable -import androidx.compose.ui.Modifier -import androidx.compose.ui.unit.dp - -@Composable -fun VerticalSpacer(height: Int) = Spacer(Modifier.height(height.dp)) - -@Composable -fun HorizontalSpacer(width: Int) = Spacer(Modifier.width(width.dp)) diff --git a/core/common/src/main/java/com/example/common/ui/theme/Color.kt b/core/common/src/main/java/com/example/common/ui/theme/Color.kt deleted file mode 100644 index d1beb5e1..00000000 --- a/core/common/src/main/java/com/example/common/ui/theme/Color.kt +++ /dev/null @@ -1,21 +0,0 @@ -package com.example.common.ui.theme - -import androidx.compose.ui.graphics.Color - -val Primary = Color(0xFFFF5F61) -val Primary80 = Color(0xFFFFB3B0) -val Primary50 = Color(0xFFAD625F) -val Primary20 = Color(0xFF571D1E) -val Blue = Color(0xFF6B84FF) - -val Purple = Color(0xFFBB8280) -val Purple15 = Color(0x26BB8280) -val PurpleGrey = Color(0xFFB4AEAE) - -val Black = Color(0xFF000000) -val Dark = Color(0xFF151515) -val DarkGray = Color(0xFF646464) -val Gray = Color(0xFFAAAAAA) -val White = Color(0xFFFFFFFF) - -val PlayerBackground = Color(0xFF353535) diff --git a/core/common/src/main/java/com/example/common/ui/theme/Theme.kt b/core/common/src/main/java/com/example/common/ui/theme/Theme.kt deleted file mode 100644 index 6d4909df..00000000 --- a/core/common/src/main/java/com/example/common/ui/theme/Theme.kt +++ /dev/null @@ -1,45 +0,0 @@ -package com.example.common.ui.theme - -import androidx.compose.foundation.isSystemInDarkTheme -import androidx.compose.material3.MaterialTheme -import androidx.compose.material3.darkColorScheme -import androidx.compose.material3.lightColorScheme -import androidx.compose.runtime.Composable - -private val DarkColorScheme = darkColorScheme( - primary = Primary, - onPrimary = Black, - primaryContainer = White, - onPrimaryContainer = Black, - secondary = Blue, - tertiary = Purple, - surface = Black, - onSurface = White, - onSurfaceVariant = DarkGray, - onSecondary = Gray -) - -private val LightColorScheme = lightColorScheme( - primary = Primary, - onPrimary = White, - primaryContainer = Dark, - onPrimaryContainer = White, - secondary = Blue, - tertiary = Purple, - surface = White, - onSurface = Black, - onSurfaceVariant = Gray, - onSecondary = DarkGray -) - -@Composable -fun MusicRoadTheme( - darkTheme: Boolean = isSystemInDarkTheme(), - content: @Composable () -> Unit -) { - MaterialTheme( - colorScheme = if (darkTheme) DarkColorScheme else LightColorScheme, - typography = Typography, - content = content - ) -} diff --git a/core/common/src/main/java/com/example/common/ui/theme/Type.kt b/core/common/src/main/java/com/example/common/ui/theme/Type.kt deleted file mode 100644 index c5aebc5b..00000000 --- a/core/common/src/main/java/com/example/common/ui/theme/Type.kt +++ /dev/null @@ -1,34 +0,0 @@ -package com.example.common.ui.theme - -import androidx.compose.material3.Typography -import androidx.compose.ui.text.TextStyle -import androidx.compose.ui.text.font.FontFamily -import androidx.compose.ui.text.font.FontWeight -import androidx.compose.ui.unit.sp - -// Set of Material typography styles to start with -val Typography = Typography( - bodyLarge = TextStyle( - fontFamily = FontFamily.Default, - fontWeight = FontWeight.Normal, - fontSize = 16.sp, - lineHeight = 24.sp, - letterSpacing = 0.5.sp - ) - /* Other default text styles to override - titleLarge = TextStyle( - fontFamily = FontFamily.Default, - fontWeight = FontWeight.Normal, - fontSize = 22.sp, - lineHeight = 28.sp, - letterSpacing = 0.sp - ), - labelSmall = TextStyle( - fontFamily = FontFamily.Default, - fontWeight = FontWeight.Medium, - fontSize = 11.sp, - lineHeight = 16.sp, - letterSpacing = 0.5.sp - ) - */ -) diff --git a/core/mediaservice/build.gradle.kts b/core/mediaservice/build.gradle.kts index 399001d3..3a7aa9b3 100644 --- a/core/mediaservice/build.gradle.kts +++ b/core/mediaservice/build.gradle.kts @@ -6,7 +6,7 @@ plugins { } android { - namespace = "com.example.mediaservice" + namespace = "com.squirtles.mediaservice" compileSdk = 34 defaultConfig { diff --git a/core/mediaservice/src/androidTest/java/com/example/mediaservice/ExampleInstrumentedTest.kt b/core/mediaservice/src/androidTest/java/com/squirtles/mediaservice/ExampleInstrumentedTest.kt similarity index 83% rename from core/mediaservice/src/androidTest/java/com/example/mediaservice/ExampleInstrumentedTest.kt rename to core/mediaservice/src/androidTest/java/com/squirtles/mediaservice/ExampleInstrumentedTest.kt index d7dfa445..75004631 100644 --- a/core/mediaservice/src/androidTest/java/com/example/mediaservice/ExampleInstrumentedTest.kt +++ b/core/mediaservice/src/androidTest/java/com/squirtles/mediaservice/ExampleInstrumentedTest.kt @@ -1,4 +1,4 @@ -package com.example.mediaservice +package com.squirtles.mediaservice import androidx.test.platform.app.InstrumentationRegistry import androidx.test.ext.junit.runners.AndroidJUnit4 @@ -19,6 +19,6 @@ class ExampleInstrumentedTest { fun useAppContext() { // Context of the app under test. val appContext = InstrumentationRegistry.getInstrumentation().targetContext - assertEquals("com.example.mediaservice.test", appContext.packageName) + assertEquals("com.squirtles.mediaservice.test", appContext.packageName) } } diff --git a/core/mediaservice/src/main/java/com/example/mediaservice/CustomMediaSessionCallback.kt b/core/mediaservice/src/main/java/com/squirtles/mediaservice/CustomMediaSessionCallback.kt similarity index 99% rename from core/mediaservice/src/main/java/com/example/mediaservice/CustomMediaSessionCallback.kt rename to core/mediaservice/src/main/java/com/squirtles/mediaservice/CustomMediaSessionCallback.kt index ddf184f3..ac9ae731 100644 --- a/core/mediaservice/src/main/java/com/example/mediaservice/CustomMediaSessionCallback.kt +++ b/core/mediaservice/src/main/java/com/squirtles/mediaservice/CustomMediaSessionCallback.kt @@ -1,4 +1,4 @@ -package com.example.mediaservice +package com.squirtles.mediaservice import android.os.Build import android.os.Bundle diff --git a/core/mediaservice/src/main/java/com/example/mediaservice/MediaControllerProvider.kt b/core/mediaservice/src/main/java/com/squirtles/mediaservice/MediaControllerProvider.kt similarity index 98% rename from core/mediaservice/src/main/java/com/example/mediaservice/MediaControllerProvider.kt rename to core/mediaservice/src/main/java/com/squirtles/mediaservice/MediaControllerProvider.kt index 647294ee..b17d9b54 100644 --- a/core/mediaservice/src/main/java/com/example/mediaservice/MediaControllerProvider.kt +++ b/core/mediaservice/src/main/java/com/squirtles/mediaservice/MediaControllerProvider.kt @@ -1,4 +1,4 @@ -package com.example.mediaservice +package com.squirtles.mediaservice import androidx.media3.common.util.UnstableApi import androidx.media3.session.MediaController diff --git a/core/mediaservice/src/main/java/com/example/mediaservice/MediaNotificationProvider.kt b/core/mediaservice/src/main/java/com/squirtles/mediaservice/MediaNotificationProvider.kt similarity index 99% rename from core/mediaservice/src/main/java/com/example/mediaservice/MediaNotificationProvider.kt rename to core/mediaservice/src/main/java/com/squirtles/mediaservice/MediaNotificationProvider.kt index e6159137..ada84fe7 100644 --- a/core/mediaservice/src/main/java/com/example/mediaservice/MediaNotificationProvider.kt +++ b/core/mediaservice/src/main/java/com/squirtles/mediaservice/MediaNotificationProvider.kt @@ -1,4 +1,4 @@ -package com.example.mediaservice +package com.squirtles.mediaservice import android.annotation.SuppressLint import android.app.NotificationChannel diff --git a/core/mediaservice/src/main/java/com/example/mediaservice/MediaPlayerService.kt b/core/mediaservice/src/main/java/com/squirtles/mediaservice/MediaPlayerService.kt similarity index 98% rename from core/mediaservice/src/main/java/com/example/mediaservice/MediaPlayerService.kt rename to core/mediaservice/src/main/java/com/squirtles/mediaservice/MediaPlayerService.kt index 4dff67ca..b5a028a4 100644 --- a/core/mediaservice/src/main/java/com/example/mediaservice/MediaPlayerService.kt +++ b/core/mediaservice/src/main/java/com/squirtles/mediaservice/MediaPlayerService.kt @@ -1,4 +1,4 @@ -package com.example.mediaservice +package com.squirtles.mediaservice import android.content.Intent import android.os.Bundle diff --git a/core/mediaservice/src/main/java/com/example/mediaservice/PlayerCommands.kt b/core/mediaservice/src/main/java/com/squirtles/mediaservice/PlayerCommands.kt similarity index 97% rename from core/mediaservice/src/main/java/com/example/mediaservice/PlayerCommands.kt rename to core/mediaservice/src/main/java/com/squirtles/mediaservice/PlayerCommands.kt index 705e1923..a26936d0 100644 --- a/core/mediaservice/src/main/java/com/example/mediaservice/PlayerCommands.kt +++ b/core/mediaservice/src/main/java/com/squirtles/mediaservice/PlayerCommands.kt @@ -1,4 +1,4 @@ -package com.example.mediaservice +package com.squirtles.mediaservice import android.os.Bundle import androidx.media3.session.SessionCommand diff --git a/core/mediaservice/src/main/java/com/example/mediaservice/di/MediaDiModule.kt b/core/mediaservice/src/main/java/com/squirtles/mediaservice/di/MediaDiModule.kt similarity index 86% rename from core/mediaservice/src/main/java/com/example/mediaservice/di/MediaDiModule.kt rename to core/mediaservice/src/main/java/com/squirtles/mediaservice/di/MediaDiModule.kt index fc4799cb..e056396a 100644 --- a/core/mediaservice/src/main/java/com/example/mediaservice/di/MediaDiModule.kt +++ b/core/mediaservice/src/main/java/com/squirtles/mediaservice/di/MediaDiModule.kt @@ -1,4 +1,4 @@ -package com.example.mediaservice.di +package com.squirtles.mediaservice.di import android.content.ComponentName import android.content.Context @@ -9,12 +9,12 @@ import androidx.media3.exoplayer.ExoPlayer import androidx.media3.session.MediaController import androidx.media3.session.MediaSession import androidx.media3.session.SessionToken -import com.example.mediaservice.CustomMediaSessionCallback -import com.example.mediaservice.MediaControllerProvider -import com.example.mediaservice.MediaControllerProviderImpl -import com.example.mediaservice.MediaNotificationProvider -import com.example.mediaservice.MediaNotificationProviderImpl -import com.example.mediaservice.MediaPlayerService +import com.squirtles.mediaservice.CustomMediaSessionCallback +import com.squirtles.mediaservice.MediaControllerProvider +import com.squirtles.mediaservice.MediaControllerProviderImpl +import com.squirtles.mediaservice.MediaNotificationProvider +import com.squirtles.mediaservice.MediaNotificationProviderImpl +import com.squirtles.mediaservice.MediaPlayerService import com.google.common.util.concurrent.ListenableFuture import dagger.Binds import dagger.Module diff --git a/core/mediaservice/src/test/java/com/example/mediaservice/ExampleUnitTest.kt b/core/mediaservice/src/test/java/com/squirtles/mediaservice/ExampleUnitTest.kt similarity index 90% rename from core/mediaservice/src/test/java/com/example/mediaservice/ExampleUnitTest.kt rename to core/mediaservice/src/test/java/com/squirtles/mediaservice/ExampleUnitTest.kt index e9395953..fb1aeef9 100644 --- a/core/mediaservice/src/test/java/com/example/mediaservice/ExampleUnitTest.kt +++ b/core/mediaservice/src/test/java/com/squirtles/mediaservice/ExampleUnitTest.kt @@ -1,4 +1,4 @@ -package com.example.mediaservice +package com.squirtles.mediaservice import org.junit.Test diff --git a/core/model/src/main/java/com/example/model/MusicVideo.kt b/core/model/src/main/java/com/squirtles/model/MusicVideo.kt similarity index 89% rename from core/model/src/main/java/com/example/model/MusicVideo.kt rename to core/model/src/main/java/com/squirtles/model/MusicVideo.kt index 3f327200..9516977f 100644 --- a/core/model/src/main/java/com/example/model/MusicVideo.kt +++ b/core/model/src/main/java/com/squirtles/model/MusicVideo.kt @@ -1,4 +1,4 @@ -package com.example.model +package com.squirtles.model import java.time.LocalDate diff --git a/core/model/src/main/java/com/example/model/Order.kt b/core/model/src/main/java/com/squirtles/model/Order.kt similarity index 69% rename from core/model/src/main/java/com/example/model/Order.kt rename to core/model/src/main/java/com/squirtles/model/Order.kt index bcf7dd21..267e8d99 100644 --- a/core/model/src/main/java/com/example/model/Order.kt +++ b/core/model/src/main/java/com/squirtles/model/Order.kt @@ -1,4 +1,4 @@ -package com.example.model +package com.squirtles.model enum class Order { LATEST, diff --git a/core/model/src/main/java/com/example/model/Pick.kt b/core/model/src/main/java/com/squirtles/model/Pick.kt similarity index 95% rename from core/model/src/main/java/com/example/model/Pick.kt rename to core/model/src/main/java/com/squirtles/model/Pick.kt index 4c9aa335..756d182a 100644 --- a/core/model/src/main/java/com/example/model/Pick.kt +++ b/core/model/src/main/java/com/squirtles/model/Pick.kt @@ -1,4 +1,4 @@ -package com.example.model +package com.squirtles.model /** * 앱에서 사용하기 위한 Pick 정보 데이터클래스 diff --git a/core/model/src/main/java/com/example/model/PlayerState.kt b/core/model/src/main/java/com/squirtles/model/PlayerState.kt similarity index 90% rename from core/model/src/main/java/com/example/model/PlayerState.kt rename to core/model/src/main/java/com/squirtles/model/PlayerState.kt index e40b2e2b..451b5179 100644 --- a/core/model/src/main/java/com/example/model/PlayerState.kt +++ b/core/model/src/main/java/com/squirtles/model/PlayerState.kt @@ -1,4 +1,4 @@ -package com.example.model +package com.squirtles.model data class PlayerState( val id: String = "", diff --git a/core/model/src/main/java/com/example/model/Song.kt b/core/model/src/main/java/com/squirtles/model/Song.kt similarity index 95% rename from core/model/src/main/java/com/example/model/Song.kt rename to core/model/src/main/java/com/squirtles/model/Song.kt index ceb1c1c0..0b2ae786 100644 --- a/core/model/src/main/java/com/example/model/Song.kt +++ b/core/model/src/main/java/com/squirtles/model/Song.kt @@ -1,4 +1,4 @@ -package com.example.model +package com.squirtles.model import kotlinx.serialization.Serializable diff --git a/core/model/src/main/java/com/example/model/User.kt b/core/model/src/main/java/com/squirtles/model/User.kt similarity index 82% rename from core/model/src/main/java/com/example/model/User.kt rename to core/model/src/main/java/com/squirtles/model/User.kt index 22eb9bb4..7060376a 100644 --- a/core/model/src/main/java/com/example/model/User.kt +++ b/core/model/src/main/java/com/squirtles/model/User.kt @@ -1,4 +1,4 @@ -package com.example.model +package com.squirtles.model data class User( val userId: String, diff --git a/core/musicplayer/build.gradle.kts b/core/musicplayer/build.gradle.kts index b2216d58..16713631 100644 --- a/core/musicplayer/build.gradle.kts +++ b/core/musicplayer/build.gradle.kts @@ -6,7 +6,7 @@ plugins { } android { - namespace = "com.example.musicplayer" + namespace = "com.squirtles.musicplayer" compileSdk = 34 defaultConfig { diff --git a/core/musicplayer/src/main/java/com/example/musicplayer/PlayerServiceViewModel.kt b/core/musicplayer/src/main/java/com/squirtles/musicplayer/PlayerServiceViewModel.kt similarity index 91% rename from core/musicplayer/src/main/java/com/example/musicplayer/PlayerServiceViewModel.kt rename to core/musicplayer/src/main/java/com/squirtles/musicplayer/PlayerServiceViewModel.kt index 768bc29e..3fa6fbb9 100644 --- a/core/musicplayer/src/main/java/com/example/musicplayer/PlayerServiceViewModel.kt +++ b/core/musicplayer/src/main/java/com/squirtles/musicplayer/PlayerServiceViewModel.kt @@ -1,12 +1,12 @@ -package com.example.musicplayer +package com.squirtles.musicplayer import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope -import com.example.model.Pick -import com.example.model.PlayerState -import com.example.model.Song -import com.example.player.MediaPlayerListenerUseCase -import com.example.player.MediaPlayerUseCase +import com.squirtles.model.Pick +import com.squirtles.model.PlayerState +import com.squirtles.model.Song +import com.squirtles.player.MediaPlayerListenerUseCase +import com.squirtles.player.MediaPlayerUseCase import dagger.hilt.android.lifecycle.HiltViewModel import kotlinx.coroutines.async import kotlinx.coroutines.flow.SharingStarted diff --git a/core/musicplayer/src/main/java/com/example/musicplayer/PlayerUiState.kt b/core/musicplayer/src/main/java/com/squirtles/musicplayer/PlayerUiState.kt similarity index 90% rename from core/musicplayer/src/main/java/com/example/musicplayer/PlayerUiState.kt rename to core/musicplayer/src/main/java/com/squirtles/musicplayer/PlayerUiState.kt index 53044f66..5a5e06fa 100644 --- a/core/musicplayer/src/main/java/com/example/musicplayer/PlayerUiState.kt +++ b/core/musicplayer/src/main/java/com/squirtles/musicplayer/PlayerUiState.kt @@ -1,4 +1,4 @@ -package com.example.musicplayer +package com.squirtles.musicplayer data class PlayerUiState( val isReady: Boolean = true, diff --git a/core/musicplayer/src/main/java/com/example/musicplayer/PlayerViewModel.kt b/core/musicplayer/src/main/java/com/squirtles/musicplayer/PlayerViewModel.kt similarity index 97% rename from core/musicplayer/src/main/java/com/example/musicplayer/PlayerViewModel.kt rename to core/musicplayer/src/main/java/com/squirtles/musicplayer/PlayerViewModel.kt index a2382a4f..927fc552 100644 --- a/core/musicplayer/src/main/java/com/example/musicplayer/PlayerViewModel.kt +++ b/core/musicplayer/src/main/java/com/squirtles/musicplayer/PlayerViewModel.kt @@ -1,4 +1,4 @@ -package com.example.musicplayer +package com.squirtles.musicplayer import android.content.Context import android.util.Log @@ -11,8 +11,8 @@ import androidx.media3.common.PlaybackException import androidx.media3.common.Player import androidx.media3.common.util.UnstableApi import androidx.media3.exoplayer.ExoPlayer -import com.example.musicplayer.PlayerUiState.Companion.PLAYER_STATE_INITIAL -import com.example.musicplayer.PlayerUiState.Companion.PLAYER_STATE_STOP +import com.squirtles.musicplayer.PlayerUiState.Companion.PLAYER_STATE_INITIAL +import com.squirtles.musicplayer.PlayerUiState.Companion.PLAYER_STATE_STOP import dagger.hilt.android.lifecycle.HiltViewModel import kotlinx.coroutines.delay import kotlinx.coroutines.flow.MutableStateFlow diff --git a/core/navigation/src/main/java/com/example/navigation/MainRoute.kt b/core/navigation/src/main/java/com/squirtles/navigation/MainRoute.kt similarity index 90% rename from core/navigation/src/main/java/com/example/navigation/MainRoute.kt rename to core/navigation/src/main/java/com/squirtles/navigation/MainRoute.kt index 377459d6..a6c9621a 100644 --- a/core/navigation/src/main/java/com/example/navigation/MainRoute.kt +++ b/core/navigation/src/main/java/com/squirtles/navigation/MainRoute.kt @@ -1,4 +1,4 @@ -package com.example.navigation +package com.squirtles.navigation import kotlinx.serialization.Serializable diff --git a/core/navigation/src/main/java/com/example/navigation/MapRoute.kt b/core/navigation/src/main/java/com/squirtles/navigation/MapRoute.kt similarity index 83% rename from core/navigation/src/main/java/com/example/navigation/MapRoute.kt rename to core/navigation/src/main/java/com/squirtles/navigation/MapRoute.kt index efddc0a4..816e5ef5 100644 --- a/core/navigation/src/main/java/com/example/navigation/MapRoute.kt +++ b/core/navigation/src/main/java/com/squirtles/navigation/MapRoute.kt @@ -1,4 +1,4 @@ -package com.example.navigation +package com.squirtles.navigation import kotlinx.serialization.Serializable diff --git a/core/navigation/src/main/java/com/example/navigation/ProfileRoute.kt b/core/navigation/src/main/java/com/squirtles/navigation/ProfileRoute.kt similarity index 90% rename from core/navigation/src/main/java/com/example/navigation/ProfileRoute.kt rename to core/navigation/src/main/java/com/squirtles/navigation/ProfileRoute.kt index 44621d44..132230e4 100644 --- a/core/navigation/src/main/java/com/example/navigation/ProfileRoute.kt +++ b/core/navigation/src/main/java/com/squirtles/navigation/ProfileRoute.kt @@ -1,4 +1,4 @@ -package com.example.navigation +package com.squirtles.navigation import kotlinx.serialization.Serializable diff --git a/core/navigation/src/main/java/com/example/navigation/Route.kt b/core/navigation/src/main/java/com/squirtles/navigation/Route.kt similarity index 79% rename from core/navigation/src/main/java/com/example/navigation/Route.kt rename to core/navigation/src/main/java/com/squirtles/navigation/Route.kt index 45366dff..faa1a6fb 100644 --- a/core/navigation/src/main/java/com/example/navigation/Route.kt +++ b/core/navigation/src/main/java/com/squirtles/navigation/Route.kt @@ -1,4 +1,4 @@ -package com.example.navigation +package com.squirtles.navigation import kotlinx.serialization.Serializable diff --git a/core/navigation/src/main/java/com/example/navigation/SearchRoute.kt b/core/navigation/src/main/java/com/squirtles/navigation/SearchRoute.kt similarity index 65% rename from core/navigation/src/main/java/com/example/navigation/SearchRoute.kt rename to core/navigation/src/main/java/com/squirtles/navigation/SearchRoute.kt index a03dec38..f4f77b17 100644 --- a/core/navigation/src/main/java/com/example/navigation/SearchRoute.kt +++ b/core/navigation/src/main/java/com/squirtles/navigation/SearchRoute.kt @@ -1,8 +1,7 @@ -package com.example.navigation +package com.squirtles.navigation -import com.example.model.Song +import com.squirtles.model.Song import kotlinx.serialization.Serializable -import kotlin.reflect.typeOf @Serializable sealed interface SearchRoute : Route { diff --git a/core/picklist/build.gradle.kts b/core/picklist/build.gradle.kts index 49319587..c67453a5 100644 --- a/core/picklist/build.gradle.kts +++ b/core/picklist/build.gradle.kts @@ -4,7 +4,7 @@ plugins { } android { - namespace = "com.example.picklist" + namespace = "com.squirtles.picklist" compileSdk = 34 buildFeatures { @@ -12,7 +12,7 @@ android { } composeOptions { - kotlinCompilerExtensionVersion = "1.5.10" + kotlinCompilerExtensionVersion = "1.5.14" } defaultConfig { @@ -49,11 +49,11 @@ dependencies { androidTestImplementation(libs.androidx.espresso.core) // Compose - implementation(platform(libs.androidx.compose.bom)) - implementation(libs.androidx.compose.material) - implementation(libs.androidx.compose.material3) - implementation(libs.androidx.material.icons.extended) - implementation(libs.androidx.ui.tooling.preview) + implementation(platform(libs.compose.bom)) + implementation(libs.compose.material) + implementation(libs.compose.material3) + implementation(libs.compose.material.icons.extended) + implementation(libs.compose.ui.tooling.preview) // Coil implementation(libs.coil.compose) diff --git a/core/util/src/androidTest/java/com/example/util/ExampleInstrumentedTest.kt b/core/picklist/src/androidTest/java/com/squirtles/picklist/ExampleInstrumentedTest.kt similarity index 84% rename from core/util/src/androidTest/java/com/example/util/ExampleInstrumentedTest.kt rename to core/picklist/src/androidTest/java/com/squirtles/picklist/ExampleInstrumentedTest.kt index 451b6124..3fe06e20 100644 --- a/core/util/src/androidTest/java/com/example/util/ExampleInstrumentedTest.kt +++ b/core/picklist/src/androidTest/java/com/squirtles/picklist/ExampleInstrumentedTest.kt @@ -1,4 +1,4 @@ -package com.example.util +package com.squirtles.picklist import androidx.test.platform.app.InstrumentationRegistry import androidx.test.ext.junit.runners.AndroidJUnit4 @@ -19,6 +19,6 @@ class ExampleInstrumentedTest { fun useAppContext() { // Context of the app under test. val appContext = InstrumentationRegistry.getInstrumentation().targetContext - assertEquals("com.example.util.test", appContext.packageName) + assertEquals("com.squirtles.picklist.test", appContext.packageName) } } diff --git a/core/picklist/src/main/java/com/example/picklist/Extension.kt b/core/picklist/src/main/java/com/squirtles/picklist/Extension.kt similarity index 78% rename from core/picklist/src/main/java/com/example/picklist/Extension.kt rename to core/picklist/src/main/java/com/squirtles/picklist/Extension.kt index 0413e18a..1bbca774 100644 --- a/core/picklist/src/main/java/com/example/picklist/Extension.kt +++ b/core/picklist/src/main/java/com/squirtles/picklist/Extension.kt @@ -1,7 +1,7 @@ -package com.example.picklist +package com.squirtles.picklist -import com.example.model.Order -import com.example.model.Pick +import com.squirtles.model.Order +import com.squirtles.model.Pick fun List.setOrderedList(order: Order): PickListUiState.Success { return PickListUiState.Success( diff --git a/core/picklist/src/main/java/com/example/picklist/OrderBottomSheet.kt b/core/picklist/src/main/java/com/squirtles/picklist/OrderBottomSheet.kt similarity index 95% rename from core/picklist/src/main/java/com/example/picklist/OrderBottomSheet.kt rename to core/picklist/src/main/java/com/squirtles/picklist/OrderBottomSheet.kt index 2dd14475..735f90ab 100644 --- a/core/picklist/src/main/java/com/example/picklist/OrderBottomSheet.kt +++ b/core/picklist/src/main/java/com/squirtles/picklist/OrderBottomSheet.kt @@ -1,4 +1,4 @@ -package com.example.picklist +package com.squirtles.picklist import androidx.compose.foundation.clickable import androidx.compose.foundation.layout.Arrangement @@ -25,11 +25,11 @@ import androidx.compose.ui.Modifier import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.text.style.TextAlign -import com.example.common.ui.Constants -import com.example.common.ui.theme.Dark -import com.example.common.ui.theme.Primary -import com.example.common.ui.theme.White -import com.example.model.Order +import com.squirtles.common.ui.Constants +import com.squirtles.common.ui.theme.Dark +import com.squirtles.common.ui.theme.Primary +import com.squirtles.common.ui.theme.White +import com.squirtles.model.Order import kotlinx.coroutines.launch @OptIn(ExperimentalMaterial3Api::class) diff --git a/core/picklist/src/main/java/com/example/picklist/PickListContents.kt b/core/picklist/src/main/java/com/squirtles/picklist/PickListContents.kt similarity index 95% rename from core/picklist/src/main/java/com/example/picklist/PickListContents.kt rename to core/picklist/src/main/java/com/squirtles/picklist/PickListContents.kt index db539602..bc30a490 100644 --- a/core/picklist/src/main/java/com/example/picklist/PickListContents.kt +++ b/core/picklist/src/main/java/com/squirtles/picklist/PickListContents.kt @@ -1,4 +1,4 @@ -package com.example.picklist +package com.squirtles.picklist import android.content.res.Configuration import androidx.compose.foundation.background @@ -14,7 +14,6 @@ import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.size import androidx.compose.foundation.layout.wrapContentSize import androidx.compose.foundation.lazy.LazyColumn -import androidx.compose.foundation.lazy.items import androidx.compose.foundation.shape.CircleShape import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Scaffold @@ -29,13 +28,13 @@ import androidx.compose.ui.platform.LocalConfiguration import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.dp import androidx.wear.compose.material.CircularProgressIndicator -import com.example.common.ui.Constants.COLOR_STOPS -import com.example.common.ui.Constants.DEFAULT_PADDING -import com.example.common.ui.CountText -import com.example.common.ui.DefaultTopAppBar -import com.example.common.ui.VerticalSpacer -import com.example.common.ui.theme.Primary -import com.example.model.Order +import com.squirtles.common.ui.Constants.COLOR_STOPS +import com.squirtles.common.ui.Constants.DEFAULT_PADDING +import com.squirtles.common.ui.CountText +import com.squirtles.common.ui.DefaultTopAppBar +import com.squirtles.common.ui.VerticalSpacer +import com.squirtles.common.ui.theme.Primary +import com.squirtles.model.Order @Composable fun PickListContents( diff --git a/core/picklist/src/main/java/com/example/picklist/PickListItem.kt b/core/picklist/src/main/java/com/squirtles/picklist/PickListItem.kt similarity index 87% rename from core/picklist/src/main/java/com/example/picklist/PickListItem.kt rename to core/picklist/src/main/java/com/squirtles/picklist/PickListItem.kt index 3be24d7b..345b1703 100644 --- a/core/picklist/src/main/java/com/example/picklist/PickListItem.kt +++ b/core/picklist/src/main/java/com/squirtles/picklist/PickListItem.kt @@ -1,4 +1,4 @@ -package com.example.picklist +package com.squirtles.picklist import androidx.compose.foundation.clickable import androidx.compose.foundation.interaction.MutableInteractionSource @@ -19,16 +19,16 @@ import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clip import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.unit.dp -import com.example.common.ui.AlbumImage -import com.example.common.ui.CommentText -import com.example.common.ui.Constants -import com.example.common.ui.CreatedByOtherUserText -import com.example.common.ui.FavoriteCountText -import com.example.common.ui.HorizontalSpacer -import com.example.common.ui.SongInfoText -import com.example.common.ui.theme.Gray -import com.example.common.ui.theme.White -import com.example.model.Song +import com.squirtles.common.ui.AlbumImage +import com.squirtles.common.ui.CommentText +import com.squirtles.common.ui.Constants +import com.squirtles.common.ui.CreatedByOtherUserText +import com.squirtles.common.ui.FavoriteCountText +import com.squirtles.common.ui.HorizontalSpacer +import com.squirtles.common.ui.SongInfoText +import com.squirtles.common.ui.theme.Gray +import com.squirtles.common.ui.theme.White +import com.squirtles.model.Song @Composable fun PickListItem( diff --git a/core/picklist/src/main/java/com/example/picklist/PickListType.kt b/core/picklist/src/main/java/com/squirtles/picklist/PickListType.kt similarity index 62% rename from core/picklist/src/main/java/com/example/picklist/PickListType.kt rename to core/picklist/src/main/java/com/squirtles/picklist/PickListType.kt index baeacf72..3f6c0764 100644 --- a/core/picklist/src/main/java/com/example/picklist/PickListType.kt +++ b/core/picklist/src/main/java/com/squirtles/picklist/PickListType.kt @@ -1,4 +1,4 @@ -package com.example.picklist +package com.squirtles.picklist enum class PickListType { FAVORITE, CREATED diff --git a/core/picklist/src/main/java/com/example/picklist/PickListUiState.kt b/core/picklist/src/main/java/com/squirtles/picklist/PickListUiState.kt similarity index 68% rename from core/picklist/src/main/java/com/example/picklist/PickListUiState.kt rename to core/picklist/src/main/java/com/squirtles/picklist/PickListUiState.kt index 9ebe307a..1f6278e8 100644 --- a/core/picklist/src/main/java/com/example/picklist/PickListUiState.kt +++ b/core/picklist/src/main/java/com/squirtles/picklist/PickListUiState.kt @@ -1,7 +1,7 @@ -package com.example.picklist +package com.squirtles.picklist -import com.example.model.Order -import com.example.model.Pick +import com.squirtles.model.Order +import com.squirtles.model.Pick sealed class PickListUiState { data object Loading : PickListUiState() diff --git a/core/picklist/src/test/java/com/example/picklist/ExampleUnitTest.kt b/core/picklist/src/test/java/com/squirtles/picklist/ExampleUnitTest.kt similarity index 91% rename from core/picklist/src/test/java/com/example/picklist/ExampleUnitTest.kt rename to core/picklist/src/test/java/com/squirtles/picklist/ExampleUnitTest.kt index 1bf523f2..be0b9f6c 100644 --- a/core/picklist/src/test/java/com/example/picklist/ExampleUnitTest.kt +++ b/core/picklist/src/test/java/com/squirtles/picklist/ExampleUnitTest.kt @@ -1,4 +1,4 @@ -package com.example.picklist +package com.squirtles.picklist import org.junit.Test diff --git a/core/util/build.gradle.kts b/core/util/build.gradle.kts index 8e376d47..2e14baeb 100644 --- a/core/util/build.gradle.kts +++ b/core/util/build.gradle.kts @@ -5,7 +5,7 @@ plugins { } android { - namespace = "com.example.util" + namespace = "com.squirtles.util" compileSdk = 34 defaultConfig { @@ -34,7 +34,6 @@ dependencies { implementation(libs.androidx.core.ktx) implementation(libs.androidx.appcompat) - implementation(libs.androidx.navigation.common.ktx) implementation(libs.material) testImplementation(libs.junit) androidTestImplementation(libs.androidx.junit) @@ -42,4 +41,5 @@ dependencies { // Serialization implementation(libs.kotlinx.serialization.json) + implementation(libs.androidx.navigation.common.ktx) } diff --git a/feature/create/src/androidTest/java/com/example/create/ExampleInstrumentedTest.kt b/core/util/src/androidTest/java/com/squirtles/util/ExampleInstrumentedTest.kt similarity index 86% rename from feature/create/src/androidTest/java/com/example/create/ExampleInstrumentedTest.kt rename to core/util/src/androidTest/java/com/squirtles/util/ExampleInstrumentedTest.kt index be3c1df4..1249c7b2 100644 --- a/feature/create/src/androidTest/java/com/example/create/ExampleInstrumentedTest.kt +++ b/core/util/src/androidTest/java/com/squirtles/util/ExampleInstrumentedTest.kt @@ -1,4 +1,4 @@ -package com.example.create +package com.squirtles.util import androidx.test.platform.app.InstrumentationRegistry import androidx.test.ext.junit.runners.AndroidJUnit4 @@ -19,6 +19,6 @@ class ExampleInstrumentedTest { fun useAppContext() { // Context of the app under test. val appContext = InstrumentationRegistry.getInstrumentation().targetContext - assertEquals("com.example.create.test", appContext.packageName) + assertEquals("com.squirtles.util.test", appContext.packageName) } } diff --git a/core/util/src/main/java/com/example/util/SerializableType.kt b/core/util/src/main/java/com/squirtles/util/SerializableType.kt similarity index 96% rename from core/util/src/main/java/com/example/util/SerializableType.kt rename to core/util/src/main/java/com/squirtles/util/SerializableType.kt index 9f03efc7..6775ea5d 100644 --- a/core/util/src/main/java/com/example/util/SerializableType.kt +++ b/core/util/src/main/java/com/squirtles/util/SerializableType.kt @@ -1,4 +1,4 @@ -package com.example.util +package com.squirtles.util import android.os.Bundle import androidx.navigation.NavType diff --git a/core/util/src/main/java/com/example/util/ThrottleFirst.kt b/core/util/src/main/java/com/squirtles/util/ThrottleFirst.kt similarity index 94% rename from core/util/src/main/java/com/example/util/ThrottleFirst.kt rename to core/util/src/main/java/com/squirtles/util/ThrottleFirst.kt index a707d53b..5ede902c 100644 --- a/core/util/src/main/java/com/example/util/ThrottleFirst.kt +++ b/core/util/src/main/java/com/squirtles/util/ThrottleFirst.kt @@ -1,4 +1,4 @@ -package com.example.util +package com.squirtles.util import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.flow diff --git a/core/util/src/test/java/com/example/util/ExampleUnitTest.kt b/core/util/src/test/java/com/squirtles/util/ExampleUnitTest.kt similarity index 92% rename from core/util/src/test/java/com/example/util/ExampleUnitTest.kt rename to core/util/src/test/java/com/squirtles/util/ExampleUnitTest.kt index 797793ac..fe01fb0c 100644 --- a/core/util/src/test/java/com/example/util/ExampleUnitTest.kt +++ b/core/util/src/test/java/com/squirtles/util/ExampleUnitTest.kt @@ -1,4 +1,4 @@ -package com.example.util +package com.squirtles.util import org.junit.Test diff --git a/data/applemusic/build.gradle.kts b/data/applemusic/build.gradle.kts index 6cfc0e97..e9fc6328 100644 --- a/data/applemusic/build.gradle.kts +++ b/data/applemusic/build.gradle.kts @@ -13,7 +13,7 @@ plugins { } android { - namespace = "com.example.applemusic" + namespace = "com.squirtles.applemusic" compileSdk = 34 defaultConfig { diff --git a/data/applemusic/src/main/java/com/example/applemusic/AppleMusicDataSourceImpl.kt b/data/applemusic/src/main/java/com/squirtles/applemusic/AppleMusicDataSourceImpl.kt similarity index 83% rename from data/applemusic/src/main/java/com/example/applemusic/AppleMusicDataSourceImpl.kt rename to data/applemusic/src/main/java/com/squirtles/applemusic/AppleMusicDataSourceImpl.kt index 9926f9a3..022f20a4 100644 --- a/data/applemusic/src/main/java/com/example/applemusic/AppleMusicDataSourceImpl.kt +++ b/data/applemusic/src/main/java/com/squirtles/applemusic/AppleMusicDataSourceImpl.kt @@ -1,14 +1,14 @@ -package com.example.applemusic +package com.squirtles.applemusic import androidx.paging.Pager import androidx.paging.PagingConfig import androidx.paging.PagingData -import com.example.applemusic.SearchSongsPagingSource.Companion.SEARCH_PAGE_SIZE -import com.example.applemusic.api.AppleMusicApi -import com.example.applemusic.model.SearchResponse -import com.example.applemusic.model.toMusicVideo -import com.example.model.MusicVideo -import com.example.model.Song +import com.squirtles.applemusic.SearchSongsPagingSource.Companion.SEARCH_PAGE_SIZE +import com.squirtles.applemusic.api.AppleMusicApi +import com.squirtles.applemusic.model.SearchResponse +import com.squirtles.applemusic.model.toMusicVideo +import com.squirtles.model.MusicVideo +import com.squirtles.model.Song import kotlinx.coroutines.flow.Flow import retrofit2.Response import javax.inject.Inject diff --git a/data/applemusic/src/main/java/com/example/applemusic/AppleMusicRepositoryImpl.kt b/data/applemusic/src/main/java/com/squirtles/applemusic/AppleMusicRepositoryImpl.kt similarity index 90% rename from data/applemusic/src/main/java/com/example/applemusic/AppleMusicRepositoryImpl.kt rename to data/applemusic/src/main/java/com/squirtles/applemusic/AppleMusicRepositoryImpl.kt index 92eb18aa..4815a4d1 100644 --- a/data/applemusic/src/main/java/com/example/applemusic/AppleMusicRepositoryImpl.kt +++ b/data/applemusic/src/main/java/com/squirtles/applemusic/AppleMusicRepositoryImpl.kt @@ -1,8 +1,8 @@ -package com.example.applemusic +package com.squirtles.applemusic import androidx.paging.PagingData -import com.example.model.MusicVideo -import com.example.model.Song +import com.squirtles.model.MusicVideo +import com.squirtles.model.Song import kotlinx.coroutines.flow.Flow import javax.inject.Inject diff --git a/data/applemusic/src/main/java/com/example/applemusic/SearchSongsPagingSource.kt b/data/applemusic/src/main/java/com/squirtles/applemusic/SearchSongsPagingSource.kt similarity index 92% rename from data/applemusic/src/main/java/com/example/applemusic/SearchSongsPagingSource.kt rename to data/applemusic/src/main/java/com/squirtles/applemusic/SearchSongsPagingSource.kt index 0d4b80e9..98699149 100644 --- a/data/applemusic/src/main/java/com/example/applemusic/SearchSongsPagingSource.kt +++ b/data/applemusic/src/main/java/com/squirtles/applemusic/SearchSongsPagingSource.kt @@ -1,10 +1,10 @@ -package com.example.applemusic +package com.squirtles.applemusic import androidx.paging.PagingSource import androidx.paging.PagingState -import com.example.applemusic.api.AppleMusicApi -import com.example.applemusic.model.toSong -import com.example.model.Song +import com.squirtles.applemusic.api.AppleMusicApi +import com.squirtles.applemusic.model.toSong +import com.squirtles.model.Song import retrofit2.HttpException import java.io.IOException diff --git a/data/applemusic/src/main/java/com/example/applemusic/api/AppleMusicApi.kt b/data/applemusic/src/main/java/com/squirtles/applemusic/api/AppleMusicApi.kt similarity index 83% rename from data/applemusic/src/main/java/com/example/applemusic/api/AppleMusicApi.kt rename to data/applemusic/src/main/java/com/squirtles/applemusic/api/AppleMusicApi.kt index 6330d147..c3a1212c 100644 --- a/data/applemusic/src/main/java/com/example/applemusic/api/AppleMusicApi.kt +++ b/data/applemusic/src/main/java/com/squirtles/applemusic/api/AppleMusicApi.kt @@ -1,6 +1,6 @@ -package com.example.applemusic.api +package com.squirtles.applemusic.api -import com.example.applemusic.model.SearchResponse +import com.squirtles.applemusic.model.SearchResponse import retrofit2.Response import retrofit2.http.GET import retrofit2.http.Path diff --git a/data/applemusic/src/main/java/com/example/applemusic/api/NetworkModule.kt b/data/applemusic/src/main/java/com/squirtles/applemusic/api/NetworkModule.kt similarity index 94% rename from data/applemusic/src/main/java/com/example/applemusic/api/NetworkModule.kt rename to data/applemusic/src/main/java/com/squirtles/applemusic/api/NetworkModule.kt index 99ebda89..b4fb6ec0 100644 --- a/data/applemusic/src/main/java/com/example/applemusic/api/NetworkModule.kt +++ b/data/applemusic/src/main/java/com/squirtles/applemusic/api/NetworkModule.kt @@ -1,6 +1,6 @@ -package com.example.applemusic.api +package com.squirtles.applemusic.api -import com.example.applemusic.BuildConfig +import com.squirtles.applemusic.BuildConfig import dagger.Module import dagger.Provides import dagger.hilt.InstallIn diff --git a/data/applemusic/src/main/java/com/example/applemusic/di/AppleMusicModule.kt b/data/applemusic/src/main/java/com/squirtles/applemusic/di/AppleMusicModule.kt similarity index 78% rename from data/applemusic/src/main/java/com/example/applemusic/di/AppleMusicModule.kt rename to data/applemusic/src/main/java/com/squirtles/applemusic/di/AppleMusicModule.kt index ed0ac533..f562aba0 100644 --- a/data/applemusic/src/main/java/com/example/applemusic/di/AppleMusicModule.kt +++ b/data/applemusic/src/main/java/com/squirtles/applemusic/di/AppleMusicModule.kt @@ -1,10 +1,10 @@ -package com.example.applemusic.di +package com.squirtles.applemusic.di -import com.example.applemusic.AppleMusicDataSourceImpl -import com.example.applemusic.AppleMusicRemoteDataSource -import com.example.applemusic.AppleMusicRepository -import com.example.applemusic.AppleMusicRepositoryImpl -import com.example.applemusic.api.AppleMusicApi +import com.squirtles.applemusic.AppleMusicDataSourceImpl +import com.squirtles.applemusic.AppleMusicRemoteDataSource +import com.squirtles.applemusic.AppleMusicRepository +import com.squirtles.applemusic.AppleMusicRepositoryImpl +import com.squirtles.applemusic.api.AppleMusicApi import dagger.Module import dagger.Provides import dagger.hilt.InstallIn diff --git a/data/applemusic/src/main/java/com/example/applemusic/model/AppleMusicMapper.kt b/data/applemusic/src/main/java/com/squirtles/applemusic/model/AppleMusicMapper.kt similarity index 91% rename from data/applemusic/src/main/java/com/example/applemusic/model/AppleMusicMapper.kt rename to data/applemusic/src/main/java/com/squirtles/applemusic/model/AppleMusicMapper.kt index 983ac6e7..af346217 100644 --- a/data/applemusic/src/main/java/com/example/applemusic/model/AppleMusicMapper.kt +++ b/data/applemusic/src/main/java/com/squirtles/applemusic/model/AppleMusicMapper.kt @@ -1,8 +1,8 @@ -package com.example.applemusic.model +package com.squirtles.applemusic.model import androidx.core.graphics.toColorInt -import com.example.model.MusicVideo -import com.example.model.Song +import com.squirtles.model.MusicVideo +import com.squirtles.model.Song import java.time.LocalDate import java.time.format.DateTimeFormatter diff --git a/data/applemusic/src/main/java/com/example/applemusic/model/Artwork.kt b/data/applemusic/src/main/java/com/squirtles/applemusic/model/Artwork.kt similarity index 81% rename from data/applemusic/src/main/java/com/example/applemusic/model/Artwork.kt rename to data/applemusic/src/main/java/com/squirtles/applemusic/model/Artwork.kt index 04990f93..8764ab55 100644 --- a/data/applemusic/src/main/java/com/example/applemusic/model/Artwork.kt +++ b/data/applemusic/src/main/java/com/squirtles/applemusic/model/Artwork.kt @@ -1,4 +1,4 @@ -package com.example.applemusic.model +package com.squirtles.applemusic.model import kotlinx.serialization.Serializable diff --git a/data/applemusic/src/main/java/com/example/applemusic/model/Attributes.kt b/data/applemusic/src/main/java/com/squirtles/applemusic/model/Attributes.kt similarity index 93% rename from data/applemusic/src/main/java/com/example/applemusic/model/Attributes.kt rename to data/applemusic/src/main/java/com/squirtles/applemusic/model/Attributes.kt index 95e75010..7cc5a8c0 100644 --- a/data/applemusic/src/main/java/com/example/applemusic/model/Attributes.kt +++ b/data/applemusic/src/main/java/com/squirtles/applemusic/model/Attributes.kt @@ -1,4 +1,4 @@ -package com.example.applemusic.model +package com.squirtles.applemusic.model import kotlinx.serialization.SerialName import kotlinx.serialization.Serializable diff --git a/data/applemusic/src/main/java/com/example/applemusic/model/Data.kt b/data/applemusic/src/main/java/com/squirtles/applemusic/model/Data.kt similarity index 76% rename from data/applemusic/src/main/java/com/example/applemusic/model/Data.kt rename to data/applemusic/src/main/java/com/squirtles/applemusic/model/Data.kt index a92da6c5..49322912 100644 --- a/data/applemusic/src/main/java/com/example/applemusic/model/Data.kt +++ b/data/applemusic/src/main/java/com/squirtles/applemusic/model/Data.kt @@ -1,4 +1,4 @@ -package com.example.applemusic.model +package com.squirtles.applemusic.model import kotlinx.serialization.Serializable diff --git a/data/applemusic/src/main/java/com/example/applemusic/model/MusicVideoResponse.kt b/data/applemusic/src/main/java/com/squirtles/applemusic/model/MusicVideoResponse.kt similarity index 75% rename from data/applemusic/src/main/java/com/example/applemusic/model/MusicVideoResponse.kt rename to data/applemusic/src/main/java/com/squirtles/applemusic/model/MusicVideoResponse.kt index 783f9049..4a9fc053 100644 --- a/data/applemusic/src/main/java/com/example/applemusic/model/MusicVideoResponse.kt +++ b/data/applemusic/src/main/java/com/squirtles/applemusic/model/MusicVideoResponse.kt @@ -1,4 +1,4 @@ -package com.example.applemusic.model +package com.squirtles.applemusic.model import kotlinx.serialization.Serializable diff --git a/data/applemusic/src/main/java/com/example/applemusic/model/Preview.kt b/data/applemusic/src/main/java/com/squirtles/applemusic/model/Preview.kt similarity index 81% rename from data/applemusic/src/main/java/com/example/applemusic/model/Preview.kt rename to data/applemusic/src/main/java/com/squirtles/applemusic/model/Preview.kt index 507ea01a..03d2a301 100644 --- a/data/applemusic/src/main/java/com/example/applemusic/model/Preview.kt +++ b/data/applemusic/src/main/java/com/squirtles/applemusic/model/Preview.kt @@ -1,4 +1,4 @@ -package com.example.applemusic.model +package com.squirtles.applemusic.model import kotlinx.serialization.Serializable diff --git a/data/applemusic/src/main/java/com/example/applemusic/model/Results.kt b/data/applemusic/src/main/java/com/squirtles/applemusic/model/Results.kt similarity index 86% rename from data/applemusic/src/main/java/com/example/applemusic/model/Results.kt rename to data/applemusic/src/main/java/com/squirtles/applemusic/model/Results.kt index 4674e0b7..85a903eb 100644 --- a/data/applemusic/src/main/java/com/example/applemusic/model/Results.kt +++ b/data/applemusic/src/main/java/com/squirtles/applemusic/model/Results.kt @@ -1,4 +1,4 @@ -package com.example.applemusic.model +package com.squirtles.applemusic.model import kotlinx.serialization.SerialName import kotlinx.serialization.Serializable diff --git a/data/applemusic/src/main/java/com/example/applemusic/model/SearchResponse.kt b/data/applemusic/src/main/java/com/squirtles/applemusic/model/SearchResponse.kt similarity index 74% rename from data/applemusic/src/main/java/com/example/applemusic/model/SearchResponse.kt rename to data/applemusic/src/main/java/com/squirtles/applemusic/model/SearchResponse.kt index 10c99668..59e66b69 100644 --- a/data/applemusic/src/main/java/com/example/applemusic/model/SearchResponse.kt +++ b/data/applemusic/src/main/java/com/squirtles/applemusic/model/SearchResponse.kt @@ -1,4 +1,4 @@ -package com.example.applemusic.model +package com.squirtles.applemusic.model import kotlinx.serialization.Serializable diff --git a/data/applemusic/src/main/java/com/example/applemusic/model/Songs.kt b/data/applemusic/src/main/java/com/squirtles/applemusic/model/Songs.kt similarity index 77% rename from data/applemusic/src/main/java/com/example/applemusic/model/Songs.kt rename to data/applemusic/src/main/java/com/squirtles/applemusic/model/Songs.kt index 14b85769..aa82869e 100644 --- a/data/applemusic/src/main/java/com/example/applemusic/model/Songs.kt +++ b/data/applemusic/src/main/java/com/squirtles/applemusic/model/Songs.kt @@ -1,4 +1,4 @@ -package com.example.applemusic.model +package com.squirtles.applemusic.model import kotlinx.serialization.Serializable diff --git a/data/build.gradle.kts b/data/build.gradle.kts index e8c916e5..92a6efd5 100644 --- a/data/build.gradle.kts +++ b/data/build.gradle.kts @@ -93,7 +93,7 @@ dependencies { implementation(libs.firebase.firestore.ktx) implementation(libs.firebase.functions.ktx) implementation(libs.geofire.android.common) - implementation(libs.kotlinx.coroutines.play.services) +// implementation(libs.kotlinx.coroutines.play.services) // Hilt implementation(libs.hilt.android) diff --git a/data/favorite/build.gradle.kts b/data/favorite/build.gradle.kts index 6db85a60..d21ac157 100644 --- a/data/favorite/build.gradle.kts +++ b/data/favorite/build.gradle.kts @@ -13,7 +13,7 @@ plugins { } android { - namespace = "com.example.favorite" + namespace = "com.squirtles.favorite" compileSdk = 34 defaultConfig { @@ -76,5 +76,5 @@ dependencies { implementation(libs.inject) // Firebase - implementation(libs.google.firebase.firestore.ktx) + implementation(libs.firebase.firestore.ktx) } diff --git a/data/favorite/src/main/java/com/example/favorite/CloudFunctionHelper.kt b/data/favorite/src/main/java/com/squirtles/favorite/CloudFunctionHelper.kt similarity index 96% rename from data/favorite/src/main/java/com/example/favorite/CloudFunctionHelper.kt rename to data/favorite/src/main/java/com/squirtles/favorite/CloudFunctionHelper.kt index 39848b9d..f0d50559 100644 --- a/data/favorite/src/main/java/com/example/favorite/CloudFunctionHelper.kt +++ b/data/favorite/src/main/java/com/squirtles/favorite/CloudFunctionHelper.kt @@ -1,4 +1,4 @@ -package com.example.favorite +package com.squirtles.favorite import com.google.firebase.functions.FirebaseFunctions import com.google.firebase.functions.ktx.functions diff --git a/data/favorite/src/main/java/com/example/favorite/FirebaseFavoriteDataSourceImpl.kt b/data/favorite/src/main/java/com/squirtles/favorite/FirebaseFavoriteDataSourceImpl.kt similarity index 93% rename from data/favorite/src/main/java/com/example/favorite/FirebaseFavoriteDataSourceImpl.kt rename to data/favorite/src/main/java/com/squirtles/favorite/FirebaseFavoriteDataSourceImpl.kt index a62df7dc..456c95bc 100644 --- a/data/favorite/src/main/java/com/example/favorite/FirebaseFavoriteDataSourceImpl.kt +++ b/data/favorite/src/main/java/com/squirtles/favorite/FirebaseFavoriteDataSourceImpl.kt @@ -1,10 +1,10 @@ -package com.example.favorite +package com.squirtles.favorite import android.util.Log -import com.example.firebase.FirebaseDataSourceConstants.COLLECTION_FAVORITES -import com.example.firebase.FirebaseDataSourceConstants.FIELD_PICK_ID -import com.example.firebase.FirebaseDataSourceConstants.FIELD_USER_ID -import com.example.firebase.model.FirebaseFavorite +import com.squirtles.firebase.FirebaseDataSourceConstants.COLLECTION_FAVORITES +import com.squirtles.firebase.FirebaseDataSourceConstants.FIELD_PICK_ID +import com.squirtles.firebase.FirebaseDataSourceConstants.FIELD_USER_ID +import com.squirtles.firebase.model.FirebaseFavorite import com.google.firebase.firestore.FirebaseFirestore import com.google.firebase.firestore.QuerySnapshot import kotlinx.coroutines.CoroutineScope diff --git a/data/favorite/src/main/java/com/example/favorite/FirebaseFavoriteRepositoryImpl.kt b/data/favorite/src/main/java/com/squirtles/favorite/FirebaseFavoriteRepositoryImpl.kt similarity index 91% rename from data/favorite/src/main/java/com/example/favorite/FirebaseFavoriteRepositoryImpl.kt rename to data/favorite/src/main/java/com/squirtles/favorite/FirebaseFavoriteRepositoryImpl.kt index be2582c4..fc6a18b0 100644 --- a/data/favorite/src/main/java/com/example/favorite/FirebaseFavoriteRepositoryImpl.kt +++ b/data/favorite/src/main/java/com/squirtles/favorite/FirebaseFavoriteRepositoryImpl.kt @@ -1,6 +1,6 @@ -package com.example.favorite +package com.squirtles.favorite -import com.example.firebase.handleResult +import com.squirtles.firebase.handleResult import javax.inject.Inject import javax.inject.Singleton diff --git a/data/favorite/src/main/java/com/example/favorite/di/FirebaseFavoriteModule.kt b/data/favorite/src/main/java/com/squirtles/favorite/di/FirebaseFavoriteModule.kt similarity index 73% rename from data/favorite/src/main/java/com/example/favorite/di/FirebaseFavoriteModule.kt rename to data/favorite/src/main/java/com/squirtles/favorite/di/FirebaseFavoriteModule.kt index 0f3e0900..bbca107a 100644 --- a/data/favorite/src/main/java/com/example/favorite/di/FirebaseFavoriteModule.kt +++ b/data/favorite/src/main/java/com/squirtles/favorite/di/FirebaseFavoriteModule.kt @@ -1,10 +1,10 @@ -package com.example.favorite.di +package com.squirtles.favorite.di -import com.example.favorite.CloudFunctionHelper -import com.example.favorite.FirebaseFavoriteDataSource -import com.example.favorite.FirebaseFavoriteDataSourceImpl -import com.example.favorite.FirebaseFavoriteRepository -import com.example.favorite.FirebaseFavoriteRepositoryImpl +import com.squirtles.favorite.CloudFunctionHelper +import com.squirtles.favorite.FirebaseFavoriteDataSource +import com.squirtles.favorite.FirebaseFavoriteDataSourceImpl +import com.squirtles.favorite.FirebaseFavoriteRepository +import com.squirtles.favorite.FirebaseFavoriteRepositoryImpl import com.google.firebase.firestore.FirebaseFirestore import dagger.Module import dagger.Provides diff --git a/data/firebase/build.gradle.kts b/data/firebase/build.gradle.kts index 5dfff9d4..e803d299 100644 --- a/data/firebase/build.gradle.kts +++ b/data/firebase/build.gradle.kts @@ -12,7 +12,7 @@ plugins { } android { - namespace = "com.example.firebase" + namespace = "com.squirtles.firebase" compileSdk = 34 defaultConfig { @@ -70,6 +70,6 @@ dependencies { implementation(libs.inject) // Firebase - implementation(libs.google.firebase.firestore.ktx) + implementation(libs.firebase.firestore.ktx) implementation(libs.geofire.android.common) } diff --git a/data/firebase/src/main/java/com/example/firebase/FirebaseDataSourceConstants.kt b/data/firebase/src/main/java/com/squirtles/firebase/FirebaseDataSourceConstants.kt similarity index 92% rename from data/firebase/src/main/java/com/example/firebase/FirebaseDataSourceConstants.kt rename to data/firebase/src/main/java/com/squirtles/firebase/FirebaseDataSourceConstants.kt index 1ee8af66..f33052de 100644 --- a/data/firebase/src/main/java/com/example/firebase/FirebaseDataSourceConstants.kt +++ b/data/firebase/src/main/java/com/squirtles/firebase/FirebaseDataSourceConstants.kt @@ -1,4 +1,4 @@ -package com.example.firebase +package com.squirtles.firebase object FirebaseDataSourceConstants { const val TAG_LOG = "FirebaseDataSourceImpl" diff --git a/data/firebase/src/main/java/com/example/firebase/FirebaseException.kt b/data/firebase/src/main/java/com/squirtles/firebase/FirebaseException.kt similarity index 95% rename from data/firebase/src/main/java/com/example/firebase/FirebaseException.kt rename to data/firebase/src/main/java/com/squirtles/firebase/FirebaseException.kt index bb5781c5..87491119 100644 --- a/data/firebase/src/main/java/com/example/firebase/FirebaseException.kt +++ b/data/firebase/src/main/java/com/squirtles/firebase/FirebaseException.kt @@ -1,4 +1,4 @@ -package com.example.firebase +package com.squirtles.firebase sealed class FirebaseException(override val message: String) : Exception() { data class CreatedUserFailedException(override val message: String = "Failed to create a user") : diff --git a/data/firebase/src/main/java/com/example/firebase/FirebaseModule.kt b/data/firebase/src/main/java/com/squirtles/firebase/FirebaseModule.kt similarity index 93% rename from data/firebase/src/main/java/com/example/firebase/FirebaseModule.kt rename to data/firebase/src/main/java/com/squirtles/firebase/FirebaseModule.kt index 28f4bc65..c2ff9b6b 100644 --- a/data/firebase/src/main/java/com/example/firebase/FirebaseModule.kt +++ b/data/firebase/src/main/java/com/squirtles/firebase/FirebaseModule.kt @@ -1,4 +1,4 @@ -package com.example.firebase +package com.squirtles.firebase import com.google.firebase.firestore.FirebaseFirestore import dagger.Module diff --git a/data/firebase/src/main/java/com/example/firebase/FirebaseRepositoryUtils.kt b/data/firebase/src/main/java/com/squirtles/firebase/FirebaseRepositoryUtils.kt similarity index 91% rename from data/firebase/src/main/java/com/example/firebase/FirebaseRepositoryUtils.kt rename to data/firebase/src/main/java/com/squirtles/firebase/FirebaseRepositoryUtils.kt index 80ca0a84..137e5fbe 100644 --- a/data/firebase/src/main/java/com/example/firebase/FirebaseRepositoryUtils.kt +++ b/data/firebase/src/main/java/com/squirtles/firebase/FirebaseRepositoryUtils.kt @@ -1,4 +1,4 @@ -package com.example.firebase +package com.squirtles.firebase suspend fun handleResult( firebaseRepositoryException: FirebaseException, diff --git a/data/firebase/src/main/java/com/example/firebase/model/FirebaseFavorite.kt b/data/firebase/src/main/java/com/squirtles/firebase/model/FirebaseFavorite.kt similarity index 86% rename from data/firebase/src/main/java/com/example/firebase/model/FirebaseFavorite.kt rename to data/firebase/src/main/java/com/squirtles/firebase/model/FirebaseFavorite.kt index 926c8189..bd111ef8 100644 --- a/data/firebase/src/main/java/com/example/firebase/model/FirebaseFavorite.kt +++ b/data/firebase/src/main/java/com/squirtles/firebase/model/FirebaseFavorite.kt @@ -1,4 +1,4 @@ -package com.example.firebase.model +package com.squirtles.firebase.model import com.google.firebase.Timestamp import com.google.firebase.firestore.ServerTimestamp diff --git a/data/firebase/src/main/java/com/example/firebase/model/FirebasePick.kt b/data/firebase/src/main/java/com/squirtles/firebase/model/FirebasePick.kt similarity index 96% rename from data/firebase/src/main/java/com/example/firebase/model/FirebasePick.kt rename to data/firebase/src/main/java/com/squirtles/firebase/model/FirebasePick.kt index 297cbeb3..c2de0b59 100644 --- a/data/firebase/src/main/java/com/example/firebase/model/FirebasePick.kt +++ b/data/firebase/src/main/java/com/squirtles/firebase/model/FirebasePick.kt @@ -1,4 +1,4 @@ -package com.example.firebase.model +package com.squirtles.firebase.model import com.google.firebase.Timestamp import com.google.firebase.firestore.GeoPoint diff --git a/data/firebase/src/main/java/com/example/firebase/model/FirebaseUser.kt b/data/firebase/src/main/java/com/squirtles/firebase/model/FirebaseUser.kt similarity index 79% rename from data/firebase/src/main/java/com/example/firebase/model/FirebaseUser.kt rename to data/firebase/src/main/java/com/squirtles/firebase/model/FirebaseUser.kt index cfa64956..19c5cb09 100644 --- a/data/firebase/src/main/java/com/example/firebase/model/FirebaseUser.kt +++ b/data/firebase/src/main/java/com/squirtles/firebase/model/FirebaseUser.kt @@ -1,4 +1,4 @@ -package com.example.firebase.model +package com.squirtles.firebase.model data class FirebaseUser( val name: String? = null, diff --git a/data/firebase/src/main/java/com/example/firebase/model/Mapper.kt b/data/firebase/src/main/java/com/squirtles/firebase/model/Mapper.kt similarity index 92% rename from data/firebase/src/main/java/com/example/firebase/model/Mapper.kt rename to data/firebase/src/main/java/com/squirtles/firebase/model/Mapper.kt index 0aec15cc..0593e827 100644 --- a/data/firebase/src/main/java/com/example/firebase/model/Mapper.kt +++ b/data/firebase/src/main/java/com/squirtles/firebase/model/Mapper.kt @@ -1,11 +1,11 @@ -package com.example.firebase.model +package com.squirtles.firebase.model import androidx.core.graphics.toColorInt -import com.example.model.Creator -import com.example.model.LocationPoint -import com.example.model.Pick -import com.example.model.Song -import com.example.model.User +import com.squirtles.model.Creator +import com.squirtles.model.LocationPoint +import com.squirtles.model.Pick +import com.squirtles.model.Song +import com.squirtles.model.User import com.firebase.geofire.GeoFireUtils import com.firebase.geofire.GeoLocation import com.google.firebase.firestore.GeoPoint diff --git a/data/location/build.gradle.kts b/data/location/build.gradle.kts index 6a34658a..0b726ce3 100644 --- a/data/location/build.gradle.kts +++ b/data/location/build.gradle.kts @@ -6,7 +6,7 @@ plugins { } android { - namespace = "com.example.location" + namespace = "com.squirtles.location" compileSdk = 34 defaultConfig { diff --git a/data/location/src/main/java/com/example/order/LocalLocationRepositoryImpl.kt b/data/location/src/main/java/com/squirtles/order/LocalLocationRepositoryImpl.kt similarity index 87% rename from data/location/src/main/java/com/example/order/LocalLocationRepositoryImpl.kt rename to data/location/src/main/java/com/squirtles/order/LocalLocationRepositoryImpl.kt index ae164412..7f574a01 100644 --- a/data/location/src/main/java/com/example/order/LocalLocationRepositoryImpl.kt +++ b/data/location/src/main/java/com/squirtles/order/LocalLocationRepositoryImpl.kt @@ -1,7 +1,7 @@ -package com.example.order +package com.squirtles.order import android.location.Location -import com.example.location.LocalLocationRepository +import com.squirtles.location.LocalLocationRepository import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.asStateFlow diff --git a/data/location/src/main/java/com/example/order/di/LocationModule.kt b/data/location/src/main/java/com/squirtles/order/di/LocationModule.kt similarity index 72% rename from data/location/src/main/java/com/example/order/di/LocationModule.kt rename to data/location/src/main/java/com/squirtles/order/di/LocationModule.kt index 9bb10b6e..0b898ccf 100644 --- a/data/location/src/main/java/com/example/order/di/LocationModule.kt +++ b/data/location/src/main/java/com/squirtles/order/di/LocationModule.kt @@ -1,7 +1,7 @@ -package com.example.order.di +package com.squirtles.order.di -import com.example.location.LocalLocationRepository -import com.example.order.LocalLocationRepositoryImpl +import com.squirtles.location.LocalLocationRepository +import com.squirtles.order.LocalLocationRepositoryImpl import dagger.Module import dagger.Provides import dagger.hilt.InstallIn diff --git a/data/order/build.gradle.kts b/data/order/build.gradle.kts index f4df2cac..60a20058 100644 --- a/data/order/build.gradle.kts +++ b/data/order/build.gradle.kts @@ -6,7 +6,7 @@ plugins { } android { - namespace = "com.example.order" + namespace = "com.squirtles.order" compileSdk = 34 defaultConfig { diff --git a/data/order/src/main/java/com/example/order/LocalPickListOrderRepositoryImpl.kt b/data/order/src/main/java/com/squirtles/order/LocalPickListOrderRepositoryImpl.kt similarity index 89% rename from data/order/src/main/java/com/example/order/LocalPickListOrderRepositoryImpl.kt rename to data/order/src/main/java/com/squirtles/order/LocalPickListOrderRepositoryImpl.kt index 3e714887..27543d48 100644 --- a/data/order/src/main/java/com/example/order/LocalPickListOrderRepositoryImpl.kt +++ b/data/order/src/main/java/com/squirtles/order/LocalPickListOrderRepositoryImpl.kt @@ -1,6 +1,6 @@ -package com.example.order +package com.squirtles.order -import com.example.model.Order +import com.squirtles.model.Order import javax.inject.Singleton @Singleton diff --git a/data/order/src/main/java/com/example/order/di/OrderModule.kt b/data/order/src/main/java/com/squirtles/order/di/OrderModule.kt similarity index 71% rename from data/order/src/main/java/com/example/order/di/OrderModule.kt rename to data/order/src/main/java/com/squirtles/order/di/OrderModule.kt index 7b66f651..89261b1e 100644 --- a/data/order/src/main/java/com/example/order/di/OrderModule.kt +++ b/data/order/src/main/java/com/squirtles/order/di/OrderModule.kt @@ -1,7 +1,7 @@ -package com.example.order.di +package com.squirtles.order.di -import com.example.order.LocalPickListOrderRepository -import com.example.order.LocalPickListOrderRepositoryImpl +import com.squirtles.order.LocalPickListOrderRepository +import com.squirtles.order.LocalPickListOrderRepositoryImpl import dagger.Module import dagger.Provides import dagger.hilt.InstallIn diff --git a/data/pick/build.gradle.kts b/data/pick/build.gradle.kts index 829de00f..21015038 100644 --- a/data/pick/build.gradle.kts +++ b/data/pick/build.gradle.kts @@ -6,7 +6,7 @@ plugins { } android { - namespace = "com.example.pick" + namespace = "com.squirtles.pick" compileSdk = 34 defaultConfig { @@ -43,6 +43,6 @@ dependencies { implementation(libs.inject) // Firebase - implementation(libs.google.firebase.firestore.ktx) + implementation(libs.firebase.firestore.ktx) implementation(libs.geofire.android.common) } diff --git a/data/pick/src/main/java/com/example/pick/FirebasePickDataSourceImpl.kt b/data/pick/src/main/java/com/squirtles/pick/FirebasePickDataSourceImpl.kt similarity index 92% rename from data/pick/src/main/java/com/example/pick/FirebasePickDataSourceImpl.kt rename to data/pick/src/main/java/com/squirtles/pick/FirebasePickDataSourceImpl.kt index 0e1a1894..6061d6b9 100644 --- a/data/pick/src/main/java/com/example/pick/FirebasePickDataSourceImpl.kt +++ b/data/pick/src/main/java/com/squirtles/pick/FirebasePickDataSourceImpl.kt @@ -1,19 +1,19 @@ -package com.example.pick +package com.squirtles.pick import android.util.Log -import com.example.firebase.FirebaseDataSourceConstants.COLLECTION_FAVORITES -import com.example.firebase.FirebaseDataSourceConstants.COLLECTION_PICKS -import com.example.firebase.FirebaseDataSourceConstants.COLLECTION_USERS -import com.example.firebase.FirebaseDataSourceConstants.FIELD_ADDED_AT -import com.example.firebase.FirebaseDataSourceConstants.FIELD_MY_PICKS -import com.example.firebase.FirebaseDataSourceConstants.FIELD_PICK_ID -import com.example.firebase.FirebaseDataSourceConstants.FIELD_USER_ID -import com.example.firebase.FirebaseDataSourceConstants.TAG_LOG -import com.example.firebase.model.FirebasePick -import com.example.firebase.model.FirebaseUser -import com.example.firebase.model.toFirebasePick -import com.example.firebase.model.toPick -import com.example.model.Pick +import com.squirtles.firebase.FirebaseDataSourceConstants.COLLECTION_FAVORITES +import com.squirtles.firebase.FirebaseDataSourceConstants.COLLECTION_PICKS +import com.squirtles.firebase.FirebaseDataSourceConstants.COLLECTION_USERS +import com.squirtles.firebase.FirebaseDataSourceConstants.FIELD_ADDED_AT +import com.squirtles.firebase.FirebaseDataSourceConstants.FIELD_MY_PICKS +import com.squirtles.firebase.FirebaseDataSourceConstants.FIELD_PICK_ID +import com.squirtles.firebase.FirebaseDataSourceConstants.FIELD_USER_ID +import com.squirtles.firebase.FirebaseDataSourceConstants.TAG_LOG +import com.squirtles.firebase.model.FirebasePick +import com.squirtles.firebase.model.FirebaseUser +import com.squirtles.firebase.model.toFirebasePick +import com.squirtles.firebase.model.toPick +import com.squirtles.model.Pick import com.firebase.geofire.GeoFireUtils import com.firebase.geofire.GeoLocation import com.google.android.gms.tasks.Task diff --git a/data/pick/src/main/java/com/example/pick/FirebasePickRepositoryImpl.kt b/data/pick/src/main/java/com/squirtles/pick/FirebasePickRepositoryImpl.kt similarity index 90% rename from data/pick/src/main/java/com/example/pick/FirebasePickRepositoryImpl.kt rename to data/pick/src/main/java/com/squirtles/pick/FirebasePickRepositoryImpl.kt index d91b1dc8..8b911757 100644 --- a/data/pick/src/main/java/com/example/pick/FirebasePickRepositoryImpl.kt +++ b/data/pick/src/main/java/com/squirtles/pick/FirebasePickRepositoryImpl.kt @@ -1,8 +1,8 @@ -package com.example.pick +package com.squirtles.pick -import com.example.firebase.FirebaseException -import com.example.firebase.handleResult -import com.example.model.Pick +import com.squirtles.firebase.FirebaseException +import com.squirtles.firebase.handleResult +import com.squirtles.model.Pick import javax.inject.Inject import javax.inject.Singleton diff --git a/data/pick/src/main/java/com/example/pick/di/PickModule.kt b/data/pick/src/main/java/com/squirtles/pick/di/PickModule.kt similarity index 73% rename from data/pick/src/main/java/com/example/pick/di/PickModule.kt rename to data/pick/src/main/java/com/squirtles/pick/di/PickModule.kt index dbe42d44..8b5cc418 100644 --- a/data/pick/src/main/java/com/example/pick/di/PickModule.kt +++ b/data/pick/src/main/java/com/squirtles/pick/di/PickModule.kt @@ -1,9 +1,9 @@ -package com.example.pick.di +package com.squirtles.pick.di -import com.example.pick.FirebasePickDataSource -import com.example.pick.FirebasePickDataSourceImpl -import com.example.pick.FirebasePickRepository -import com.example.pick.FirebasePickRepositoryImpl +import com.squirtles.pick.FirebasePickDataSource +import com.squirtles.pick.FirebasePickDataSourceImpl +import com.squirtles.pick.FirebasePickRepository +import com.squirtles.pick.FirebasePickRepositoryImpl import com.google.firebase.firestore.FirebaseFirestore import dagger.Module import dagger.Provides diff --git a/data/src/main/java/com/squirtles/data/applemusic/SearchSongsPagingSource.kt b/data/src/main/java/com/squirtles/data/applemusic/SearchSongsPagingSource.kt index 45ba6acb..72c6aa6b 100644 --- a/data/src/main/java/com/squirtles/data/applemusic/SearchSongsPagingSource.kt +++ b/data/src/main/java/com/squirtles/data/applemusic/SearchSongsPagingSource.kt @@ -1,5 +1,6 @@ package com.squirtles.data.applemusic +import android.util.Log import androidx.paging.PagingSource import androidx.paging.PagingState import com.squirtles.data.applemusic.api.AppleMusicApi @@ -29,6 +30,8 @@ class SearchSongsPagingSource( ?.map { it.toSong() } ?: emptyList() + Log.d("SearchSongsPagingSource", "songs: $songs") + val nextKey = if (response.body()?.results?.songs?.next == null) null else pageIndex + 1 LoadResult.Page( data = songs, diff --git a/data/user/build.gradle.kts b/data/user/build.gradle.kts index 42e5cbd3..b95bccc8 100644 --- a/data/user/build.gradle.kts +++ b/data/user/build.gradle.kts @@ -6,7 +6,7 @@ plugins { } android { - namespace = "com.example.user" + namespace = "com.squirtles.user" compileSdk = 34 defaultConfig { @@ -46,5 +46,5 @@ dependencies { implementation(libs.inject) // firebase - implementation(libs.google.firebase.firestore.ktx) + implementation(libs.firebase.firestore.ktx) } diff --git a/data/user/src/main/java/com/example/user/FirebaseUserDataSourceImpl.kt b/data/user/src/main/java/com/squirtles/user/FirebaseUserDataSourceImpl.kt similarity index 95% rename from data/user/src/main/java/com/example/user/FirebaseUserDataSourceImpl.kt rename to data/user/src/main/java/com/squirtles/user/FirebaseUserDataSourceImpl.kt index 8f73f9c7..503e0e35 100644 --- a/data/user/src/main/java/com/example/user/FirebaseUserDataSourceImpl.kt +++ b/data/user/src/main/java/com/squirtles/user/FirebaseUserDataSourceImpl.kt @@ -1,9 +1,9 @@ -package com.example.user +package com.squirtles.user import android.util.Log -import com.example.firebase.model.FirebaseUser -import com.example.firebase.model.toUser -import com.example.model.User +import com.squirtles.firebase.model.FirebaseUser +import com.squirtles.firebase.model.toUser +import com.squirtles.model.User import com.google.firebase.firestore.FirebaseFirestore import com.google.firebase.firestore.toObject import kotlinx.coroutines.suspendCancellableCoroutine diff --git a/data/user/src/main/java/com/example/user/FirebaseUserRepositoryImpl.kt b/data/user/src/main/java/com/squirtles/user/FirebaseUserRepositoryImpl.kt similarity index 85% rename from data/user/src/main/java/com/example/user/FirebaseUserRepositoryImpl.kt rename to data/user/src/main/java/com/squirtles/user/FirebaseUserRepositoryImpl.kt index 95584647..c2d9deb7 100644 --- a/data/user/src/main/java/com/example/user/FirebaseUserRepositoryImpl.kt +++ b/data/user/src/main/java/com/squirtles/user/FirebaseUserRepositoryImpl.kt @@ -1,8 +1,8 @@ -package com.example.user +package com.squirtles.user -import com.example.firebase.FirebaseException -import com.example.firebase.handleResult -import com.example.model.User +import com.squirtles.firebase.FirebaseException +import com.squirtles.firebase.handleResult +import com.squirtles.model.User import javax.inject.Singleton @Singleton diff --git a/data/user/src/main/java/com/example/user/LocalUserDataSourceImpl.kt b/data/user/src/main/java/com/squirtles/user/LocalUserDataSourceImpl.kt similarity index 96% rename from data/user/src/main/java/com/example/user/LocalUserDataSourceImpl.kt rename to data/user/src/main/java/com/squirtles/user/LocalUserDataSourceImpl.kt index 1edc1cfa..a04d9e43 100644 --- a/data/user/src/main/java/com/example/user/LocalUserDataSourceImpl.kt +++ b/data/user/src/main/java/com/squirtles/user/LocalUserDataSourceImpl.kt @@ -1,10 +1,10 @@ -package com.example.user +package com.squirtles.user import android.content.Context import androidx.datastore.preferences.core.edit import androidx.datastore.preferences.core.stringPreferencesKey import androidx.datastore.preferences.preferencesDataStore -import com.example.model.User +import com.squirtles.model.User import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.map import javax.inject.Inject diff --git a/data/user/src/main/java/com/example/user/LocalUserRepositoryImpl.kt b/data/user/src/main/java/com/squirtles/user/LocalUserRepositoryImpl.kt similarity index 93% rename from data/user/src/main/java/com/example/user/LocalUserRepositoryImpl.kt rename to data/user/src/main/java/com/squirtles/user/LocalUserRepositoryImpl.kt index 84e7c625..62aa586a 100644 --- a/data/user/src/main/java/com/example/user/LocalUserRepositoryImpl.kt +++ b/data/user/src/main/java/com/squirtles/user/LocalUserRepositoryImpl.kt @@ -1,6 +1,6 @@ -package com.example.user +package com.squirtles.user -import com.example.model.User +import com.squirtles.model.User import kotlinx.coroutines.flow.Flow import javax.inject.Inject import javax.inject.Singleton diff --git a/data/user/src/main/java/com/example/user/di/UserDi.kt b/data/user/src/main/java/com/squirtles/user/di/UserDi.kt similarity index 71% rename from data/user/src/main/java/com/example/user/di/UserDi.kt rename to data/user/src/main/java/com/squirtles/user/di/UserDi.kt index c209c3d1..f181ca43 100644 --- a/data/user/src/main/java/com/example/user/di/UserDi.kt +++ b/data/user/src/main/java/com/squirtles/user/di/UserDi.kt @@ -1,14 +1,14 @@ -package com.example.user.di +package com.squirtles.user.di import android.content.Context -import com.example.user.FirebaseUserDataSource -import com.example.user.FirebaseUserDataSourceImpl -import com.example.user.FirebaseUserRepository -import com.example.user.FirebaseUserRepositoryImpl -import com.example.user.LocalUserDataSource -import com.example.user.LocalUserDataSourceImpl -import com.example.user.LocalUserRepository -import com.example.user.LocalUserRepositoryImpl +import com.squirtles.user.FirebaseUserDataSource +import com.squirtles.user.FirebaseUserDataSourceImpl +import com.squirtles.user.FirebaseUserRepository +import com.squirtles.user.FirebaseUserRepositoryImpl +import com.squirtles.user.LocalUserDataSource +import com.squirtles.user.LocalUserDataSourceImpl +import com.squirtles.user.LocalUserRepository +import com.squirtles.user.LocalUserRepositoryImpl import com.google.firebase.firestore.FirebaseFirestore import dagger.Module import dagger.Provides diff --git a/domain/applemusic/build.gradle.kts b/domain/applemusic/build.gradle.kts index 3b013582..364fd0b8 100644 --- a/domain/applemusic/build.gradle.kts +++ b/domain/applemusic/build.gradle.kts @@ -4,7 +4,7 @@ plugins { } android { - namespace = "com.example.applemusic" + namespace = "com.squirtles.applemusic" compileSdk = 34 defaultConfig { diff --git a/domain/applemusic/src/main/java/com/example/applemusic/AppleMusicException.kt b/domain/applemusic/src/main/java/com/squirtles/applemusic/AppleMusicException.kt similarity index 92% rename from domain/applemusic/src/main/java/com/example/applemusic/AppleMusicException.kt rename to domain/applemusic/src/main/java/com/squirtles/applemusic/AppleMusicException.kt index ed060391..81c066a1 100644 --- a/domain/applemusic/src/main/java/com/example/applemusic/AppleMusicException.kt +++ b/domain/applemusic/src/main/java/com/squirtles/applemusic/AppleMusicException.kt @@ -1,4 +1,4 @@ -package com.example.applemusic +package com.squirtles.applemusic /** * 400 에러가 여러 종류가 있는데 이를 구분할 용도로 만든 예외 클래스 diff --git a/domain/applemusic/src/main/java/com/example/applemusic/AppleMusicRemoteDataSource.kt b/domain/applemusic/src/main/java/com/squirtles/applemusic/AppleMusicRemoteDataSource.kt similarity index 70% rename from domain/applemusic/src/main/java/com/example/applemusic/AppleMusicRemoteDataSource.kt rename to domain/applemusic/src/main/java/com/squirtles/applemusic/AppleMusicRemoteDataSource.kt index fb22083b..b585dcae 100644 --- a/domain/applemusic/src/main/java/com/example/applemusic/AppleMusicRemoteDataSource.kt +++ b/domain/applemusic/src/main/java/com/squirtles/applemusic/AppleMusicRemoteDataSource.kt @@ -1,8 +1,8 @@ -package com.example.applemusic +package com.squirtles.applemusic import androidx.paging.PagingData -import com.example.model.MusicVideo -import com.example.model.Song +import com.squirtles.model.MusicVideo +import com.squirtles.model.Song import kotlinx.coroutines.flow.Flow interface AppleMusicRemoteDataSource { diff --git a/domain/applemusic/src/main/java/com/example/applemusic/AppleMusicRepository.kt b/domain/applemusic/src/main/java/com/squirtles/applemusic/AppleMusicRepository.kt similarity index 75% rename from domain/applemusic/src/main/java/com/example/applemusic/AppleMusicRepository.kt rename to domain/applemusic/src/main/java/com/squirtles/applemusic/AppleMusicRepository.kt index ba66663e..aba7253c 100644 --- a/domain/applemusic/src/main/java/com/example/applemusic/AppleMusicRepository.kt +++ b/domain/applemusic/src/main/java/com/squirtles/applemusic/AppleMusicRepository.kt @@ -1,8 +1,8 @@ -package com.example.applemusic +package com.squirtles.applemusic import androidx.paging.PagingData -import com.example.model.MusicVideo -import com.example.model.Song +import com.squirtles.model.MusicVideo +import com.squirtles.model.Song import kotlinx.coroutines.flow.Flow interface AppleMusicRepository { diff --git a/domain/applemusic/src/main/java/com/example/applemusic/usecase/FetchMusicVideoUseCase.kt b/domain/applemusic/src/main/java/com/squirtles/applemusic/usecase/FetchMusicVideoUseCase.kt similarity index 75% rename from domain/applemusic/src/main/java/com/example/applemusic/usecase/FetchMusicVideoUseCase.kt rename to domain/applemusic/src/main/java/com/squirtles/applemusic/usecase/FetchMusicVideoUseCase.kt index c84b4857..3b14deaf 100644 --- a/domain/applemusic/src/main/java/com/example/applemusic/usecase/FetchMusicVideoUseCase.kt +++ b/domain/applemusic/src/main/java/com/squirtles/applemusic/usecase/FetchMusicVideoUseCase.kt @@ -1,8 +1,8 @@ -package com.example.applemusic.usecase +package com.squirtles.applemusic.usecase -import com.example.applemusic.AppleMusicRepository -import com.example.model.MusicVideo -import com.example.model.Song +import com.squirtles.applemusic.AppleMusicRepository +import com.squirtles.model.MusicVideo +import com.squirtles.model.Song import javax.inject.Inject diff --git a/domain/applemusic/src/main/java/com/example/applemusic/usecase/FetchSongsUseCase.kt b/domain/applemusic/src/main/java/com/squirtles/applemusic/usecase/FetchSongsUseCase.kt similarity index 70% rename from domain/applemusic/src/main/java/com/example/applemusic/usecase/FetchSongsUseCase.kt rename to domain/applemusic/src/main/java/com/squirtles/applemusic/usecase/FetchSongsUseCase.kt index 8d95072d..cbf29445 100644 --- a/domain/applemusic/src/main/java/com/example/applemusic/usecase/FetchSongsUseCase.kt +++ b/domain/applemusic/src/main/java/com/squirtles/applemusic/usecase/FetchSongsUseCase.kt @@ -1,6 +1,6 @@ -package com.example.applemusic.usecase +package com.squirtles.applemusic.usecase -import com.example.applemusic.AppleMusicRepository +import com.squirtles.applemusic.AppleMusicRepository import javax.inject.Inject class FetchSongsUseCase @Inject constructor( diff --git a/domain/favorite/src/main/java/com/example/favorite/FirebaseFavoriteDataSource.kt b/domain/favorite/src/main/java/com/squirtles/favorite/FirebaseFavoriteDataSource.kt similarity index 81% rename from domain/favorite/src/main/java/com/example/favorite/FirebaseFavoriteDataSource.kt rename to domain/favorite/src/main/java/com/squirtles/favorite/FirebaseFavoriteDataSource.kt index ec113547..d8cf0734 100644 --- a/domain/favorite/src/main/java/com/example/favorite/FirebaseFavoriteDataSource.kt +++ b/domain/favorite/src/main/java/com/squirtles/favorite/FirebaseFavoriteDataSource.kt @@ -1,6 +1,4 @@ -package com.example.favorite - -import com.example.model.Pick +package com.squirtles.favorite interface FirebaseFavoriteDataSource { suspend fun fetchIsFavorite(pickId: String, userId: String): Boolean diff --git a/domain/favorite/src/main/java/com/example/favorite/FirebaseFavoriteRepository.kt b/domain/favorite/src/main/java/com/squirtles/favorite/FirebaseFavoriteRepository.kt similarity index 90% rename from domain/favorite/src/main/java/com/example/favorite/FirebaseFavoriteRepository.kt rename to domain/favorite/src/main/java/com/squirtles/favorite/FirebaseFavoriteRepository.kt index 27d46a8e..5618a797 100644 --- a/domain/favorite/src/main/java/com/example/favorite/FirebaseFavoriteRepository.kt +++ b/domain/favorite/src/main/java/com/squirtles/favorite/FirebaseFavoriteRepository.kt @@ -1,4 +1,4 @@ -package com.example.favorite +package com.squirtles.favorite interface FirebaseFavoriteRepository { suspend fun fetchIsFavorite(pickId: String, userId: String): Result diff --git a/domain/favorite/src/main/java/com/example/favorite/usecase/CreateFavoriteUseCase.kt b/domain/favorite/src/main/java/com/squirtles/favorite/usecase/CreateFavoriteUseCase.kt similarity index 73% rename from domain/favorite/src/main/java/com/example/favorite/usecase/CreateFavoriteUseCase.kt rename to domain/favorite/src/main/java/com/squirtles/favorite/usecase/CreateFavoriteUseCase.kt index 0a9b881e..d6283958 100644 --- a/domain/favorite/src/main/java/com/example/favorite/usecase/CreateFavoriteUseCase.kt +++ b/domain/favorite/src/main/java/com/squirtles/favorite/usecase/CreateFavoriteUseCase.kt @@ -1,6 +1,6 @@ -package com.example.favorite.usecase +package com.squirtles.favorite.usecase -import com.example.favorite.FirebaseFavoriteRepository +import com.squirtles.favorite.FirebaseFavoriteRepository import javax.inject.Inject class CreateFavoriteUseCase @Inject constructor( diff --git a/domain/favorite/src/main/java/com/example/favorite/usecase/DeleteFavoriteUseCase.kt b/domain/favorite/src/main/java/com/squirtles/favorite/usecase/DeleteFavoriteUseCase.kt similarity index 66% rename from domain/favorite/src/main/java/com/example/favorite/usecase/DeleteFavoriteUseCase.kt rename to domain/favorite/src/main/java/com/squirtles/favorite/usecase/DeleteFavoriteUseCase.kt index 2dce1262..2edfd3fc 100644 --- a/domain/favorite/src/main/java/com/example/favorite/usecase/DeleteFavoriteUseCase.kt +++ b/domain/favorite/src/main/java/com/squirtles/favorite/usecase/DeleteFavoriteUseCase.kt @@ -1,7 +1,7 @@ -package com.example.favorite.usecase +package com.squirtles.favorite.usecase -import com.example.favorite.FirebaseFavoriteRepository -import com.example.picklist.RemovePickUseCaseInterface +import com.squirtles.favorite.FirebaseFavoriteRepository +import com.squirtles.picklist.RemovePickUseCaseInterface import javax.inject.Inject class DeleteFavoriteUseCase @Inject constructor( diff --git a/domain/favorite/src/main/java/com/example/favorite/usecase/FetchIsFavoriteUseCase.kt b/domain/favorite/src/main/java/com/squirtles/favorite/usecase/FetchIsFavoriteUseCase.kt similarity index 73% rename from domain/favorite/src/main/java/com/example/favorite/usecase/FetchIsFavoriteUseCase.kt rename to domain/favorite/src/main/java/com/squirtles/favorite/usecase/FetchIsFavoriteUseCase.kt index 3f300231..18044962 100644 --- a/domain/favorite/src/main/java/com/example/favorite/usecase/FetchIsFavoriteUseCase.kt +++ b/domain/favorite/src/main/java/com/squirtles/favorite/usecase/FetchIsFavoriteUseCase.kt @@ -1,6 +1,6 @@ -package com.example.favorite.usecase +package com.squirtles.favorite.usecase -import com.example.favorite.FirebaseFavoriteRepository +import com.squirtles.favorite.FirebaseFavoriteRepository import javax.inject.Inject class FetchIsFavoriteUseCase @Inject constructor( diff --git a/domain/location/build.gradle.kts b/domain/location/build.gradle.kts index b7417737..564bb1cc 100644 --- a/domain/location/build.gradle.kts +++ b/domain/location/build.gradle.kts @@ -4,7 +4,7 @@ plugins { } android { - namespace = "com.example.location" + namespace = "com.squirtles.location" compileSdk = 34 defaultConfig { diff --git a/domain/location/src/main/java/com/example/location/LocalLocationRepository.kt b/domain/location/src/main/java/com/squirtles/location/LocalLocationRepository.kt similarity index 87% rename from domain/location/src/main/java/com/example/location/LocalLocationRepository.kt rename to domain/location/src/main/java/com/squirtles/location/LocalLocationRepository.kt index d0112c9c..a0fd1ab1 100644 --- a/domain/location/src/main/java/com/example/location/LocalLocationRepository.kt +++ b/domain/location/src/main/java/com/squirtles/location/LocalLocationRepository.kt @@ -1,4 +1,4 @@ -package com.example.location +package com.squirtles.location import android.location.Location import kotlinx.coroutines.flow.StateFlow diff --git a/domain/location/src/main/java/com/example/location/usecase/GetLastLocationUseCase.kt b/domain/location/src/main/java/com/squirtles/location/usecase/GetLastLocationUseCase.kt similarity index 69% rename from domain/location/src/main/java/com/example/location/usecase/GetLastLocationUseCase.kt rename to domain/location/src/main/java/com/squirtles/location/usecase/GetLastLocationUseCase.kt index d5e8d21e..22ec39b5 100644 --- a/domain/location/src/main/java/com/example/location/usecase/GetLastLocationUseCase.kt +++ b/domain/location/src/main/java/com/squirtles/location/usecase/GetLastLocationUseCase.kt @@ -1,6 +1,6 @@ -package com.example.location.usecase +package com.squirtles.location.usecase -import com.example.location.LocalLocationRepository +import com.squirtles.location.LocalLocationRepository import javax.inject.Inject class GetLastLocationUseCase @Inject constructor( diff --git a/domain/location/src/main/java/com/example/location/usecase/SaveLastLocationUseCase.kt b/domain/location/src/main/java/com/squirtles/location/usecase/SaveLastLocationUseCase.kt similarity index 75% rename from domain/location/src/main/java/com/example/location/usecase/SaveLastLocationUseCase.kt rename to domain/location/src/main/java/com/squirtles/location/usecase/SaveLastLocationUseCase.kt index ac376f51..e250f0ba 100644 --- a/domain/location/src/main/java/com/example/location/usecase/SaveLastLocationUseCase.kt +++ b/domain/location/src/main/java/com/squirtles/location/usecase/SaveLastLocationUseCase.kt @@ -1,7 +1,7 @@ -package com.example.location.usecase +package com.squirtles.location.usecase import android.location.Location -import com.example.location.LocalLocationRepository +import com.squirtles.location.LocalLocationRepository import javax.inject.Inject class SaveLastLocationUseCase @Inject constructor( diff --git a/domain/order/src/main/java/com/example/order/LocalPickListOrderRepository.kt b/domain/order/src/main/java/com/squirtles/order/LocalPickListOrderRepository.kt similarity index 81% rename from domain/order/src/main/java/com/example/order/LocalPickListOrderRepository.kt rename to domain/order/src/main/java/com/squirtles/order/LocalPickListOrderRepository.kt index e75053f7..6c0234c8 100644 --- a/domain/order/src/main/java/com/example/order/LocalPickListOrderRepository.kt +++ b/domain/order/src/main/java/com/squirtles/order/LocalPickListOrderRepository.kt @@ -1,6 +1,6 @@ -package com.example.order +package com.squirtles.order -import com.example.model.Order +import com.squirtles.model.Order interface LocalPickListOrderRepository { val favoriteListOrder: Order // 픽 보관함 정렬 순서 diff --git a/domain/order/src/main/java/com/example/order/usecase/GetFavoriteListOrderUseCase.kt b/domain/order/src/main/java/com/squirtles/order/usecase/GetFavoriteListOrderUseCase.kt similarity index 65% rename from domain/order/src/main/java/com/example/order/usecase/GetFavoriteListOrderUseCase.kt rename to domain/order/src/main/java/com/squirtles/order/usecase/GetFavoriteListOrderUseCase.kt index 337605bf..6a660340 100644 --- a/domain/order/src/main/java/com/example/order/usecase/GetFavoriteListOrderUseCase.kt +++ b/domain/order/src/main/java/com/squirtles/order/usecase/GetFavoriteListOrderUseCase.kt @@ -1,7 +1,7 @@ -package com.example.order.usecase +package com.squirtles.order.usecase -import com.example.order.LocalPickListOrderRepository -import com.example.picklist.GetPickListOrderUseCaseInterface +import com.squirtles.order.LocalPickListOrderRepository +import com.squirtles.picklist.GetPickListOrderUseCaseInterface import javax.inject.Inject class GetFavoriteListOrderUseCase @Inject constructor( diff --git a/domain/order/src/main/java/com/example/order/usecase/GetMyPickListOrderUseCase.kt b/domain/order/src/main/java/com/squirtles/order/usecase/GetMyPickListOrderUseCase.kt similarity index 64% rename from domain/order/src/main/java/com/example/order/usecase/GetMyPickListOrderUseCase.kt rename to domain/order/src/main/java/com/squirtles/order/usecase/GetMyPickListOrderUseCase.kt index aab74652..06894205 100644 --- a/domain/order/src/main/java/com/example/order/usecase/GetMyPickListOrderUseCase.kt +++ b/domain/order/src/main/java/com/squirtles/order/usecase/GetMyPickListOrderUseCase.kt @@ -1,7 +1,7 @@ -package com.example.order.usecase +package com.squirtles.order.usecase -import com.example.order.LocalPickListOrderRepository -import com.example.picklist.GetPickListOrderUseCaseInterface +import com.squirtles.order.LocalPickListOrderRepository +import com.squirtles.picklist.GetPickListOrderUseCaseInterface import javax.inject.Inject class GetMyPickListOrderUseCase @Inject constructor( diff --git a/domain/order/src/main/java/com/example/order/usecase/SaveFavoriteListOrderUseCase.kt b/domain/order/src/main/java/com/squirtles/order/usecase/SaveFavoriteListOrderUseCase.kt similarity index 62% rename from domain/order/src/main/java/com/example/order/usecase/SaveFavoriteListOrderUseCase.kt rename to domain/order/src/main/java/com/squirtles/order/usecase/SaveFavoriteListOrderUseCase.kt index cfca34ef..7b315746 100644 --- a/domain/order/src/main/java/com/example/order/usecase/SaveFavoriteListOrderUseCase.kt +++ b/domain/order/src/main/java/com/squirtles/order/usecase/SaveFavoriteListOrderUseCase.kt @@ -1,8 +1,8 @@ -package com.example.order.usecase +package com.squirtles.order.usecase -import com.example.model.Order -import com.example.order.LocalPickListOrderRepository -import com.example.picklist.SavePickListOrderUseCaseInterface +import com.squirtles.model.Order +import com.squirtles.order.LocalPickListOrderRepository +import com.squirtles.picklist.SavePickListOrderUseCaseInterface import javax.inject.Inject class SaveFavoriteListOrderUseCase @Inject constructor( diff --git a/domain/order/src/main/java/com/example/order/usecase/SaveMyPickListOrderUseCase.kt b/domain/order/src/main/java/com/squirtles/order/usecase/SaveMyPickListOrderUseCase.kt similarity index 62% rename from domain/order/src/main/java/com/example/order/usecase/SaveMyPickListOrderUseCase.kt rename to domain/order/src/main/java/com/squirtles/order/usecase/SaveMyPickListOrderUseCase.kt index e2d73319..323a134b 100644 --- a/domain/order/src/main/java/com/example/order/usecase/SaveMyPickListOrderUseCase.kt +++ b/domain/order/src/main/java/com/squirtles/order/usecase/SaveMyPickListOrderUseCase.kt @@ -1,8 +1,8 @@ -package com.example.order.usecase +package com.squirtles.order.usecase -import com.example.model.Order -import com.example.order.LocalPickListOrderRepository -import com.example.picklist.SavePickListOrderUseCaseInterface +import com.squirtles.model.Order +import com.squirtles.order.LocalPickListOrderRepository +import com.squirtles.picklist.SavePickListOrderUseCaseInterface import javax.inject.Inject class SaveMyPickListOrderUseCase @Inject constructor( diff --git a/domain/pick/src/main/java/com/example/pick/FirebasePickDataSource.kt b/domain/pick/src/main/java/com/squirtles/pick/FirebasePickDataSource.kt similarity index 87% rename from domain/pick/src/main/java/com/example/pick/FirebasePickDataSource.kt rename to domain/pick/src/main/java/com/squirtles/pick/FirebasePickDataSource.kt index be55c151..42c4dff0 100644 --- a/domain/pick/src/main/java/com/example/pick/FirebasePickDataSource.kt +++ b/domain/pick/src/main/java/com/squirtles/pick/FirebasePickDataSource.kt @@ -1,6 +1,6 @@ -package com.example.pick +package com.squirtles.pick -import com.example.model.Pick +import com.squirtles.model.Pick interface FirebasePickDataSource { suspend fun createPick(pick: Pick): String diff --git a/domain/pick/src/main/java/com/example/pick/FirebasePickRepository.kt b/domain/pick/src/main/java/com/squirtles/pick/FirebasePickRepository.kt similarity index 88% rename from domain/pick/src/main/java/com/example/pick/FirebasePickRepository.kt rename to domain/pick/src/main/java/com/squirtles/pick/FirebasePickRepository.kt index 589a6019..29edd4ee 100644 --- a/domain/pick/src/main/java/com/example/pick/FirebasePickRepository.kt +++ b/domain/pick/src/main/java/com/squirtles/pick/FirebasePickRepository.kt @@ -1,6 +1,6 @@ -package com.example.pick +package com.squirtles.pick -import com.example.model.Pick +import com.squirtles.model.Pick interface FirebasePickRepository { suspend fun createPick(pick: Pick): Result diff --git a/domain/pick/src/main/java/com/example/pick/usecase/CreatePickUseCase.kt b/domain/pick/src/main/java/com/squirtles/pick/usecase/CreatePickUseCase.kt similarity index 66% rename from domain/pick/src/main/java/com/example/pick/usecase/CreatePickUseCase.kt rename to domain/pick/src/main/java/com/squirtles/pick/usecase/CreatePickUseCase.kt index 8c2a4b42..7fef8ae5 100644 --- a/domain/pick/src/main/java/com/example/pick/usecase/CreatePickUseCase.kt +++ b/domain/pick/src/main/java/com/squirtles/pick/usecase/CreatePickUseCase.kt @@ -1,7 +1,7 @@ -package com.example.pick.usecase +package com.squirtles.pick.usecase -import com.example.model.Pick -import com.example.pick.FirebasePickRepository +import com.squirtles.model.Pick +import com.squirtles.pick.FirebasePickRepository import javax.inject.Inject class CreatePickUseCase @Inject constructor( diff --git a/domain/pick/src/main/java/com/example/pick/usecase/DeletePickUseCase.kt b/domain/pick/src/main/java/com/squirtles/pick/usecase/DeletePickUseCase.kt similarity index 68% rename from domain/pick/src/main/java/com/example/pick/usecase/DeletePickUseCase.kt rename to domain/pick/src/main/java/com/squirtles/pick/usecase/DeletePickUseCase.kt index bde4761a..d1eee8a5 100644 --- a/domain/pick/src/main/java/com/example/pick/usecase/DeletePickUseCase.kt +++ b/domain/pick/src/main/java/com/squirtles/pick/usecase/DeletePickUseCase.kt @@ -1,7 +1,7 @@ -package com.example.pick.usecase +package com.squirtles.pick.usecase -import com.example.pick.FirebasePickRepository -import com.example.picklist.RemovePickUseCaseInterface +import com.squirtles.pick.FirebasePickRepository +import com.squirtles.picklist.RemovePickUseCaseInterface import javax.inject.Inject class DeletePickUseCase @Inject constructor( diff --git a/domain/pick/src/main/java/com/example/pick/usecase/FetchMyPicksUseCase.kt b/domain/pick/src/main/java/com/squirtles/pick/usecase/FetchMyPicksUseCase.kt similarity index 65% rename from domain/pick/src/main/java/com/example/pick/usecase/FetchMyPicksUseCase.kt rename to domain/pick/src/main/java/com/squirtles/pick/usecase/FetchMyPicksUseCase.kt index 26e2cf18..49f764bb 100644 --- a/domain/pick/src/main/java/com/example/pick/usecase/FetchMyPicksUseCase.kt +++ b/domain/pick/src/main/java/com/squirtles/pick/usecase/FetchMyPicksUseCase.kt @@ -1,7 +1,7 @@ -package com.example.pick.usecase +package com.squirtles.pick.usecase -import com.example.pick.FirebasePickRepository -import com.example.picklist.FetchPickListUseCaseInterface +import com.squirtles.pick.FirebasePickRepository +import com.squirtles.picklist.FetchPickListUseCaseInterface import javax.inject.Inject class FetchMyPicksUseCase @Inject constructor( diff --git a/domain/pick/src/main/java/com/example/pick/usecase/FetchPickUseCase.kt b/domain/pick/src/main/java/com/squirtles/pick/usecase/FetchPickUseCase.kt similarity index 81% rename from domain/pick/src/main/java/com/example/pick/usecase/FetchPickUseCase.kt rename to domain/pick/src/main/java/com/squirtles/pick/usecase/FetchPickUseCase.kt index 15307d2c..c9f2befa 100644 --- a/domain/pick/src/main/java/com/example/pick/usecase/FetchPickUseCase.kt +++ b/domain/pick/src/main/java/com/squirtles/pick/usecase/FetchPickUseCase.kt @@ -1,6 +1,6 @@ -package com.example.pick.usecase +package com.squirtles.pick.usecase -import com.example.pick.FirebasePickRepository +import com.squirtles.pick.FirebasePickRepository import javax.inject.Inject class FetchPickUseCase @Inject constructor( diff --git a/domain/picklist/src/main/java/com/example/picklist/FetchPickListUseCaseInterface.kt b/domain/picklist/src/main/java/com/squirtles/picklist/FetchPickListUseCaseInterface.kt similarity index 64% rename from domain/picklist/src/main/java/com/example/picklist/FetchPickListUseCaseInterface.kt rename to domain/picklist/src/main/java/com/squirtles/picklist/FetchPickListUseCaseInterface.kt index febb6887..c6d19e14 100644 --- a/domain/picklist/src/main/java/com/example/picklist/FetchPickListUseCaseInterface.kt +++ b/domain/picklist/src/main/java/com/squirtles/picklist/FetchPickListUseCaseInterface.kt @@ -1,6 +1,6 @@ -package com.example.picklist +package com.squirtles.picklist -import com.example.model.Pick +import com.squirtles.model.Pick interface FetchPickListUseCaseInterface { suspend operator fun invoke(userId: String): Result> diff --git a/domain/picklist/src/main/java/com/example/picklist/GetPickListOrderUseCaseInterface.kt b/domain/picklist/src/main/java/com/squirtles/picklist/GetPickListOrderUseCaseInterface.kt similarity index 58% rename from domain/picklist/src/main/java/com/example/picklist/GetPickListOrderUseCaseInterface.kt rename to domain/picklist/src/main/java/com/squirtles/picklist/GetPickListOrderUseCaseInterface.kt index f07dd755..3494f825 100644 --- a/domain/picklist/src/main/java/com/example/picklist/GetPickListOrderUseCaseInterface.kt +++ b/domain/picklist/src/main/java/com/squirtles/picklist/GetPickListOrderUseCaseInterface.kt @@ -1,6 +1,6 @@ -package com.example.picklist +package com.squirtles.picklist -import com.example.model.Order +import com.squirtles.model.Order interface GetPickListOrderUseCaseInterface { suspend operator fun invoke(): Order diff --git a/domain/picklist/src/main/java/com/example/picklist/RemovePickUseCaseInterface.kt b/domain/picklist/src/main/java/com/squirtles/picklist/RemovePickUseCaseInterface.kt similarity index 79% rename from domain/picklist/src/main/java/com/example/picklist/RemovePickUseCaseInterface.kt rename to domain/picklist/src/main/java/com/squirtles/picklist/RemovePickUseCaseInterface.kt index b67e8fa7..2475ff83 100644 --- a/domain/picklist/src/main/java/com/example/picklist/RemovePickUseCaseInterface.kt +++ b/domain/picklist/src/main/java/com/squirtles/picklist/RemovePickUseCaseInterface.kt @@ -1,4 +1,4 @@ -package com.example.picklist +package com.squirtles.picklist interface RemovePickUseCaseInterface { suspend operator fun invoke(pickId: String, userId: String): Result diff --git a/domain/picklist/src/main/java/com/example/picklist/SavePickListOrderUseCaseInterface.kt b/domain/picklist/src/main/java/com/squirtles/picklist/SavePickListOrderUseCaseInterface.kt similarity index 60% rename from domain/picklist/src/main/java/com/example/picklist/SavePickListOrderUseCaseInterface.kt rename to domain/picklist/src/main/java/com/squirtles/picklist/SavePickListOrderUseCaseInterface.kt index 08dd5c0b..8a4ab6d7 100644 --- a/domain/picklist/src/main/java/com/example/picklist/SavePickListOrderUseCaseInterface.kt +++ b/domain/picklist/src/main/java/com/squirtles/picklist/SavePickListOrderUseCaseInterface.kt @@ -1,6 +1,6 @@ -package com.example.picklist +package com.squirtles.picklist -import com.example.model.Order +import com.squirtles.model.Order interface SavePickListOrderUseCaseInterface { suspend operator fun invoke(order: Order) diff --git a/domain/player/build.gradle.kts b/domain/player/build.gradle.kts index 2b7a42ec..dcda8056 100644 --- a/domain/player/build.gradle.kts +++ b/domain/player/build.gradle.kts @@ -4,7 +4,7 @@ plugins { } android { - namespace = "com.example.player" + namespace = "com.squirtles.player" compileSdk = 34 defaultConfig { diff --git a/domain/player/src/main/java/com/example/player/MediaPlayerListenerUseCase.kt b/domain/player/src/main/java/com/squirtles/player/MediaPlayerListenerUseCase.kt similarity index 97% rename from domain/player/src/main/java/com/example/player/MediaPlayerListenerUseCase.kt rename to domain/player/src/main/java/com/squirtles/player/MediaPlayerListenerUseCase.kt index 7630d006..e02dcd40 100644 --- a/domain/player/src/main/java/com/example/player/MediaPlayerListenerUseCase.kt +++ b/domain/player/src/main/java/com/squirtles/player/MediaPlayerListenerUseCase.kt @@ -1,9 +1,9 @@ -package com.example.player +package com.squirtles.player import androidx.media3.common.Player import androidx.media3.common.Tracks -import com.example.mediaservice.MediaControllerProvider -import com.example.model.PlayerState +import com.squirtles.mediaservice.MediaControllerProvider +import com.squirtles.model.PlayerState import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Job diff --git a/domain/player/src/main/java/com/example/player/MediaPlayerUseCase.kt b/domain/player/src/main/java/com/squirtles/player/MediaPlayerUseCase.kt similarity index 94% rename from domain/player/src/main/java/com/example/player/MediaPlayerUseCase.kt rename to domain/player/src/main/java/com/squirtles/player/MediaPlayerUseCase.kt index f5a0790d..8ff3d68e 100644 --- a/domain/player/src/main/java/com/example/player/MediaPlayerUseCase.kt +++ b/domain/player/src/main/java/com/squirtles/player/MediaPlayerUseCase.kt @@ -1,12 +1,12 @@ -package com.example.player +package com.squirtles.player import androidx.media3.common.MediaItem import androidx.media3.common.MediaMetadata import androidx.media3.common.Player import androidx.media3.session.MediaController -import com.example.mediaservice.MediaControllerProvider -import com.example.mediaservice.SEEK_TO_DURATION -import com.example.model.Pick +import com.squirtles.mediaservice.MediaControllerProvider +import com.squirtles.mediaservice.SEEK_TO_DURATION +import com.squirtles.model.Pick import kotlinx.coroutines.flow.first import kotlinx.coroutines.flow.flow import javax.inject.Inject diff --git a/domain/src/main/java/com/squirtles/domain/applemusic/usecase/FetchSongsUseCase.kt b/domain/src/main/java/com/squirtles/domain/applemusic/usecase/FetchSongsUseCase.kt index 3f7e87fa..e24548ef 100644 --- a/domain/src/main/java/com/squirtles/domain/applemusic/usecase/FetchSongsUseCase.kt +++ b/domain/src/main/java/com/squirtles/domain/applemusic/usecase/FetchSongsUseCase.kt @@ -6,5 +6,6 @@ import javax.inject.Inject class FetchSongsUseCase @Inject constructor( private val appleMusicRepository: AppleMusicRepository ) { - operator fun invoke(searchText: String) = appleMusicRepository.searchSongs(searchText) + operator fun invoke(searchText: String) + = appleMusicRepository.searchSongs(searchText) } diff --git a/domain/user/src/main/java/com/example/user/FirebaseUserDataSource.kt b/domain/user/src/main/java/com/squirtles/user/FirebaseUserDataSource.kt similarity index 82% rename from domain/user/src/main/java/com/example/user/FirebaseUserDataSource.kt rename to domain/user/src/main/java/com/squirtles/user/FirebaseUserDataSource.kt index e4639a55..82a6bb26 100644 --- a/domain/user/src/main/java/com/example/user/FirebaseUserDataSource.kt +++ b/domain/user/src/main/java/com/squirtles/user/FirebaseUserDataSource.kt @@ -1,6 +1,6 @@ -package com.example.user +package com.squirtles.user -import com.example.model.User +import com.squirtles.model.User interface FirebaseUserDataSource { suspend fun fetchUser(userId: String): User? diff --git a/domain/user/src/main/java/com/example/user/FirebaseUserRepository.kt b/domain/user/src/main/java/com/squirtles/user/FirebaseUserRepository.kt similarity index 83% rename from domain/user/src/main/java/com/example/user/FirebaseUserRepository.kt rename to domain/user/src/main/java/com/squirtles/user/FirebaseUserRepository.kt index 110de5af..c2f74658 100644 --- a/domain/user/src/main/java/com/example/user/FirebaseUserRepository.kt +++ b/domain/user/src/main/java/com/squirtles/user/FirebaseUserRepository.kt @@ -1,6 +1,6 @@ -package com.example.user +package com.squirtles.user -import com.example.model.User +import com.squirtles.model.User interface FirebaseUserRepository { // user diff --git a/domain/user/src/main/java/com/example/user/LocalUserDataSource.kt b/domain/user/src/main/java/com/squirtles/user/LocalUserDataSource.kt similarity index 82% rename from domain/user/src/main/java/com/example/user/LocalUserDataSource.kt rename to domain/user/src/main/java/com/squirtles/user/LocalUserDataSource.kt index 34433601..322cce1a 100644 --- a/domain/user/src/main/java/com/example/user/LocalUserDataSource.kt +++ b/domain/user/src/main/java/com/squirtles/user/LocalUserDataSource.kt @@ -1,6 +1,6 @@ -package com.example.user +package com.squirtles.user -import com.example.model.User +import com.squirtles.model.User import kotlinx.coroutines.flow.Flow interface LocalUserDataSource { diff --git a/domain/user/src/main/java/com/example/user/LocalUserRepository.kt b/domain/user/src/main/java/com/squirtles/user/LocalUserRepository.kt similarity index 82% rename from domain/user/src/main/java/com/example/user/LocalUserRepository.kt rename to domain/user/src/main/java/com/squirtles/user/LocalUserRepository.kt index 80ee5e86..0a8e5029 100644 --- a/domain/user/src/main/java/com/example/user/LocalUserRepository.kt +++ b/domain/user/src/main/java/com/squirtles/user/LocalUserRepository.kt @@ -1,6 +1,6 @@ -package com.example.user +package com.squirtles.user -import com.example.model.User +import com.squirtles.model.User import kotlinx.coroutines.flow.Flow interface LocalUserRepository { diff --git a/domain/user/src/main/java/com/example/user/usecase/ClearUserUseCase.kt b/domain/user/src/main/java/com/squirtles/user/usecase/ClearUserUseCase.kt similarity index 71% rename from domain/user/src/main/java/com/example/user/usecase/ClearUserUseCase.kt rename to domain/user/src/main/java/com/squirtles/user/usecase/ClearUserUseCase.kt index d19ec554..36672586 100644 --- a/domain/user/src/main/java/com/example/user/usecase/ClearUserUseCase.kt +++ b/domain/user/src/main/java/com/squirtles/user/usecase/ClearUserUseCase.kt @@ -1,6 +1,6 @@ -package com.example.user.usecase +package com.squirtles.user.usecase -import com.example.user.LocalUserRepository +import com.squirtles.user.LocalUserRepository import javax.inject.Inject class ClearUserUseCase @Inject constructor( diff --git a/domain/user/src/main/java/com/example/user/usecase/CreateGoogleIdUserUseCase.kt b/domain/user/src/main/java/com/squirtles/user/usecase/CreateGoogleIdUserUseCase.kt similarity index 82% rename from domain/user/src/main/java/com/example/user/usecase/CreateGoogleIdUserUseCase.kt rename to domain/user/src/main/java/com/squirtles/user/usecase/CreateGoogleIdUserUseCase.kt index 7918cc56..b2f7d805 100644 --- a/domain/user/src/main/java/com/example/user/usecase/CreateGoogleIdUserUseCase.kt +++ b/domain/user/src/main/java/com/squirtles/user/usecase/CreateGoogleIdUserUseCase.kt @@ -1,8 +1,8 @@ -package com.example.user.usecase +package com.squirtles.user.usecase -import com.example.model.User -import com.example.user.FirebaseUserRepository -import com.example.user.LocalUserRepository +import com.squirtles.model.User +import com.squirtles.user.FirebaseUserRepository +import com.squirtles.user.LocalUserRepository import javax.inject.Inject class CreateGoogleIdUserUseCase @Inject constructor( diff --git a/domain/user/src/main/java/com/example/user/usecase/FetchUserByIdUseCase.kt b/domain/user/src/main/java/com/squirtles/user/usecase/FetchUserByIdUseCase.kt similarity index 74% rename from domain/user/src/main/java/com/example/user/usecase/FetchUserByIdUseCase.kt rename to domain/user/src/main/java/com/squirtles/user/usecase/FetchUserByIdUseCase.kt index f51b24d7..51513e84 100644 --- a/domain/user/src/main/java/com/example/user/usecase/FetchUserByIdUseCase.kt +++ b/domain/user/src/main/java/com/squirtles/user/usecase/FetchUserByIdUseCase.kt @@ -1,6 +1,6 @@ -package com.example.user.usecase +package com.squirtles.user.usecase -import com.example.user.FirebaseUserRepository +import com.squirtles.user.FirebaseUserRepository import javax.inject.Inject class FetchUserByIdUseCase @Inject constructor( diff --git a/domain/user/src/main/java/com/example/user/usecase/FetchUserUseCase.kt b/domain/user/src/main/java/com/squirtles/user/usecase/FetchUserUseCase.kt similarity index 84% rename from domain/user/src/main/java/com/example/user/usecase/FetchUserUseCase.kt rename to domain/user/src/main/java/com/squirtles/user/usecase/FetchUserUseCase.kt index 4e0793e5..cd5f9ed8 100644 --- a/domain/user/src/main/java/com/example/user/usecase/FetchUserUseCase.kt +++ b/domain/user/src/main/java/com/squirtles/user/usecase/FetchUserUseCase.kt @@ -1,7 +1,7 @@ -package com.example.user.usecase +package com.squirtles.user.usecase -import com.example.model.User -import com.example.user.LocalUserRepository +import com.squirtles.model.User +import com.squirtles.user.LocalUserRepository import javax.inject.Inject class FetchUserUseCase @Inject constructor( diff --git a/domain/user/src/main/java/com/example/user/usecase/GetCurrentUserUseCase.kt b/domain/user/src/main/java/com/squirtles/user/usecase/GetCurrentUserUseCase.kt similarity index 71% rename from domain/user/src/main/java/com/example/user/usecase/GetCurrentUserUseCase.kt rename to domain/user/src/main/java/com/squirtles/user/usecase/GetCurrentUserUseCase.kt index def59a5e..5ee90743 100644 --- a/domain/user/src/main/java/com/example/user/usecase/GetCurrentUserUseCase.kt +++ b/domain/user/src/main/java/com/squirtles/user/usecase/GetCurrentUserUseCase.kt @@ -1,6 +1,6 @@ -package com.example.user.usecase +package com.squirtles.user.usecase -import com.example.user.LocalUserRepository +import com.squirtles.user.LocalUserRepository import javax.inject.Inject class GetCurrentUserUseCase @Inject constructor( diff --git a/domain/user/src/main/java/com/example/user/usecase/GetUserIdFromDataStoreUseCase.kt b/domain/user/src/main/java/com/squirtles/user/usecase/GetUserIdFromDataStoreUseCase.kt similarity index 73% rename from domain/user/src/main/java/com/example/user/usecase/GetUserIdFromDataStoreUseCase.kt rename to domain/user/src/main/java/com/squirtles/user/usecase/GetUserIdFromDataStoreUseCase.kt index 5b8ee41e..3812aa9c 100644 --- a/domain/user/src/main/java/com/example/user/usecase/GetUserIdFromDataStoreUseCase.kt +++ b/domain/user/src/main/java/com/squirtles/user/usecase/GetUserIdFromDataStoreUseCase.kt @@ -1,6 +1,6 @@ -package com.example.user.usecase +package com.squirtles.user.usecase -import com.example.user.LocalUserRepository +import com.squirtles.user.LocalUserRepository import javax.inject.Inject class GetUserIdFromDataStoreUseCase @Inject constructor( diff --git a/domain/user/src/main/java/com/example/user/usecase/UpdateUserNameUseCase.kt b/domain/user/src/main/java/com/squirtles/user/usecase/UpdateUserNameUseCase.kt similarity index 77% rename from domain/user/src/main/java/com/example/user/usecase/UpdateUserNameUseCase.kt rename to domain/user/src/main/java/com/squirtles/user/usecase/UpdateUserNameUseCase.kt index 7d3c9c96..44f554da 100644 --- a/domain/user/src/main/java/com/example/user/usecase/UpdateUserNameUseCase.kt +++ b/domain/user/src/main/java/com/squirtles/user/usecase/UpdateUserNameUseCase.kt @@ -1,6 +1,6 @@ -package com.example.user.usecase +package com.squirtles.user.usecase -import com.example.user.FirebaseUserRepository +import com.squirtles.user.FirebaseUserRepository import javax.inject.Inject class UpdateUserNameUseCase @Inject constructor( diff --git a/feature/create/build.gradle.kts b/feature/create/build.gradle.kts index 901245e4..5fd15b5c 100644 --- a/feature/create/build.gradle.kts +++ b/feature/create/build.gradle.kts @@ -7,7 +7,7 @@ plugins { } android { - namespace = "com.example.create" + namespace = "com.squirtles.create" compileSdk = 34 defaultConfig { @@ -30,6 +30,12 @@ android { kotlinOptions { jvmTarget = "1.8" } + buildFeatures { // Enables Jetpack Compose for this module + compose = true + } + composeOptions { + kotlinCompilerExtensionVersion = "1.5.14" + } } dependencies { @@ -50,18 +56,18 @@ dependencies { androidTestImplementation(libs.androidx.espresso.core) // Compose - implementation(platform(libs.androidx.compose.bom)) - implementation(libs.androidx.compose.material) - implementation(libs.androidx.compose.material3) - implementation(libs.androidx.material.icons.extended) - implementation(libs.androidx.ui.tooling.preview) - implementation(libs.androidx.navigation.compose) + implementation(platform(libs.compose.bom)) + implementation(libs.compose.material) + implementation(libs.compose.material3) + implementation(libs.compose.material.icons.extended) + implementation(libs.compose.ui.tooling.preview) + implementation(libs.navigation.compose) // Hilt implementation(libs.hilt.android) ksp(libs.hilt.android.compiler) implementation(libs.inject) - implementation(libs.androidx.hilt.navigation.compose) + implementation(libs.hilt.navigation.compose) // Serialization implementation(libs.kotlinx.serialization.json) diff --git a/core/picklist/src/androidTest/java/com/example/picklist/ExampleInstrumentedTest.kt b/feature/create/src/androidTest/java/com/squirtles/create/ExampleInstrumentedTest.kt similarity index 86% rename from core/picklist/src/androidTest/java/com/example/picklist/ExampleInstrumentedTest.kt rename to feature/create/src/androidTest/java/com/squirtles/create/ExampleInstrumentedTest.kt index 789902a4..c0e7429a 100644 --- a/core/picklist/src/androidTest/java/com/example/picklist/ExampleInstrumentedTest.kt +++ b/feature/create/src/androidTest/java/com/squirtles/create/ExampleInstrumentedTest.kt @@ -1,4 +1,4 @@ -package com.example.picklist +package com.squirtles.create import androidx.test.platform.app.InstrumentationRegistry import androidx.test.ext.junit.runners.AndroidJUnit4 @@ -19,6 +19,6 @@ class ExampleInstrumentedTest { fun useAppContext() { // Context of the app under test. val appContext = InstrumentationRegistry.getInstrumentation().targetContext - assertEquals("com.example.picklist.test", appContext.packageName) + assertEquals("com.squirtles.create.test", appContext.packageName) } } diff --git a/feature/create/src/main/java/com/example/create/CreatePickScreen.kt b/feature/create/src/main/java/com/example/create/CreatePickScreen.kt deleted file mode 100644 index a48c9685..00000000 --- a/feature/create/src/main/java/com/example/create/CreatePickScreen.kt +++ /dev/null @@ -1,351 +0,0 @@ -package com.example.create - -import android.app.Activity -import android.util.Size -import androidx.activity.compose.BackHandler -import androidx.compose.foundation.background -import androidx.compose.foundation.clickable -import androidx.compose.foundation.interaction.MutableInteractionSource -import androidx.compose.foundation.layout.Arrangement -import androidx.compose.foundation.layout.Box -import androidx.compose.foundation.layout.Column -import androidx.compose.foundation.layout.aspectRatio -import androidx.compose.foundation.layout.fillMaxSize -import androidx.compose.foundation.layout.fillMaxWidth -import androidx.compose.foundation.layout.height -import androidx.compose.foundation.layout.padding -import androidx.compose.foundation.layout.statusBarsPadding -import androidx.compose.foundation.rememberScrollState -import androidx.compose.foundation.shape.RoundedCornerShape -import androidx.compose.foundation.verticalScroll -import androidx.compose.material.icons.Icons -import androidx.compose.material.icons.automirrored.filled.ArrowBack -import androidx.compose.material.icons.filled.Check -import androidx.compose.material3.CenterAlignedTopAppBar -import androidx.compose.material3.CircularProgressIndicator -import androidx.compose.material3.ExperimentalMaterial3Api -import androidx.compose.material3.Icon -import androidx.compose.material3.IconButton -import androidx.compose.material3.MaterialTheme -import androidx.compose.material3.OutlinedTextField -import androidx.compose.material3.OutlinedTextFieldDefaults -import androidx.compose.material3.Scaffold -import androidx.compose.material3.Text -import androidx.compose.material3.TopAppBarDefaults -import androidx.compose.runtime.Composable -import androidx.compose.runtime.LaunchedEffect -import androidx.compose.runtime.SideEffect -import androidx.compose.runtime.getValue -import androidx.compose.runtime.mutableStateOf -import androidx.compose.runtime.remember -import androidx.compose.runtime.saveable.rememberSaveable -import androidx.compose.runtime.setValue -import androidx.compose.ui.Alignment -import androidx.compose.ui.Modifier -import androidx.compose.ui.draw.clip -import androidx.compose.ui.graphics.Brush -import androidx.compose.ui.graphics.Color -import androidx.compose.ui.graphics.luminance -import androidx.compose.ui.platform.LocalView -import androidx.compose.ui.res.stringResource -import androidx.compose.ui.text.font.FontWeight -import androidx.compose.ui.text.style.TextAlign -import androidx.compose.ui.tooling.preview.Preview -import androidx.compose.ui.unit.dp -import androidx.core.graphics.toColorInt -import androidx.core.view.WindowInsetsControllerCompat -import androidx.hilt.navigation.compose.hiltViewModel -import androidx.lifecycle.compose.collectAsStateWithLifecycle -import com.example.common.ui.AlbumImage -import com.example.common.ui.VerticalSpacer -import com.example.common.ui.theme.Black -import com.example.common.ui.theme.Dark -import com.example.common.ui.theme.Gray -import com.example.common.ui.theme.White -import com.example.model.Song - -@Composable -fun CreatePickScreen( - song: Song, - onBackClick: () -> Unit, - onCreateClick: (String) -> Unit, - createPickViewModel: CreatePickViewModel = hiltViewModel(), -) { - - val comment = createPickViewModel.comment.collectAsStateWithLifecycle() - val uiState by createPickViewModel.createPickUiState.collectAsStateWithLifecycle() - var showCreateIndicator by rememberSaveable { mutableStateOf(false) } - - val dynamicBackgroundColor = Color(song.bgColor) - val dynamicOnBackgroundColor = if (dynamicBackgroundColor.luminance() >= 0.5f) Black else White - val view = LocalView.current - - if (!view.isInEditMode) { - SideEffect { - val window = (view.context as Activity).window - val windowInsetsController = WindowInsetsControllerCompat(window, view) - val isLightStatusBar = dynamicBackgroundColor.luminance() >= 0.5f - windowInsetsController.isAppearanceLightStatusBars = isLightStatusBar - } - } - - // 생성 중 인디케이터가 표시되고 있을 때는 시스템의 뒤로 가기 버튼 클릭을 무시 - BackHandler(enabled = showCreateIndicator) { } - - when (uiState) { - CreateUiState.Default -> { - CreatePickDisplay( - song = song, - comment = comment.value, - dynamicBackgroundColor = dynamicBackgroundColor, - dynamicOnBackgroundColor = dynamicOnBackgroundColor, - onBackClick = { - createPickViewModel.resetComment() - onBackClick() - }, - onCreateClick = { - createPickViewModel.onCreatePickClick() - showCreateIndicator = true - }, - onCommentChange = createPickViewModel::onCommentChange - ) - } - - is CreateUiState.Success -> { - LaunchedEffect(Unit) { - showCreateIndicator = false - val pickId = (uiState as CreateUiState.Success).data - onCreateClick(pickId) - } - } - - CreateUiState.Error -> { - // TODO() - showCreateIndicator = false - Text("생성 오류") - } - - else -> {} - } - - if (showCreateIndicator) { - Box( - modifier = Modifier - .fillMaxSize() - .background(Black.copy(alpha = 0.5F)) - .clickable( // 클릭 효과 제거 및 클릭 이벤트 무시 - interactionSource = remember { MutableInteractionSource() }, - indication = null, - onClick = {} - ), - contentAlignment = Alignment.Center - ) { - CircularProgressIndicator() - } - } -} - -@Composable -private fun CreatePickDisplay( - song: Song, - comment: String, - dynamicBackgroundColor: Color, - dynamicOnBackgroundColor: Color, - onCommentChange: (String) -> Unit, - onBackClick: () -> Unit, - onCreateClick: () -> Unit, -) { - Scaffold( - containerColor = dynamicBackgroundColor, - topBar = { - CreatePickScreenTopBar( - dynamicOnBackgroundColor = dynamicOnBackgroundColor, - onBackClick = onBackClick, - onCreateClick = onCreateClick - ) - } - ) { innerPadding -> - Box( - modifier = Modifier - .padding(innerPadding) - .fillMaxSize() - .background( - brush = Brush.verticalGradient( - colorStops = arrayOf( - 0.0f to dynamicBackgroundColor, - 0.47f to Black - ) - ) - ) - ) { - CreatePickContent( - song = song, - comment = comment, - onValueChange = onCommentChange, - dynamicOnBackgroundColor = dynamicOnBackgroundColor, - ) - } - } -} - -@Composable -private fun CreatePickContent( - song: Song, - comment: String, - onValueChange: (String) -> Unit, - dynamicOnBackgroundColor: Color, -) { - val scrollState = rememberScrollState() - - Column( - modifier = Modifier - .fillMaxSize() - .verticalScroll(scrollState) - .padding(vertical = 10.dp), - horizontalAlignment = Alignment.CenterHorizontally, - verticalArrangement = Arrangement.spacedBy(10.dp), - ) { - Text( - text = song.songName, - color = dynamicOnBackgroundColor, - textAlign = TextAlign.Center, - style = MaterialTheme.typography.titleLarge.copy(fontWeight = FontWeight.Bold) - ) - - Text( - text = song.artistName, - color = dynamicOnBackgroundColor, - textAlign = TextAlign.Center, - style = MaterialTheme.typography.bodyLarge - ) - - AlbumImage( - imageUrl = song.getImageUrlWithSize(RequestImageSize.width, RequestImageSize.height), - modifier = Modifier - .fillMaxWidth() - .padding(horizontal = 30.dp) - .aspectRatio(1f) - .clip(RoundedCornerShape(20.dp)), - contentDescription = song.albumName + stringResource(id = R.string.pick_album_description) - ) - - VerticalSpacer(40) - - CommentTextBox( - comment = comment, - onValueChange = onValueChange, - ) - } -} - -@Composable -private fun CommentTextBox( - comment: String, - onValueChange: (String) -> Unit, -) { - OutlinedTextField( - value = comment, - onValueChange = { textValue -> - onValueChange(textValue) - }, - modifier = Modifier - .fillMaxWidth() - .height(100.dp) - .padding(horizontal = 30.dp), - textStyle = MaterialTheme.typography.bodyLarge.copy(White), - placeholder = { - Text( - text = stringResource(id = R.string.pick_comment_placeholder), - style = MaterialTheme.typography.bodyLarge.copy(Gray) - ) - }, - shape = RoundedCornerShape(10.dp), - colors = OutlinedTextFieldDefaults.colors( - unfocusedContainerColor = Dark, - focusedContainerColor = Dark, - focusedBorderColor = Color.Transparent, - unfocusedBorderColor = Color.Transparent - ) - ) -} - -@OptIn(ExperimentalMaterial3Api::class) -@Composable -private fun CreatePickScreenTopBar( - dynamicOnBackgroundColor: Color, - onBackClick: () -> Unit, - onCreateClick: () -> Unit -) { - CenterAlignedTopAppBar( - modifier = Modifier.statusBarsPadding(), - title = { - Text( - text = stringResource(id = R.string.pick_app_bar_title), - color = dynamicOnBackgroundColor, - style = MaterialTheme.typography.titleLarge.copy(fontWeight = FontWeight.Bold) - ) - }, - navigationIcon = { - IconButton(onClick = onBackClick) { - Icon( - imageVector = Icons.AutoMirrored.Filled.ArrowBack, - contentDescription = stringResource(id = R.string.top_app_bar_back_description), - tint = dynamicOnBackgroundColor - ) - } - }, - actions = { - IconButton(onClick = onCreateClick) { - Icon( - imageVector = Icons.Filled.Check, - contentDescription = stringResource(id = R.string.pick_app_bar_upload_description), - tint = dynamicOnBackgroundColor - ) - } - }, - colors = TopAppBarDefaults.centerAlignedTopAppBarColors( - containerColor = Color.Transparent - ) - ) -} - -@Preview -@Composable -private fun CreatePickScreenPreview() { - CreatePickDisplay( - song = Song( - id = "1778132734", - songName = "Super Shy", - artistName = "뉴진스", - albumName = "NewJeans 'Super Shy' - Single", - imageUrl = "https://i.scdn.co/image/ab67616d0000b2733d98a0ae7c78a3a9babaf8af", - genreNames = listOf("K-Pop"), - bgColor = "#8FC1E2".toColorInt(), - externalUrl = "", - previewUrl = "" - ), - onBackClick = {}, - comment = "TEST COMMENT", - dynamicBackgroundColor = Color.White, - dynamicOnBackgroundColor = Color.Gray, - onCommentChange = { - - }, - onCreateClick = { - - } - ) -} - -private val RequestImageSize = Size(720, 720) -private val DEFAULT_SONG = Song( - id = "1778132734", - songName = "", - artistName = "", - albumName = "", - imageUrl = "", - genreNames = listOf("K-Pop"), - bgColor = "#8FC1E2".toColorInt(), - externalUrl = "", - previewUrl = "" -) diff --git a/feature/create/src/main/java/com/example/create/CreatePickUiState.kt b/feature/create/src/main/java/com/example/create/CreatePickUiState.kt deleted file mode 100644 index f9f7da26..00000000 --- a/feature/create/src/main/java/com/example/create/CreatePickUiState.kt +++ /dev/null @@ -1,12 +0,0 @@ -package com.example.create - -sealed class SearchUiState { - data object HotResult : SearchUiState() - data object SearchResult : SearchUiState() -} - -sealed class CreateUiState { - data object Default : CreateUiState() - data class Success(val data: T) : CreateUiState() - data object Error : CreateUiState() -} diff --git a/feature/create/src/main/java/com/example/create/CreatePickViewModel.kt b/feature/create/src/main/java/com/example/create/CreatePickViewModel.kt deleted file mode 100644 index 50ee67ef..00000000 --- a/feature/create/src/main/java/com/example/create/CreatePickViewModel.kt +++ /dev/null @@ -1,120 +0,0 @@ -package com.example.create - -import android.location.Location -import android.util.Log -import androidx.lifecycle.SavedStateHandle -import androidx.lifecycle.ViewModel -import androidx.lifecycle.viewModelScope -import androidx.navigation.toRoute -import com.example.applemusic.usecase.FetchMusicVideoUseCase -import com.example.location.usecase.GetLastLocationUseCase -import com.example.model.Creator -import com.example.model.LocationPoint -import com.example.model.Pick -import com.example.model.Song -import com.example.navigation.SearchRoute -import com.example.pick.usecase.CreatePickUseCase -import com.example.user.usecase.GetCurrentUserUseCase -import com.example.util.serializableType -import com.example.util.throttleFirst -import dagger.hilt.android.lifecycle.HiltViewModel -import kotlinx.coroutines.FlowPreview -import kotlinx.coroutines.flow.MutableSharedFlow -import kotlinx.coroutines.flow.MutableStateFlow -import kotlinx.coroutines.flow.asStateFlow -import kotlinx.coroutines.launch -import javax.inject.Inject -import kotlin.reflect.typeOf - -@OptIn(FlowPreview::class) -@HiltViewModel -class CreatePickViewModel @Inject constructor( - savedStateHandle: SavedStateHandle, - getLastLocationUseCase: GetLastLocationUseCase, - private val fetchMusicVideoUseCase: FetchMusicVideoUseCase, - private val createPickUseCase: CreatePickUseCase, - private val getCurrentUserUseCase: GetCurrentUserUseCase -) : ViewModel() { - - private val song = - savedStateHandle.toRoute(mapOf(typeOf() to serializableType())).song - - private val _createPickUiState = MutableStateFlow>(CreateUiState.Default) - val createPickUiState = _createPickUiState.asStateFlow() - - private val _comment = MutableStateFlow("") - val comment get() = _comment - - private var lastLocation: Location? = null - private val createPickClick = MutableSharedFlow() - - init { - // 데이터소스의 위치값을 계속 collect하며 curLocation 변수에 저장 - viewModelScope.launch { - getLastLocationUseCase().collect { location -> - lastLocation = location - } - } - - // 등록 버튼 클릭 후 3초 이내의 클릭은 무시하고 픽 생성하기 - viewModelScope.launch { - createPickClick - .throttleFirst(3000) - .collect { - createPick(song) - } - } - } - - fun onCommentChange(text: String) { - _comment.value = text - } - - fun resetComment() { - _comment.value = "" - } - - fun onCreatePickClick() { - viewModelScope.launch { - createPickClick.emit(Unit) - } - } - - private fun createPick(song: Song) { - viewModelScope.launch { - if (lastLocation == null) { - /* TODO: DEFAULT 인 경우 -> LocalDataSource 위치 데이터 못 불러옴 */ - return@launch - } - - val musicVideo = fetchMusicVideoUseCase(song) - - /* 등록 결과 - pick ID 담긴 Result */ - getCurrentUserUseCase()?.let { user -> - val createResult = createPickUseCase( - Pick( - id = "", - song = song, - comment = _comment.value, - createdAt = "", - createdBy = Creator( - userId = user.userId, - userName = user.userName - ), - location = LocationPoint(lastLocation!!.latitude, lastLocation!!.longitude), - musicVideoUrl = musicVideo?.previewUrl ?: "", - musicVideoThumbnailUrl = musicVideo?.thumbnailUrl ?: "" - ) - ) - - createResult.onSuccess { pickId -> - _createPickUiState.emit(CreateUiState.Success(pickId)) - }.onFailure { - /* TODO: Firestore 등록 실패처리 */ - _createPickUiState.emit(CreateUiState.Error) - Log.d("CreatePickViewModel", createResult.exceptionOrNull()?.message.toString()) - } - } - } - } -} diff --git a/feature/create/src/test/java/com/example/create/ExampleUnitTest.kt b/feature/create/src/test/java/com/squirtles/create/ExampleUnitTest.kt similarity index 91% rename from feature/create/src/test/java/com/example/create/ExampleUnitTest.kt rename to feature/create/src/test/java/com/squirtles/create/ExampleUnitTest.kt index f747d23f..4edd7162 100644 --- a/feature/create/src/test/java/com/example/create/ExampleUnitTest.kt +++ b/feature/create/src/test/java/com/squirtles/create/ExampleUnitTest.kt @@ -1,4 +1,4 @@ -package com.example.create +package com.squirtles.create import org.junit.Test diff --git a/settings.gradle.kts b/settings.gradle.kts index fd1b10ba..f853d08d 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -1,4 +1,7 @@ pluginManagement { + + includeBuild("build-logic") + repositories { google { content { @@ -50,3 +53,4 @@ include(":data:user") include(":core:musicplayer") include(":core:account") include(":feature:create") +include(":feature:favorite") From ae0b00b0fadf0be72510a4370d48ea124439e738 Mon Sep 17 00:00:00 2001 From: miller198 Date: Thu, 27 Feb 2025 21:33:00 +0900 Subject: [PATCH 19/62] =?UTF-8?q?[feature]=20build=20login=20=EB=AA=A8?= =?UTF-8?q?=EB=93=88?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- build-logic/convention/.gitignore | 1 + build-logic/convention/build.gradle.kts | 41 +++++++ .../convention/AndroidApplicationPlugin.kt | 29 +++++ .../convention/AndroidComposePlugin.kt | 30 +++++ .../convention/AndroidLibraryPlugin.kt | 25 +++++ .../com/squirtles/convention/HiltPlugin.kt | 26 +++++ .../squirtles/convention/JavaLibraryPlugin.kt | 37 +++++++ .../convention/extensions/ComposeAndroid.kt | 22 ++++ .../DependencyHandlerScopeExtension.kt | 55 +++++++++ .../convention/extensions/KotlinAndroid.kt | 57 ++++++++++ .../convention/extensions/KotlinCoroutine.kt | 13 +++ .../convention/extensions/ProjectExtension.kt | 9 ++ .../extensions/VersionCatalogExtension.kt | 22 ++++ gradle/libs.versions.toml | 104 +++++++++--------- 14 files changed, 421 insertions(+), 50 deletions(-) create mode 100644 build-logic/convention/.gitignore create mode 100644 build-logic/convention/build.gradle.kts create mode 100644 build-logic/convention/src/main/java/com/squirtles/convention/AndroidApplicationPlugin.kt create mode 100644 build-logic/convention/src/main/java/com/squirtles/convention/AndroidComposePlugin.kt create mode 100644 build-logic/convention/src/main/java/com/squirtles/convention/AndroidLibraryPlugin.kt create mode 100644 build-logic/convention/src/main/java/com/squirtles/convention/HiltPlugin.kt create mode 100644 build-logic/convention/src/main/java/com/squirtles/convention/JavaLibraryPlugin.kt create mode 100644 build-logic/convention/src/main/java/com/squirtles/convention/extensions/ComposeAndroid.kt create mode 100644 build-logic/convention/src/main/java/com/squirtles/convention/extensions/DependencyHandlerScopeExtension.kt create mode 100644 build-logic/convention/src/main/java/com/squirtles/convention/extensions/KotlinAndroid.kt create mode 100644 build-logic/convention/src/main/java/com/squirtles/convention/extensions/KotlinCoroutine.kt create mode 100644 build-logic/convention/src/main/java/com/squirtles/convention/extensions/ProjectExtension.kt create mode 100644 build-logic/convention/src/main/java/com/squirtles/convention/extensions/VersionCatalogExtension.kt diff --git a/build-logic/convention/.gitignore b/build-logic/convention/.gitignore new file mode 100644 index 00000000..42afabfd --- /dev/null +++ b/build-logic/convention/.gitignore @@ -0,0 +1 @@ +/build \ No newline at end of file diff --git a/build-logic/convention/build.gradle.kts b/build-logic/convention/build.gradle.kts new file mode 100644 index 00000000..5ef9a554 --- /dev/null +++ b/build-logic/convention/build.gradle.kts @@ -0,0 +1,41 @@ +plugins { + `kotlin-dsl` +} + +group = "com.squirtles.build-logic" + +dependencies { + compileOnly(libs.android.gradlePlugin) + compileOnly(libs.android.tools.common) + compileOnly(libs.kotlin.gradlePlugin) + compileOnly(libs.ksp.gradlePlugin) +} + +gradlePlugin { + plugins { + register("androidApplication") { + id = "musicroad.android.application" + implementationClass = "com.squirtles.convention.AndroidApplicationPlugin" + } + + register("androidLibrary") { + id = "musicroad.android.library" + implementationClass = "com.squirtles.convention.AndroidLibraryPlugin" + } + + register("androidCompose") { + id = "musicroad.android.compose" + implementationClass = "com.squirtles.convention.AndroidComposePlugin" + } + + register("javaLibrary") { + id = "musicroad.java.library" + implementationClass = "com.squirtles.convention.JavaLibraryPlugin" + } + + register("hilt") { + id = "musicroad.hilt" + implementationClass = "com.squirtles.convention.HiltPlugin" + } + } +} diff --git a/build-logic/convention/src/main/java/com/squirtles/convention/AndroidApplicationPlugin.kt b/build-logic/convention/src/main/java/com/squirtles/convention/AndroidApplicationPlugin.kt new file mode 100644 index 00000000..0d7f0fe6 --- /dev/null +++ b/build-logic/convention/src/main/java/com/squirtles/convention/AndroidApplicationPlugin.kt @@ -0,0 +1,29 @@ +package com.squirtles.convention + +import com.android.build.api.dsl.ApplicationExtension +import com.squirtles.convention.extensions.configureKotlinAndroid +import com.squirtles.convention.extensions.libs +import org.gradle.api.Plugin +import org.gradle.api.Project +import org.gradle.kotlin.dsl.configure + +class AndroidApplicationPlugin: Plugin { + override fun apply(target: Project) { + target.run { + pluginManager.run { + apply("com.android.application") + apply("org.jetbrains.kotlin.android") + } + + extensions.configure { + defaultConfig { + targetSdk = libs.findVersion("targetSdk").get().toString().toInt() + versionCode = libs.findVersion("versionCode").get().toString().toInt() + versionName = libs.findVersion("versionName").get().toString() + } + + configureKotlinAndroid(this) + } + } + } +} diff --git a/build-logic/convention/src/main/java/com/squirtles/convention/AndroidComposePlugin.kt b/build-logic/convention/src/main/java/com/squirtles/convention/AndroidComposePlugin.kt new file mode 100644 index 00000000..67b2ca83 --- /dev/null +++ b/build-logic/convention/src/main/java/com/squirtles/convention/AndroidComposePlugin.kt @@ -0,0 +1,30 @@ +package com.boostcamp.mapisode.convention + +import com.android.build.gradle.LibraryExtension +import com.squirtles.convention.extensions.configureComposeAndroid +import com.squirtles.convention.extensions.getLibrary +import com.squirtles.convention.extensions.implementation +import com.squirtles.convention.extensions.libs +import org.gradle.api.Plugin +import org.gradle.api.Project +import org.gradle.kotlin.dsl.configure +import org.gradle.kotlin.dsl.dependencies + +class AndroidComposePlugin : Plugin { + override fun apply(target: Project) { + with(target) { + pluginManager.apply { + apply("musicroad.android.library") + apply("org.jetbrains.kotlin.plugin.compose") + } + + extensions.configure { + configureComposeAndroid(this) + } + + dependencies { + implementation(libs.getLibrary("kotlinx.immutable")) + } + } + } +} diff --git a/build-logic/convention/src/main/java/com/squirtles/convention/AndroidLibraryPlugin.kt b/build-logic/convention/src/main/java/com/squirtles/convention/AndroidLibraryPlugin.kt new file mode 100644 index 00000000..50eab2b2 --- /dev/null +++ b/build-logic/convention/src/main/java/com/squirtles/convention/AndroidLibraryPlugin.kt @@ -0,0 +1,25 @@ +package com.squirtles.convention + +import com.android.build.gradle.LibraryExtension +import com.squirtles.convention.extensions.configureKotlinAndroid +import com.squirtles.convention.extensions.configureKotlinCoroutine +import com.squirtles.convention.extensions.getLibrary +import com.squirtles.convention.extensions.implementation +import com.squirtles.convention.extensions.libs +import org.gradle.api.Plugin +import org.gradle.api.Project +import org.gradle.kotlin.dsl.configure +import org.gradle.kotlin.dsl.dependencies + +class AndroidLibraryPlugin : Plugin { + override fun apply(target: Project) { + with(target) { + pluginManager.apply("com.android.library") + + extensions.configure { + configureKotlinAndroid(this) + configureKotlinCoroutine(this) + } + } + } +} diff --git a/build-logic/convention/src/main/java/com/squirtles/convention/HiltPlugin.kt b/build-logic/convention/src/main/java/com/squirtles/convention/HiltPlugin.kt new file mode 100644 index 00000000..fc11e833 --- /dev/null +++ b/build-logic/convention/src/main/java/com/squirtles/convention/HiltPlugin.kt @@ -0,0 +1,26 @@ +package com.squirtles.convention + +import com.squirtles.convention.extensions.getBundle +import com.squirtles.convention.extensions.getLibrary +import com.squirtles.convention.extensions.implementation +import com.squirtles.convention.extensions.ksp +import com.squirtles.convention.extensions.libs +import org.gradle.api.Plugin +import org.gradle.api.Project +import org.gradle.kotlin.dsl.dependencies + +class HiltPlugin : Plugin { + override fun apply(target: Project) { + with(target) { + pluginManager.apply { + apply("dagger.hilt.android.plugin") + apply("com.google.devtools.ksp") + } + + dependencies { + implementation(libs.getBundle("di")) + ksp(libs.getLibrary("hilt.android.compiler")) + } + } + } +} diff --git a/build-logic/convention/src/main/java/com/squirtles/convention/JavaLibraryPlugin.kt b/build-logic/convention/src/main/java/com/squirtles/convention/JavaLibraryPlugin.kt new file mode 100644 index 00000000..7b2a1430 --- /dev/null +++ b/build-logic/convention/src/main/java/com/squirtles/convention/JavaLibraryPlugin.kt @@ -0,0 +1,37 @@ +package com.squirtles.convention + +import com.squirtles.convention.extensions.getLibrary +import com.squirtles.convention.extensions.getVersion +import com.squirtles.convention.extensions.implementation +import com.squirtles.convention.extensions.libs +import org.gradle.api.JavaVersion +import org.gradle.api.Plugin +import org.gradle.api.Project +import org.gradle.api.plugins.JavaPluginExtension +import org.gradle.kotlin.dsl.configure +import org.gradle.kotlin.dsl.dependencies +import org.jetbrains.kotlin.gradle.dsl.KotlinProjectExtension + +class JavaLibraryPlugin : Plugin { + override fun apply(target: Project) { + with(target) { + pluginManager.apply { + apply("org.jetbrains.kotlin.jvm") + apply("java-library") + } + + extensions.configure { + sourceCompatibility = JavaVersion.VERSION_1_8 + targetCompatibility = JavaVersion.VERSION_1_8 + } + + extensions.configure { + jvmToolchain(libs.getVersion("jdkVersion").requiredVersion.toInt()) + } + + dependencies { + implementation(libs.getLibrary("javax.inject")) + } + } + } +} diff --git a/build-logic/convention/src/main/java/com/squirtles/convention/extensions/ComposeAndroid.kt b/build-logic/convention/src/main/java/com/squirtles/convention/extensions/ComposeAndroid.kt new file mode 100644 index 00000000..a10fae13 --- /dev/null +++ b/build-logic/convention/src/main/java/com/squirtles/convention/extensions/ComposeAndroid.kt @@ -0,0 +1,22 @@ +package com.squirtles.convention.extensions + +import com.android.build.api.dsl.CommonExtension +import org.gradle.api.Project +import org.gradle.kotlin.dsl.dependencies + +internal fun Project.configureComposeAndroid(commonExtension: CommonExtension<*, *, *, *, *, *>) { + pluginManager.apply("org.jetbrains.kotlin.plugin.compose") + + commonExtension.apply { + buildFeatures { + compose = true + } + + dependencies { + val composeBom = libs.getLibrary("compose.bom") + implementation(platform(composeBom)) + implementation(libs.getBundle("compose")) + debugImplementation(libs.getBundle("compose-debug")) + } + } +} diff --git a/build-logic/convention/src/main/java/com/squirtles/convention/extensions/DependencyHandlerScopeExtension.kt b/build-logic/convention/src/main/java/com/squirtles/convention/extensions/DependencyHandlerScopeExtension.kt new file mode 100644 index 00000000..5421bc94 --- /dev/null +++ b/build-logic/convention/src/main/java/com/squirtles/convention/extensions/DependencyHandlerScopeExtension.kt @@ -0,0 +1,55 @@ +package com.squirtles.convention.extensions + +import org.gradle.api.Project +import org.gradle.api.file.ConfigurableFileCollection +import org.gradle.api.file.ConfigurableFileTree +import org.gradle.api.provider.Provider +import org.gradle.kotlin.dsl.DependencyHandlerScope + +fun DependencyHandlerScope.implementation(project: Project) { + "implementation"(project) +} + +fun DependencyHandlerScope.implementation(provider: Provider<*>) { + "implementation"(provider) +} + +fun DependencyHandlerScope.implementation(fileTree: ConfigurableFileTree) { + "implementation"(fileTree) +} + +fun DependencyHandlerScope.implementation(fileCollection: ConfigurableFileCollection) { + "implementation"(fileCollection) +} + +fun DependencyHandlerScope.debugImplementation(provider: Provider<*>) { + "debugImplementation"(provider) +} + +fun DependencyHandlerScope.releaseImplementation(provider: Provider<*>) { + "releaseImplementation"(provider) +} + +fun DependencyHandlerScope.ksp(provider: Provider<*>) { + "ksp"(provider) +} + +fun DependencyHandlerScope.kspTest(provider: Provider<*>) { + "kspTest"(provider) +} + +fun DependencyHandlerScope.coreLibraryDesugaring(provider: Provider<*>) { + "coreLibraryDesugaring"(provider) +} + +fun DependencyHandlerScope.androidTestImplementation(provider: Provider<*>) { + "androidTestImplementation"(provider) +} + +fun DependencyHandlerScope.testImplementation(provider: Provider<*>) { + "testImplementation"(provider) +} + +fun DependencyHandlerScope.compileOnly(provider: Provider<*>) { + "compileOnly"(provider) +} diff --git a/build-logic/convention/src/main/java/com/squirtles/convention/extensions/KotlinAndroid.kt b/build-logic/convention/src/main/java/com/squirtles/convention/extensions/KotlinAndroid.kt new file mode 100644 index 00000000..6f94a231 --- /dev/null +++ b/build-logic/convention/src/main/java/com/squirtles/convention/extensions/KotlinAndroid.kt @@ -0,0 +1,57 @@ +package com.squirtles.convention.extensions + +import com.android.build.api.dsl.CommonExtension +import org.gradle.api.JavaVersion +import org.gradle.api.Project +import org.gradle.kotlin.dsl.withType +import org.jetbrains.kotlin.gradle.dsl.JvmTarget +import org.jetbrains.kotlin.gradle.tasks.KotlinCompile + +internal fun Project.configureKotlinAndroid(commonExtension: CommonExtension<*, *, *, *, *, *>) { + pluginManager.apply("org.jetbrains.kotlin.android") + + commonExtension.apply { + compileSdk = libs.getVersion("compileSdk").requiredVersion.toInt() + + defaultConfig { + minSdk = libs.getVersion("minSdk").requiredVersion.toInt() + } + + compileOptions { + sourceCompatibility = JavaVersion.VERSION_1_8 + targetCompatibility = JavaVersion.VERSION_1_8 + } + + buildTypes { + getByName("debug") { + proguardFiles( + getDefaultProguardFile("proguard-android.txt"), + "proguard-debug.pro", + ) + } + + getByName("release") { + isMinifyEnabled = false + proguardFiles( + getDefaultProguardFile("proguard-android.txt"), + "proguard-rules.pro", + ) + } + } + + tasks.withType().configureEach { + compilerOptions { + jvmTarget.set(JvmTarget.JVM_1_8) + + freeCompilerArgs.addAll( + listOf( + "-opt-in=kotlin.RequiresOptIn", + "-opt-in=kotlinx.coroutines.ExperimentalCoroutinesApi", + "-opt-in=androidx.compose.material3.ExperimentalMaterial3Api", + "-opt-in=androidx.lifecycle.compose.ExperimentalLifecycleComposeApi", + ), + ) + } + } + } +} diff --git a/build-logic/convention/src/main/java/com/squirtles/convention/extensions/KotlinCoroutine.kt b/build-logic/convention/src/main/java/com/squirtles/convention/extensions/KotlinCoroutine.kt new file mode 100644 index 00000000..47024133 --- /dev/null +++ b/build-logic/convention/src/main/java/com/squirtles/convention/extensions/KotlinCoroutine.kt @@ -0,0 +1,13 @@ +package com.squirtles.convention.extensions + +import com.android.build.api.dsl.CommonExtension +import org.gradle.api.Project +import org.gradle.kotlin.dsl.dependencies + +internal fun Project.configureKotlinCoroutine(commonExtension: CommonExtension<*, *, *, *, *, *>) { + commonExtension.apply { + dependencies { + implementation(libs.getBundle("coroutines")) + } + } +} diff --git a/build-logic/convention/src/main/java/com/squirtles/convention/extensions/ProjectExtension.kt b/build-logic/convention/src/main/java/com/squirtles/convention/extensions/ProjectExtension.kt new file mode 100644 index 00000000..b1c6e448 --- /dev/null +++ b/build-logic/convention/src/main/java/com/squirtles/convention/extensions/ProjectExtension.kt @@ -0,0 +1,9 @@ +package com.squirtles.convention.extensions + +import org.gradle.api.Project +import org.gradle.api.artifacts.VersionCatalog +import org.gradle.api.artifacts.VersionCatalogsExtension +import org.gradle.kotlin.dsl.getByType + +val Project.libs: VersionCatalog + get() = extensions.getByType().named("libs") diff --git a/build-logic/convention/src/main/java/com/squirtles/convention/extensions/VersionCatalogExtension.kt b/build-logic/convention/src/main/java/com/squirtles/convention/extensions/VersionCatalogExtension.kt new file mode 100644 index 00000000..fd071610 --- /dev/null +++ b/build-logic/convention/src/main/java/com/squirtles/convention/extensions/VersionCatalogExtension.kt @@ -0,0 +1,22 @@ +package com.squirtles.convention.extensions + +import org.gradle.api.artifacts.ExternalModuleDependencyBundle +import org.gradle.api.artifacts.MinimalExternalModuleDependency +import org.gradle.api.artifacts.VersionCatalog +import org.gradle.api.artifacts.VersionConstraint +import org.gradle.api.provider.Provider + +fun VersionCatalog.getBundle(bundleName: String): Provider = + findBundle(bundleName).orElseThrow { + NoSuchElementException("Bundle with name $bundleName not found in the catalog") + } + +fun VersionCatalog.getLibrary(libraryName: String): Provider = + findLibrary(libraryName).orElseThrow { + NoSuchElementException("Library with name $libraryName not found in the catalog") + } + +fun VersionCatalog.getVersion(versionName: String): VersionConstraint = + findVersion(versionName).orElseThrow { + NoSuchElementException("Version with name $versionName not found in the catalog") + } diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 43fd0dcc..5d624b2d 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -109,7 +109,7 @@ androidx-media3-ui = { module = "androidx.media3:media3-ui", version.ref = "medi androidx-media3-session = { group = "androidx.media3", name = "media3-session", version.ref = "media3Ui" } androidx-media3-common = { group = "androidx.media3", name = "media3-common", version.ref = "media3Ui" } -# Hilt +# Di inject = { group = "javax.inject", name = "javax.inject", version.ref = "inject" } hilt-android = { group = "com.google.dagger", name = "hilt-android", version.ref = "hilt" } hilt-android-compiler = { group = "com.google.dagger", name = "hilt-android-compiler", version.ref = "hilt" } @@ -127,6 +127,7 @@ compose-ui-tooling-preview = { group = "androidx.compose.ui", name = "ui-tooling compose-ui-test-manifest = { group = "androidx.compose.ui", name = "ui-test-manifest" } compose-ui-test-junit4 = { group = "androidx.compose.ui", name = "ui-test-junit4" } compose-runtime-android = { group = "androidx.compose.runtime", name = "runtime-android", version.ref = "compose-runtime-android" } +kotlinx-immutable = { group = "org.jetbrains.kotlinx", name = "kotlinx-collections-immutable", version.ref = "kotlinxImmutable" } # Paging androidx-paging-runtime = { module = "androidx.paging:paging-runtime", version.ref = "pagingRuntime" } @@ -139,7 +140,6 @@ googleid = { module = "com.google.android.libraries.identity.googleid:googleid", # Coroutines kotlinx-coroutines-core = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-core", version.ref = "kotlinxCoroutinesCore" } kotlinx-coroutines-android = { group = "org.jetbrains.kotlinx", name = "kotlinx-coroutines-android", version.ref = "kotlinxCoroutinesCore" } -kotlinx-immutable = { group = "org.jetbrains.kotlinx", name = "kotlinx-collections-immutable", version.ref = "kotlinxImmutable" } # Firebase firebase-bom = { module = "com.google.firebase:firebase-bom", version.ref = "firebaseBom" } @@ -194,95 +194,99 @@ ksp-gradlePlugin = { group = "com.google.devtools.ksp", name = "com.google.devto [bundles] androidx-core = [ - "androidx-core-ktx", "androidx-appcompat", + "androidx-core-ktx", "androidx-lifecycle-runtime-ktx", ] -# Compose +analytics = [ + "firebase-analytics", + "firebase-crashlytics", +] + +auth = [ + "androidx-credentials", + "androidx-credentials-play-services-auth", + "googleid", +] + compose = [ - "compose-ui", - "compose-ui-tooling-preview", - "compose-runtime-android", "androidx-activity-compose", "androidx-lifecycle-viewmodel-compose", + "compose-runtime-android", + "compose-ui", + "compose-ui-tooling-preview", ] + compose-debug = [ - "compose-ui-tooling", - "compose-ui-test-manifest", "compose-ui-test-junit4", -] -material = [ - "material", - "compose-material", - "compose-material3", - "compose-material-icons-extended", -] - -navigation = [ - "navigation-compose", - "hilt-navigation-compose" + "compose-ui-test-manifest", + "compose-ui-tooling", ] coroutines = [ - "kotlinx-coroutines-core", "kotlinx-coroutines-android", + "kotlinx-coroutines-core", ] datastore = [ "androidx-datastore-preferences", ] -auth = [ - "androidx-credentials", - "androidx-credentials-play-services-auth", - "googleid", +di = [ + "hilt-android", + "hilt-android-compiler", + "hilt-android-testing", + "inject", ] -# Network -retrofit = [ - "retrofit-core", - "retrofit-kotlin-serialization", -] -okhttp = [ - "okhttp", - "okhttp-logging", -] - -# Firebase firebase = [ "firebase-firestore-ktx", "firebase-functions-ktx", ] -analytics = [ - "firebase-analytics", - "firebase-crashlytics", -] + geofire = [ "geofire-android-common", "google-firebase-dynamic-module-support", ] -# Coil -coil = [ - "coil", - "coil-compose", - "coil-network-okhttp", +map = [ + "map-sdk", + "play-services-location", +] + +material = [ + "compose-material", + "compose-material-icons-extended", + "compose-material3", + "material", ] media3 = [ + "androidx-media3-common", "androidx-media3-exoplayer", "androidx-media3-exoplayer-dash", - "androidx-media3-ui", "androidx-media3-session", - "androidx-media3-common", + "androidx-media3-ui", ] -map = [ - "map-sdk", - "play-services-location", +navigation = [ + "hilt-navigation-compose", + "navigation-compose", ] +network = [ + "okhttp", + "okhttp-logging", + "retrofit-core", + "retrofit-kotlin-serialization", +] + +coil = [ + "coil", + "coil-compose", + "coil-network-okhttp", +] # Plugins [plugins] From d2769add70fbd6812c1ed1c55b3b37967e6b4836 Mon Sep 17 00:00:00 2001 From: miller198 Date: Thu, 27 Feb 2025 21:34:19 +0900 Subject: [PATCH 20/62] =?UTF-8?q?[chore]=20gitignore=20=EB=88=84=EB=9D=BD?= =?UTF-8?q?=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- core/mediaservice/.gitignore | 1 + core/musicplayer/.gitignore | 1 + data/applemusic/.gitignore | 1 + data/favorite/.gitignore | 1 + data/firebase/.gitignore | 1 + data/location/.gitignore | 1 + data/order/.gitignore | 1 + data/pick/.gitignore | 1 + data/user/.gitignore | 1 + domain/applemusic/.gitignore | 1 + domain/favorite/.gitignore | 1 + domain/location/.gitignore | 1 + domain/order/.gitignore | 1 + domain/pick/.gitignore | 1 + domain/picklist/.gitignore | 1 + domain/player/.gitignore | 1 + domain/user/.gitignore | 1 + 17 files changed, 17 insertions(+) create mode 100644 core/mediaservice/.gitignore create mode 100644 core/musicplayer/.gitignore create mode 100644 data/applemusic/.gitignore create mode 100644 data/favorite/.gitignore create mode 100644 data/firebase/.gitignore create mode 100644 data/location/.gitignore create mode 100644 data/order/.gitignore create mode 100644 data/pick/.gitignore create mode 100644 data/user/.gitignore create mode 100644 domain/applemusic/.gitignore create mode 100644 domain/favorite/.gitignore create mode 100644 domain/location/.gitignore create mode 100644 domain/order/.gitignore create mode 100644 domain/pick/.gitignore create mode 100644 domain/picklist/.gitignore create mode 100644 domain/player/.gitignore create mode 100644 domain/user/.gitignore diff --git a/core/mediaservice/.gitignore b/core/mediaservice/.gitignore new file mode 100644 index 00000000..42afabfd --- /dev/null +++ b/core/mediaservice/.gitignore @@ -0,0 +1 @@ +/build \ No newline at end of file diff --git a/core/musicplayer/.gitignore b/core/musicplayer/.gitignore new file mode 100644 index 00000000..42afabfd --- /dev/null +++ b/core/musicplayer/.gitignore @@ -0,0 +1 @@ +/build \ No newline at end of file diff --git a/data/applemusic/.gitignore b/data/applemusic/.gitignore new file mode 100644 index 00000000..42afabfd --- /dev/null +++ b/data/applemusic/.gitignore @@ -0,0 +1 @@ +/build \ No newline at end of file diff --git a/data/favorite/.gitignore b/data/favorite/.gitignore new file mode 100644 index 00000000..42afabfd --- /dev/null +++ b/data/favorite/.gitignore @@ -0,0 +1 @@ +/build \ No newline at end of file diff --git a/data/firebase/.gitignore b/data/firebase/.gitignore new file mode 100644 index 00000000..42afabfd --- /dev/null +++ b/data/firebase/.gitignore @@ -0,0 +1 @@ +/build \ No newline at end of file diff --git a/data/location/.gitignore b/data/location/.gitignore new file mode 100644 index 00000000..42afabfd --- /dev/null +++ b/data/location/.gitignore @@ -0,0 +1 @@ +/build \ No newline at end of file diff --git a/data/order/.gitignore b/data/order/.gitignore new file mode 100644 index 00000000..42afabfd --- /dev/null +++ b/data/order/.gitignore @@ -0,0 +1 @@ +/build \ No newline at end of file diff --git a/data/pick/.gitignore b/data/pick/.gitignore new file mode 100644 index 00000000..42afabfd --- /dev/null +++ b/data/pick/.gitignore @@ -0,0 +1 @@ +/build \ No newline at end of file diff --git a/data/user/.gitignore b/data/user/.gitignore new file mode 100644 index 00000000..42afabfd --- /dev/null +++ b/data/user/.gitignore @@ -0,0 +1 @@ +/build \ No newline at end of file diff --git a/domain/applemusic/.gitignore b/domain/applemusic/.gitignore new file mode 100644 index 00000000..42afabfd --- /dev/null +++ b/domain/applemusic/.gitignore @@ -0,0 +1 @@ +/build \ No newline at end of file diff --git a/domain/favorite/.gitignore b/domain/favorite/.gitignore new file mode 100644 index 00000000..42afabfd --- /dev/null +++ b/domain/favorite/.gitignore @@ -0,0 +1 @@ +/build \ No newline at end of file diff --git a/domain/location/.gitignore b/domain/location/.gitignore new file mode 100644 index 00000000..42afabfd --- /dev/null +++ b/domain/location/.gitignore @@ -0,0 +1 @@ +/build \ No newline at end of file diff --git a/domain/order/.gitignore b/domain/order/.gitignore new file mode 100644 index 00000000..42afabfd --- /dev/null +++ b/domain/order/.gitignore @@ -0,0 +1 @@ +/build \ No newline at end of file diff --git a/domain/pick/.gitignore b/domain/pick/.gitignore new file mode 100644 index 00000000..42afabfd --- /dev/null +++ b/domain/pick/.gitignore @@ -0,0 +1 @@ +/build \ No newline at end of file diff --git a/domain/picklist/.gitignore b/domain/picklist/.gitignore new file mode 100644 index 00000000..42afabfd --- /dev/null +++ b/domain/picklist/.gitignore @@ -0,0 +1 @@ +/build \ No newline at end of file diff --git a/domain/player/.gitignore b/domain/player/.gitignore new file mode 100644 index 00000000..42afabfd --- /dev/null +++ b/domain/player/.gitignore @@ -0,0 +1 @@ +/build \ No newline at end of file diff --git a/domain/user/.gitignore b/domain/user/.gitignore new file mode 100644 index 00000000..42afabfd --- /dev/null +++ b/domain/user/.gitignore @@ -0,0 +1 @@ +/build \ No newline at end of file From 4722fecb0dfeeeaece663c02fa1e55b129e3841a Mon Sep 17 00:00:00 2001 From: miller198 Date: Sun, 2 Mar 2025 19:51:43 +0900 Subject: [PATCH 21/62] =?UTF-8?q?[build]=20version=20=EC=B9=B4=ED=83=88?= =?UTF-8?q?=EB=A1=9C=EA=B7=B8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 버전추가, 번들추가 --- gradle/libs.versions.toml | 24 +++++++++++++++++++----- 1 file changed, 19 insertions(+), 5 deletions(-) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 5d624b2d..0a91f6f5 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -4,7 +4,8 @@ compileSdk = "34" minSdk = "26" targetSdk = "34" -jvmTarget = "1.8" +jvmTarget = "17" +jdkVersion = "17" # App Version versionCode = "10100" @@ -29,6 +30,7 @@ androidx-core-splashscreen = "1.0.1" media3Ui = "1.4.1" # Compose +compose-compiler = "1.5.14" androidx-compose-animation = "1.7.5" activityCompose = "1.9.3" composeBom = "2024.10.01" @@ -90,6 +92,8 @@ okhttp = "4.12.0" retrofit = "2.11.0" material = "1.12.0" +composeMaterial3 = "1.3.1" +composeMaterialIconsExtended = "1.7.8" [libraries] # AndroidX @@ -103,12 +107,14 @@ androidx-navigation-common-ktx = { group = "androidx.navigation", name = "naviga androidx-core-splashscreen = { module = "androidx.core:core-splashscreen", version.ref = "androidx-core-splashscreen" } # Media3 -androidx-media3-exoplayer = { module = "androidx.media3:media3-exoplayer", version.ref = "media3Ui" } -androidx-media3-exoplayer-dash = { module = "androidx.media3:media3-exoplayer-dash", version.ref = "media3Ui" } androidx-media3-ui = { module = "androidx.media3:media3-ui", version.ref = "media3Ui" } androidx-media3-session = { group = "androidx.media3", name = "media3-session", version.ref = "media3Ui" } androidx-media3-common = { group = "androidx.media3", name = "media3-common", version.ref = "media3Ui" } +# Exoplayer +androidx-media3-exoplayer = { module = "androidx.media3:media3-exoplayer", version.ref = "media3Ui" } +androidx-media3-exoplayer-dash = { module = "androidx.media3:media3-exoplayer-dash", version.ref = "media3Ui" } + # Di inject = { group = "javax.inject", name = "javax.inject", version.ref = "inject" } hilt-android = { group = "com.google.dagger", name = "hilt-android", version.ref = "hilt" } @@ -165,9 +171,9 @@ play-services-location = { module = "com.google.android.gms:play-services-locati # Material Design material = { group = "com.google.android.material", name = "material", version.ref = "material" } -compose-material3 = { group = "androidx.compose.material3", name = "material3" } +compose-material3 = { group = "androidx.compose.material3", name = "material3", version.ref = "composeMaterial3" } compose-material = { group = "androidx.wear.compose", name = "compose-material", version.ref = "composeMaterial" } -compose-material-icons-extended = { module = "androidx.compose.material:material-icons-extended" } +compose-material-icons-extended = { module = "androidx.compose.material:material-icons-extended", version.ref = "composeMaterialIconsExtended" } # Network okhttp = { group = "com.squareup.okhttp3", name = "okhttp" } @@ -240,6 +246,11 @@ di = [ "inject", ] +exoplayer = [ + "androidx-media3-exoplayer", + "androidx-media3-exoplayer-dash", +] + firebase = [ "firebase-firestore-ktx", "firebase-functions-ktx", @@ -302,3 +313,6 @@ jetbrains-kotlin-jvm = { id = "org.jetbrains.kotlin.jvm", version.ref = "jetbrai # Custom Plugins musicroad-android-application = { id = "musicroad.android.application", version = "unspecified" } +musicroad-java-library = { id = "musicroad.java.library", version = "unspecified" } +musicroad-android-library = { id = "musicroad.android.library", version = "unspecified" } +musicroad-compose-library = { id = "musicroad.android.compose", version = "unspecified" } From f73dacc2f8a4a06ab940ae32b91f0875a9db705c Mon Sep 17 00:00:00 2001 From: miller198 Date: Sun, 2 Mar 2025 19:52:32 +0900 Subject: [PATCH 22/62] =?UTF-8?q?[fix]=20build-logic=20=EB=AA=A8=EB=93=88?= =?UTF-8?q?=20=EB=B9=8C=EB=93=9C=20=EC=97=90=EB=9F=AC=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit https://hyosikchoi.tistory.com/entry/build-logic-rebuild-project-%EC%8B%9C-%EC%97%90%EB%9F%AC-%EA%B4%80%EB%A0%A8 --- settings.gradle.kts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/settings.gradle.kts b/settings.gradle.kts index f853d08d..204c0c2b 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -1,6 +1,7 @@ pluginManagement { includeBuild("build-logic") + gradle.startParameter.excludedTaskNames.addAll(listOf(":build-logic:convention:testClasses")) repositories { google { @@ -54,3 +55,4 @@ include(":core:musicplayer") include(":core:account") include(":feature:create") include(":feature:favorite") +include(":core:localproperties") From 6d4ead7775f2026b486f1b2851e346cb0fc4e5a1 Mon Sep 17 00:00:00 2001 From: miller198 Date: Sun, 2 Mar 2025 19:57:00 +0900 Subject: [PATCH 23/62] =?UTF-8?q?[feature]=20build=20config=20=C2=B8=C3=B0?= =?UTF-8?q?=C2=B5=C3=A2=20=C3=83=DF=B0=C2=A1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- core/localproperties/.gitignore | 1 + core/localproperties/build.gradle.kts | 71 +++++++++++++++++++ .../localproperties/LocalPropertyProvider.kt | 15 ++++ 3 files changed, 87 insertions(+) create mode 100644 core/localproperties/.gitignore create mode 100644 core/localproperties/build.gradle.kts create mode 100644 core/localproperties/src/main/java/com/squirtles/localproperties/LocalPropertyProvider.kt diff --git a/core/localproperties/.gitignore b/core/localproperties/.gitignore new file mode 100644 index 00000000..42afabfd --- /dev/null +++ b/core/localproperties/.gitignore @@ -0,0 +1 @@ +/build \ No newline at end of file diff --git a/core/localproperties/build.gradle.kts b/core/localproperties/build.gradle.kts new file mode 100644 index 00000000..162217e3 --- /dev/null +++ b/core/localproperties/build.gradle.kts @@ -0,0 +1,71 @@ +import java.io.FileInputStream +import java.util.Properties + +val properties = Properties().apply { + load(FileInputStream(rootProject.file("local.properties"))) +} + +plugins { + alias(libs.plugins.musicroad.android.library) +} + +android { + namespace = "com.squirtles.localproperties" + + buildFeatures { + buildConfig = true + } + + defaultConfig { + + buildConfigField( + "String", + "GOOGLE_CLIENT_ID", + "\"${properties.getProperty("GOOGLE_CLIENT_ID")}\"" + ) + + buildConfigField( + "String", + "APPLE_MUSIC_API_TOKEN", + "\"${properties.getProperty("APPLE_MUSIC_API_TOKEN")}\"" + ) + } + + buildTypes { + getByName("debug") { + isMinifyEnabled = false + + buildConfigField( + "String", + "FIRESTORE_DB_ID", + "\"${properties.getProperty("FIRESTORE_DB_ID_DEBUG")}\"" + ) + + buildConfigField( + "String", + "HTTPS_CALLABLE", + "\"${properties.getProperty("HTTPS_CALLABLE_DEBUG")}\"" + ) + } + + getByName("debug") { + isMinifyEnabled = false + + buildConfigField( + "String", + "FIRESTORE_DB_ID", + "\"${properties.getProperty("FIRESTORE_DB_ID_RELEASE")}\"" + ) + + buildConfigField( + "String", + "HTTPS_CALLABLE", + "\"${properties.getProperty("HTTPS_CALLABLE_RELEASE")}\"" + ) + } + } +} + +dependencies { + +} diff --git a/core/localproperties/src/main/java/com/squirtles/localproperties/LocalPropertyProvider.kt b/core/localproperties/src/main/java/com/squirtles/localproperties/LocalPropertyProvider.kt new file mode 100644 index 00000000..148ca373 --- /dev/null +++ b/core/localproperties/src/main/java/com/squirtles/localproperties/LocalPropertyProvider.kt @@ -0,0 +1,15 @@ +package com.squirtles.localproperties + +object LocalPropertyProvider { + val googleClientId: String + get() = BuildConfig.GOOGLE_CLIENT_ID + + val appleMusicApiToken: String + get() = BuildConfig.APPLE_MUSIC_API_TOKEN + + val firestoreDbId: String + get() = BuildConfig.FIRESTORE_DB_ID + + val httpsCallable: String + get() = BuildConfig.HTTPS_CALLABLE +} From 32568add4622d85096e1b26afbf6703ca602465a Mon Sep 17 00:00:00 2001 From: miller198 Date: Sun, 2 Mar 2025 20:02:29 +0900 Subject: [PATCH 24/62] [refactor] core.localproperties -> core.buildconfig --- core/{localproperties => buildconfig}/.gitignore | 0 core/{localproperties => buildconfig}/build.gradle.kts | 0 .../java/com/squirtles/localproperties/LocalPropertyProvider.kt | 0 3 files changed, 0 insertions(+), 0 deletions(-) rename core/{localproperties => buildconfig}/.gitignore (100%) rename core/{localproperties => buildconfig}/build.gradle.kts (100%) rename core/{localproperties => buildconfig}/src/main/java/com/squirtles/localproperties/LocalPropertyProvider.kt (100%) diff --git a/core/localproperties/.gitignore b/core/buildconfig/.gitignore similarity index 100% rename from core/localproperties/.gitignore rename to core/buildconfig/.gitignore diff --git a/core/localproperties/build.gradle.kts b/core/buildconfig/build.gradle.kts similarity index 100% rename from core/localproperties/build.gradle.kts rename to core/buildconfig/build.gradle.kts diff --git a/core/localproperties/src/main/java/com/squirtles/localproperties/LocalPropertyProvider.kt b/core/buildconfig/src/main/java/com/squirtles/localproperties/LocalPropertyProvider.kt similarity index 100% rename from core/localproperties/src/main/java/com/squirtles/localproperties/LocalPropertyProvider.kt rename to core/buildconfig/src/main/java/com/squirtles/localproperties/LocalPropertyProvider.kt From c015f8dc2718232b450e3b312153b58816243207 Mon Sep 17 00:00:00 2001 From: miller198 Date: Sun, 2 Mar 2025 20:40:28 +0900 Subject: [PATCH 25/62] =?UTF-8?q?[build]=20=EC=9E=90=EB=B0=94=20=EB=B2=84?= =?UTF-8?q?=EC=A0=84=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/build.gradle.kts | 6 +- .../convention/AndroidApplicationPlugin.kt | 1 + .../convention/AndroidComposePlugin.kt | 4 +- .../squirtles/convention/JavaLibraryPlugin.kt | 6 +- .../convention/extensions/ComposeAndroid.kt | 4 + .../convention/extensions/KotlinAndroid.kt | 6 +- build-logic/gradle.properties | 3 + build-logic/settings.gradle.kts | 15 + core/account/build.gradle.kts | 6 +- .../com/squirtles/account/AccountViewModel.kt | 64 ++++ .../java/com/squirtles/account/GoogleId.kt | 70 ++++ core/buildconfig/build.gradle.kts | 2 +- core/common/build.gradle.kts | 48 +-- core/common/consumer-rules.pro | 0 core/common/proguard-rules.pro | 21 ++ .../com/squirtles/common/ui/AlbumImage.kt | 39 ++ .../java/com/squirtles/common/ui/Constants.kt | 17 + .../squirtles/common/ui/CreatedByPickText.kt | 64 ++++ .../squirtles/common/ui/DefaultTopAppBar.kt | 56 +++ .../squirtles/common/ui/MessageAlertDialog.kt | 126 +++++++ .../com/squirtles/common/ui/PickInfoText.kt | 102 +++++ .../java/com/squirtles/common/ui/Spacer.kt | 14 + .../com/squirtles/common/ui/theme/Color.kt | 21 ++ .../com/squirtles/common/ui/theme/Theme.kt | 45 +++ .../com/squirtles/common/ui/theme/Type.kt | 34 ++ core/mediaservice/build.gradle.kts | 6 +- core/model/build.gradle.kts | 12 +- core/model/consumer-rules.pro | 0 core/model/proguard-rules.pro | 21 ++ core/musicplayer/build.gradle.kts | 6 +- core/navigation/build.gradle.kts | 4 +- core/navigation/consumer-rules.pro | 0 core/navigation/proguard-rules.pro | 21 ++ core/picklist/build.gradle.kts | 6 +- core/util/build.gradle.kts | 6 +- data/applemusic/build.gradle.kts | 6 +- data/build.gradle.kts | 39 +- data/favorite/build.gradle.kts | 6 +- data/firebase/build.gradle.kts | 6 +- data/location/build.gradle.kts | 6 +- data/order/build.gradle.kts | 6 +- data/pick/build.gradle.kts | 6 +- data/user/build.gradle.kts | 6 +- domain/applemusic/build.gradle.kts | 6 +- domain/applemusic/consumer-rules.pro | 0 domain/applemusic/proguard-rules.pro | 21 ++ domain/build.gradle.kts | 6 +- domain/favorite/build.gradle.kts | 4 +- domain/favorite/consumer-rules.pro | 0 domain/favorite/proguard-rules.pro | 21 ++ domain/location/build.gradle.kts | 6 +- domain/location/consumer-rules.pro | 0 domain/location/proguard-rules.pro | 21 ++ domain/order/build.gradle.kts | 4 +- domain/order/consumer-rules.pro | 0 domain/order/proguard-rules.pro | 21 ++ domain/pick/build.gradle.kts | 4 +- domain/pick/consumer-rules.pro | 0 domain/pick/proguard-rules.pro | 21 ++ domain/picklist/build.gradle.kts | 4 +- domain/picklist/consumer-rules.pro | 0 domain/picklist/proguard-rules.pro | 21 ++ domain/player/build.gradle.kts | 6 +- domain/player/consumer-rules.pro | 0 domain/player/proguard-rules.pro | 21 ++ domain/user/build.gradle.kts | 4 +- domain/user/consumer-rules.pro | 0 domain/user/proguard-rules.pro | 21 ++ feature/create/build.gradle.kts | 6 +- .../com/squirtles/create/CreatePickScreen.kt | 349 ++++++++++++++++++ .../com/squirtles/create/CreatePickUiState.kt | 12 + .../squirtles/create/CreatePickViewModel.kt | 120 ++++++ feature/favorite/proguard-rules.pro | 21 ++ mediaservice/build.gradle.kts | 6 +- settings.gradle.kts | 2 +- 75 files changed, 1500 insertions(+), 165 deletions(-) create mode 100644 build-logic/gradle.properties create mode 100644 build-logic/settings.gradle.kts create mode 100644 core/account/src/main/java/com/squirtles/account/AccountViewModel.kt create mode 100644 core/account/src/main/java/com/squirtles/account/GoogleId.kt create mode 100644 core/common/consumer-rules.pro create mode 100644 core/common/proguard-rules.pro create mode 100644 core/common/src/main/java/com/squirtles/common/ui/AlbumImage.kt create mode 100644 core/common/src/main/java/com/squirtles/common/ui/Constants.kt create mode 100644 core/common/src/main/java/com/squirtles/common/ui/CreatedByPickText.kt create mode 100644 core/common/src/main/java/com/squirtles/common/ui/DefaultTopAppBar.kt create mode 100644 core/common/src/main/java/com/squirtles/common/ui/MessageAlertDialog.kt create mode 100644 core/common/src/main/java/com/squirtles/common/ui/PickInfoText.kt create mode 100644 core/common/src/main/java/com/squirtles/common/ui/Spacer.kt create mode 100644 core/common/src/main/java/com/squirtles/common/ui/theme/Color.kt create mode 100644 core/common/src/main/java/com/squirtles/common/ui/theme/Theme.kt create mode 100644 core/common/src/main/java/com/squirtles/common/ui/theme/Type.kt create mode 100644 core/model/consumer-rules.pro create mode 100644 core/model/proguard-rules.pro create mode 100644 core/navigation/consumer-rules.pro create mode 100644 core/navigation/proguard-rules.pro create mode 100644 domain/applemusic/consumer-rules.pro create mode 100644 domain/applemusic/proguard-rules.pro create mode 100644 domain/favorite/consumer-rules.pro create mode 100644 domain/favorite/proguard-rules.pro create mode 100644 domain/location/consumer-rules.pro create mode 100644 domain/location/proguard-rules.pro create mode 100644 domain/order/consumer-rules.pro create mode 100644 domain/order/proguard-rules.pro create mode 100644 domain/pick/consumer-rules.pro create mode 100644 domain/pick/proguard-rules.pro create mode 100644 domain/picklist/consumer-rules.pro create mode 100644 domain/picklist/proguard-rules.pro create mode 100644 domain/player/consumer-rules.pro create mode 100644 domain/player/proguard-rules.pro create mode 100644 domain/user/consumer-rules.pro create mode 100644 domain/user/proguard-rules.pro create mode 100644 feature/create/src/main/java/com/squirtles/create/CreatePickScreen.kt create mode 100644 feature/create/src/main/java/com/squirtles/create/CreatePickUiState.kt create mode 100644 feature/create/src/main/java/com/squirtles/create/CreatePickViewModel.kt create mode 100644 feature/favorite/proguard-rules.pro diff --git a/app/build.gradle.kts b/app/build.gradle.kts index 681d6521..c39d546b 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -76,11 +76,11 @@ android { } } compileOptions { - sourceCompatibility = JavaVersion.VERSION_1_8 - targetCompatibility = JavaVersion.VERSION_1_8 + sourceCompatibility = JavaVersion.VERSION_17 + targetCompatibility = JavaVersion.VERSION_17 } kotlinOptions { - jvmTarget = "1.8" + jvmTarget = "17" } buildFeatures { viewBinding = true diff --git a/build-logic/convention/src/main/java/com/squirtles/convention/AndroidApplicationPlugin.kt b/build-logic/convention/src/main/java/com/squirtles/convention/AndroidApplicationPlugin.kt index 0d7f0fe6..32a0db74 100644 --- a/build-logic/convention/src/main/java/com/squirtles/convention/AndroidApplicationPlugin.kt +++ b/build-logic/convention/src/main/java/com/squirtles/convention/AndroidApplicationPlugin.kt @@ -7,6 +7,7 @@ import org.gradle.api.Plugin import org.gradle.api.Project import org.gradle.kotlin.dsl.configure +// app module class AndroidApplicationPlugin: Plugin { override fun apply(target: Project) { target.run { diff --git a/build-logic/convention/src/main/java/com/squirtles/convention/AndroidComposePlugin.kt b/build-logic/convention/src/main/java/com/squirtles/convention/AndroidComposePlugin.kt index 67b2ca83..4895730b 100644 --- a/build-logic/convention/src/main/java/com/squirtles/convention/AndroidComposePlugin.kt +++ b/build-logic/convention/src/main/java/com/squirtles/convention/AndroidComposePlugin.kt @@ -12,8 +12,8 @@ import org.gradle.kotlin.dsl.dependencies class AndroidComposePlugin : Plugin { override fun apply(target: Project) { - with(target) { - pluginManager.apply { + target.run { + pluginManager.run { apply("musicroad.android.library") apply("org.jetbrains.kotlin.plugin.compose") } diff --git a/build-logic/convention/src/main/java/com/squirtles/convention/JavaLibraryPlugin.kt b/build-logic/convention/src/main/java/com/squirtles/convention/JavaLibraryPlugin.kt index 7b2a1430..5ec15d7a 100644 --- a/build-logic/convention/src/main/java/com/squirtles/convention/JavaLibraryPlugin.kt +++ b/build-logic/convention/src/main/java/com/squirtles/convention/JavaLibraryPlugin.kt @@ -21,8 +21,8 @@ class JavaLibraryPlugin : Plugin { } extensions.configure { - sourceCompatibility = JavaVersion.VERSION_1_8 - targetCompatibility = JavaVersion.VERSION_1_8 + sourceCompatibility = JavaVersion.VERSION_17 + targetCompatibility = JavaVersion.VERSION_17 } extensions.configure { @@ -30,7 +30,7 @@ class JavaLibraryPlugin : Plugin { } dependencies { - implementation(libs.getLibrary("javax.inject")) + implementation(libs.getLibrary("inject")) } } } diff --git a/build-logic/convention/src/main/java/com/squirtles/convention/extensions/ComposeAndroid.kt b/build-logic/convention/src/main/java/com/squirtles/convention/extensions/ComposeAndroid.kt index a10fae13..d6a84629 100644 --- a/build-logic/convention/src/main/java/com/squirtles/convention/extensions/ComposeAndroid.kt +++ b/build-logic/convention/src/main/java/com/squirtles/convention/extensions/ComposeAndroid.kt @@ -12,6 +12,10 @@ internal fun Project.configureComposeAndroid(commonExtension: CommonExtension<*, compose = true } + composeOptions { + kotlinCompilerExtensionVersion = libs.getVersion("compose-compiler").requiredVersion + } + dependencies { val composeBom = libs.getLibrary("compose.bom") implementation(platform(composeBom)) diff --git a/build-logic/convention/src/main/java/com/squirtles/convention/extensions/KotlinAndroid.kt b/build-logic/convention/src/main/java/com/squirtles/convention/extensions/KotlinAndroid.kt index 6f94a231..ebad7ec7 100644 --- a/build-logic/convention/src/main/java/com/squirtles/convention/extensions/KotlinAndroid.kt +++ b/build-logic/convention/src/main/java/com/squirtles/convention/extensions/KotlinAndroid.kt @@ -18,8 +18,8 @@ internal fun Project.configureKotlinAndroid(commonExtension: CommonExtension<*, } compileOptions { - sourceCompatibility = JavaVersion.VERSION_1_8 - targetCompatibility = JavaVersion.VERSION_1_8 + sourceCompatibility = JavaVersion.VERSION_17 + targetCompatibility = JavaVersion.VERSION_17 } buildTypes { @@ -41,7 +41,7 @@ internal fun Project.configureKotlinAndroid(commonExtension: CommonExtension<*, tasks.withType().configureEach { compilerOptions { - jvmTarget.set(JvmTarget.JVM_1_8) + jvmTarget.set(JvmTarget.JVM_17) freeCompilerArgs.addAll( listOf( diff --git a/build-logic/gradle.properties b/build-logic/gradle.properties new file mode 100644 index 00000000..c6cd2a7e --- /dev/null +++ b/build-logic/gradle.properties @@ -0,0 +1,3 @@ +org.gradle.parallel=true +org.gradle.caching=true +org.gradle.configureondemand=true diff --git a/build-logic/settings.gradle.kts b/build-logic/settings.gradle.kts new file mode 100644 index 00000000..c55e37ec --- /dev/null +++ b/build-logic/settings.gradle.kts @@ -0,0 +1,15 @@ +dependencyResolutionManagement { + repositories { + google() + mavenCentral() + } + + versionCatalogs { + create("libs") { + from(files("../gradle/libs.versions.toml")) + } + } +} + +rootProject.name = "build-logic" +include(":convention") diff --git a/core/account/build.gradle.kts b/core/account/build.gradle.kts index 8cf01839..ee35e44a 100644 --- a/core/account/build.gradle.kts +++ b/core/account/build.gradle.kts @@ -36,11 +36,11 @@ android { } } compileOptions { - sourceCompatibility = JavaVersion.VERSION_1_8 - targetCompatibility = JavaVersion.VERSION_1_8 + sourceCompatibility = JavaVersion.VERSION_17 + targetCompatibility = JavaVersion.VERSION_17 } kotlinOptions { - jvmTarget = "1.8" + jvmTarget = "17" } buildFeatures { buildConfig = true diff --git a/core/account/src/main/java/com/squirtles/account/AccountViewModel.kt b/core/account/src/main/java/com/squirtles/account/AccountViewModel.kt new file mode 100644 index 00000000..42174cd6 --- /dev/null +++ b/core/account/src/main/java/com/squirtles/account/AccountViewModel.kt @@ -0,0 +1,64 @@ +package com.squirtles.account + +import android.util.Log +import androidx.lifecycle.ViewModel +import androidx.lifecycle.viewModelScope +import com.squirtles.user.usecase.ClearUserUseCase +import com.squirtles.user.usecase.CreateGoogleIdUserUseCase +import com.squirtles.user.usecase.FetchUserUseCase +import com.google.android.libraries.identity.googleid.GoogleIdTokenCredential +import dagger.hilt.android.lifecycle.HiltViewModel +import kotlinx.coroutines.flow.MutableSharedFlow +import kotlinx.coroutines.flow.asSharedFlow +import kotlinx.coroutines.launch +import javax.inject.Inject + +@HiltViewModel +class AccountViewModel @Inject constructor( + private val fetchUserUseCase: FetchUserUseCase, + private val createGoogleIdUserUseCase: CreateGoogleIdUserUseCase, + private val clearUserUseCase: ClearUserUseCase, +) : ViewModel() { + + private val _signInSuccess = MutableSharedFlow() + val signInSuccess = _signInSuccess.asSharedFlow() + + private val _signOutSuccess = MutableSharedFlow() + val signOutSuccess = _signOutSuccess.asSharedFlow() + + fun signIn(credential: GoogleIdTokenCredential) { + viewModelScope.launch { + fetchUserUseCase(credential.id) + .onSuccess { + Log.d("SignIn", "기존 계정 ${it.userId} 로그인") + _signInSuccess.emit(true) + } + .onFailure { + createGoogleIdUser(credential) + } + } + } + + private fun createGoogleIdUser(credential: GoogleIdTokenCredential) { + viewModelScope.launch { + createGoogleIdUserUseCase( + userId = credential.id, + userName = credential.displayName, + userProfileImage = credential.profilePictureUri.toString() + ).onSuccess { + Log.d("SignIn", "새로운 계정 ${it.userId} 로그인") + _signInSuccess.emit(true) + }.onFailure { + _signInSuccess.emit(false) + } + } + } + + fun signOut() { + viewModelScope.launch { + clearUserUseCase() + .onSuccess { _signOutSuccess.emit(true) } + .onFailure { _signOutSuccess.emit(false) } + } + } +} diff --git a/core/account/src/main/java/com/squirtles/account/GoogleId.kt b/core/account/src/main/java/com/squirtles/account/GoogleId.kt new file mode 100644 index 00000000..fba8aa93 --- /dev/null +++ b/core/account/src/main/java/com/squirtles/account/GoogleId.kt @@ -0,0 +1,70 @@ +package com.squirtles.account + +import android.content.Context +import android.util.Log +import android.widget.Toast +import androidx.credentials.ClearCredentialStateRequest +import androidx.credentials.CredentialManager +import androidx.credentials.CustomCredential +import androidx.credentials.GetCredentialRequest +import androidx.credentials.GetCredentialResponse +import androidx.credentials.exceptions.NoCredentialException +import com.google.android.libraries.identity.googleid.GetGoogleIdOption +import com.google.android.libraries.identity.googleid.GoogleIdTokenCredential +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.launch + +class GoogleId(private val context: Context) { + private val credentialManager = CredentialManager.create(context) + + private val googleIdOption: GetGoogleIdOption = GetGoogleIdOption.Builder() + .setFilterByAuthorizedAccounts(false) + .setServerClientId(BuildConfig.GOOGLE_CLIENT_ID) + .setAutoSelectEnabled(true) + .build() + + private val request = GetCredentialRequest.Builder() + .addCredentialOption(googleIdOption) + .build() + + private fun handleSignIn(result: GetCredentialResponse, onSuccess: (GoogleIdTokenCredential) -> Unit) { + when (val data = result.credential) { + is CustomCredential -> { + if (data.type == GoogleIdTokenCredential.TYPE_GOOGLE_ID_TOKEN_CREDENTIAL) { + val googleIdTokenCredential = GoogleIdTokenCredential.createFrom(data.data) + Log.d("GoogleId", "data.type : ${googleIdTokenCredential.id}") + Log.d("GoogleId", "data.type : ${googleIdTokenCredential.displayName}") + Log.d("GoogleId", "data.type : ${googleIdTokenCredential.profilePictureUri.toString()}") + onSuccess(googleIdTokenCredential) + } + } + } + } + + fun signIn(onSuccess: (GoogleIdTokenCredential) -> Unit) { + CoroutineScope(Dispatchers.Main).launch { + runCatching { + val result = credentialManager.getCredential(context, request) + handleSignIn(result, onSuccess) + }.onFailure { exception -> + when (exception) { + is NoCredentialException -> Toast.makeText( + context, + context.getString(R.string.google_id_no_credential_exception_message), + Toast.LENGTH_SHORT + ).show() + } + Log.e("GoogleId", "Google SignIn Error : $exception") + } + } + } + + fun signOut() { + CoroutineScope(Dispatchers.Main).launch { + credentialManager.clearCredentialState( + request = ClearCredentialStateRequest() + ) + } + } +} diff --git a/core/buildconfig/build.gradle.kts b/core/buildconfig/build.gradle.kts index 162217e3..9cf488bd 100644 --- a/core/buildconfig/build.gradle.kts +++ b/core/buildconfig/build.gradle.kts @@ -48,7 +48,7 @@ android { ) } - getByName("debug") { + getByName("release") { isMinifyEnabled = false buildConfigField( diff --git a/core/common/build.gradle.kts b/core/common/build.gradle.kts index d16e4dcc..ed6b8647 100644 --- a/core/common/build.gradle.kts +++ b/core/common/build.gradle.kts @@ -1,11 +1,10 @@ plugins { - alias(libs.plugins.android.library) - alias(libs.plugins.jetbrains.kotlin.android) + alias(libs.plugins.musicroad.android.library) +// alias(libs.plugins.musicroad.compose.library) } android { namespace = "com.squirtles.common" - compileSdk = 34 buildFeatures { compose = true @@ -14,48 +13,17 @@ android { composeOptions { kotlinCompilerExtensionVersion = "1.5.14" } - - defaultConfig { - minSdk = 26 - - testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner" - consumerProguardFiles("consumer-rules.pro") - } - - buildTypes { - release { - isMinifyEnabled = false - proguardFiles(getDefaultProguardFile("proguard-android-optimize.txt"), "proguard-rules.pro") - } - } - compileOptions { - sourceCompatibility = JavaVersion.VERSION_1_8 - targetCompatibility = JavaVersion.VERSION_1_8 - } - kotlinOptions { - jvmTarget = "1.8" - } } dependencies { - implementation(libs.androidx.core.ktx) - implementation(libs.androidx.appcompat) - implementation(libs.material) - implementation(libs.compose.runtime.android) - testImplementation(libs.junit) - androidTestImplementation(libs.androidx.junit) - androidTestImplementation(libs.androidx.espresso.core) - // Compose - implementation(platform(libs.compose.bom)) - implementation(libs.compose.material) - implementation(libs.compose.material3) - implementation(libs.compose.material.icons.extended) - implementation(libs.compose.ui.tooling.preview) +// implementation(platform(libs.compose.bom)) +// implementation(libs.compose.runtime.android) +// implementation(libs.compose.ui.tooling.preview) + implementation(libs.bundles.compose) + implementation(libs.bundles.material) // Coil - implementation(libs.coil) - implementation(libs.coil.compose) - implementation(libs.coil.network.okhttp) + implementation(libs.bundles.coil) } diff --git a/core/common/consumer-rules.pro b/core/common/consumer-rules.pro new file mode 100644 index 00000000..e69de29b diff --git a/core/common/proguard-rules.pro b/core/common/proguard-rules.pro new file mode 100644 index 00000000..481bb434 --- /dev/null +++ b/core/common/proguard-rules.pro @@ -0,0 +1,21 @@ +# Add project specific ProGuard rules here. +# You can control the set of applied configuration files using the +# proguardFiles setting in build.gradle. +# +# For more details, see +# http://developer.android.com/guide/developing/tools/proguard.html + +# If your project uses WebView with JS, uncomment the following +# and specify the fully qualified class name to the JavaScript interface +# class: +#-keepclassmembers class fqcn.of.javascript.interface.for.webview { +# public *; +#} + +# Uncomment this to preserve the line number information for +# debugging stack traces. +#-keepattributes SourceFile,LineNumberTable + +# If you keep the line number information, uncomment this to +# hide the original source file name. +#-renamesourcefileattribute SourceFile \ No newline at end of file diff --git a/core/common/src/main/java/com/squirtles/common/ui/AlbumImage.kt b/core/common/src/main/java/com/squirtles/common/ui/AlbumImage.kt new file mode 100644 index 00000000..104690b9 --- /dev/null +++ b/core/common/src/main/java/com/squirtles/common/ui/AlbumImage.kt @@ -0,0 +1,39 @@ +package com.squirtles.common.ui + +import android.util.Size +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.painter.ColorPainter +import androidx.compose.ui.layout.ContentScale +import androidx.compose.ui.platform.LocalContext +import androidx.compose.ui.res.stringResource +import coil3.compose.AsyncImage +import coil3.request.ImageRequest +import coil3.request.crossfade +import com.squirtles.common.R +import com.squirtles.common.ui.theme.Gray + +@Composable +fun AlbumImage( + imageUrl: String?, + modifier: Modifier = Modifier, + contentDescription: String = stringResource(R.string.map_album_image_description), +) { + AsyncImage( + model = ImageRequest.Builder(LocalContext.current) + .data(imageUrl) + .crossfade(true) + .build(), + contentDescription = contentDescription, + modifier = modifier, + placeholder = ColorPainter(Gray), + error = ColorPainter(Gray), + contentScale = ContentScale.Crop, + ) +} + +fun String.toImageUrlWithSize(size: Size): String? { + return if (isEmpty()) null + else replace("{w}", size.width.toString()) + .replace("{h}", size.height.toString()) +} diff --git a/core/common/src/main/java/com/squirtles/common/ui/Constants.kt b/core/common/src/main/java/com/squirtles/common/ui/Constants.kt new file mode 100644 index 00000000..702acc09 --- /dev/null +++ b/core/common/src/main/java/com/squirtles/common/ui/Constants.kt @@ -0,0 +1,17 @@ +package com.squirtles.common.ui + +import android.util.Size +import androidx.compose.ui.unit.dp +import com.squirtles.common.ui.theme.Black +import com.squirtles.common.ui.theme.Primary + +object Constants { + val DEFAULT_PADDING = 16.dp + + val REQUEST_IMAGE_SIZE_DEFAULT = Size(300, 300) + + val COLOR_STOPS = arrayOf( + 0.0f to Primary, + 0.25f to Black + ) +} diff --git a/core/common/src/main/java/com/squirtles/common/ui/CreatedByPickText.kt b/core/common/src/main/java/com/squirtles/common/ui/CreatedByPickText.kt new file mode 100644 index 00000000..5cfc8e0c --- /dev/null +++ b/core/common/src/main/java/com/squirtles/common/ui/CreatedByPickText.kt @@ -0,0 +1,64 @@ +package com.squirtles.common.ui + +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.text.SpanStyle +import androidx.compose.ui.text.TextStyle +import androidx.compose.ui.text.buildAnnotatedString +import androidx.compose.ui.text.style.TextDecoration +import androidx.compose.ui.text.style.TextOverflow +import androidx.compose.ui.text.withStyle +import com.squirtles.common.R + +@Composable +fun CreatedBySelfText( + modifier: Modifier = Modifier, + showUnderline: Boolean = false, + color: Color = MaterialTheme.colorScheme.onSurface, + style: TextStyle = MaterialTheme.typography.bodyMedium +) { + val myPickDescription = buildAnnotatedString { + withStyle(style = SpanStyle(textDecoration = if (showUnderline) TextDecoration.Underline else null)) { + append(stringResource(R.string.pick_created_by_self_1)) + } + append(" ${stringResource(R.string.pick_created_by_self_2)}") + } + + Text( + text = myPickDescription, + modifier = modifier, + color = color, + overflow = TextOverflow.Ellipsis, + maxLines = 1, + style = style + ) +} + +@Composable +fun CreatedByOtherUserText( + userName: String, + modifier: Modifier = Modifier, + showUnderline: Boolean = false, + color: Color = MaterialTheme.colorScheme.onSurface, + style: TextStyle = MaterialTheme.typography.bodyMedium +) { + Text( + text = userName, + modifier = modifier, + color = color, + textDecoration = if (showUnderline) TextDecoration.Underline else null, + overflow = TextOverflow.Ellipsis, + maxLines = 1, + style = style + ) + + Text( + text = stringResource(id = R.string.map_info_window_pick_user), + color = color, + style = style, + ) +} diff --git a/core/common/src/main/java/com/squirtles/common/ui/DefaultTopAppBar.kt b/core/common/src/main/java/com/squirtles/common/ui/DefaultTopAppBar.kt new file mode 100644 index 00000000..9172674e --- /dev/null +++ b/core/common/src/main/java/com/squirtles/common/ui/DefaultTopAppBar.kt @@ -0,0 +1,56 @@ +package com.squirtles.common.ui + +import androidx.compose.foundation.layout.RowScope +import androidx.compose.foundation.layout.displayCutoutPadding +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.automirrored.filled.ArrowBack +import androidx.compose.material3.CenterAlignedTopAppBar +import androidx.compose.material3.ExperimentalMaterial3Api +import androidx.compose.material3.Icon +import androidx.compose.material3.IconButton +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.Text +import androidx.compose.material3.TopAppBarDefaults +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.text.TextStyle +import androidx.compose.ui.text.font.FontWeight +import com.squirtles.common.R +import com.squirtles.common.ui.theme.White + +@OptIn(ExperimentalMaterial3Api::class) +@Composable +fun DefaultTopAppBar( + title: String, + titleStyle: TextStyle = MaterialTheme.typography.titleLarge.copy(fontWeight = FontWeight.Bold), + onBackClick: () -> Unit, + actions: @Composable RowScope.() -> Unit = {}, +) { + CenterAlignedTopAppBar( + title = { + Text( + text = title, + style = titleStyle + ) + }, + modifier = Modifier.displayCutoutPadding(), + navigationIcon = { + IconButton( + onClick = onBackClick + ) { + Icon( + imageVector = Icons.AutoMirrored.Filled.ArrowBack, + contentDescription = stringResource(R.string.top_app_bar_back_description), + tint = White + ) + } + }, + actions = actions, + colors = TopAppBarDefaults.centerAlignedTopAppBarColors().copy( + containerColor = Color.Transparent, + titleContentColor = White + ) + ) +} diff --git a/core/common/src/main/java/com/squirtles/common/ui/MessageAlertDialog.kt b/core/common/src/main/java/com/squirtles/common/ui/MessageAlertDialog.kt new file mode 100644 index 00000000..ad73e258 --- /dev/null +++ b/core/common/src/main/java/com/squirtles/common/ui/MessageAlertDialog.kt @@ -0,0 +1,126 @@ +package com.squirtles.common.ui + +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.RowScope +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.material3.BasicAlertDialog +import androidx.compose.material3.ButtonDefaults +import androidx.compose.material3.ExperimentalMaterial3Api +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.Surface +import androidx.compose.material3.Text +import androidx.compose.material3.TextButton +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.dp +import com.squirtles.common.R +import com.squirtles.common.ui.theme.Black +import com.squirtles.common.ui.theme.MusicRoadTheme +import com.squirtles.common.ui.theme.Primary +import com.squirtles.common.ui.theme.White + +@OptIn(ExperimentalMaterial3Api::class) +@Composable +internal fun MessageAlertDialog( + onDismissRequest: () -> Unit, + title: String, + body: String, + buttons: @Composable RowScope.() -> Unit, +) { + BasicAlertDialog( + onDismissRequest = { onDismissRequest() }, + ) { + Surface( + modifier = Modifier.fillMaxWidth(), + shape = RoundedCornerShape(16.dp), + color = White + ) { + Column( + modifier = Modifier.padding(24.dp), + verticalArrangement = Arrangement.Center + ) { + Text( + text = title, + color = Black, + fontWeight = FontWeight.Bold, + style = MaterialTheme.typography.bodyLarge + ) + + VerticalSpacer(8) + + Text( + text = body, + color = Black, + style = MaterialTheme.typography.bodyLarge + ) + + VerticalSpacer(24) + + Row( + modifier = Modifier.fillMaxWidth(), + horizontalArrangement = Arrangement.End, + verticalAlignment = Alignment.CenterVertically, + content = buttons + ) + } + } + } +} + +@Composable +internal fun DialogTextButton( + onClick: () -> Unit, + text: String, + textColor: Color = Black, + buttonColor: Color = Color.Transparent, + fontWeight: FontWeight? = null, +) { + TextButton( + onClick = onClick, + colors = ButtonDefaults.buttonColors().copy( + containerColor = buttonColor, + contentColor = textColor + ) + ) { + Text( + text = text, + fontWeight = fontWeight, + ) + } +} + +@Preview(showBackground = true) +@Composable +private fun DeletePickDialogPreview() { + MusicRoadTheme { + MessageAlertDialog( + onDismissRequest = {}, + title = stringResource(R.string.delete_pick_dialog_title), + body = stringResource(R.string.delete_pick_dialog_body), + buttons = { + DialogTextButton( + onClick = {}, + text = "취소" + ) + + HorizontalSpacer(8) + + DialogTextButton( + onClick = {}, + text = "삭제하기", + textColor = Primary, + fontWeight = FontWeight.Bold + ) + } + ) + } +} diff --git a/core/common/src/main/java/com/squirtles/common/ui/PickInfoText.kt b/core/common/src/main/java/com/squirtles/common/ui/PickInfoText.kt new file mode 100644 index 00000000..38c6617b --- /dev/null +++ b/core/common/src/main/java/com/squirtles/common/ui/PickInfoText.kt @@ -0,0 +1,102 @@ +package com.squirtles.common.ui + +import androidx.compose.material3.Icon +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.res.painterResource +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.text.SpanStyle +import androidx.compose.ui.text.TextStyle +import androidx.compose.ui.text.buildAnnotatedString +import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.text.style.TextOverflow +import androidx.compose.ui.text.withStyle +import com.squirtles.common.R +import com.squirtles.common.ui.theme.Primary + +@Composable +fun SongInfoText( + songInfo: String, + color: Color = MaterialTheme.colorScheme.onSurface +) { + Text( + text = songInfo, + color = color, + overflow = TextOverflow.Ellipsis, + maxLines = 1, + style = MaterialTheme.typography.titleMedium, + ) +} + +@Composable +fun FavoriteCountText( + favoriteCount: Int, + iconTint: Color = Primary, + color: Color = MaterialTheme.colorScheme.onSurface, + style: TextStyle = MaterialTheme.typography.bodyMedium +) { + Icon( + painter = painterResource(id = R.drawable.ic_favorite), + contentDescription = stringResource(R.string.map_info_window_favorite_count_icon_description), + tint = iconTint + ) + + Text( + text = " $favoriteCount", + color = color, + style = style, + ) +} + +@Composable +fun CommentText( + comment: String, + color: Color = MaterialTheme.colorScheme.onSecondary, + overflow: TextOverflow = TextOverflow.Ellipsis, + maxLines: Int = 1, + style: TextStyle = MaterialTheme.typography.bodyMedium +) { + Text( + text = comment, + color = color, + overflow = overflow, + maxLines = maxLines, + style = style, + ) +} + +@Composable +fun CountText( + totalCount: Int, + modifier: Modifier = Modifier, + countLabel: String = stringResource(R.string.total_count_text), + defaultColor: Color = MaterialTheme.colorScheme.onSurface, + pointColor: Color = MaterialTheme.colorScheme.primary, + style: TextStyle = MaterialTheme.typography.titleMedium +) { + Text( + text = buildAnnotatedString { + withStyle( + SpanStyle( + color = defaultColor, + fontWeight = FontWeight.Bold + ) + ) { + append("$countLabel ") + } + withStyle( + SpanStyle( + color = pointColor, + fontWeight = FontWeight.Bold + ) + ) { + append("$totalCount") + } + }, + modifier = modifier, + style = style + ) +} diff --git a/core/common/src/main/java/com/squirtles/common/ui/Spacer.kt b/core/common/src/main/java/com/squirtles/common/ui/Spacer.kt new file mode 100644 index 00000000..2c2e9750 --- /dev/null +++ b/core/common/src/main/java/com/squirtles/common/ui/Spacer.kt @@ -0,0 +1,14 @@ +package com.squirtles.common.ui + +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.width +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import androidx.compose.ui.unit.dp + +@Composable +fun VerticalSpacer(height: Int) = Spacer(Modifier.height(height.dp)) + +@Composable +fun HorizontalSpacer(width: Int) = Spacer(Modifier.width(width.dp)) diff --git a/core/common/src/main/java/com/squirtles/common/ui/theme/Color.kt b/core/common/src/main/java/com/squirtles/common/ui/theme/Color.kt new file mode 100644 index 00000000..daf81b42 --- /dev/null +++ b/core/common/src/main/java/com/squirtles/common/ui/theme/Color.kt @@ -0,0 +1,21 @@ +package com.squirtles.common.ui.theme + +import androidx.compose.ui.graphics.Color + +val Primary = Color(0xFFFF5F61) +val Primary80 = Color(0xFFFFB3B0) +val Primary50 = Color(0xFFAD625F) +val Primary20 = Color(0xFF571D1E) +val Blue = Color(0xFF6B84FF) + +val Purple = Color(0xFFBB8280) +val Purple15 = Color(0x26BB8280) +val PurpleGrey = Color(0xFFB4AEAE) + +val Black = Color(0xFF000000) +val Dark = Color(0xFF151515) +val DarkGray = Color(0xFF646464) +val Gray = Color(0xFFAAAAAA) +val White = Color(0xFFFFFFFF) + +val PlayerBackground = Color(0xFF353535) diff --git a/core/common/src/main/java/com/squirtles/common/ui/theme/Theme.kt b/core/common/src/main/java/com/squirtles/common/ui/theme/Theme.kt new file mode 100644 index 00000000..a27ee6e2 --- /dev/null +++ b/core/common/src/main/java/com/squirtles/common/ui/theme/Theme.kt @@ -0,0 +1,45 @@ +package com.squirtles.common.ui.theme + +import androidx.compose.foundation.isSystemInDarkTheme +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.darkColorScheme +import androidx.compose.material3.lightColorScheme +import androidx.compose.runtime.Composable + +private val DarkColorScheme = darkColorScheme( + primary = Primary, + onPrimary = Black, + primaryContainer = White, + onPrimaryContainer = Black, + secondary = Blue, + tertiary = Purple, + surface = Black, + onSurface = White, + onSurfaceVariant = DarkGray, + onSecondary = Gray +) + +private val LightColorScheme = lightColorScheme( + primary = Primary, + onPrimary = White, + primaryContainer = Dark, + onPrimaryContainer = White, + secondary = Blue, + tertiary = Purple, + surface = White, + onSurface = Black, + onSurfaceVariant = Gray, + onSecondary = DarkGray +) + +@Composable +fun MusicRoadTheme( + darkTheme: Boolean = isSystemInDarkTheme(), + content: @Composable () -> Unit +) { + MaterialTheme( + colorScheme = if (darkTheme) DarkColorScheme else LightColorScheme, + typography = Typography, + content = content + ) +} diff --git a/core/common/src/main/java/com/squirtles/common/ui/theme/Type.kt b/core/common/src/main/java/com/squirtles/common/ui/theme/Type.kt new file mode 100644 index 00000000..17a898e2 --- /dev/null +++ b/core/common/src/main/java/com/squirtles/common/ui/theme/Type.kt @@ -0,0 +1,34 @@ +package com.squirtles.common.ui.theme + +import androidx.compose.material3.Typography +import androidx.compose.ui.text.TextStyle +import androidx.compose.ui.text.font.FontFamily +import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.unit.sp + +// Set of Material typography styles to start with +val Typography = Typography( + bodyLarge = TextStyle( + fontFamily = FontFamily.Default, + fontWeight = FontWeight.Normal, + fontSize = 16.sp, + lineHeight = 24.sp, + letterSpacing = 0.5.sp + ) + /* Other default text styles to override + titleLarge = TextStyle( + fontFamily = FontFamily.Default, + fontWeight = FontWeight.Normal, + fontSize = 22.sp, + lineHeight = 28.sp, + letterSpacing = 0.sp + ), + labelSmall = TextStyle( + fontFamily = FontFamily.Default, + fontWeight = FontWeight.Medium, + fontSize = 11.sp, + lineHeight = 16.sp, + letterSpacing = 0.5.sp + ) + */ +) diff --git a/core/mediaservice/build.gradle.kts b/core/mediaservice/build.gradle.kts index 3a7aa9b3..39081611 100644 --- a/core/mediaservice/build.gradle.kts +++ b/core/mediaservice/build.gradle.kts @@ -23,11 +23,11 @@ android { } } compileOptions { - sourceCompatibility = JavaVersion.VERSION_1_8 - targetCompatibility = JavaVersion.VERSION_1_8 + sourceCompatibility = JavaVersion.VERSION_17 + targetCompatibility = JavaVersion.VERSION_17 } kotlinOptions { - jvmTarget = "1.8" + jvmTarget = "17" } } diff --git a/core/model/build.gradle.kts b/core/model/build.gradle.kts index 47e6f1ab..89e1c426 100644 --- a/core/model/build.gradle.kts +++ b/core/model/build.gradle.kts @@ -1,18 +1,8 @@ plugins { - id("java-library") - alias(libs.plugins.jetbrains.kotlin.jvm) + alias(libs.plugins.musicroad.java.library) alias(libs.plugins.kotlin.serialization) } -java { - sourceCompatibility = JavaVersion.VERSION_1_8 - targetCompatibility = JavaVersion.VERSION_1_8 -} - -kotlin { - jvmToolchain(8) -} - dependencies { // Serialization implementation(libs.kotlinx.serialization.json) diff --git a/core/model/consumer-rules.pro b/core/model/consumer-rules.pro new file mode 100644 index 00000000..e69de29b diff --git a/core/model/proguard-rules.pro b/core/model/proguard-rules.pro new file mode 100644 index 00000000..481bb434 --- /dev/null +++ b/core/model/proguard-rules.pro @@ -0,0 +1,21 @@ +# Add project specific ProGuard rules here. +# You can control the set of applied configuration files using the +# proguardFiles setting in build.gradle. +# +# For more details, see +# http://developer.android.com/guide/developing/tools/proguard.html + +# If your project uses WebView with JS, uncomment the following +# and specify the fully qualified class name to the JavaScript interface +# class: +#-keepclassmembers class fqcn.of.javascript.interface.for.webview { +# public *; +#} + +# Uncomment this to preserve the line number information for +# debugging stack traces. +#-keepattributes SourceFile,LineNumberTable + +# If you keep the line number information, uncomment this to +# hide the original source file name. +#-renamesourcefileattribute SourceFile \ No newline at end of file diff --git a/core/musicplayer/build.gradle.kts b/core/musicplayer/build.gradle.kts index 16713631..af7ec7ac 100644 --- a/core/musicplayer/build.gradle.kts +++ b/core/musicplayer/build.gradle.kts @@ -23,11 +23,11 @@ android { } } compileOptions { - sourceCompatibility = JavaVersion.VERSION_1_8 - targetCompatibility = JavaVersion.VERSION_1_8 + sourceCompatibility = JavaVersion.VERSION_17 + targetCompatibility = JavaVersion.VERSION_17 } kotlinOptions { - jvmTarget = "1.8" + jvmTarget = "17" } } diff --git a/core/navigation/build.gradle.kts b/core/navigation/build.gradle.kts index 38061110..32fffc37 100644 --- a/core/navigation/build.gradle.kts +++ b/core/navigation/build.gradle.kts @@ -5,8 +5,8 @@ plugins { } java { - sourceCompatibility = JavaVersion.VERSION_21 - targetCompatibility = JavaVersion.VERSION_21 + sourceCompatibility = JavaVersion.VERSION_17 + targetCompatibility = JavaVersion.VERSION_17 } dependencies { diff --git a/core/navigation/consumer-rules.pro b/core/navigation/consumer-rules.pro new file mode 100644 index 00000000..e69de29b diff --git a/core/navigation/proguard-rules.pro b/core/navigation/proguard-rules.pro new file mode 100644 index 00000000..481bb434 --- /dev/null +++ b/core/navigation/proguard-rules.pro @@ -0,0 +1,21 @@ +# Add project specific ProGuard rules here. +# You can control the set of applied configuration files using the +# proguardFiles setting in build.gradle. +# +# For more details, see +# http://developer.android.com/guide/developing/tools/proguard.html + +# If your project uses WebView with JS, uncomment the following +# and specify the fully qualified class name to the JavaScript interface +# class: +#-keepclassmembers class fqcn.of.javascript.interface.for.webview { +# public *; +#} + +# Uncomment this to preserve the line number information for +# debugging stack traces. +#-keepattributes SourceFile,LineNumberTable + +# If you keep the line number information, uncomment this to +# hide the original source file name. +#-renamesourcefileattribute SourceFile \ No newline at end of file diff --git a/core/picklist/build.gradle.kts b/core/picklist/build.gradle.kts index c67453a5..13b26018 100644 --- a/core/picklist/build.gradle.kts +++ b/core/picklist/build.gradle.kts @@ -29,11 +29,11 @@ android { } } compileOptions { - sourceCompatibility = JavaVersion.VERSION_1_8 - targetCompatibility = JavaVersion.VERSION_1_8 + sourceCompatibility = JavaVersion.VERSION_17 + targetCompatibility = JavaVersion.VERSION_17 } kotlinOptions { - jvmTarget = "1.8" + jvmTarget = "17" } } diff --git a/core/util/build.gradle.kts b/core/util/build.gradle.kts index 2e14baeb..aca3eb7c 100644 --- a/core/util/build.gradle.kts +++ b/core/util/build.gradle.kts @@ -22,11 +22,11 @@ android { } } compileOptions { - sourceCompatibility = JavaVersion.VERSION_1_8 - targetCompatibility = JavaVersion.VERSION_1_8 + sourceCompatibility = JavaVersion.VERSION_17 + targetCompatibility = JavaVersion.VERSION_17 } kotlinOptions { - jvmTarget = "1.8" + jvmTarget = "17" } } diff --git a/data/applemusic/build.gradle.kts b/data/applemusic/build.gradle.kts index e9fc6328..cae3631d 100644 --- a/data/applemusic/build.gradle.kts +++ b/data/applemusic/build.gradle.kts @@ -36,11 +36,11 @@ android { } } compileOptions { - sourceCompatibility = JavaVersion.VERSION_1_8 - targetCompatibility = JavaVersion.VERSION_1_8 + sourceCompatibility = JavaVersion.VERSION_17 + targetCompatibility = JavaVersion.VERSION_17 } kotlinOptions { - jvmTarget = "1.8" + jvmTarget = "17" } buildFeatures { buildConfig = true diff --git a/data/build.gradle.kts b/data/build.gradle.kts index 92a6efd5..7f1a6e8c 100644 --- a/data/build.gradle.kts +++ b/data/build.gradle.kts @@ -21,30 +21,11 @@ android { testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner" consumerProguardFiles("consumer-rules.pro") - - buildConfigField( - "String", - "APPLE_MUSIC_API_TOKEN", - "\"${properties.getProperty("APPLE_MUSIC_API_TOKEN")}\"" - ) - } buildTypes { debug { isMinifyEnabled = false - - buildConfigField( - "String", - "FIRESTORE_DB_ID", - "\"${properties.getProperty("FIRESTORE_DB_ID_DEBUG")}\"" - ) - - buildConfigField( - "String", - "HTTPS_CALLABLE", - "\"${properties.getProperty("HTTPS_CALLABLE_DEBUG")}\"" - ) } release { @@ -53,26 +34,14 @@ android { getDefaultProguardFile("proguard-android-optimize.txt"), "proguard-rules.pro" ) - - buildConfigField( - "String", - "FIRESTORE_DB_ID", - "\"${properties.getProperty("FIRESTORE_DB_ID_RELEASE")}\"" - ) - - buildConfigField( - "String", - "HTTPS_CALLABLE", - "\"${properties.getProperty("HTTPS_CALLABLE_RELEASE")}\"" - ) } } compileOptions { - sourceCompatibility = JavaVersion.VERSION_1_8 - targetCompatibility = JavaVersion.VERSION_1_8 + sourceCompatibility = JavaVersion.VERSION_17 + targetCompatibility = JavaVersion.VERSION_17 } kotlinOptions { - jvmTarget = "1.8" + jvmTarget = "17" } buildFeatures { buildConfig = true @@ -81,6 +50,7 @@ android { dependencies { implementation(projects.domain) + implementation(projects.core.buildconfig) implementation(libs.androidx.core.ktx) implementation(libs.androidx.appcompat) @@ -93,7 +63,6 @@ dependencies { implementation(libs.firebase.firestore.ktx) implementation(libs.firebase.functions.ktx) implementation(libs.geofire.android.common) -// implementation(libs.kotlinx.coroutines.play.services) // Hilt implementation(libs.hilt.android) diff --git a/data/favorite/build.gradle.kts b/data/favorite/build.gradle.kts index d21ac157..ab7610bb 100644 --- a/data/favorite/build.gradle.kts +++ b/data/favorite/build.gradle.kts @@ -49,11 +49,11 @@ android { } } compileOptions { - sourceCompatibility = JavaVersion.VERSION_1_8 - targetCompatibility = JavaVersion.VERSION_1_8 + sourceCompatibility = JavaVersion.VERSION_17 + targetCompatibility = JavaVersion.VERSION_17 } kotlinOptions { - jvmTarget = "1.8" + jvmTarget = "17" } buildFeatures { buildConfig = true diff --git a/data/firebase/build.gradle.kts b/data/firebase/build.gradle.kts index e803d299..25d84681 100644 --- a/data/firebase/build.gradle.kts +++ b/data/firebase/build.gradle.kts @@ -49,11 +49,11 @@ android { } } compileOptions { - sourceCompatibility = JavaVersion.VERSION_1_8 - targetCompatibility = JavaVersion.VERSION_1_8 + sourceCompatibility = JavaVersion.VERSION_17 + targetCompatibility = JavaVersion.VERSION_17 } kotlinOptions { - jvmTarget = "1.8" + jvmTarget = "17" } buildFeatures { buildConfig = true diff --git a/data/location/build.gradle.kts b/data/location/build.gradle.kts index 0b726ce3..98ea2722 100644 --- a/data/location/build.gradle.kts +++ b/data/location/build.gradle.kts @@ -23,11 +23,11 @@ android { } } compileOptions { - sourceCompatibility = JavaVersion.VERSION_1_8 - targetCompatibility = JavaVersion.VERSION_1_8 + sourceCompatibility = JavaVersion.VERSION_17 + targetCompatibility = JavaVersion.VERSION_17 } kotlinOptions { - jvmTarget = "1.8" + jvmTarget = "17" } } diff --git a/data/order/build.gradle.kts b/data/order/build.gradle.kts index 60a20058..02e8cd68 100644 --- a/data/order/build.gradle.kts +++ b/data/order/build.gradle.kts @@ -23,11 +23,11 @@ android { } } compileOptions { - sourceCompatibility = JavaVersion.VERSION_1_8 - targetCompatibility = JavaVersion.VERSION_1_8 + sourceCompatibility = JavaVersion.VERSION_17 + targetCompatibility = JavaVersion.VERSION_17 } kotlinOptions { - jvmTarget = "1.8" + jvmTarget = "17" } } diff --git a/data/pick/build.gradle.kts b/data/pick/build.gradle.kts index 21015038..488d06b4 100644 --- a/data/pick/build.gradle.kts +++ b/data/pick/build.gradle.kts @@ -23,11 +23,11 @@ android { } } compileOptions { - sourceCompatibility = JavaVersion.VERSION_1_8 - targetCompatibility = JavaVersion.VERSION_1_8 + sourceCompatibility = JavaVersion.VERSION_17 + targetCompatibility = JavaVersion.VERSION_17 } kotlinOptions { - jvmTarget = "1.8" + jvmTarget = "17" } } diff --git a/data/user/build.gradle.kts b/data/user/build.gradle.kts index b95bccc8..413686c1 100644 --- a/data/user/build.gradle.kts +++ b/data/user/build.gradle.kts @@ -23,11 +23,11 @@ android { } } compileOptions { - sourceCompatibility = JavaVersion.VERSION_1_8 - targetCompatibility = JavaVersion.VERSION_1_8 + sourceCompatibility = JavaVersion.VERSION_17 + targetCompatibility = JavaVersion.VERSION_17 } kotlinOptions { - jvmTarget = "1.8" + jvmTarget = "17" } } diff --git a/domain/applemusic/build.gradle.kts b/domain/applemusic/build.gradle.kts index 364fd0b8..8aec2ced 100644 --- a/domain/applemusic/build.gradle.kts +++ b/domain/applemusic/build.gradle.kts @@ -11,11 +11,11 @@ android { minSdk = 26 } compileOptions { - sourceCompatibility = JavaVersion.VERSION_1_8 - targetCompatibility = JavaVersion.VERSION_1_8 + sourceCompatibility = JavaVersion.VERSION_17 + targetCompatibility = JavaVersion.VERSION_17 } kotlinOptions { - jvmTarget = "1.8" + jvmTarget = "17" } } diff --git a/domain/applemusic/consumer-rules.pro b/domain/applemusic/consumer-rules.pro new file mode 100644 index 00000000..e69de29b diff --git a/domain/applemusic/proguard-rules.pro b/domain/applemusic/proguard-rules.pro new file mode 100644 index 00000000..481bb434 --- /dev/null +++ b/domain/applemusic/proguard-rules.pro @@ -0,0 +1,21 @@ +# Add project specific ProGuard rules here. +# You can control the set of applied configuration files using the +# proguardFiles setting in build.gradle. +# +# For more details, see +# http://developer.android.com/guide/developing/tools/proguard.html + +# If your project uses WebView with JS, uncomment the following +# and specify the fully qualified class name to the JavaScript interface +# class: +#-keepclassmembers class fqcn.of.javascript.interface.for.webview { +# public *; +#} + +# Uncomment this to preserve the line number information for +# debugging stack traces. +#-keepattributes SourceFile,LineNumberTable + +# If you keep the line number information, uncomment this to +# hide the original source file name. +#-renamesourcefileattribute SourceFile \ No newline at end of file diff --git a/domain/build.gradle.kts b/domain/build.gradle.kts index 5255e9d6..5a2535ea 100644 --- a/domain/build.gradle.kts +++ b/domain/build.gradle.kts @@ -24,11 +24,11 @@ android { } } compileOptions { - sourceCompatibility = JavaVersion.VERSION_1_8 - targetCompatibility = JavaVersion.VERSION_1_8 + sourceCompatibility = JavaVersion.VERSION_17 + targetCompatibility = JavaVersion.VERSION_17 } kotlinOptions { - jvmTarget = "1.8" + jvmTarget = "17" } } diff --git a/domain/favorite/build.gradle.kts b/domain/favorite/build.gradle.kts index a54401f8..64611a79 100644 --- a/domain/favorite/build.gradle.kts +++ b/domain/favorite/build.gradle.kts @@ -4,8 +4,8 @@ plugins { } java { - sourceCompatibility = JavaVersion.VERSION_21 - targetCompatibility = JavaVersion.VERSION_21 + sourceCompatibility = JavaVersion.VERSION_17 + targetCompatibility = JavaVersion.VERSION_17 } dependencies { diff --git a/domain/favorite/consumer-rules.pro b/domain/favorite/consumer-rules.pro new file mode 100644 index 00000000..e69de29b diff --git a/domain/favorite/proguard-rules.pro b/domain/favorite/proguard-rules.pro new file mode 100644 index 00000000..481bb434 --- /dev/null +++ b/domain/favorite/proguard-rules.pro @@ -0,0 +1,21 @@ +# Add project specific ProGuard rules here. +# You can control the set of applied configuration files using the +# proguardFiles setting in build.gradle. +# +# For more details, see +# http://developer.android.com/guide/developing/tools/proguard.html + +# If your project uses WebView with JS, uncomment the following +# and specify the fully qualified class name to the JavaScript interface +# class: +#-keepclassmembers class fqcn.of.javascript.interface.for.webview { +# public *; +#} + +# Uncomment this to preserve the line number information for +# debugging stack traces. +#-keepattributes SourceFile,LineNumberTable + +# If you keep the line number information, uncomment this to +# hide the original source file name. +#-renamesourcefileattribute SourceFile \ No newline at end of file diff --git a/domain/location/build.gradle.kts b/domain/location/build.gradle.kts index 564bb1cc..fa2c42be 100644 --- a/domain/location/build.gradle.kts +++ b/domain/location/build.gradle.kts @@ -11,11 +11,11 @@ android { minSdk = 26 } compileOptions { - sourceCompatibility = JavaVersion.VERSION_1_8 - targetCompatibility = JavaVersion.VERSION_1_8 + sourceCompatibility = JavaVersion.VERSION_17 + targetCompatibility = JavaVersion.VERSION_17 } kotlinOptions { - jvmTarget = "1.8" + jvmTarget = "17" } } diff --git a/domain/location/consumer-rules.pro b/domain/location/consumer-rules.pro new file mode 100644 index 00000000..e69de29b diff --git a/domain/location/proguard-rules.pro b/domain/location/proguard-rules.pro new file mode 100644 index 00000000..481bb434 --- /dev/null +++ b/domain/location/proguard-rules.pro @@ -0,0 +1,21 @@ +# Add project specific ProGuard rules here. +# You can control the set of applied configuration files using the +# proguardFiles setting in build.gradle. +# +# For more details, see +# http://developer.android.com/guide/developing/tools/proguard.html + +# If your project uses WebView with JS, uncomment the following +# and specify the fully qualified class name to the JavaScript interface +# class: +#-keepclassmembers class fqcn.of.javascript.interface.for.webview { +# public *; +#} + +# Uncomment this to preserve the line number information for +# debugging stack traces. +#-keepattributes SourceFile,LineNumberTable + +# If you keep the line number information, uncomment this to +# hide the original source file name. +#-renamesourcefileattribute SourceFile \ No newline at end of file diff --git a/domain/order/build.gradle.kts b/domain/order/build.gradle.kts index 41bb4029..511ba9af 100644 --- a/domain/order/build.gradle.kts +++ b/domain/order/build.gradle.kts @@ -4,8 +4,8 @@ plugins { } java { - sourceCompatibility = JavaVersion.VERSION_21 - targetCompatibility = JavaVersion.VERSION_21 + sourceCompatibility = JavaVersion.VERSION_17 + targetCompatibility = JavaVersion.VERSION_17 } dependencies { implementation(projects.core.model) diff --git a/domain/order/consumer-rules.pro b/domain/order/consumer-rules.pro new file mode 100644 index 00000000..e69de29b diff --git a/domain/order/proguard-rules.pro b/domain/order/proguard-rules.pro new file mode 100644 index 00000000..481bb434 --- /dev/null +++ b/domain/order/proguard-rules.pro @@ -0,0 +1,21 @@ +# Add project specific ProGuard rules here. +# You can control the set of applied configuration files using the +# proguardFiles setting in build.gradle. +# +# For more details, see +# http://developer.android.com/guide/developing/tools/proguard.html + +# If your project uses WebView with JS, uncomment the following +# and specify the fully qualified class name to the JavaScript interface +# class: +#-keepclassmembers class fqcn.of.javascript.interface.for.webview { +# public *; +#} + +# Uncomment this to preserve the line number information for +# debugging stack traces. +#-keepattributes SourceFile,LineNumberTable + +# If you keep the line number information, uncomment this to +# hide the original source file name. +#-renamesourcefileattribute SourceFile \ No newline at end of file diff --git a/domain/pick/build.gradle.kts b/domain/pick/build.gradle.kts index a54401f8..64611a79 100644 --- a/domain/pick/build.gradle.kts +++ b/domain/pick/build.gradle.kts @@ -4,8 +4,8 @@ plugins { } java { - sourceCompatibility = JavaVersion.VERSION_21 - targetCompatibility = JavaVersion.VERSION_21 + sourceCompatibility = JavaVersion.VERSION_17 + targetCompatibility = JavaVersion.VERSION_17 } dependencies { diff --git a/domain/pick/consumer-rules.pro b/domain/pick/consumer-rules.pro new file mode 100644 index 00000000..e69de29b diff --git a/domain/pick/proguard-rules.pro b/domain/pick/proguard-rules.pro new file mode 100644 index 00000000..481bb434 --- /dev/null +++ b/domain/pick/proguard-rules.pro @@ -0,0 +1,21 @@ +# Add project specific ProGuard rules here. +# You can control the set of applied configuration files using the +# proguardFiles setting in build.gradle. +# +# For more details, see +# http://developer.android.com/guide/developing/tools/proguard.html + +# If your project uses WebView with JS, uncomment the following +# and specify the fully qualified class name to the JavaScript interface +# class: +#-keepclassmembers class fqcn.of.javascript.interface.for.webview { +# public *; +#} + +# Uncomment this to preserve the line number information for +# debugging stack traces. +#-keepattributes SourceFile,LineNumberTable + +# If you keep the line number information, uncomment this to +# hide the original source file name. +#-renamesourcefileattribute SourceFile \ No newline at end of file diff --git a/domain/picklist/build.gradle.kts b/domain/picklist/build.gradle.kts index 2d8b61e1..3926c5c5 100644 --- a/domain/picklist/build.gradle.kts +++ b/domain/picklist/build.gradle.kts @@ -4,8 +4,8 @@ plugins { } java { - sourceCompatibility = JavaVersion.VERSION_21 - targetCompatibility = JavaVersion.VERSION_21 + sourceCompatibility = JavaVersion.VERSION_17 + targetCompatibility = JavaVersion.VERSION_17 } dependencies { diff --git a/domain/picklist/consumer-rules.pro b/domain/picklist/consumer-rules.pro new file mode 100644 index 00000000..e69de29b diff --git a/domain/picklist/proguard-rules.pro b/domain/picklist/proguard-rules.pro new file mode 100644 index 00000000..481bb434 --- /dev/null +++ b/domain/picklist/proguard-rules.pro @@ -0,0 +1,21 @@ +# Add project specific ProGuard rules here. +# You can control the set of applied configuration files using the +# proguardFiles setting in build.gradle. +# +# For more details, see +# http://developer.android.com/guide/developing/tools/proguard.html + +# If your project uses WebView with JS, uncomment the following +# and specify the fully qualified class name to the JavaScript interface +# class: +#-keepclassmembers class fqcn.of.javascript.interface.for.webview { +# public *; +#} + +# Uncomment this to preserve the line number information for +# debugging stack traces. +#-keepattributes SourceFile,LineNumberTable + +# If you keep the line number information, uncomment this to +# hide the original source file name. +#-renamesourcefileattribute SourceFile \ No newline at end of file diff --git a/domain/player/build.gradle.kts b/domain/player/build.gradle.kts index dcda8056..5337c968 100644 --- a/domain/player/build.gradle.kts +++ b/domain/player/build.gradle.kts @@ -11,11 +11,11 @@ android { minSdk = 26 } compileOptions { - sourceCompatibility = JavaVersion.VERSION_1_8 - targetCompatibility = JavaVersion.VERSION_1_8 + sourceCompatibility = JavaVersion.VERSION_17 + targetCompatibility = JavaVersion.VERSION_17 } kotlinOptions { - jvmTarget = "1.8" + jvmTarget = "17" } } diff --git a/domain/player/consumer-rules.pro b/domain/player/consumer-rules.pro new file mode 100644 index 00000000..e69de29b diff --git a/domain/player/proguard-rules.pro b/domain/player/proguard-rules.pro new file mode 100644 index 00000000..481bb434 --- /dev/null +++ b/domain/player/proguard-rules.pro @@ -0,0 +1,21 @@ +# Add project specific ProGuard rules here. +# You can control the set of applied configuration files using the +# proguardFiles setting in build.gradle. +# +# For more details, see +# http://developer.android.com/guide/developing/tools/proguard.html + +# If your project uses WebView with JS, uncomment the following +# and specify the fully qualified class name to the JavaScript interface +# class: +#-keepclassmembers class fqcn.of.javascript.interface.for.webview { +# public *; +#} + +# Uncomment this to preserve the line number information for +# debugging stack traces. +#-keepattributes SourceFile,LineNumberTable + +# If you keep the line number information, uncomment this to +# hide the original source file name. +#-renamesourcefileattribute SourceFile \ No newline at end of file diff --git a/domain/user/build.gradle.kts b/domain/user/build.gradle.kts index 2142e940..a8705207 100644 --- a/domain/user/build.gradle.kts +++ b/domain/user/build.gradle.kts @@ -4,8 +4,8 @@ plugins { } java { - sourceCompatibility = JavaVersion.VERSION_21 - targetCompatibility = JavaVersion.VERSION_21 + sourceCompatibility = JavaVersion.VERSION_17 + targetCompatibility = JavaVersion.VERSION_17 } dependencies { diff --git a/domain/user/consumer-rules.pro b/domain/user/consumer-rules.pro new file mode 100644 index 00000000..e69de29b diff --git a/domain/user/proguard-rules.pro b/domain/user/proguard-rules.pro new file mode 100644 index 00000000..481bb434 --- /dev/null +++ b/domain/user/proguard-rules.pro @@ -0,0 +1,21 @@ +# Add project specific ProGuard rules here. +# You can control the set of applied configuration files using the +# proguardFiles setting in build.gradle. +# +# For more details, see +# http://developer.android.com/guide/developing/tools/proguard.html + +# If your project uses WebView with JS, uncomment the following +# and specify the fully qualified class name to the JavaScript interface +# class: +#-keepclassmembers class fqcn.of.javascript.interface.for.webview { +# public *; +#} + +# Uncomment this to preserve the line number information for +# debugging stack traces. +#-keepattributes SourceFile,LineNumberTable + +# If you keep the line number information, uncomment this to +# hide the original source file name. +#-renamesourcefileattribute SourceFile \ No newline at end of file diff --git a/feature/create/build.gradle.kts b/feature/create/build.gradle.kts index 5fd15b5c..c3ad95cc 100644 --- a/feature/create/build.gradle.kts +++ b/feature/create/build.gradle.kts @@ -24,11 +24,11 @@ android { } } compileOptions { - sourceCompatibility = JavaVersion.VERSION_1_8 - targetCompatibility = JavaVersion.VERSION_1_8 + sourceCompatibility = JavaVersion.VERSION_17 + targetCompatibility = JavaVersion.VERSION_17 } kotlinOptions { - jvmTarget = "1.8" + jvmTarget = "17" } buildFeatures { // Enables Jetpack Compose for this module compose = true diff --git a/feature/create/src/main/java/com/squirtles/create/CreatePickScreen.kt b/feature/create/src/main/java/com/squirtles/create/CreatePickScreen.kt new file mode 100644 index 00000000..2346ccf4 --- /dev/null +++ b/feature/create/src/main/java/com/squirtles/create/CreatePickScreen.kt @@ -0,0 +1,349 @@ +package com.squirtles.create + +import android.app.Activity +import android.util.Size +import androidx.activity.compose.BackHandler +import androidx.compose.foundation.background +import androidx.compose.foundation.clickable +import androidx.compose.foundation.interaction.MutableInteractionSource +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.aspectRatio +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.statusBarsPadding +import androidx.compose.foundation.rememberScrollState +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.foundation.verticalScroll +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.automirrored.filled.ArrowBack +import androidx.compose.material.icons.filled.Check +import androidx.compose.material3.CenterAlignedTopAppBar +import androidx.compose.material3.CircularProgressIndicator +import androidx.compose.material3.ExperimentalMaterial3Api +import androidx.compose.material3.Icon +import androidx.compose.material3.IconButton +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.OutlinedTextField +import androidx.compose.material3.OutlinedTextFieldDefaults +import androidx.compose.material3.Scaffold +import androidx.compose.material3.Text +import androidx.compose.material3.TopAppBarDefaults +import androidx.compose.runtime.Composable +import androidx.compose.runtime.LaunchedEffect +import androidx.compose.runtime.SideEffect +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.saveable.rememberSaveable +import androidx.compose.runtime.setValue +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.clip +import androidx.compose.ui.graphics.Brush +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.graphics.luminance +import androidx.compose.ui.platform.LocalView +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.text.style.TextAlign +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.dp +import androidx.core.graphics.toColorInt +import androidx.core.view.WindowInsetsControllerCompat +import androidx.hilt.navigation.compose.hiltViewModel +import androidx.lifecycle.compose.collectAsStateWithLifecycle +import com.squirtles.common.ui.AlbumImage +import com.squirtles.common.ui.VerticalSpacer +import com.squirtles.common.ui.theme.Black +import com.squirtles.common.ui.theme.Dark +import com.squirtles.common.ui.theme.Gray +import com.squirtles.common.ui.theme.White +import com.squirtles.model.Song + +@Composable +fun CreatePickScreen( + song: Song, + onBackClick: () -> Unit, + onCreateClick: (String) -> Unit, + createPickViewModel: CreatePickViewModel = hiltViewModel(), +) { + + val comment = createPickViewModel.comment.collectAsStateWithLifecycle() + val uiState by createPickViewModel.createPickUiState.collectAsStateWithLifecycle() + var showCreateIndicator by rememberSaveable { mutableStateOf(false) } + + val dynamicBackgroundColor = Color(song.bgColor) + val dynamicOnBackgroundColor = if (dynamicBackgroundColor.luminance() >= 0.5f) Black else White + val view = LocalView.current + + if (!view.isInEditMode) { + SideEffect { + val window = (view.context as Activity).window + val windowInsetsController = WindowInsetsControllerCompat(window, view) + val isLightStatusBar = dynamicBackgroundColor.luminance() >= 0.5f + windowInsetsController.isAppearanceLightStatusBars = isLightStatusBar + } + } + + // 생성 중 인디케이터가 표시되고 있을 때는 시스템의 뒤로 가기 버튼 클릭을 무시 + BackHandler(enabled = showCreateIndicator) { } + + when (uiState) { + CreateUiState.Default -> { + CreatePickDisplay( + song = song, + comment = comment.value, + dynamicBackgroundColor = dynamicBackgroundColor, + dynamicOnBackgroundColor = dynamicOnBackgroundColor, + onBackClick = { + createPickViewModel.resetComment() + onBackClick() + }, + onCreateClick = { + createPickViewModel.onCreatePickClick() + showCreateIndicator = true + }, + onCommentChange = createPickViewModel::onCommentChange + ) + } + + is CreateUiState.Success -> { + LaunchedEffect(Unit) { + showCreateIndicator = false + val pickId = (uiState as CreateUiState.Success).data + onCreateClick(pickId) + } + } + + CreateUiState.Error -> { + // TODO() + showCreateIndicator = false + Text("생성 오류") + } + } + + if (showCreateIndicator) { + Box( + modifier = Modifier + .fillMaxSize() + .background(Black.copy(alpha = 0.5F)) + .clickable( // 클릭 효과 제거 및 클릭 이벤트 무시 + interactionSource = remember { MutableInteractionSource() }, + indication = null, + onClick = {} + ), + contentAlignment = Alignment.Center + ) { + CircularProgressIndicator() + } + } +} + +@Composable +private fun CreatePickDisplay( + song: Song, + comment: String, + dynamicBackgroundColor: Color, + dynamicOnBackgroundColor: Color, + onCommentChange: (String) -> Unit, + onBackClick: () -> Unit, + onCreateClick: () -> Unit, +) { + Scaffold( + containerColor = dynamicBackgroundColor, + topBar = { + CreatePickScreenTopBar( + dynamicOnBackgroundColor = dynamicOnBackgroundColor, + onBackClick = onBackClick, + onCreateClick = onCreateClick + ) + } + ) { innerPadding -> + Box( + modifier = Modifier + .padding(innerPadding) + .fillMaxSize() + .background( + brush = Brush.verticalGradient( + colorStops = arrayOf( + 0.0f to dynamicBackgroundColor, + 0.47f to Black + ) + ) + ) + ) { + CreatePickContent( + song = song, + comment = comment, + onValueChange = onCommentChange, + dynamicOnBackgroundColor = dynamicOnBackgroundColor, + ) + } + } +} + +@Composable +private fun CreatePickContent( + song: Song, + comment: String, + onValueChange: (String) -> Unit, + dynamicOnBackgroundColor: Color, +) { + val scrollState = rememberScrollState() + + Column( + modifier = Modifier + .fillMaxSize() + .verticalScroll(scrollState) + .padding(vertical = 10.dp), + horizontalAlignment = Alignment.CenterHorizontally, + verticalArrangement = Arrangement.spacedBy(10.dp), + ) { + Text( + text = song.songName, + color = dynamicOnBackgroundColor, + textAlign = TextAlign.Center, + style = MaterialTheme.typography.titleLarge.copy(fontWeight = FontWeight.Bold) + ) + + Text( + text = song.artistName, + color = dynamicOnBackgroundColor, + textAlign = TextAlign.Center, + style = MaterialTheme.typography.bodyLarge + ) + + AlbumImage( + imageUrl = song.getImageUrlWithSize(RequestImageSize.width, RequestImageSize.height), + modifier = Modifier + .fillMaxWidth() + .padding(horizontal = 30.dp) + .aspectRatio(1f) + .clip(RoundedCornerShape(20.dp)), + contentDescription = song.albumName + stringResource(id = R.string.pick_album_description) + ) + + VerticalSpacer(40) + + CommentTextBox( + comment = comment, + onValueChange = onValueChange, + ) + } +} + +@Composable +private fun CommentTextBox( + comment: String, + onValueChange: (String) -> Unit, +) { + OutlinedTextField( + value = comment, + onValueChange = { textValue -> + onValueChange(textValue) + }, + modifier = Modifier + .fillMaxWidth() + .height(100.dp) + .padding(horizontal = 30.dp), + textStyle = MaterialTheme.typography.bodyLarge.copy(White), + placeholder = { + Text( + text = stringResource(id = R.string.pick_comment_placeholder), + style = MaterialTheme.typography.bodyLarge.copy(Gray) + ) + }, + shape = RoundedCornerShape(10.dp), + colors = OutlinedTextFieldDefaults.colors( + unfocusedContainerColor = Dark, + focusedContainerColor = Dark, + focusedBorderColor = Color.Transparent, + unfocusedBorderColor = Color.Transparent + ) + ) +} + +@OptIn(ExperimentalMaterial3Api::class) +@Composable +private fun CreatePickScreenTopBar( + dynamicOnBackgroundColor: Color, + onBackClick: () -> Unit, + onCreateClick: () -> Unit +) { + CenterAlignedTopAppBar( + modifier = Modifier.statusBarsPadding(), + title = { + Text( + text = stringResource(id = R.string.pick_app_bar_title), + color = dynamicOnBackgroundColor, + style = MaterialTheme.typography.titleLarge.copy(fontWeight = FontWeight.Bold) + ) + }, + navigationIcon = { + IconButton(onClick = onBackClick) { + Icon( + imageVector = Icons.AutoMirrored.Filled.ArrowBack, + contentDescription = stringResource(id = R.string.top_app_bar_back_description), + tint = dynamicOnBackgroundColor + ) + } + }, + actions = { + IconButton(onClick = onCreateClick) { + Icon( + imageVector = Icons.Filled.Check, + contentDescription = stringResource(id = R.string.pick_app_bar_upload_description), + tint = dynamicOnBackgroundColor + ) + } + }, + colors = TopAppBarDefaults.centerAlignedTopAppBarColors( + containerColor = Color.Transparent + ) + ) +} + +@Preview +@Composable +private fun CreatePickScreenPreview() { + CreatePickDisplay( + song = Song( + id = "1778132734", + songName = "Super Shy", + artistName = "뉴진스", + albumName = "NewJeans 'Super Shy' - Single", + imageUrl = "https://i.scdn.co/image/ab67616d0000b2733d98a0ae7c78a3a9babaf8af", + genreNames = listOf("K-Pop"), + bgColor = "#8FC1E2".toColorInt(), + externalUrl = "", + previewUrl = "" + ), + onBackClick = {}, + comment = "TEST COMMENT", + dynamicBackgroundColor = Color.White, + dynamicOnBackgroundColor = Color.Gray, + onCommentChange = { + + }, + onCreateClick = { + + } + ) +} + +private val RequestImageSize = Size(720, 720) +private val DEFAULT_SONG = Song( + id = "1778132734", + songName = "", + artistName = "", + albumName = "", + imageUrl = "", + genreNames = listOf("K-Pop"), + bgColor = "#8FC1E2".toColorInt(), + externalUrl = "", + previewUrl = "" +) diff --git a/feature/create/src/main/java/com/squirtles/create/CreatePickUiState.kt b/feature/create/src/main/java/com/squirtles/create/CreatePickUiState.kt new file mode 100644 index 00000000..a6fae3e3 --- /dev/null +++ b/feature/create/src/main/java/com/squirtles/create/CreatePickUiState.kt @@ -0,0 +1,12 @@ +package com.squirtles.create + +sealed class SearchUiState { + data object HotResult : SearchUiState() + data object SearchResult : SearchUiState() +} + +sealed class CreateUiState { + data object Default : CreateUiState() + data class Success(val data: T) : CreateUiState() + data object Error : CreateUiState() +} diff --git a/feature/create/src/main/java/com/squirtles/create/CreatePickViewModel.kt b/feature/create/src/main/java/com/squirtles/create/CreatePickViewModel.kt new file mode 100644 index 00000000..add9845b --- /dev/null +++ b/feature/create/src/main/java/com/squirtles/create/CreatePickViewModel.kt @@ -0,0 +1,120 @@ +package com.squirtles.create + +import android.location.Location +import android.util.Log +import androidx.lifecycle.SavedStateHandle +import androidx.lifecycle.ViewModel +import androidx.lifecycle.viewModelScope +import androidx.navigation.toRoute +import com.squirtles.applemusic.usecase.FetchMusicVideoUseCase +import com.squirtles.location.usecase.GetLastLocationUseCase +import com.squirtles.model.Creator +import com.squirtles.model.LocationPoint +import com.squirtles.model.Pick +import com.squirtles.model.Song +import com.squirtles.navigation.SearchRoute +import com.squirtles.pick.usecase.CreatePickUseCase +import com.squirtles.user.usecase.GetCurrentUserUseCase +import com.squirtles.util.serializableType +import com.squirtles.util.throttleFirst +import dagger.hilt.android.lifecycle.HiltViewModel +import kotlinx.coroutines.FlowPreview +import kotlinx.coroutines.flow.MutableSharedFlow +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.asStateFlow +import kotlinx.coroutines.launch +import javax.inject.Inject +import kotlin.reflect.typeOf + +@OptIn(FlowPreview::class) +@HiltViewModel +class CreatePickViewModel @Inject constructor( + savedStateHandle: SavedStateHandle, + getLastLocationUseCase: GetLastLocationUseCase, + private val fetchMusicVideoUseCase: FetchMusicVideoUseCase, + private val createPickUseCase: CreatePickUseCase, + private val getCurrentUserUseCase: GetCurrentUserUseCase +) : ViewModel() { + + private val typeMap = mapOf(typeOf() to serializableType()) + private val song = savedStateHandle.toRoute(typeMap).song + + private val _createPickUiState = MutableStateFlow>(CreateUiState.Default) + val createPickUiState = _createPickUiState.asStateFlow() + + private val _comment = MutableStateFlow("") + val comment get() = _comment + + private var lastLocation: Location? = null + private val createPickClick = MutableSharedFlow() + + init { + // 데이터소스의 위치값을 계속 collect하며 curLocation 변수에 저장 + viewModelScope.launch { + getLastLocationUseCase().collect { location -> + lastLocation = location + } + } + + // 등록 버튼 클릭 후 3초 이내의 클릭은 무시하고 픽 생성하기 + viewModelScope.launch { + createPickClick + .throttleFirst(3000) + .collect { + createPick(song) + } + } + } + + fun onCommentChange(text: String) { + _comment.value = text + } + + fun resetComment() { + _comment.value = "" + } + + fun onCreatePickClick() { + viewModelScope.launch { + createPickClick.emit(Unit) + } + } + + private fun createPick(song: Song) { + viewModelScope.launch { + if (lastLocation == null) { + /* TODO: DEFAULT 인 경우 -> LocalDataSource 위치 데이터 못 불러옴 */ + return@launch + } + + val musicVideo = fetchMusicVideoUseCase(song) + + /* 등록 결과 - pick ID 담긴 Result */ + getCurrentUserUseCase()?.let { user -> + val createResult = createPickUseCase( + Pick( + id = "", + song = song, + comment = _comment.value, + createdAt = "", + createdBy = Creator( + userId = user.userId, + userName = user.userName + ), + location = LocationPoint(lastLocation!!.latitude, lastLocation!!.longitude), + musicVideoUrl = musicVideo?.previewUrl ?: "", + musicVideoThumbnailUrl = musicVideo?.thumbnailUrl ?: "" + ) + ) + + createResult.onSuccess { pickId -> + _createPickUiState.emit(CreateUiState.Success(pickId)) + }.onFailure { + /* TODO: Firestore 등록 실패처리 */ + _createPickUiState.emit(CreateUiState.Error) + Log.d("CreatePickViewModel", createResult.exceptionOrNull()?.message.toString()) + } + } + } + } +} diff --git a/feature/favorite/proguard-rules.pro b/feature/favorite/proguard-rules.pro new file mode 100644 index 00000000..481bb434 --- /dev/null +++ b/feature/favorite/proguard-rules.pro @@ -0,0 +1,21 @@ +# Add project specific ProGuard rules here. +# You can control the set of applied configuration files using the +# proguardFiles setting in build.gradle. +# +# For more details, see +# http://developer.android.com/guide/developing/tools/proguard.html + +# If your project uses WebView with JS, uncomment the following +# and specify the fully qualified class name to the JavaScript interface +# class: +#-keepclassmembers class fqcn.of.javascript.interface.for.webview { +# public *; +#} + +# Uncomment this to preserve the line number information for +# debugging stack traces. +#-keepattributes SourceFile,LineNumberTable + +# If you keep the line number information, uncomment this to +# hide the original source file name. +#-renamesourcefileattribute SourceFile \ No newline at end of file diff --git a/mediaservice/build.gradle.kts b/mediaservice/build.gradle.kts index 5907a17d..add99e55 100644 --- a/mediaservice/build.gradle.kts +++ b/mediaservice/build.gradle.kts @@ -23,11 +23,11 @@ android { } } compileOptions { - sourceCompatibility = JavaVersion.VERSION_1_8 - targetCompatibility = JavaVersion.VERSION_1_8 + sourceCompatibility = JavaVersion.VERSION_17 + targetCompatibility = JavaVersion.VERSION_17 } kotlinOptions { - jvmTarget = "1.8" + jvmTarget = "17" } } diff --git a/settings.gradle.kts b/settings.gradle.kts index 204c0c2b..af6651e5 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -55,4 +55,4 @@ include(":core:musicplayer") include(":core:account") include(":feature:create") include(":feature:favorite") -include(":core:localproperties") +include(":core:buildconfig") From 454b72956a1045b2f7b5d4c64d63404cd9e113c9 Mon Sep 17 00:00:00 2001 From: miller198 Date: Sun, 2 Mar 2025 20:40:43 +0900 Subject: [PATCH 26/62] =?UTF-8?q?[refactor]=20buildconfig=20=EB=AA=A8?= =?UTF-8?q?=EB=93=88=20=EC=A0=81=EC=9A=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/com/squirtles/data/favorite/CloudFunctionHelper.kt | 3 ++- .../main/java/com/squirtles/data/firebase/FirebaseModule.kt | 3 ++- .../src/main/java/com/squirtles/data/network/NetworkModule.kt | 4 ++-- 3 files changed, 6 insertions(+), 4 deletions(-) diff --git a/data/src/main/java/com/squirtles/data/favorite/CloudFunctionHelper.kt b/data/src/main/java/com/squirtles/data/favorite/CloudFunctionHelper.kt index 788f725a..7998c0ba 100644 --- a/data/src/main/java/com/squirtles/data/favorite/CloudFunctionHelper.kt +++ b/data/src/main/java/com/squirtles/data/favorite/CloudFunctionHelper.kt @@ -4,6 +4,7 @@ import com.google.firebase.functions.FirebaseFunctions import com.google.firebase.functions.ktx.functions import com.google.firebase.ktx.Firebase import com.squirtles.data.BuildConfig +import com.squirtles.localproperties.LocalPropertyProvider import kotlinx.coroutines.tasks.await import javax.inject.Singleton @@ -15,7 +16,7 @@ class CloudFunctionHelper { return try { val data = hashMapOf("pickId" to pickId) val result = functions - .getHttpsCallable(BuildConfig.HTTPS_CALLABLE) + .getHttpsCallable(LocalPropertyProvider.httpsCallable) .call(data) .await() diff --git a/data/src/main/java/com/squirtles/data/firebase/FirebaseModule.kt b/data/src/main/java/com/squirtles/data/firebase/FirebaseModule.kt index 782cfa71..c4a73428 100644 --- a/data/src/main/java/com/squirtles/data/firebase/FirebaseModule.kt +++ b/data/src/main/java/com/squirtles/data/firebase/FirebaseModule.kt @@ -2,6 +2,7 @@ package com.squirtles.data.firebase import com.google.firebase.firestore.FirebaseFirestore import com.squirtles.data.BuildConfig +import com.squirtles.localproperties.LocalPropertyProvider import dagger.Module import dagger.Provides import dagger.hilt.InstallIn @@ -15,6 +16,6 @@ object FirebaseModule { @Provides @Singleton fun provideFirebaseFirestore(): FirebaseFirestore { - return FirebaseFirestore.getInstance(BuildConfig.FIRESTORE_DB_ID) + return FirebaseFirestore.getInstance(LocalPropertyProvider.firestoreDbId) } } diff --git a/data/src/main/java/com/squirtles/data/network/NetworkModule.kt b/data/src/main/java/com/squirtles/data/network/NetworkModule.kt index 3acc6dd6..32e823e6 100644 --- a/data/src/main/java/com/squirtles/data/network/NetworkModule.kt +++ b/data/src/main/java/com/squirtles/data/network/NetworkModule.kt @@ -1,6 +1,6 @@ package com.squirtles.data.network -import com.squirtles.data.BuildConfig +import com.squirtles.localproperties.LocalPropertyProvider import dagger.Module import dagger.Provides import dagger.hilt.InstallIn @@ -25,7 +25,7 @@ object NetworkModule { return OkHttpClient.Builder() .addInterceptor { chain -> val newRequest = chain.request().newBuilder() - .addHeader("Authorization", "Bearer ${BuildConfig.APPLE_MUSIC_API_TOKEN}") + .addHeader("Authorization", "Bearer ${LocalPropertyProvider.appleMusicApiToken}") .build() chain.proceed(newRequest) } From cd2d3588769c347bfa35c46e2534df24f6a82c10 Mon Sep 17 00:00:00 2001 From: miller198 Date: Sun, 2 Mar 2025 21:03:32 +0900 Subject: [PATCH 27/62] =?UTF-8?q?[refactor]=20buildconfig=20=EB=AA=A8?= =?UTF-8?q?=EB=93=88=20=EC=A0=81=EC=9A=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- core/account/build.gradle.kts | 14 +---------- .../java/com/squirtles/account/GoogleId.kt | 3 ++- core/navigation/build.gradle.kts | 8 +------ data/applemusic/build.gradle.kts | 16 +------------ .../squirtles/applemusic/api/NetworkModule.kt | 4 ++-- data/build.gradle.kts | 9 -------- data/favorite/build.gradle.kts | 22 +----------------- .../squirtles/favorite/CloudFunctionHelper.kt | 3 ++- data/firebase/build.gradle.kts | 23 ++----------------- .../com/squirtles/firebase/FirebaseModule.kt | 3 ++- .../data/favorite/CloudFunctionHelper.kt | 1 - .../squirtles/data/firebase/FirebaseModule.kt | 1 - 12 files changed, 14 insertions(+), 93 deletions(-) diff --git a/core/account/build.gradle.kts b/core/account/build.gradle.kts index ee35e44a..620792d4 100644 --- a/core/account/build.gradle.kts +++ b/core/account/build.gradle.kts @@ -1,10 +1,3 @@ -import java.io.FileInputStream -import java.util.Properties - -val properties = Properties().apply { - load(FileInputStream(rootProject.file("local.properties"))) -} - plugins { alias(libs.plugins.android.library) alias(libs.plugins.jetbrains.kotlin.android) @@ -21,12 +14,6 @@ android { testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner" consumerProguardFiles("consumer-rules.pro") - - buildConfigField( - "String", - "GOOGLE_CLIENT_ID", - "\"${properties.getProperty("GOOGLE_CLIENT_ID")}\"" - ) } buildTypes { @@ -50,6 +37,7 @@ android { dependencies { implementation(projects.domain.user) implementation(projects.core.model) + implementation(projects.core.buildconfig) implementation(libs.androidx.lifecycle.runtime.ktx) // Hilt diff --git a/core/account/src/main/java/com/squirtles/account/GoogleId.kt b/core/account/src/main/java/com/squirtles/account/GoogleId.kt index fba8aa93..69abfe90 100644 --- a/core/account/src/main/java/com/squirtles/account/GoogleId.kt +++ b/core/account/src/main/java/com/squirtles/account/GoogleId.kt @@ -11,6 +11,7 @@ import androidx.credentials.GetCredentialResponse import androidx.credentials.exceptions.NoCredentialException import com.google.android.libraries.identity.googleid.GetGoogleIdOption import com.google.android.libraries.identity.googleid.GoogleIdTokenCredential +import com.squirtles.localproperties.LocalPropertyProvider import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.launch @@ -20,7 +21,7 @@ class GoogleId(private val context: Context) { private val googleIdOption: GetGoogleIdOption = GetGoogleIdOption.Builder() .setFilterByAuthorizedAccounts(false) - .setServerClientId(BuildConfig.GOOGLE_CLIENT_ID) + .setServerClientId(LocalPropertyProvider.googleClientId) .setAutoSelectEnabled(true) .build() diff --git a/core/navigation/build.gradle.kts b/core/navigation/build.gradle.kts index 32fffc37..c7c44215 100644 --- a/core/navigation/build.gradle.kts +++ b/core/navigation/build.gradle.kts @@ -1,14 +1,8 @@ plugins { - id("java-library") - alias(libs.plugins.jetbrains.kotlin.jvm) + alias(libs.plugins.musicroad.java.library) alias(libs.plugins.kotlin.serialization) } -java { - sourceCompatibility = JavaVersion.VERSION_17 - targetCompatibility = JavaVersion.VERSION_17 -} - dependencies { implementation(projects.core.model) diff --git a/data/applemusic/build.gradle.kts b/data/applemusic/build.gradle.kts index cae3631d..a6a7c0b7 100644 --- a/data/applemusic/build.gradle.kts +++ b/data/applemusic/build.gradle.kts @@ -1,9 +1,3 @@ -import java.io.FileInputStream -import java.util.Properties - -var properties = Properties() -properties.load(FileInputStream("local.properties")) - plugins { alias(libs.plugins.android.library) alias(libs.plugins.jetbrains.kotlin.android) @@ -21,12 +15,6 @@ android { testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner" consumerProguardFiles("consumer-rules.pro") - - buildConfigField( - "String", - "APPLE_MUSIC_API_TOKEN", - "\"${properties.getProperty("APPLE_MUSIC_API_TOKEN")}\"" - ) } buildTypes { @@ -42,14 +30,12 @@ android { kotlinOptions { jvmTarget = "17" } - buildFeatures { - buildConfig = true - } } dependencies { implementation(projects.domain.applemusic) implementation(projects.core.model) + implementation(projects.core.buildconfig) implementation(libs.androidx.paging.runtime) // Kotlinx Serialization implementation(libs.kotlinx.serialization.json) diff --git a/data/applemusic/src/main/java/com/squirtles/applemusic/api/NetworkModule.kt b/data/applemusic/src/main/java/com/squirtles/applemusic/api/NetworkModule.kt index b4fb6ec0..1ece320e 100644 --- a/data/applemusic/src/main/java/com/squirtles/applemusic/api/NetworkModule.kt +++ b/data/applemusic/src/main/java/com/squirtles/applemusic/api/NetworkModule.kt @@ -1,6 +1,6 @@ package com.squirtles.applemusic.api -import com.squirtles.applemusic.BuildConfig +import com.squirtles.localproperties.LocalPropertyProvider import dagger.Module import dagger.Provides import dagger.hilt.InstallIn @@ -25,7 +25,7 @@ object NetworkModule { return OkHttpClient.Builder() .addInterceptor { chain -> val newRequest = chain.request().newBuilder() - .addHeader("Authorization", "Bearer ${BuildConfig.APPLE_MUSIC_API_TOKEN}") + .addHeader("Authorization", "Bearer ${LocalPropertyProvider.appleMusicApiToken}") .build() chain.proceed(newRequest) } diff --git a/data/build.gradle.kts b/data/build.gradle.kts index 7f1a6e8c..a7290dcb 100644 --- a/data/build.gradle.kts +++ b/data/build.gradle.kts @@ -1,9 +1,3 @@ -import java.io.FileInputStream -import java.util.Properties - -var properties = Properties() -properties.load(FileInputStream("local.properties")) - plugins { alias(libs.plugins.android.library) alias(libs.plugins.jetbrains.kotlin.android) @@ -43,9 +37,6 @@ android { kotlinOptions { jvmTarget = "17" } - buildFeatures { - buildConfig = true - } } dependencies { diff --git a/data/favorite/build.gradle.kts b/data/favorite/build.gradle.kts index ab7610bb..c6f8bafd 100644 --- a/data/favorite/build.gradle.kts +++ b/data/favorite/build.gradle.kts @@ -1,9 +1,3 @@ -import java.io.FileInputStream -import java.util.Properties - -var properties = Properties() -properties.load(FileInputStream("local.properties")) - plugins { alias(libs.plugins.android.library) alias(libs.plugins.jetbrains.kotlin.android) @@ -26,12 +20,6 @@ android { buildTypes { debug { isMinifyEnabled = false - - buildConfigField( - "String", - "HTTPS_CALLABLE", - "\"${properties.getProperty("HTTPS_CALLABLE_DEBUG")}\"" - ) } release { @@ -40,12 +28,6 @@ android { getDefaultProguardFile("proguard-android-optimize.txt"), "proguard-rules.pro" ) - - buildConfigField( - "String", - "HTTPS_CALLABLE", - "\"${properties.getProperty("HTTPS_CALLABLE_RELEASE")}\"" - ) } } compileOptions { @@ -55,14 +37,12 @@ android { kotlinOptions { jvmTarget = "17" } - buildFeatures { - buildConfig = true - } } dependencies { implementation(projects.data.firebase) implementation(projects.domain.favorite) + implementation(projects.core.buildconfig) // Kotlinx Serialization implementation(libs.kotlinx.serialization.json) diff --git a/data/favorite/src/main/java/com/squirtles/favorite/CloudFunctionHelper.kt b/data/favorite/src/main/java/com/squirtles/favorite/CloudFunctionHelper.kt index f0d50559..6b865e0d 100644 --- a/data/favorite/src/main/java/com/squirtles/favorite/CloudFunctionHelper.kt +++ b/data/favorite/src/main/java/com/squirtles/favorite/CloudFunctionHelper.kt @@ -3,6 +3,7 @@ package com.squirtles.favorite import com.google.firebase.functions.FirebaseFunctions import com.google.firebase.functions.ktx.functions import com.google.firebase.ktx.Firebase +import com.squirtles.localproperties.LocalPropertyProvider import kotlinx.coroutines.tasks.await import javax.inject.Singleton @@ -14,7 +15,7 @@ class CloudFunctionHelper { return try { val data = hashMapOf("pickId" to pickId) val result = functions - .getHttpsCallable(BuildConfig.HTTPS_CALLABLE) + .getHttpsCallable(LocalPropertyProvider.httpsCallable) .call(data) .await() diff --git a/data/firebase/build.gradle.kts b/data/firebase/build.gradle.kts index 25d84681..963b5934 100644 --- a/data/firebase/build.gradle.kts +++ b/data/firebase/build.gradle.kts @@ -1,9 +1,3 @@ -import java.io.FileInputStream -import java.util.Properties - -var properties = Properties() -properties.load(FileInputStream("local.properties")) - plugins { alias(libs.plugins.android.library) alias(libs.plugins.jetbrains.kotlin.android) @@ -25,12 +19,6 @@ android { buildTypes { debug { isMinifyEnabled = false - - buildConfigField( - "String", - "FIRESTORE_DB_ID", - "\"${properties.getProperty("FIRESTORE_DB_ID_DEBUG")}\"" - ) } release { @@ -40,12 +28,6 @@ android { getDefaultProguardFile("proguard-android-optimize.txt"), "proguard-rules.pro" ) - - buildConfigField( - "String", - "FIRESTORE_DB_ID", - "\"${properties.getProperty("FIRESTORE_DB_ID_RELEASE")}\"" - ) } } compileOptions { @@ -55,13 +37,12 @@ android { kotlinOptions { jvmTarget = "17" } - buildFeatures { - buildConfig = true - } + } dependencies { implementation(projects.core.model) + implementation(projects.core.buildconfig) // hilt implementation(libs.hilt.android) diff --git a/data/firebase/src/main/java/com/squirtles/firebase/FirebaseModule.kt b/data/firebase/src/main/java/com/squirtles/firebase/FirebaseModule.kt index c2ff9b6b..705ac17d 100644 --- a/data/firebase/src/main/java/com/squirtles/firebase/FirebaseModule.kt +++ b/data/firebase/src/main/java/com/squirtles/firebase/FirebaseModule.kt @@ -1,6 +1,7 @@ package com.squirtles.firebase import com.google.firebase.firestore.FirebaseFirestore +import com.squirtles.localproperties.LocalPropertyProvider import dagger.Module import dagger.Provides import dagger.hilt.InstallIn @@ -14,6 +15,6 @@ object FirebaseModule { @Provides @Singleton fun provideFirebaseFirestore(): FirebaseFirestore { - return FirebaseFirestore.getInstance(BuildConfig.FIRESTORE_DB_ID) + return FirebaseFirestore.getInstance(LocalPropertyProvider.firestoreDbId) } } diff --git a/data/src/main/java/com/squirtles/data/favorite/CloudFunctionHelper.kt b/data/src/main/java/com/squirtles/data/favorite/CloudFunctionHelper.kt index 7998c0ba..20505a53 100644 --- a/data/src/main/java/com/squirtles/data/favorite/CloudFunctionHelper.kt +++ b/data/src/main/java/com/squirtles/data/favorite/CloudFunctionHelper.kt @@ -3,7 +3,6 @@ package com.squirtles.data.favorite import com.google.firebase.functions.FirebaseFunctions import com.google.firebase.functions.ktx.functions import com.google.firebase.ktx.Firebase -import com.squirtles.data.BuildConfig import com.squirtles.localproperties.LocalPropertyProvider import kotlinx.coroutines.tasks.await import javax.inject.Singleton diff --git a/data/src/main/java/com/squirtles/data/firebase/FirebaseModule.kt b/data/src/main/java/com/squirtles/data/firebase/FirebaseModule.kt index c4a73428..8cad67d7 100644 --- a/data/src/main/java/com/squirtles/data/firebase/FirebaseModule.kt +++ b/data/src/main/java/com/squirtles/data/firebase/FirebaseModule.kt @@ -1,7 +1,6 @@ package com.squirtles.data.firebase import com.google.firebase.firestore.FirebaseFirestore -import com.squirtles.data.BuildConfig import com.squirtles.localproperties.LocalPropertyProvider import dagger.Module import dagger.Provides From 88d3a1540717d49832f4ab205a46e36171752bd9 Mon Sep 17 00:00:00 2001 From: miller198 Date: Mon, 3 Mar 2025 00:35:28 +0900 Subject: [PATCH 28/62] =?UTF-8?q?[refactor]=20build=20logic=20=EC=A0=81?= =?UTF-8?q?=EC=9A=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- build-logic/convention/build.gradle.kts | 10 ++++ .../convention/AndroidComposePlugin.kt | 2 +- .../convention/AndroidLibraryPlugin.kt | 5 ++ .../com/squirtles/convention/HiltPlugin.kt | 6 +- .../convention/MusicRoadDataPlugin.kt | 22 ++++++++ build.gradle.kts | 10 ++++ core/account/build.gradle.kts | 41 +------------- core/buildconfig/build.gradle.kts | 5 -- core/common/build.gradle.kts | 2 + core/mediaservice/build.gradle.kts | 42 ++------------ core/musicplayer/build.gradle.kts | 36 +----------- core/picklist/build.gradle.kts | 40 +++---------- core/util/build.gradle.kts | 34 +---------- .../squirtles/util/ExampleInstrumentedTest.kt | 24 -------- .../com/squirtles/util/ExampleUnitTest.kt | 17 ------ data/applemusic/build.gradle.kts | 42 ++------------ data/build.gradle.kts | 56 ++----------------- data/favorite/build.gradle.kts | 47 +--------------- data/firebase/build.gradle.kts | 45 +-------------- data/location/build.gradle.kts | 34 +---------- data/order/build.gradle.kts | 34 +---------- data/pick/build.gradle.kts | 34 +---------- data/user/build.gradle.kts | 34 +---------- gradle/libs.versions.toml | 22 +++----- settings.gradle.kts | 5 +- 25 files changed, 105 insertions(+), 544 deletions(-) create mode 100644 build-logic/convention/src/main/java/com/squirtles/convention/MusicRoadDataPlugin.kt delete mode 100644 core/util/src/androidTest/java/com/squirtles/util/ExampleInstrumentedTest.kt delete mode 100644 core/util/src/test/java/com/squirtles/util/ExampleUnitTest.kt diff --git a/build-logic/convention/build.gradle.kts b/build-logic/convention/build.gradle.kts index 5ef9a554..894c9a24 100644 --- a/build-logic/convention/build.gradle.kts +++ b/build-logic/convention/build.gradle.kts @@ -4,6 +4,11 @@ plugins { group = "com.squirtles.build-logic" +java { + sourceCompatibility = JavaVersion.VERSION_17 + targetCompatibility = JavaVersion.VERSION_17 +} + dependencies { compileOnly(libs.android.gradlePlugin) compileOnly(libs.android.tools.common) @@ -37,5 +42,10 @@ gradlePlugin { id = "musicroad.hilt" implementationClass = "com.squirtles.convention.HiltPlugin" } + + register("data") { + id = "musicroad.data" + implementationClass = "com.squirtles.convention.MusicRoadDataPlugin" + } } } diff --git a/build-logic/convention/src/main/java/com/squirtles/convention/AndroidComposePlugin.kt b/build-logic/convention/src/main/java/com/squirtles/convention/AndroidComposePlugin.kt index 4895730b..8d39bca1 100644 --- a/build-logic/convention/src/main/java/com/squirtles/convention/AndroidComposePlugin.kt +++ b/build-logic/convention/src/main/java/com/squirtles/convention/AndroidComposePlugin.kt @@ -1,4 +1,4 @@ -package com.boostcamp.mapisode.convention +package com.squirtles.convention import com.android.build.gradle.LibraryExtension import com.squirtles.convention.extensions.configureComposeAndroid diff --git a/build-logic/convention/src/main/java/com/squirtles/convention/AndroidLibraryPlugin.kt b/build-logic/convention/src/main/java/com/squirtles/convention/AndroidLibraryPlugin.kt index 50eab2b2..162b628f 100644 --- a/build-logic/convention/src/main/java/com/squirtles/convention/AndroidLibraryPlugin.kt +++ b/build-logic/convention/src/main/java/com/squirtles/convention/AndroidLibraryPlugin.kt @@ -3,6 +3,7 @@ package com.squirtles.convention import com.android.build.gradle.LibraryExtension import com.squirtles.convention.extensions.configureKotlinAndroid import com.squirtles.convention.extensions.configureKotlinCoroutine +import com.squirtles.convention.extensions.getBundle import com.squirtles.convention.extensions.getLibrary import com.squirtles.convention.extensions.implementation import com.squirtles.convention.extensions.libs @@ -20,6 +21,10 @@ class AndroidLibraryPlugin : Plugin { configureKotlinAndroid(this) configureKotlinCoroutine(this) } + + dependencies { + implementation(libs.getBundle("androidx-core")) + } } } } diff --git a/build-logic/convention/src/main/java/com/squirtles/convention/HiltPlugin.kt b/build-logic/convention/src/main/java/com/squirtles/convention/HiltPlugin.kt index fc11e833..e5860e02 100644 --- a/build-logic/convention/src/main/java/com/squirtles/convention/HiltPlugin.kt +++ b/build-logic/convention/src/main/java/com/squirtles/convention/HiltPlugin.kt @@ -1,9 +1,11 @@ package com.squirtles.convention +import com.squirtles.convention.extensions.androidTestImplementation import com.squirtles.convention.extensions.getBundle import com.squirtles.convention.extensions.getLibrary import com.squirtles.convention.extensions.implementation import com.squirtles.convention.extensions.ksp +import com.squirtles.convention.extensions.kspTest import com.squirtles.convention.extensions.libs import org.gradle.api.Plugin import org.gradle.api.Project @@ -18,8 +20,10 @@ class HiltPlugin : Plugin { } dependencies { - implementation(libs.getBundle("di")) ksp(libs.getLibrary("hilt.android.compiler")) + kspTest(libs.getLibrary("hilt.android.compiler")) + implementation(libs.getLibrary("hilt.android")) + androidTestImplementation(libs.getLibrary("hilt.android.testing")) } } } diff --git a/build-logic/convention/src/main/java/com/squirtles/convention/MusicRoadDataPlugin.kt b/build-logic/convention/src/main/java/com/squirtles/convention/MusicRoadDataPlugin.kt new file mode 100644 index 00000000..ed1ee95a --- /dev/null +++ b/build-logic/convention/src/main/java/com/squirtles/convention/MusicRoadDataPlugin.kt @@ -0,0 +1,22 @@ +package com.squirtles.convention + +import com.squirtles.convention.extensions.implementation +import org.gradle.api.Plugin +import org.gradle.api.Project +import org.gradle.kotlin.dsl.dependencies + +class MusicRoadDataPlugin : Plugin { + override fun apply(target: Project) { + with(target) { + pluginManager.apply { + apply("musicroad.android.library") + apply("musicroad.hilt") + } + + dependencies { + implementation(project(":core:model")) + implementation(project(":core:buildconfig")) + } + } + } +} diff --git a/build.gradle.kts b/build.gradle.kts index d04c04a8..4610a785 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -10,3 +10,13 @@ plugins { alias(libs.plugins.firebase.crashlytics) apply false alias(libs.plugins.jetbrains.kotlin.jvm) apply false } + +buildscript { + repositories { + google() + mavenCentral() + maven { + url = uri("https://repository.map.naver.com/archive/maven") + } + } +} diff --git a/core/account/build.gradle.kts b/core/account/build.gradle.kts index 620792d4..9d05faf5 100644 --- a/core/account/build.gradle.kts +++ b/core/account/build.gradle.kts @@ -1,52 +1,17 @@ plugins { - alias(libs.plugins.android.library) - alias(libs.plugins.jetbrains.kotlin.android) - alias(libs.plugins.ksp) - alias(libs.plugins.hilt) + alias(libs.plugins.musicroad.android.library) + alias(libs.plugins.musicroad.hilt) } android { namespace = "com.squirtles.account" - compileSdk = 34 - - defaultConfig { - minSdk = 26 - - testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner" - consumerProguardFiles("consumer-rules.pro") - } - - buildTypes { - release { - isMinifyEnabled = false - proguardFiles(getDefaultProguardFile("proguard-android-optimize.txt"), "proguard-rules.pro") - } - } - compileOptions { - sourceCompatibility = JavaVersion.VERSION_17 - targetCompatibility = JavaVersion.VERSION_17 - } - kotlinOptions { - jvmTarget = "17" - } - buildFeatures { - buildConfig = true - } } dependencies { implementation(projects.domain.user) implementation(projects.core.model) implementation(projects.core.buildconfig) - implementation(libs.androidx.lifecycle.runtime.ktx) - - // Hilt - implementation(libs.hilt.android) - ksp(libs.hilt.android.compiler) - implementation(libs.inject) // Credentials - implementation(libs.androidx.credentials) - implementation(libs.androidx.credentials.play.services.auth) - implementation(libs.googleid) + implementation(libs.bundles.auth) } diff --git a/core/buildconfig/build.gradle.kts b/core/buildconfig/build.gradle.kts index 9cf488bd..72da1336 100644 --- a/core/buildconfig/build.gradle.kts +++ b/core/buildconfig/build.gradle.kts @@ -17,7 +17,6 @@ android { } defaultConfig { - buildConfigField( "String", "GOOGLE_CLIENT_ID", @@ -65,7 +64,3 @@ android { } } } - -dependencies { - -} diff --git a/core/common/build.gradle.kts b/core/common/build.gradle.kts index ed6b8647..1d80ffaf 100644 --- a/core/common/build.gradle.kts +++ b/core/common/build.gradle.kts @@ -21,7 +21,9 @@ dependencies { // implementation(platform(libs.compose.bom)) // implementation(libs.compose.runtime.android) // implementation(libs.compose.ui.tooling.preview) + implementation(platform(libs.compose.bom)) implementation(libs.bundles.compose) + implementation(libs.bundles.compose.debug) implementation(libs.bundles.material) // Coil diff --git a/core/mediaservice/build.gradle.kts b/core/mediaservice/build.gradle.kts index 39081611..a41d1331 100644 --- a/core/mediaservice/build.gradle.kts +++ b/core/mediaservice/build.gradle.kts @@ -1,50 +1,16 @@ plugins { - alias(libs.plugins.android.library) - alias(libs.plugins.jetbrains.kotlin.android) - alias(libs.plugins.ksp) - alias(libs.plugins.hilt) + alias(libs.plugins.musicroad.android.library) + alias(libs.plugins.musicroad.hilt) } android { namespace = "com.squirtles.mediaservice" - compileSdk = 34 - - defaultConfig { - minSdk = 26 - - testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner" - consumerProguardFiles("consumer-rules.pro") - } - - buildTypes { - release { - isMinifyEnabled = false - proguardFiles(getDefaultProguardFile("proguard-android-optimize.txt"), "proguard-rules.pro") - } - } - compileOptions { - sourceCompatibility = JavaVersion.VERSION_17 - targetCompatibility = JavaVersion.VERSION_17 - } - kotlinOptions { - jvmTarget = "17" - } } dependencies { - implementation(libs.androidx.core.ktx) - implementation(libs.androidx.appcompat) testImplementation(libs.junit) - androidTestImplementation(libs.androidx.junit) - androidTestImplementation(libs.androidx.espresso.core) - - // Hilt - implementation(libs.hilt.android) - ksp(libs.hilt.android.compiler) - implementation(libs.inject) - + androidTestImplementation(libs.bundles.test) // ExoPlayer - implementation(libs.androidx.media3.exoplayer) - implementation(libs.androidx.media3.exoplayer.dash) + implementation(libs.bundles.exoplayer) implementation(libs.androidx.media3.session) } diff --git a/core/musicplayer/build.gradle.kts b/core/musicplayer/build.gradle.kts index af7ec7ac..bc23a991 100644 --- a/core/musicplayer/build.gradle.kts +++ b/core/musicplayer/build.gradle.kts @@ -1,47 +1,17 @@ plugins { - alias(libs.plugins.android.library) - alias(libs.plugins.jetbrains.kotlin.android) - alias(libs.plugins.ksp) - alias(libs.plugins.hilt) + alias(libs.plugins.musicroad.android.library) + alias(libs.plugins.musicroad.hilt) } android { namespace = "com.squirtles.musicplayer" - compileSdk = 34 - - defaultConfig { - minSdk = 26 - - testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner" - consumerProguardFiles("consumer-rules.pro") - } - - buildTypes { - release { - isMinifyEnabled = false - proguardFiles(getDefaultProguardFile("proguard-android-optimize.txt"), "proguard-rules.pro") - } - } - compileOptions { - sourceCompatibility = JavaVersion.VERSION_17 - targetCompatibility = JavaVersion.VERSION_17 - } - kotlinOptions { - jvmTarget = "17" - } } dependencies { implementation(projects.domain.player) implementation(projects.core.model) - implementation(libs.androidx.lifecycle.runtime.ktx) - // Hilt - implementation(libs.hilt.android) - ksp(libs.hilt.android.compiler) - implementation(libs.inject) // ExoPlayer - implementation(libs.androidx.media3.exoplayer) - implementation(libs.androidx.media3.exoplayer.dash) + implementation(libs.bundles.exoplayer) implementation(libs.androidx.media3.session) } diff --git a/core/picklist/build.gradle.kts b/core/picklist/build.gradle.kts index 13b26018..db243ce3 100644 --- a/core/picklist/build.gradle.kts +++ b/core/picklist/build.gradle.kts @@ -1,6 +1,5 @@ plugins { - alias(libs.plugins.android.library) - alias(libs.plugins.jetbrains.kotlin.android) + alias(libs.plugins.musicroad.android.library) } android { @@ -14,47 +13,22 @@ android { composeOptions { kotlinCompilerExtensionVersion = "1.5.14" } - - defaultConfig { - minSdk = 26 - - testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner" - consumerProguardFiles("consumer-rules.pro") - } - - buildTypes { - release { - isMinifyEnabled = false - proguardFiles(getDefaultProguardFile("proguard-android-optimize.txt"), "proguard-rules.pro") - } - } - compileOptions { - sourceCompatibility = JavaVersion.VERSION_17 - targetCompatibility = JavaVersion.VERSION_17 - } - kotlinOptions { - jvmTarget = "17" - } } dependencies { implementation(projects.core.model) implementation(projects.core.common) - implementation(libs.androidx.core.ktx) - implementation(libs.androidx.appcompat) - implementation(libs.material) testImplementation(libs.junit) - androidTestImplementation(libs.androidx.junit) - androidTestImplementation(libs.androidx.espresso.core) + androidTestImplementation(libs.bundles.test) // Compose implementation(platform(libs.compose.bom)) - implementation(libs.compose.material) - implementation(libs.compose.material3) - implementation(libs.compose.material.icons.extended) - implementation(libs.compose.ui.tooling.preview) + implementation(libs.bundles.compose) + implementation(libs.bundles.compose.debug) + + implementation(libs.bundles.material) // Coil - implementation(libs.coil.compose) + implementation(libs.bundles.coil) } diff --git a/core/util/build.gradle.kts b/core/util/build.gradle.kts index aca3eb7c..34ac6616 100644 --- a/core/util/build.gradle.kts +++ b/core/util/build.gradle.kts @@ -1,45 +1,15 @@ plugins { - alias(libs.plugins.android.library) - alias(libs.plugins.jetbrains.kotlin.android) + alias(libs.plugins.musicroad.android.library) alias(libs.plugins.kotlin.serialization) } android { namespace = "com.squirtles.util" - compileSdk = 34 - - defaultConfig { - minSdk = 26 - - testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner" - consumerProguardFiles("consumer-rules.pro") - } - - buildTypes { - release { - isMinifyEnabled = false - proguardFiles(getDefaultProguardFile("proguard-android-optimize.txt"), "proguard-rules.pro") - } - } - compileOptions { - sourceCompatibility = JavaVersion.VERSION_17 - targetCompatibility = JavaVersion.VERSION_17 - } - kotlinOptions { - jvmTarget = "17" - } } dependencies { - - implementation(libs.androidx.core.ktx) - implementation(libs.androidx.appcompat) - implementation(libs.material) - testImplementation(libs.junit) - androidTestImplementation(libs.androidx.junit) - androidTestImplementation(libs.androidx.espresso.core) + implementation(libs.androidx.navigation.common.ktx) // Serialization implementation(libs.kotlinx.serialization.json) - implementation(libs.androidx.navigation.common.ktx) } diff --git a/core/util/src/androidTest/java/com/squirtles/util/ExampleInstrumentedTest.kt b/core/util/src/androidTest/java/com/squirtles/util/ExampleInstrumentedTest.kt deleted file mode 100644 index 1249c7b2..00000000 --- a/core/util/src/androidTest/java/com/squirtles/util/ExampleInstrumentedTest.kt +++ /dev/null @@ -1,24 +0,0 @@ -package com.squirtles.util - -import androidx.test.platform.app.InstrumentationRegistry -import androidx.test.ext.junit.runners.AndroidJUnit4 - -import org.junit.Test -import org.junit.runner.RunWith - -import org.junit.Assert.* - -/** - * Instrumented test, which will execute on an Android device. - * - * See [testing documentation](http://d.android.com/tools/testing). - */ -@RunWith(AndroidJUnit4::class) -class ExampleInstrumentedTest { - @Test - fun useAppContext() { - // Context of the app under test. - val appContext = InstrumentationRegistry.getInstrumentation().targetContext - assertEquals("com.squirtles.util.test", appContext.packageName) - } -} diff --git a/core/util/src/test/java/com/squirtles/util/ExampleUnitTest.kt b/core/util/src/test/java/com/squirtles/util/ExampleUnitTest.kt deleted file mode 100644 index fe01fb0c..00000000 --- a/core/util/src/test/java/com/squirtles/util/ExampleUnitTest.kt +++ /dev/null @@ -1,17 +0,0 @@ -package com.squirtles.util - -import org.junit.Test - -import org.junit.Assert.* - -/** - * Example local unit test, which will execute on the development machine (host). - * - * See [testing documentation](http://d.android.com/tools/testing). - */ -class ExampleUnitTest { - @Test - fun addition_isCorrect() { - assertEquals(4, 2 + 2) - } -} diff --git a/data/applemusic/build.gradle.kts b/data/applemusic/build.gradle.kts index a6a7c0b7..00d7b581 100644 --- a/data/applemusic/build.gradle.kts +++ b/data/applemusic/build.gradle.kts @@ -1,54 +1,20 @@ plugins { - alias(libs.plugins.android.library) - alias(libs.plugins.jetbrains.kotlin.android) - alias(libs.plugins.ksp) - alias(libs.plugins.hilt) + id(libs.plugins.musicroad.data.get().pluginId) alias(libs.plugins.kotlin.serialization) } android { namespace = "com.squirtles.applemusic" - compileSdk = 34 - - defaultConfig { - minSdk = 26 - - testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner" - consumerProguardFiles("consumer-rules.pro") - } - - buildTypes { - release { - isMinifyEnabled = false - proguardFiles(getDefaultProguardFile("proguard-android-optimize.txt"), "proguard-rules.pro") - } - } - compileOptions { - sourceCompatibility = JavaVersion.VERSION_17 - targetCompatibility = JavaVersion.VERSION_17 - } - kotlinOptions { - jvmTarget = "17" - } } dependencies { implementation(projects.domain.applemusic) - implementation(projects.core.model) - implementation(projects.core.buildconfig) + implementation(libs.androidx.paging.runtime) + // Kotlinx Serialization implementation(libs.kotlinx.serialization.json) - // hilt - implementation(libs.hilt.android) - ksp(libs.hilt.android.compiler) - implementation(libs.inject) - // OkHttp - implementation(libs.okhttp.logging) - - // Retrofit - implementation(libs.retrofit.core) - implementation(libs.retrofit.kotlin.serialization) + implementation(libs.bundles.network) } diff --git a/data/build.gradle.kts b/data/build.gradle.kts index a7290dcb..3bf54053 100644 --- a/data/build.gradle.kts +++ b/data/build.gradle.kts @@ -1,72 +1,26 @@ plugins { - alias(libs.plugins.android.library) - alias(libs.plugins.jetbrains.kotlin.android) - alias(libs.plugins.ksp) - alias(libs.plugins.hilt) + alias(libs.plugins.musicroad.android.library) + alias(libs.plugins.musicroad.hilt) alias(libs.plugins.kotlin.serialization) } android { namespace = "com.squirtles.data" - compileSdk = 34 - - defaultConfig { - minSdk = 26 - - testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner" - consumerProguardFiles("consumer-rules.pro") - } - - buildTypes { - debug { - isMinifyEnabled = false - } - - release { - isMinifyEnabled = false - proguardFiles( - getDefaultProguardFile("proguard-android-optimize.txt"), - "proguard-rules.pro" - ) - } - } - compileOptions { - sourceCompatibility = JavaVersion.VERSION_17 - targetCompatibility = JavaVersion.VERSION_17 - } - kotlinOptions { - jvmTarget = "17" - } } dependencies { implementation(projects.domain) implementation(projects.core.buildconfig) - implementation(libs.androidx.core.ktx) - implementation(libs.androidx.appcompat) - implementation(libs.material) testImplementation(libs.junit) - androidTestImplementation(libs.androidx.junit) - androidTestImplementation(libs.androidx.espresso.core) + androidTestImplementation(libs.bundles.test) // Firebase - implementation(libs.firebase.firestore.ktx) - implementation(libs.firebase.functions.ktx) + implementation(libs.bundles.firebase) implementation(libs.geofire.android.common) - // Hilt - implementation(libs.hilt.android) - ksp(libs.hilt.android.compiler) - androidTestImplementation(libs.hilt.android.testing) - kspAndroidTest(libs.hilt.android.compiler) - // OkHttp - implementation(libs.okhttp.logging) - - // Retrofit - implementation(libs.retrofit.core) - implementation(libs.retrofit.kotlin.serialization) + implementation(libs.bundles.network) // Kotlinx Serialization implementation(libs.kotlinx.serialization.json) diff --git a/data/favorite/build.gradle.kts b/data/favorite/build.gradle.kts index c6f8bafd..157879a7 100644 --- a/data/favorite/build.gradle.kts +++ b/data/favorite/build.gradle.kts @@ -1,60 +1,19 @@ plugins { - alias(libs.plugins.android.library) - alias(libs.plugins.jetbrains.kotlin.android) - alias(libs.plugins.ksp) - alias(libs.plugins.hilt) + id(libs.plugins.musicroad.data.get().pluginId) alias(libs.plugins.kotlin.serialization) } android { namespace = "com.squirtles.favorite" - compileSdk = 34 - - defaultConfig { - minSdk = 26 - - testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner" - consumerProguardFiles("consumer-rules.pro") - } - - buildTypes { - debug { - isMinifyEnabled = false - } - - release { - isMinifyEnabled = false - proguardFiles( - getDefaultProguardFile("proguard-android-optimize.txt"), - "proguard-rules.pro" - ) - } - } - compileOptions { - sourceCompatibility = JavaVersion.VERSION_17 - targetCompatibility = JavaVersion.VERSION_17 - } - kotlinOptions { - jvmTarget = "17" - } } dependencies { implementation(projects.data.firebase) implementation(projects.domain.favorite) - implementation(projects.core.buildconfig) + // Kotlinx Serialization implementation(libs.kotlinx.serialization.json) - // retrofit - implementation(libs.retrofit.core) - - // hilt - implementation(libs.hilt.android) - implementation(libs.firebase.functions.ktx) - ksp(libs.hilt.android.compiler) - implementation(libs.inject) - // Firebase - implementation(libs.firebase.firestore.ktx) + implementation(libs.bundles.firebase) } diff --git a/data/firebase/build.gradle.kts b/data/firebase/build.gradle.kts index 963b5934..d558630b 100644 --- a/data/firebase/build.gradle.kts +++ b/data/firebase/build.gradle.kts @@ -1,55 +1,12 @@ plugins { - alias(libs.plugins.android.library) - alias(libs.plugins.jetbrains.kotlin.android) - alias(libs.plugins.ksp) - alias(libs.plugins.hilt) + id(libs.plugins.musicroad.data.get().pluginId) } android { namespace = "com.squirtles.firebase" - compileSdk = 34 - - defaultConfig { - minSdk = 26 - - testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner" - consumerProguardFiles("consumer-rules.pro") - } - - buildTypes { - debug { - isMinifyEnabled = false - } - - release { - isMinifyEnabled = false - - proguardFiles( - getDefaultProguardFile("proguard-android-optimize.txt"), - "proguard-rules.pro" - ) - } - } - compileOptions { - sourceCompatibility = JavaVersion.VERSION_17 - targetCompatibility = JavaVersion.VERSION_17 - } - kotlinOptions { - jvmTarget = "17" - } - } dependencies { - implementation(projects.core.model) - implementation(projects.core.buildconfig) - - // hilt - implementation(libs.hilt.android) - implementation(libs.firebase.functions.ktx) - ksp(libs.hilt.android.compiler) - implementation(libs.inject) - // Firebase implementation(libs.firebase.firestore.ktx) implementation(libs.geofire.android.common) diff --git a/data/location/build.gradle.kts b/data/location/build.gradle.kts index 98ea2722..3b7287ee 100644 --- a/data/location/build.gradle.kts +++ b/data/location/build.gradle.kts @@ -1,42 +1,12 @@ plugins { - alias(libs.plugins.android.library) - alias(libs.plugins.jetbrains.kotlin.android) - alias(libs.plugins.ksp) - alias(libs.plugins.hilt) + alias(libs.plugins.musicroad.android.library) + alias(libs.plugins.musicroad.hilt) } android { namespace = "com.squirtles.location" - compileSdk = 34 - - defaultConfig { - minSdk = 26 - - testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner" - consumerProguardFiles("consumer-rules.pro") - } - - buildTypes { - release { - isMinifyEnabled = false - proguardFiles(getDefaultProguardFile("proguard-android-optimize.txt"), "proguard-rules.pro") - } - } - compileOptions { - sourceCompatibility = JavaVersion.VERSION_17 - targetCompatibility = JavaVersion.VERSION_17 - } - kotlinOptions { - jvmTarget = "17" - } } dependencies { implementation(projects.domain.location) - - // hilt - implementation(libs.hilt.android) - implementation(libs.firebase.functions.ktx) - ksp(libs.hilt.android.compiler) - implementation(libs.inject) } diff --git a/data/order/build.gradle.kts b/data/order/build.gradle.kts index 02e8cd68..0fd62d2a 100644 --- a/data/order/build.gradle.kts +++ b/data/order/build.gradle.kts @@ -1,43 +1,11 @@ plugins { - alias(libs.plugins.android.library) - alias(libs.plugins.jetbrains.kotlin.android) - alias(libs.plugins.ksp) - alias(libs.plugins.hilt) + id(libs.plugins.musicroad.data.get().pluginId) } android { namespace = "com.squirtles.order" - compileSdk = 34 - - defaultConfig { - minSdk = 26 - - testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner" - consumerProguardFiles("consumer-rules.pro") - } - - buildTypes { - release { - isMinifyEnabled = false - proguardFiles(getDefaultProguardFile("proguard-android-optimize.txt"), "proguard-rules.pro") - } - } - compileOptions { - sourceCompatibility = JavaVersion.VERSION_17 - targetCompatibility = JavaVersion.VERSION_17 - } - kotlinOptions { - jvmTarget = "17" - } } dependencies { - implementation(projects.core.model) implementation(projects.domain.order) - - // hilt - implementation(libs.hilt.android) - implementation(libs.firebase.functions.ktx) - ksp(libs.hilt.android.compiler) - implementation(libs.inject) } diff --git a/data/pick/build.gradle.kts b/data/pick/build.gradle.kts index 488d06b4..800d41ca 100644 --- a/data/pick/build.gradle.kts +++ b/data/pick/build.gradle.kts @@ -1,47 +1,15 @@ plugins { - alias(libs.plugins.android.library) - alias(libs.plugins.jetbrains.kotlin.android) - alias(libs.plugins.ksp) - alias(libs.plugins.hilt) + id(libs.plugins.musicroad.data.get().pluginId) } android { namespace = "com.squirtles.pick" - compileSdk = 34 - - defaultConfig { - minSdk = 26 - - testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner" - consumerProguardFiles("consumer-rules.pro") - } - - buildTypes { - release { - isMinifyEnabled = false - proguardFiles(getDefaultProguardFile("proguard-android-optimize.txt"), "proguard-rules.pro") - } - } - compileOptions { - sourceCompatibility = JavaVersion.VERSION_17 - targetCompatibility = JavaVersion.VERSION_17 - } - kotlinOptions { - jvmTarget = "17" - } } dependencies { - implementation(projects.core.model) implementation(projects.domain.pick) implementation(projects.data.firebase) - // hilt - implementation(libs.hilt.android) - implementation(libs.firebase.functions.ktx) - ksp(libs.hilt.android.compiler) - implementation(libs.inject) - // Firebase implementation(libs.firebase.firestore.ktx) implementation(libs.geofire.android.common) diff --git a/data/user/build.gradle.kts b/data/user/build.gradle.kts index 413686c1..a98954e3 100644 --- a/data/user/build.gradle.kts +++ b/data/user/build.gradle.kts @@ -1,49 +1,17 @@ plugins { - alias(libs.plugins.android.library) - alias(libs.plugins.jetbrains.kotlin.android) - alias(libs.plugins.ksp) - alias(libs.plugins.hilt) + id(libs.plugins.musicroad.data.get().pluginId) } android { namespace = "com.squirtles.user" - compileSdk = 34 - - defaultConfig { - minSdk = 26 - - testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner" - consumerProguardFiles("consumer-rules.pro") - } - - buildTypes { - release { - isMinifyEnabled = false - proguardFiles(getDefaultProguardFile("proguard-android-optimize.txt"), "proguard-rules.pro") - } - } - compileOptions { - sourceCompatibility = JavaVersion.VERSION_17 - targetCompatibility = JavaVersion.VERSION_17 - } - kotlinOptions { - jvmTarget = "17" - } } dependencies { - implementation(projects.core.model) implementation(projects.domain.user) implementation(projects.data.firebase) // Datastore implementation(libs.androidx.datastore.preferences) - - // hilt - implementation(libs.hilt.android) - implementation(libs.firebase.functions.ktx) - ksp(libs.hilt.android.compiler) - implementation(libs.inject) // firebase implementation(libs.firebase.firestore.ktx) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 0a91f6f5..0a0f0053 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -151,10 +151,10 @@ kotlinx-coroutines-android = { group = "org.jetbrains.kotlinx", name = "kotlinx- firebase-bom = { module = "com.google.firebase:firebase-bom", version.ref = "firebaseBom" } firebase-firestore-ktx = { group = "com.google.firebase", name = "firebase-firestore-ktx", version.ref = "firebaseFirestoreKtx" } firebase-functions-ktx = { group = "com.google.firebase", name = "firebase-functions-ktx", version.ref = "firebaseFunctionsKtx" } +google-firebase-dynamic-module-support = { module = "com.google.firebase:firebase-dynamic-module-support", version.ref = "firebaseDynamicModuleSupportVersion" } # goefire geofire-android-common = { module = "com.firebase:geofire-android-common", version.ref = "geofireAndroidCommon" } -google-firebase-dynamic-module-support = { module = "com.google.firebase:firebase-dynamic-module-support", version.ref = "firebaseDynamicModuleSupportVersion" } # Analytics firebase-crashlytics = { module = "com.google.firebase:firebase-crashlytics" } @@ -221,13 +221,13 @@ compose = [ "androidx-lifecycle-viewmodel-compose", "compose-runtime-android", "compose-ui", + "compose-ui-tooling", "compose-ui-tooling-preview", ] compose-debug = [ "compose-ui-test-junit4", "compose-ui-test-manifest", - "compose-ui-tooling", ] coroutines = [ @@ -239,13 +239,6 @@ datastore = [ "androidx-datastore-preferences", ] -di = [ - "hilt-android", - "hilt-android-compiler", - "hilt-android-testing", - "inject", -] - exoplayer = [ "androidx-media3-exoplayer", "androidx-media3-exoplayer-dash", @@ -254,10 +247,6 @@ exoplayer = [ firebase = [ "firebase-firestore-ktx", "firebase-functions-ktx", -] - -geofire = [ - "geofire-android-common", "google-firebase-dynamic-module-support", ] @@ -299,6 +288,11 @@ coil = [ "coil-network-okhttp", ] +test = [ + "androidx-junit", + "androidx-espresso-core", +] + # Plugins [plugins] android-application = { id = "com.android.application", version.ref = "agp" } @@ -316,3 +310,5 @@ musicroad-android-application = { id = "musicroad.android.application", version musicroad-java-library = { id = "musicroad.java.library", version = "unspecified" } musicroad-android-library = { id = "musicroad.android.library", version = "unspecified" } musicroad-compose-library = { id = "musicroad.android.compose", version = "unspecified" } +musicroad-hilt = { id = "musicroad.hilt", version = "unspecified" } +musicroad-data = { id = "musicroad.data" } diff --git a/settings.gradle.kts b/settings.gradle.kts index af6651e5..66ec8ab7 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -1,7 +1,6 @@ pluginManagement { includeBuild("build-logic") - gradle.startParameter.excludedTaskNames.addAll(listOf(":build-logic:convention:testClasses")) repositories { google { @@ -21,9 +20,13 @@ dependencyResolutionManagement { google() mavenCentral() maven("https://repository.map.naver.com/archive/maven") + gradlePluginPortal() } } +enableFeaturePreview("TYPESAFE_PROJECT_ACCESSORS") +gradle.startParameter.excludedTaskNames.addAll(listOf(":build-logic:convention:testClasses")) + rootProject.name = "MusicRoad" include(":app") include(":domain") From 35c5f2b746b7892949838d1a230eb6e73829cb44 Mon Sep 17 00:00:00 2001 From: miller198 Date: Tue, 4 Mar 2025 16:21:18 +0900 Subject: [PATCH 29/62] =?UTF-8?q?[fix]=20material=20=EC=9D=98=EC=A1=B4?= =?UTF-8?q?=EC=84=B1=20=EB=88=84=EB=9D=BD=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/build.gradle.kts | 1 + 1 file changed, 1 insertion(+) diff --git a/app/build.gradle.kts b/app/build.gradle.kts index c39d546b..5cbc6583 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -106,6 +106,7 @@ dependencies { implementation(libs.androidx.lifecycle.runtime.ktx) implementation(libs.compose.ui) implementation(libs.androidx.core.splashscreen) + implementation(libs.material) // implementation(libs.androidx.compose.animation) testImplementation(libs.junit) androidTestImplementation(libs.androidx.junit) From a3117ea8c30ddf507c375b8f6035925d889652bd Mon Sep 17 00:00:00 2001 From: miller198 Date: Tue, 4 Mar 2025 16:22:48 +0900 Subject: [PATCH 30/62] =?UTF-8?q?[refactor]=20=EC=9D=98=EC=A1=B4=EC=84=B1?= =?UTF-8?q?=20=ED=94=8C=EB=9F=AC=EA=B7=B8=EC=9D=B8=20=EC=BD=94=EB=93=9C=20?= =?UTF-8?q?=EC=A0=95=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../com/squirtles/convention/AndroidApplicationPlugin.kt | 2 +- .../java/com/squirtles/convention/AndroidComposePlugin.kt | 7 ++----- .../java/com/squirtles/convention/AndroidLibraryPlugin.kt | 1 - .../src/main/java/com/squirtles/convention/HiltPlugin.kt | 3 +-- .../com/squirtles/convention/extensions/ComposeAndroid.kt | 3 +-- 5 files changed, 5 insertions(+), 11 deletions(-) diff --git a/build-logic/convention/src/main/java/com/squirtles/convention/AndroidApplicationPlugin.kt b/build-logic/convention/src/main/java/com/squirtles/convention/AndroidApplicationPlugin.kt index 32a0db74..b0218b13 100644 --- a/build-logic/convention/src/main/java/com/squirtles/convention/AndroidApplicationPlugin.kt +++ b/build-logic/convention/src/main/java/com/squirtles/convention/AndroidApplicationPlugin.kt @@ -10,7 +10,7 @@ import org.gradle.kotlin.dsl.configure // app module class AndroidApplicationPlugin: Plugin { override fun apply(target: Project) { - target.run { + with(target) { pluginManager.run { apply("com.android.application") apply("org.jetbrains.kotlin.android") diff --git a/build-logic/convention/src/main/java/com/squirtles/convention/AndroidComposePlugin.kt b/build-logic/convention/src/main/java/com/squirtles/convention/AndroidComposePlugin.kt index 8d39bca1..444e2f55 100644 --- a/build-logic/convention/src/main/java/com/squirtles/convention/AndroidComposePlugin.kt +++ b/build-logic/convention/src/main/java/com/squirtles/convention/AndroidComposePlugin.kt @@ -12,11 +12,8 @@ import org.gradle.kotlin.dsl.dependencies class AndroidComposePlugin : Plugin { override fun apply(target: Project) { - target.run { - pluginManager.run { - apply("musicroad.android.library") - apply("org.jetbrains.kotlin.plugin.compose") - } + with(target) { + pluginManager.apply("musicroad.android.library") extensions.configure { configureComposeAndroid(this) diff --git a/build-logic/convention/src/main/java/com/squirtles/convention/AndroidLibraryPlugin.kt b/build-logic/convention/src/main/java/com/squirtles/convention/AndroidLibraryPlugin.kt index 162b628f..9fc4303d 100644 --- a/build-logic/convention/src/main/java/com/squirtles/convention/AndroidLibraryPlugin.kt +++ b/build-logic/convention/src/main/java/com/squirtles/convention/AndroidLibraryPlugin.kt @@ -4,7 +4,6 @@ import com.android.build.gradle.LibraryExtension import com.squirtles.convention.extensions.configureKotlinAndroid import com.squirtles.convention.extensions.configureKotlinCoroutine import com.squirtles.convention.extensions.getBundle -import com.squirtles.convention.extensions.getLibrary import com.squirtles.convention.extensions.implementation import com.squirtles.convention.extensions.libs import org.gradle.api.Plugin diff --git a/build-logic/convention/src/main/java/com/squirtles/convention/HiltPlugin.kt b/build-logic/convention/src/main/java/com/squirtles/convention/HiltPlugin.kt index e5860e02..91c4b136 100644 --- a/build-logic/convention/src/main/java/com/squirtles/convention/HiltPlugin.kt +++ b/build-logic/convention/src/main/java/com/squirtles/convention/HiltPlugin.kt @@ -1,7 +1,6 @@ package com.squirtles.convention import com.squirtles.convention.extensions.androidTestImplementation -import com.squirtles.convention.extensions.getBundle import com.squirtles.convention.extensions.getLibrary import com.squirtles.convention.extensions.implementation import com.squirtles.convention.extensions.ksp @@ -14,7 +13,7 @@ import org.gradle.kotlin.dsl.dependencies class HiltPlugin : Plugin { override fun apply(target: Project) { with(target) { - pluginManager.apply { + pluginManager.run { apply("dagger.hilt.android.plugin") apply("com.google.devtools.ksp") } diff --git a/build-logic/convention/src/main/java/com/squirtles/convention/extensions/ComposeAndroid.kt b/build-logic/convention/src/main/java/com/squirtles/convention/extensions/ComposeAndroid.kt index d6a84629..76494981 100644 --- a/build-logic/convention/src/main/java/com/squirtles/convention/extensions/ComposeAndroid.kt +++ b/build-logic/convention/src/main/java/com/squirtles/convention/extensions/ComposeAndroid.kt @@ -5,8 +5,6 @@ import org.gradle.api.Project import org.gradle.kotlin.dsl.dependencies internal fun Project.configureComposeAndroid(commonExtension: CommonExtension<*, *, *, *, *, *>) { - pluginManager.apply("org.jetbrains.kotlin.plugin.compose") - commonExtension.apply { buildFeatures { compose = true @@ -20,6 +18,7 @@ internal fun Project.configureComposeAndroid(commonExtension: CommonExtension<*, val composeBom = libs.getLibrary("compose.bom") implementation(platform(composeBom)) implementation(libs.getBundle("compose")) + implementation(libs.getBundle("material")) debugImplementation(libs.getBundle("compose-debug")) } } From 458926b0d51c675c7e919d75f58c1f113b79e18d Mon Sep 17 00:00:00 2001 From: miller198 Date: Tue, 4 Mar 2025 16:23:30 +0900 Subject: [PATCH 31/62] =?UTF-8?q?[feature]=20feature=20=ED=94=8C=EB=9F=AC?= =?UTF-8?q?=EA=B7=B8=EC=9D=B8=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- build-logic/convention/build.gradle.kts | 9 ++++-- .../convention/MusicRoadFeaturePlugin.kt | 29 +++++++++++++++++++ gradle/libs.versions.toml | 7 ++--- 3 files changed, 39 insertions(+), 6 deletions(-) create mode 100644 build-logic/convention/src/main/java/com/squirtles/convention/MusicRoadFeaturePlugin.kt diff --git a/build-logic/convention/build.gradle.kts b/build-logic/convention/build.gradle.kts index 894c9a24..df21f6a0 100644 --- a/build-logic/convention/build.gradle.kts +++ b/build-logic/convention/build.gradle.kts @@ -28,8 +28,8 @@ gradlePlugin { implementationClass = "com.squirtles.convention.AndroidLibraryPlugin" } - register("androidCompose") { - id = "musicroad.android.compose" + register("composeLibrary") { + id = "musicroad.compose.library" implementationClass = "com.squirtles.convention.AndroidComposePlugin" } @@ -47,5 +47,10 @@ gradlePlugin { id = "musicroad.data" implementationClass = "com.squirtles.convention.MusicRoadDataPlugin" } + + register("feature") { + id = "musicroad.feature" + implementationClass = "com.squirtles.convention.MusicRoadFeaturePlugin" + } } } diff --git a/build-logic/convention/src/main/java/com/squirtles/convention/MusicRoadFeaturePlugin.kt b/build-logic/convention/src/main/java/com/squirtles/convention/MusicRoadFeaturePlugin.kt new file mode 100644 index 00000000..505923e6 --- /dev/null +++ b/build-logic/convention/src/main/java/com/squirtles/convention/MusicRoadFeaturePlugin.kt @@ -0,0 +1,29 @@ +package com.squirtles.convention + +import com.squirtles.convention.extensions.getBundle +import com.squirtles.convention.extensions.implementation +import com.squirtles.convention.extensions.libs +import org.gradle.api.Plugin +import org.gradle.api.Project +import org.gradle.kotlin.dsl.dependencies + + +class MusicRoadFeaturePlugin : Plugin { + override fun apply(target: Project) { + with(target) { + pluginManager.run { + apply("musicroad.compose.library") + apply("musicroad.hilt") + } + + dependencies { + implementation(project(":core:model")) + implementation(project(":core:util")) + implementation(project(":core:navigation")) + +// implementation(libs.getBundle("compose")) + implementation(libs.getBundle("navigation")) + } + } + } +} diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 0a0f0053..a559b738 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -264,8 +264,6 @@ material = [ media3 = [ "androidx-media3-common", - "androidx-media3-exoplayer", - "androidx-media3-exoplayer-dash", "androidx-media3-session", "androidx-media3-ui", ] @@ -309,6 +307,7 @@ jetbrains-kotlin-jvm = { id = "org.jetbrains.kotlin.jvm", version.ref = "jetbrai musicroad-android-application = { id = "musicroad.android.application", version = "unspecified" } musicroad-java-library = { id = "musicroad.java.library", version = "unspecified" } musicroad-android-library = { id = "musicroad.android.library", version = "unspecified" } -musicroad-compose-library = { id = "musicroad.android.compose", version = "unspecified" } +musicroad-compose-library = { id = "musicroad.compose.library", version = "unspecified" } musicroad-hilt = { id = "musicroad.hilt", version = "unspecified" } -musicroad-data = { id = "musicroad.data" } +musicroad-data = { id = "musicroad.data", version = "unspecified" } +musicroad-feature = { id = "musicroad.feature", version = "unspecified" } From b5765d07803aa7c012ef52c714fae02aa64dd92b Mon Sep 17 00:00:00 2001 From: miller198 Date: Tue, 4 Mar 2025 16:30:10 +0900 Subject: [PATCH 32/62] =?UTF-8?q?[refactor]=20compose,=20feature=20?= =?UTF-8?q?=ED=94=8C=EB=9F=AC=EA=B7=B8=EC=9D=B8=20=EC=82=AC=EC=9A=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- core/common/build.gradle.kts | 19 +++------- core/musicplayer/build.gradle.kts | 1 + core/picklist/build.gradle.kts | 18 +--------- domain/applemusic/build.gradle.kts | 15 +------- domain/build.gradle.kts | 44 +++-------------------- domain/favorite/build.gradle.kts | 9 +---- domain/location/build.gradle.kts | 16 +-------- domain/order/build.gradle.kts | 8 +---- domain/pick/build.gradle.kts | 9 +---- domain/picklist/build.gradle.kts | 8 +---- domain/player/build.gradle.kts | 20 ++--------- domain/user/build.gradle.kts | 10 ++---- feature/create/build.gradle.kts | 57 ++---------------------------- mediaservice/build.gradle.kts | 46 +++--------------------- 14 files changed, 31 insertions(+), 249 deletions(-) diff --git a/core/common/build.gradle.kts b/core/common/build.gradle.kts index 1d80ffaf..7c0b9516 100644 --- a/core/common/build.gradle.kts +++ b/core/common/build.gradle.kts @@ -1,18 +1,9 @@ plugins { - alias(libs.plugins.musicroad.android.library) -// alias(libs.plugins.musicroad.compose.library) + alias(libs.plugins.musicroad.compose.library) } android { namespace = "com.squirtles.common" - - buildFeatures { - compose = true - } - - composeOptions { - kotlinCompilerExtensionVersion = "1.5.14" - } } dependencies { @@ -21,10 +12,10 @@ dependencies { // implementation(platform(libs.compose.bom)) // implementation(libs.compose.runtime.android) // implementation(libs.compose.ui.tooling.preview) - implementation(platform(libs.compose.bom)) - implementation(libs.bundles.compose) - implementation(libs.bundles.compose.debug) - implementation(libs.bundles.material) +// implementation(platform(libs.compose.bom)) +// implementation(libs.bundles.compose) +// implementation(libs.bundles.compose.debug) +// implementation(libs.bundles.material) // Coil implementation(libs.bundles.coil) diff --git a/core/musicplayer/build.gradle.kts b/core/musicplayer/build.gradle.kts index bc23a991..86ba2411 100644 --- a/core/musicplayer/build.gradle.kts +++ b/core/musicplayer/build.gradle.kts @@ -10,6 +10,7 @@ android { dependencies { implementation(projects.domain.player) implementation(projects.core.model) + implementation(libs.material) // ExoPlayer implementation(libs.bundles.exoplayer) diff --git a/core/picklist/build.gradle.kts b/core/picklist/build.gradle.kts index db243ce3..18df075c 100644 --- a/core/picklist/build.gradle.kts +++ b/core/picklist/build.gradle.kts @@ -1,18 +1,9 @@ plugins { - alias(libs.plugins.musicroad.android.library) + alias(libs.plugins.musicroad.compose.library) } android { namespace = "com.squirtles.picklist" - compileSdk = 34 - - buildFeatures { - compose = true - } - - composeOptions { - kotlinCompilerExtensionVersion = "1.5.14" - } } dependencies { @@ -22,13 +13,6 @@ dependencies { testImplementation(libs.junit) androidTestImplementation(libs.bundles.test) - // Compose - implementation(platform(libs.compose.bom)) - implementation(libs.bundles.compose) - implementation(libs.bundles.compose.debug) - - implementation(libs.bundles.material) - // Coil implementation(libs.bundles.coil) } diff --git a/domain/applemusic/build.gradle.kts b/domain/applemusic/build.gradle.kts index 8aec2ced..0cf6f96b 100644 --- a/domain/applemusic/build.gradle.kts +++ b/domain/applemusic/build.gradle.kts @@ -1,22 +1,9 @@ plugins { - alias(libs.plugins.android.library) - alias(libs.plugins.jetbrains.kotlin.android) + alias(libs.plugins.musicroad.android.library) } android { namespace = "com.squirtles.applemusic" - compileSdk = 34 - - defaultConfig { - minSdk = 26 - } - compileOptions { - sourceCompatibility = JavaVersion.VERSION_17 - targetCompatibility = JavaVersion.VERSION_17 - } - kotlinOptions { - jvmTarget = "17" - } } dependencies { diff --git a/domain/build.gradle.kts b/domain/build.gradle.kts index 5a2535ea..786708e9 100644 --- a/domain/build.gradle.kts +++ b/domain/build.gradle.kts @@ -1,57 +1,23 @@ plugins { - alias(libs.plugins.android.library) - alias(libs.plugins.jetbrains.kotlin.android) - alias(libs.plugins.ksp) - alias(libs.plugins.hilt) + alias(libs.plugins.musicroad.android.library) + alias(libs.plugins.musicroad.hilt) alias(libs.plugins.kotlin.serialization) } android { namespace = "com.squirtles.domain" - compileSdk = 34 - - defaultConfig { - minSdk = 26 - - testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner" - consumerProguardFiles("consumer-rules.pro") - } - - buildTypes { - release { - isMinifyEnabled = false - proguardFiles(getDefaultProguardFile("proguard-android-optimize.txt"), "proguard-rules.pro") - } - } - compileOptions { - sourceCompatibility = JavaVersion.VERSION_17 - targetCompatibility = JavaVersion.VERSION_17 - } - kotlinOptions { - jvmTarget = "17" - } } dependencies { implementation(project(":mediaservice")) - implementation(libs.androidx.core.ktx) - implementation(libs.androidx.appcompat) - implementation(libs.material) - testImplementation(libs.junit) - androidTestImplementation(libs.androidx.junit) - androidTestImplementation(libs.androidx.espresso.core) - //hilt - implementation(libs.hilt.android) - ksp(libs.hilt.android.compiler) - androidTestImplementation(libs.hilt.android.testing) + testImplementation(libs.junit) + androidTestImplementation(libs.bundles.test) - implementation(libs.inject) implementation(libs.androidx.paging.runtime) - implementation(libs.androidx.media3.common) - implementation(libs.androidx.media3.session) + implementation(libs.bundles.media3) // Serialization implementation(libs.kotlinx.serialization.json) diff --git a/domain/favorite/build.gradle.kts b/domain/favorite/build.gradle.kts index 64611a79..d45c0a97 100644 --- a/domain/favorite/build.gradle.kts +++ b/domain/favorite/build.gradle.kts @@ -1,15 +1,8 @@ plugins { - id("java-library") - alias(libs.plugins.jetbrains.kotlin.jvm) -} - -java { - sourceCompatibility = JavaVersion.VERSION_17 - targetCompatibility = JavaVersion.VERSION_17 + id(libs.plugins.musicroad.java.library.get().pluginId) } dependencies { implementation(projects.core.model) implementation(projects.domain.picklist) - implementation(libs.inject) } diff --git a/domain/location/build.gradle.kts b/domain/location/build.gradle.kts index fa2c42be..7261baad 100644 --- a/domain/location/build.gradle.kts +++ b/domain/location/build.gradle.kts @@ -1,25 +1,11 @@ plugins { - alias(libs.plugins.android.library) - alias(libs.plugins.jetbrains.kotlin.android) + alias(libs.plugins.musicroad.android.library) } android { namespace = "com.squirtles.location" - compileSdk = 34 - - defaultConfig { - minSdk = 26 - } - compileOptions { - sourceCompatibility = JavaVersion.VERSION_17 - targetCompatibility = JavaVersion.VERSION_17 - } - kotlinOptions { - jvmTarget = "17" - } } dependencies { - implementation(libs.androidx.core.ktx) implementation(libs.inject) } diff --git a/domain/order/build.gradle.kts b/domain/order/build.gradle.kts index 511ba9af..d45c0a97 100644 --- a/domain/order/build.gradle.kts +++ b/domain/order/build.gradle.kts @@ -1,14 +1,8 @@ plugins { - id("java-library") - alias(libs.plugins.jetbrains.kotlin.jvm) + id(libs.plugins.musicroad.java.library.get().pluginId) } -java { - sourceCompatibility = JavaVersion.VERSION_17 - targetCompatibility = JavaVersion.VERSION_17 -} dependencies { implementation(projects.core.model) implementation(projects.domain.picklist) - implementation(libs.inject) } diff --git a/domain/pick/build.gradle.kts b/domain/pick/build.gradle.kts index 64611a79..d45c0a97 100644 --- a/domain/pick/build.gradle.kts +++ b/domain/pick/build.gradle.kts @@ -1,15 +1,8 @@ plugins { - id("java-library") - alias(libs.plugins.jetbrains.kotlin.jvm) -} - -java { - sourceCompatibility = JavaVersion.VERSION_17 - targetCompatibility = JavaVersion.VERSION_17 + id(libs.plugins.musicroad.java.library.get().pluginId) } dependencies { implementation(projects.core.model) implementation(projects.domain.picklist) - implementation(libs.inject) } diff --git a/domain/picklist/build.gradle.kts b/domain/picklist/build.gradle.kts index 3926c5c5..e0a1f12a 100644 --- a/domain/picklist/build.gradle.kts +++ b/domain/picklist/build.gradle.kts @@ -1,11 +1,5 @@ plugins { - id("java-library") - alias(libs.plugins.jetbrains.kotlin.jvm) -} - -java { - sourceCompatibility = JavaVersion.VERSION_17 - targetCompatibility = JavaVersion.VERSION_17 + id(libs.plugins.musicroad.java.library.get().pluginId) } dependencies { diff --git a/domain/player/build.gradle.kts b/domain/player/build.gradle.kts index 5337c968..9980d58d 100644 --- a/domain/player/build.gradle.kts +++ b/domain/player/build.gradle.kts @@ -1,29 +1,15 @@ plugins { - alias(libs.plugins.android.library) - alias(libs.plugins.jetbrains.kotlin.android) + alias(libs.plugins.musicroad.android.library) } android { namespace = "com.squirtles.player" - compileSdk = 34 - - defaultConfig { - minSdk = 26 - } - compileOptions { - sourceCompatibility = JavaVersion.VERSION_17 - targetCompatibility = JavaVersion.VERSION_17 - } - kotlinOptions { - jvmTarget = "17" - } } dependencies { implementation(projects.core.model) implementation(projects.core.mediaservice) + implementation(libs.inject) - implementation(libs.kotlinx.coroutines.core) - implementation(libs.androidx.media3.common) - implementation(libs.androidx.media3.session) + implementation(libs.bundles.media3) } diff --git a/domain/user/build.gradle.kts b/domain/user/build.gradle.kts index a8705207..8cb3df6f 100644 --- a/domain/user/build.gradle.kts +++ b/domain/user/build.gradle.kts @@ -1,15 +1,9 @@ plugins { - id("java-library") - alias(libs.plugins.jetbrains.kotlin.jvm) -} - -java { - sourceCompatibility = JavaVersion.VERSION_17 - targetCompatibility = JavaVersion.VERSION_17 + id(libs.plugins.musicroad.java.library.get().pluginId) } dependencies { implementation(projects.core.model) - implementation(libs.inject) + implementation(libs.kotlinx.coroutines.core) } diff --git a/feature/create/build.gradle.kts b/feature/create/build.gradle.kts index c3ad95cc..1a5bf244 100644 --- a/feature/create/build.gradle.kts +++ b/feature/create/build.gradle.kts @@ -1,73 +1,22 @@ plugins { - alias(libs.plugins.android.library) - alias(libs.plugins.jetbrains.kotlin.android) + alias(libs.plugins.musicroad.feature) alias(libs.plugins.kotlin.serialization) - alias(libs.plugins.ksp) - alias(libs.plugins.hilt) } android { namespace = "com.squirtles.create" - compileSdk = 34 - - defaultConfig { - minSdk = 26 - - testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner" - consumerProguardFiles("consumer-rules.pro") - } - - buildTypes { - release { - isMinifyEnabled = false - proguardFiles(getDefaultProguardFile("proguard-android-optimize.txt"), "proguard-rules.pro") - } - } - compileOptions { - sourceCompatibility = JavaVersion.VERSION_17 - targetCompatibility = JavaVersion.VERSION_17 - } - kotlinOptions { - jvmTarget = "17" - } - buildFeatures { // Enables Jetpack Compose for this module - compose = true - } - composeOptions { - kotlinCompilerExtensionVersion = "1.5.14" - } } dependencies { - implementation(projects.core.model) implementation(projects.core.common) - implementation(projects.core.util) - implementation(projects.core.navigation) + implementation(projects.domain.pick) implementation(projects.domain.user) implementation(projects.domain.applemusic) implementation(projects.domain.location) - implementation(libs.androidx.core.ktx) - implementation(libs.androidx.appcompat) - implementation(libs.material) testImplementation(libs.junit) - androidTestImplementation(libs.androidx.junit) - androidTestImplementation(libs.androidx.espresso.core) - - // Compose - implementation(platform(libs.compose.bom)) - implementation(libs.compose.material) - implementation(libs.compose.material3) - implementation(libs.compose.material.icons.extended) - implementation(libs.compose.ui.tooling.preview) - implementation(libs.navigation.compose) - - // Hilt - implementation(libs.hilt.android) - ksp(libs.hilt.android.compiler) - implementation(libs.inject) - implementation(libs.hilt.navigation.compose) + androidTestImplementation(libs.bundles.test) // Serialization implementation(libs.kotlinx.serialization.json) diff --git a/mediaservice/build.gradle.kts b/mediaservice/build.gradle.kts index add99e55..8c0622cf 100644 --- a/mediaservice/build.gradle.kts +++ b/mediaservice/build.gradle.kts @@ -1,53 +1,17 @@ plugins { - alias(libs.plugins.android.library) - alias(libs.plugins.jetbrains.kotlin.android) - alias(libs.plugins.ksp) - alias(libs.plugins.hilt) + alias(libs.plugins.musicroad.android.library) + alias(libs.plugins.musicroad.hilt) } android { namespace = "com.squirtles.mediaservice" - compileSdk = 34 - - defaultConfig { - minSdk = 26 - - testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner" - consumerProguardFiles("consumer-rules.pro") - } - - buildTypes { - release { - isMinifyEnabled = false - proguardFiles(getDefaultProguardFile("proguard-android-optimize.txt"), "proguard-rules.pro") - } - } - compileOptions { - sourceCompatibility = JavaVersion.VERSION_17 - targetCompatibility = JavaVersion.VERSION_17 - } - kotlinOptions { - jvmTarget = "17" - } } dependencies { - - implementation(libs.androidx.core.ktx) - implementation(libs.androidx.appcompat) - implementation(libs.material) testImplementation(libs.junit) - androidTestImplementation(libs.androidx.junit) - androidTestImplementation(libs.androidx.espresso.core) - - //hilt - implementation(libs.hilt.android) - ksp(libs.hilt.android.compiler) - androidTestImplementation(libs.hilt.android.testing) - implementation(libs.inject) + androidTestImplementation(libs.bundles.test) //media3 - implementation(libs.androidx.media3.exoplayer) - implementation(libs.androidx.media3.exoplayer.dash) - implementation(libs.androidx.media3.session) + implementation(libs.bundles.media3) + implementation(libs.bundles.exoplayer) } From d7f9bb109ecd99d713100a8395ffbda90d830352 Mon Sep 17 00:00:00 2001 From: miller198 Date: Tue, 4 Mar 2025 16:55:40 +0900 Subject: [PATCH 33/62] =?UTF-8?q?[refactor]=20=EC=9D=B4=EB=A6=84=EB=B3=80?= =?UTF-8?q?=EA=B2=BD,=20=ED=95=A8=EC=88=98=EC=82=AD=EC=A0=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit AppleMusicRemoteDataSource -> AppleMusicDataSource --- .../com/squirtles/applemusic/AppleMusicDataSourceImpl.kt | 2 +- .../com/squirtles/applemusic/AppleMusicRepositoryImpl.kt | 6 +----- .../java/com/squirtles/applemusic/di/AppleMusicModule.kt | 6 +++--- ...ppleMusicRemoteDataSource.kt => AppleMusicDataSource.kt} | 2 +- .../java/com/squirtles/applemusic/AppleMusicRepository.kt | 1 - 5 files changed, 6 insertions(+), 11 deletions(-) rename domain/applemusic/src/main/java/com/squirtles/applemusic/{AppleMusicRemoteDataSource.kt => AppleMusicDataSource.kt} (88%) diff --git a/data/applemusic/src/main/java/com/squirtles/applemusic/AppleMusicDataSourceImpl.kt b/data/applemusic/src/main/java/com/squirtles/applemusic/AppleMusicDataSourceImpl.kt index 022f20a4..fdcf8002 100644 --- a/data/applemusic/src/main/java/com/squirtles/applemusic/AppleMusicDataSourceImpl.kt +++ b/data/applemusic/src/main/java/com/squirtles/applemusic/AppleMusicDataSourceImpl.kt @@ -15,7 +15,7 @@ import javax.inject.Inject class AppleMusicDataSourceImpl @Inject constructor( private val appleMusicApi: AppleMusicApi -) : AppleMusicRemoteDataSource { +) : AppleMusicDataSource { override fun searchSongs(searchText: String): Flow> { return Pager( diff --git a/data/applemusic/src/main/java/com/squirtles/applemusic/AppleMusicRepositoryImpl.kt b/data/applemusic/src/main/java/com/squirtles/applemusic/AppleMusicRepositoryImpl.kt index 4815a4d1..ace82f57 100644 --- a/data/applemusic/src/main/java/com/squirtles/applemusic/AppleMusicRepositoryImpl.kt +++ b/data/applemusic/src/main/java/com/squirtles/applemusic/AppleMusicRepositoryImpl.kt @@ -7,16 +7,12 @@ import kotlinx.coroutines.flow.Flow import javax.inject.Inject class AppleMusicRepositoryImpl @Inject constructor( - private val appleMusicDataSource: AppleMusicRemoteDataSource + private val appleMusicDataSource: AppleMusicDataSource ) : AppleMusicRepository { override fun searchSongs(searchText: String): Flow> = appleMusicDataSource.searchSongs(searchText) - override suspend fun searchSongById(songId: String): Result { - TODO("Not yet implemented") - } - override suspend fun searchMusicVideos(searchText: String): Result> { return handleResult(AppleMusicException.NotFoundException()) { appleMusicDataSource.searchMusicVideos(searchText).ifEmpty { null } diff --git a/data/applemusic/src/main/java/com/squirtles/applemusic/di/AppleMusicModule.kt b/data/applemusic/src/main/java/com/squirtles/applemusic/di/AppleMusicModule.kt index f562aba0..84e46391 100644 --- a/data/applemusic/src/main/java/com/squirtles/applemusic/di/AppleMusicModule.kt +++ b/data/applemusic/src/main/java/com/squirtles/applemusic/di/AppleMusicModule.kt @@ -1,7 +1,7 @@ package com.squirtles.applemusic.di import com.squirtles.applemusic.AppleMusicDataSourceImpl -import com.squirtles.applemusic.AppleMusicRemoteDataSource +import com.squirtles.applemusic.AppleMusicDataSource import com.squirtles.applemusic.AppleMusicRepository import com.squirtles.applemusic.AppleMusicRepositoryImpl import com.squirtles.applemusic.api.AppleMusicApi @@ -36,11 +36,11 @@ object AppleMusicModule{ @Provides @Singleton - fun provideAppleMusicRepository(appleMusicDataSource: AppleMusicRemoteDataSource): AppleMusicRepository = + fun provideAppleMusicRepository(appleMusicDataSource: AppleMusicDataSource): AppleMusicRepository = AppleMusicRepositoryImpl(appleMusicDataSource) @Provides @Singleton - fun provideAppleMusicDataSource(api: AppleMusicApi): AppleMusicRemoteDataSource = + fun provideAppleMusicDataSource(api: AppleMusicApi): AppleMusicDataSource = AppleMusicDataSourceImpl(api) } diff --git a/domain/applemusic/src/main/java/com/squirtles/applemusic/AppleMusicRemoteDataSource.kt b/domain/applemusic/src/main/java/com/squirtles/applemusic/AppleMusicDataSource.kt similarity index 88% rename from domain/applemusic/src/main/java/com/squirtles/applemusic/AppleMusicRemoteDataSource.kt rename to domain/applemusic/src/main/java/com/squirtles/applemusic/AppleMusicDataSource.kt index b585dcae..099ea0db 100644 --- a/domain/applemusic/src/main/java/com/squirtles/applemusic/AppleMusicRemoteDataSource.kt +++ b/domain/applemusic/src/main/java/com/squirtles/applemusic/AppleMusicDataSource.kt @@ -5,7 +5,7 @@ import com.squirtles.model.MusicVideo import com.squirtles.model.Song import kotlinx.coroutines.flow.Flow -interface AppleMusicRemoteDataSource { +interface AppleMusicDataSource { fun searchSongs(searchText: String): Flow> suspend fun searchMusicVideos(searchText: String): List } diff --git a/domain/applemusic/src/main/java/com/squirtles/applemusic/AppleMusicRepository.kt b/domain/applemusic/src/main/java/com/squirtles/applemusic/AppleMusicRepository.kt index aba7253c..64b2b031 100644 --- a/domain/applemusic/src/main/java/com/squirtles/applemusic/AppleMusicRepository.kt +++ b/domain/applemusic/src/main/java/com/squirtles/applemusic/AppleMusicRepository.kt @@ -7,6 +7,5 @@ import kotlinx.coroutines.flow.Flow interface AppleMusicRepository { fun searchSongs(searchText: String): Flow> - suspend fun searchSongById(songId: String): Result suspend fun searchMusicVideos(searchText: String): Result> } From b1b807efaa5e6285924568d0e430fadbf777ffb5 Mon Sep 17 00:00:00 2001 From: miller198 Date: Fri, 7 Mar 2025 22:12:09 +0900 Subject: [PATCH 34/62] =?UTF-8?q?[refactor]=20=EC=98=88=EC=99=B8=EC=B2=98?= =?UTF-8?q?=EB=A6=AC=20=EA=B0=9C=EC=84=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../musicroad/detail/DetailViewModel.kt | 8 +- .../squirtles/common/ui/MessageAlertDialog.kt | 4 +- core/picklist/build.gradle.kts | 2 + .../squirtles/favorite/CloudFunctionHelper.kt | 12 +- .../FirebaseFavoriteDataSourceImpl.kt | 140 +++------ .../FirebaseFavoriteRepositoryImpl.kt | 17 +- .../firebase/BaseFirebaseDataSource.kt | 116 +++++++ .../firebase/FirebaseDataSourceConstants.kt | 24 +- .../squirtles/firebase/FirebaseException.kt | 50 ++- .../LocalLocationRepositoryImpl.kt | 3 +- .../{order => location}/di/LocationModule.kt | 4 +- .../pick/FirebasePickDataSourceImpl.kt | 288 ++++++------------ .../pick/FirebasePickRepositoryImpl.kt | 24 +- .../data/favorite/CloudFunctionHelper.kt | 12 +- .../FirebaseFavoriteDataSourceImpl.kt | 149 +++------ .../FirebaseFavoriteRepositoryImpl.kt | 17 +- .../data/firebase/BaseFirebaseDataSource.kt | 116 +++++++ .../firebase/FirebaseDataSourceConstants.kt | 24 +- .../data/firebase/FirebaseException.kt | 49 +++ .../data/firebase/FirebaseRepositoryUtils.kt | 19 ++ .../data/pick/FirebasePickDataSourceImpl.kt | 278 ++++++----------- .../data/pick/FirebasePickRepositoryImpl.kt | 27 +- .../squirtles/data/pick/model/FirebasePick.kt | 10 - .../user/FirebaseUserDataSourceImpl.kt | 85 +++--- .../user/FirebaseUserRepositoryImpl.kt | 21 +- .../favorite/FirebaseFavoriteDataSource.kt | 6 +- .../favorite/FirebaseFavoriteRepository.kt | 4 +- .../favorite/usecase/DeleteFavoriteUseCase.kt | 2 +- .../squirtles/pick/FirebasePickDataSource.kt | 12 +- .../squirtles/pick/FirebasePickRepository.kt | 2 +- .../pick/usecase/DeletePickUseCase.kt | 2 +- .../pick/usecase/FetchMyPicksUseCase.kt | 3 +- .../pick/usecase/FetchPickUseCase.kt | 5 +- .../picklist/RemovePickUseCaseInterface.kt | 2 +- .../favorite/FirebaseFavoriteDataSource.kt | 8 +- .../favorite/FirebaseFavoriteRepository.kt | 7 +- .../favorite/usecase/DeleteFavoriteUseCase.kt | 2 +- .../domain/pick/FirebasePickDataSource.kt | 12 +- .../domain/pick/FirebasePickRepository.kt | 5 +- .../domain/pick/usecase/DeletePickUseCase.kt | 2 +- .../pick/usecase/FetchFavoritePicksUseCase.kt | 4 +- .../pick/usecase/FetchMyPicksUseCase.kt | 3 +- .../domain/pick/usecase/FetchPickUseCase.kt | 5 +- .../picklist/RemovePickUseCaseInterface.kt | 2 +- .../squirtles/user/FirebaseUserDataSource.kt | 9 +- 45 files changed, 809 insertions(+), 787 deletions(-) create mode 100644 data/firebase/src/main/java/com/squirtles/firebase/BaseFirebaseDataSource.kt rename data/location/src/main/java/com/squirtles/{order => location}/LocalLocationRepositoryImpl.kt (87%) rename data/location/src/main/java/com/squirtles/{order => location}/di/LocationModule.kt (81%) create mode 100644 data/src/main/java/com/squirtles/data/firebase/BaseFirebaseDataSource.kt create mode 100644 data/src/main/java/com/squirtles/data/firebase/FirebaseException.kt create mode 100644 data/src/main/java/com/squirtles/data/firebase/FirebaseRepositoryUtils.kt diff --git a/app/src/main/java/com/squirtles/musicroad/detail/DetailViewModel.kt b/app/src/main/java/com/squirtles/musicroad/detail/DetailViewModel.kt index 4955bd81..068698c0 100644 --- a/app/src/main/java/com/squirtles/musicroad/detail/DetailViewModel.kt +++ b/app/src/main/java/com/squirtles/musicroad/detail/DetailViewModel.kt @@ -53,9 +53,9 @@ class DetailViewModel @Inject constructor( .collect { (pickId, isAdding) -> getUserId()?.let { userId -> if (isAdding) { - addToFavoritePicks(pickId, userId) + addFavorite(pickId, userId) } else { - deleteFromFavoritePicks(pickId, userId) + deleteFavorite(pickId, userId) } } } @@ -113,7 +113,7 @@ class DetailViewModel @Inject constructor( } } - private fun addToFavoritePicks(pickId: String, userId: String) { + private fun addFavorite(pickId: String, userId: String) { viewModelScope.launch { createFavoriteUseCase(pickId, userId) .onSuccess { @@ -129,7 +129,7 @@ class DetailViewModel @Inject constructor( } } - private fun deleteFromFavoritePicks(pickId: String, userId: String) { + private fun deleteFavorite(pickId: String, userId: String) { viewModelScope.launch { deleteFavoriteUseCase(pickId, userId) .onSuccess { diff --git a/core/common/src/main/java/com/squirtles/common/ui/MessageAlertDialog.kt b/core/common/src/main/java/com/squirtles/common/ui/MessageAlertDialog.kt index ad73e258..a8866cbf 100644 --- a/core/common/src/main/java/com/squirtles/common/ui/MessageAlertDialog.kt +++ b/core/common/src/main/java/com/squirtles/common/ui/MessageAlertDialog.kt @@ -30,7 +30,7 @@ import com.squirtles.common.ui.theme.White @OptIn(ExperimentalMaterial3Api::class) @Composable -internal fun MessageAlertDialog( +fun MessageAlertDialog( onDismissRequest: () -> Unit, title: String, body: String, @@ -77,7 +77,7 @@ internal fun MessageAlertDialog( } @Composable -internal fun DialogTextButton( +fun DialogTextButton( onClick: () -> Unit, text: String, textColor: Color = Black, diff --git a/core/picklist/build.gradle.kts b/core/picklist/build.gradle.kts index 18df075c..d08bb11c 100644 --- a/core/picklist/build.gradle.kts +++ b/core/picklist/build.gradle.kts @@ -9,6 +9,8 @@ android { dependencies { implementation(projects.core.model) implementation(projects.core.common) + implementation(projects.domain.picklist) + implementation(projects.domain.user) testImplementation(libs.junit) androidTestImplementation(libs.bundles.test) diff --git a/data/favorite/src/main/java/com/squirtles/favorite/CloudFunctionHelper.kt b/data/favorite/src/main/java/com/squirtles/favorite/CloudFunctionHelper.kt index 6b865e0d..0db5dae7 100644 --- a/data/favorite/src/main/java/com/squirtles/favorite/CloudFunctionHelper.kt +++ b/data/favorite/src/main/java/com/squirtles/favorite/CloudFunctionHelper.kt @@ -1,8 +1,10 @@ package com.squirtles.favorite +import android.util.Log import com.google.firebase.functions.FirebaseFunctions import com.google.firebase.functions.ktx.functions import com.google.firebase.ktx.Firebase +import com.squirtles.firebase.FirebaseException import com.squirtles.localproperties.LocalPropertyProvider import kotlinx.coroutines.tasks.await import javax.inject.Singleton @@ -12,7 +14,7 @@ class CloudFunctionHelper { private val functions: FirebaseFunctions = Firebase.functions suspend fun updateFavoriteCount(pickId: String): Result { - return try { + return runCatching { val data = hashMapOf("pickId" to pickId) val result = functions .getHttpsCallable(LocalPropertyProvider.httpsCallable) @@ -23,10 +25,10 @@ class CloudFunctionHelper { val message = result.getData()?.let { (it as? Map<*, *>)?.get("message") as? String ?: "Function executed successfully" } ?: "No message in response" - Result.success(message) - } catch (e: Exception) { - // 에러 처리 - Result.failure(e) + message + }.onFailure { + Log.d("CloudFunctionHelper", "Error updating favorite count: ${it.message}") + throw FirebaseException.CloudFunctionFailedException(exceptionMessage = it.message.toString()) } } } diff --git a/data/favorite/src/main/java/com/squirtles/favorite/FirebaseFavoriteDataSourceImpl.kt b/data/favorite/src/main/java/com/squirtles/favorite/FirebaseFavoriteDataSourceImpl.kt index 456c95bc..45a41d0e 100644 --- a/data/favorite/src/main/java/com/squirtles/favorite/FirebaseFavoriteDataSourceImpl.kt +++ b/data/favorite/src/main/java/com/squirtles/favorite/FirebaseFavoriteDataSourceImpl.kt @@ -1,120 +1,70 @@ package com.squirtles.favorite import android.util.Log -import com.squirtles.firebase.FirebaseDataSourceConstants.COLLECTION_FAVORITES -import com.squirtles.firebase.FirebaseDataSourceConstants.FIELD_PICK_ID -import com.squirtles.firebase.FirebaseDataSourceConstants.FIELD_USER_ID -import com.squirtles.firebase.model.FirebaseFavorite import com.google.firebase.firestore.FirebaseFirestore import com.google.firebase.firestore.QuerySnapshot -import kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.launch -import kotlinx.coroutines.suspendCancellableCoroutine +import com.squirtles.firebase.BaseFirebaseDataSource +import com.squirtles.firebase.FirebaseCollections +import com.squirtles.firebase.FirebaseDocumentFields +import com.squirtles.firebase.model.FirebaseFavorite import javax.inject.Inject import javax.inject.Singleton -import kotlin.coroutines.resume -import kotlin.coroutines.resumeWithException @Singleton class FirebaseFavoriteDataSourceImpl @Inject constructor( - private val db: FirebaseFirestore, + db: FirebaseFirestore, private val cloudFunctionHelper: CloudFunctionHelper -) : FirebaseFavoriteDataSource { +) : BaseFirebaseDataSource(db), FirebaseFavoriteDataSource { - override suspend fun fetchIsFavorite(pickId: String, userId: String): Boolean { - val favoriteDocument = fetchFavoriteByPickIdAndUserId(pickId, userId) - return favoriteDocument.isEmpty.not() + override suspend fun fetchIsFavorite(pickId: String, userId: String): Result { + return runCatching { + val favoriteDocument = queryFavoriteByPickIdAndUserId(pickId, userId).getOrThrow() + favoriteDocument.isEmpty.not() + } } - override suspend fun createFavorite(pickId: String, userId: String): Boolean { - return suspendCancellableCoroutine { continuation -> - val firebaseFavorite = FirebaseFavorite( - pickId = pickId, - userId = userId - ) + override suspend fun createFavorite(pickId: String, userId: String): Result { + val firebaseFavorite = FirebaseFavorite( + pickId = pickId, + userId = userId + ) + return runCatching { + val addResult = addDocument(FirebaseCollections.Favorites, firebaseFavorite) + val functionResultMessage = updateFavoriteCount(pickId).getOrThrow() + Log.d(TAG_LOG, "Function result message: $functionResultMessage") // XXX - db.collection(COLLECTION_FAVORITES) - .add(firebaseFavorite) - .addOnSuccessListener { - // favorites에 문서 생성 후 클라우드 함수가 완료됐을 때 담기 완료 - CoroutineScope(Dispatchers.IO).launch { - try { - updateFavoriteCount(pickId) // 클라우드 함수 호출 - continuation.resume(true) - } catch (e: Exception) { - continuation.resumeWithException(e) - } - } - } - .addOnFailureListener { exception -> - Log.e("FirebaseDataSourceImpl", "Failed to create favorite", exception) - continuation.resumeWithException(exception) - } + addResult.getOrThrow().id } } - override suspend fun deleteFavorite(pickId: String, userId: String): Boolean { - val favoriteDocument = fetchFavoriteByPickIdAndUserId(pickId, userId) - return suspendCancellableCoroutine { continuation -> - favoriteDocument.forEach { document -> - db.collection(COLLECTION_FAVORITES).document(document.id) - .delete() - .addOnSuccessListener { - // favorites에 문서 삭제 후 클라우드 함수가 완료됐을 때 담기 해제 완료 - CoroutineScope(Dispatchers.IO).launch { - try { - updateFavoriteCount(pickId) // 클라우드 함수 호출 - continuation.resume(true) - } catch (e: Exception) { - continuation.resumeWithException(e) - } - } - } - .addOnFailureListener { exception -> - Log.w( - "FirebaseDataSourceImpl", - "Error deleting favorite document", - exception - ) - continuation.resumeWithException(exception) - } - } + override suspend fun deleteFavorite(pickId: String, userId: String): Result { + return runCatching { + val favoriteDocRef = queryFavoriteByPickIdAndUserId(pickId, userId).getOrThrow().documents.first().reference + + deleteDocument(favoriteDocRef) + + val functionResultMessage = updateFavoriteCount(pickId).getOrThrow() + + Log.d(TAG_LOG, "Function result message: $functionResultMessage") // XXX + + favoriteDocRef.id + }.onFailure { exception -> + Log.w(TAG_LOG, "Error deleting favorite document", exception) } } - private suspend fun fetchFavoriteByPickIdAndUserId(pickId: String, userId: String): QuerySnapshot { - return suspendCancellableCoroutine { continuation -> - db.collection(COLLECTION_FAVORITES) - .whereEqualTo(FIELD_PICK_ID, pickId) - .whereEqualTo(FIELD_USER_ID, userId) - .get() - .addOnSuccessListener { result -> - continuation.resume(result) - } - .addOnFailureListener { exception -> - Log.w( - "FirebaseDataSourceImpl", - "Error at fetching favorite document", - exception - ) - continuation.resumeWithException(exception) - } - } + private suspend fun queryFavoriteByPickIdAndUserId(pickId: String, userId: String): Result = + queryDocumentsEquals( + collection = FirebaseCollections.Favorites, + fields = listOf(FirebaseDocumentFields.PickId, FirebaseDocumentFields.UserId), + values = listOf(pickId, userId) + ) + + private suspend fun updateFavoriteCount(pickId: String): Result { + return cloudFunctionHelper.updateFavoriteCount(pickId) } - private suspend fun updateFavoriteCount(pickId: String) { - try { - val result = cloudFunctionHelper.updateFavoriteCount(pickId) - result.onSuccess { - Log.d("FirebaseDataSourceImpl", "Success to update favorite count") - }.onFailure { exception -> - Log.e("FirebaseDataSourceImpl", "Failed to update favorite count", exception) - throw exception - } - } catch (e: Exception) { - Log.e("FirebaseDataSourceImpl", "Exception occurred while updating favorite count", e) - throw e - } + companion object { + private const val TAG_LOG = "FirebaseFavoriteDataSourceImpl" } } diff --git a/data/favorite/src/main/java/com/squirtles/favorite/FirebaseFavoriteRepositoryImpl.kt b/data/favorite/src/main/java/com/squirtles/favorite/FirebaseFavoriteRepositoryImpl.kt index fc6a18b0..a1e025dc 100644 --- a/data/favorite/src/main/java/com/squirtles/favorite/FirebaseFavoriteRepositoryImpl.kt +++ b/data/favorite/src/main/java/com/squirtles/favorite/FirebaseFavoriteRepositoryImpl.kt @@ -1,6 +1,5 @@ package com.squirtles.favorite -import com.squirtles.firebase.handleResult import javax.inject.Inject import javax.inject.Singleton @@ -10,20 +9,14 @@ class FirebaseFavoriteRepositoryImpl @Inject constructor( ) : FirebaseFavoriteRepository { override suspend fun fetchIsFavorite(pickId: String, userId: String): Result { - return handleResult { - favoriteDataSource.fetchIsFavorite(pickId, userId) - } + return favoriteDataSource.fetchIsFavorite(pickId, userId) } - override suspend fun createFavorite(pickId: String, userId: String): Result { - return handleResult { - favoriteDataSource.createFavorite(pickId, userId) - } + override suspend fun createFavorite(pickId: String, userId: String): Result { + return favoriteDataSource.createFavorite(pickId, userId) } - override suspend fun deleteFavorite(pickId: String, userId: String): Result { - return handleResult { - favoriteDataSource.deleteFavorite(pickId, userId) - } + override suspend fun deleteFavorite(pickId: String, userId: String): Result { + return favoriteDataSource.deleteFavorite(pickId, userId) } } diff --git a/data/firebase/src/main/java/com/squirtles/firebase/BaseFirebaseDataSource.kt b/data/firebase/src/main/java/com/squirtles/firebase/BaseFirebaseDataSource.kt new file mode 100644 index 00000000..c137bc8b --- /dev/null +++ b/data/firebase/src/main/java/com/squirtles/firebase/BaseFirebaseDataSource.kt @@ -0,0 +1,116 @@ +package com.squirtles.firebase + +import android.util.Log +import com.google.firebase.firestore.CollectionReference +import com.google.firebase.firestore.DocumentReference +import com.google.firebase.firestore.DocumentSnapshot +import com.google.firebase.firestore.FirebaseFirestore +import com.google.firebase.firestore.Query +import com.google.firebase.firestore.QuerySnapshot +import kotlinx.coroutines.tasks.await + +open class BaseFirebaseDataSource( + private val db: FirebaseFirestore +) { + protected fun fetchCollection(collection: FirebaseCollections): CollectionReference = db.collection(collection.name) + + protected fun fetchDocumentReference(collection: FirebaseCollections, documentId: String): DocumentReference = + fetchCollection(collection).document(documentId) + + protected suspend fun fetchDocumentSnapshot(collection: FirebaseCollections, documentId: String): Result { + return runCatching { + fetchCollection(collection).document(documentId).get().await().ifNotExist { + throw FirebaseException.NoSuchDocumentException(docId = documentId, collection = collection.name) + } + }.onFailure { + Log.e("FirebaseDataSource", "Failed to fetch document snapshot", it) + throw FirebaseException.FetchDocumentFailedException(collection = collection.name) + } + } + + protected suspend fun queryDocumentsEquals( + collection: FirebaseCollections, + fields: List, + values: List + ): Result { + return runCatching { + var query: Query = fetchCollection(collection) + fields.forEachIndexed { index, field -> + query = query.whereEqualTo(field.name, values[index]) + } + query.get().await() + }.onFailure { + Log.e("FirebaseDataSource", "Failed to query documents", it) + throw FirebaseException.ExecuteQueryFailedException(collection = collection.name) + } + } + + protected suspend fun queryDocumentsInRange( + collection: FirebaseCollections, + field: FirebaseDocumentFields, + start: String, + end: String + ): Result { + return runCatching { + fetchCollection(collection) + .whereGreaterThanOrEqualTo(field.name, start) + .whereLessThanOrEqualTo(field.name, end) + .get() + .await() + }.onFailure { + Log.e("FirebaseDataSource", "Failed to create query for range", it) + throw FirebaseException.ExecuteQueryFailedException(collection = collection.name) + } + } + + protected suspend fun updateDocument( + collection: FirebaseCollections, + documentId: String, + field: FirebaseDocumentFields, + value: Any + ): Result { + return runCatching { + fetchDocumentReference(collection, documentId).update(field.name, value).await() + }.onFailure { + Log.e("FirebaseDataSource", "Failed to update document", it) + throw FirebaseException.UpdateDocumentFailedException(docId = documentId, collection = collection.name) + } + } + + protected suspend fun setDocument(collection: FirebaseCollections, docId:String, value: Any): Result { + return runCatching { + fetchDocumentReference(collection, docId).set(value).await() + }.onFailure { + Log.e("FirebaseDataSource", "Failed to set document", it) + throw FirebaseException.AddDocumentFailedException(value = value, collection = collection.name) + } + } + + protected suspend fun addDocument(collection: FirebaseCollections, value: Any): Result { + return runCatching { + fetchCollection(collection).add(value).await() + }.onFailure { + Log.e("FirebaseDataSource", "Failed to add document", it) + throw FirebaseException.AddDocumentFailedException(value = value, collection = collection.name) + } + } + + protected suspend fun deleteDocument(document: DocumentReference, collectionName: String = ""): Result { + return runCatching { + document.delete().await() + }.onFailure { exception -> + Log.e("FirebaseDataSource", "Error deleting favorite document with ID: ${document.id}", exception) + throw FirebaseException.DeleteDocumentFailedException(docId = document.id, collection = collectionName) + } + } + + protected suspend fun deleteDocument(collection: FirebaseCollections, documentId: String): Result { + val doc = fetchDocumentReference(collection, documentId) + return deleteDocument(doc, collection.name) + } + + private fun DocumentSnapshot.ifNotExist(action: () -> Unit): DocumentSnapshot { + if (exists().not()) action() + return this + } +} diff --git a/data/firebase/src/main/java/com/squirtles/firebase/FirebaseDataSourceConstants.kt b/data/firebase/src/main/java/com/squirtles/firebase/FirebaseDataSourceConstants.kt index f33052de..26d40d7e 100644 --- a/data/firebase/src/main/java/com/squirtles/firebase/FirebaseDataSourceConstants.kt +++ b/data/firebase/src/main/java/com/squirtles/firebase/FirebaseDataSourceConstants.kt @@ -1,12 +1,18 @@ package com.squirtles.firebase -object FirebaseDataSourceConstants { - const val TAG_LOG = "FirebaseDataSourceImpl" - const val COLLECTION_FAVORITES = "favorites" - const val COLLECTION_PICKS = "picks" - const val COLLECTION_USERS = "users" - const val FIELD_PICK_ID = "pickId" - const val FIELD_USER_ID = "userId" - const val FIELD_ADDED_AT = "addedAt" - const val FIELD_MY_PICKS = "myPicks" +sealed class FirebaseCollections(val name: String) { + data object Favorites: FirebaseCollections("favorites") + data object Picks: FirebaseCollections("picks") + data object Users: FirebaseCollections("users") +} + +sealed class FirebaseDocumentFields(val name: String) { + data object AddedAt: FirebaseDocumentFields("addedAt") + data object PickId: FirebaseDocumentFields("pickId") + data object UserId: FirebaseDocumentFields("userId") + data object MyPicks: FirebaseDocumentFields("myPicks") + data object Name: FirebaseDocumentFields("name") + data object Location: FirebaseDocumentFields("location") + data object GeoHash: FirebaseDocumentFields("geoHash") + data object CreatedUserName: FirebaseDocumentFields("createdBy.userName") } diff --git a/data/firebase/src/main/java/com/squirtles/firebase/FirebaseException.kt b/data/firebase/src/main/java/com/squirtles/firebase/FirebaseException.kt index 87491119..8fe72ad7 100644 --- a/data/firebase/src/main/java/com/squirtles/firebase/FirebaseException.kt +++ b/data/firebase/src/main/java/com/squirtles/firebase/FirebaseException.kt @@ -1,13 +1,49 @@ package com.squirtles.firebase sealed class FirebaseException(override val message: String) : Exception() { - data class CreatedUserFailedException(override val message: String = "Failed to create a user") : - FirebaseException(message) + data class CreatedUserFailedException(override val message: String = "Failed to create user") : FirebaseException(message) + data class FetchUserFailedException(override val message: String = "Failed to fetch user") : FirebaseException(message) + data class UpdateUserFailedException(override val message: String = "Failed to update user info") : FirebaseException(message) + data class NoSuchPickException(override val message: String = "No such pick", val pickId: String) + : FirebaseException("message @$pickId") + data class NoSuchPickInRadiusException(override val message: String = "No such pick in area") : FirebaseException(message) - data class UserNotFoundException(override val message: String = "Failed to fetch a user") : - FirebaseException(message) + data class NoSuchDocumentException( + override val message: String = "No such document", + val docId: String, + val collection: String = "" + ) : FirebaseException("$message @$docId in $collection") - data class NoSuchPickException(override val message: String = "No such pick") : FirebaseException(message) - data class NoSuchPickInRadiusException(override val message: String = "No such pick in area") : - FirebaseException(message) + data class FetchDocumentFailedException( + override val message: String = "Failed to fetch document", + val collection: String = "" + ) : FirebaseException("$message in $collection") + + data class AddDocumentFailedException( + override val message: String = "Failed to add document", + val value: Any, + val collection: String = "" + ) : FirebaseException("$message $value in $collection") + + data class DeleteDocumentFailedException( + override val message: String = "Failed to delete document", + val docId: String, + val collection: String = "" + ): FirebaseException("$message @$docId in $collection") + + data class UpdateDocumentFailedException( + override val message: String = "Failed to update document", + val docId: String, + val collection: String = "" + ) : FirebaseException("$message @$docId in $collection") + + data class ExecuteQueryFailedException( + override val message: String = "Failed to execute query", + val collection: String = "" + ) : FirebaseException("$message in $collection") + + data class CloudFunctionFailedException( + override val message: String = "Failed to run cloud function", + val exceptionMessage: String + ) : FirebaseException("$message : $exceptionMessage") } diff --git a/data/location/src/main/java/com/squirtles/order/LocalLocationRepositoryImpl.kt b/data/location/src/main/java/com/squirtles/location/LocalLocationRepositoryImpl.kt similarity index 87% rename from data/location/src/main/java/com/squirtles/order/LocalLocationRepositoryImpl.kt rename to data/location/src/main/java/com/squirtles/location/LocalLocationRepositoryImpl.kt index 7f574a01..54cdad61 100644 --- a/data/location/src/main/java/com/squirtles/order/LocalLocationRepositoryImpl.kt +++ b/data/location/src/main/java/com/squirtles/location/LocalLocationRepositoryImpl.kt @@ -1,7 +1,6 @@ -package com.squirtles.order +package com.squirtles.location import android.location.Location -import com.squirtles.location.LocalLocationRepository import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.asStateFlow diff --git a/data/location/src/main/java/com/squirtles/order/di/LocationModule.kt b/data/location/src/main/java/com/squirtles/location/di/LocationModule.kt similarity index 81% rename from data/location/src/main/java/com/squirtles/order/di/LocationModule.kt rename to data/location/src/main/java/com/squirtles/location/di/LocationModule.kt index 0b898ccf..2856cad7 100644 --- a/data/location/src/main/java/com/squirtles/order/di/LocationModule.kt +++ b/data/location/src/main/java/com/squirtles/location/di/LocationModule.kt @@ -1,7 +1,7 @@ -package com.squirtles.order.di +package com.squirtles.location.di import com.squirtles.location.LocalLocationRepository -import com.squirtles.order.LocalLocationRepositoryImpl +import com.squirtles.location.LocalLocationRepositoryImpl import dagger.Module import dagger.Provides import dagger.hilt.InstallIn diff --git a/data/pick/src/main/java/com/squirtles/pick/FirebasePickDataSourceImpl.kt b/data/pick/src/main/java/com/squirtles/pick/FirebasePickDataSourceImpl.kt index 6061d6b9..d234d356 100644 --- a/data/pick/src/main/java/com/squirtles/pick/FirebasePickDataSourceImpl.kt +++ b/data/pick/src/main/java/com/squirtles/pick/FirebasePickDataSourceImpl.kt @@ -1,23 +1,8 @@ package com.squirtles.pick import android.util.Log -import com.squirtles.firebase.FirebaseDataSourceConstants.COLLECTION_FAVORITES -import com.squirtles.firebase.FirebaseDataSourceConstants.COLLECTION_PICKS -import com.squirtles.firebase.FirebaseDataSourceConstants.COLLECTION_USERS -import com.squirtles.firebase.FirebaseDataSourceConstants.FIELD_ADDED_AT -import com.squirtles.firebase.FirebaseDataSourceConstants.FIELD_MY_PICKS -import com.squirtles.firebase.FirebaseDataSourceConstants.FIELD_PICK_ID -import com.squirtles.firebase.FirebaseDataSourceConstants.FIELD_USER_ID -import com.squirtles.firebase.FirebaseDataSourceConstants.TAG_LOG -import com.squirtles.firebase.model.FirebasePick -import com.squirtles.firebase.model.FirebaseUser -import com.squirtles.firebase.model.toFirebasePick -import com.squirtles.firebase.model.toPick -import com.squirtles.model.Pick import com.firebase.geofire.GeoFireUtils import com.firebase.geofire.GeoLocation -import com.google.android.gms.tasks.Task -import com.google.android.gms.tasks.Tasks import com.google.firebase.firestore.DocumentReference import com.google.firebase.firestore.DocumentSnapshot import com.google.firebase.firestore.FieldValue @@ -25,6 +10,15 @@ import com.google.firebase.firestore.FirebaseFirestore import com.google.firebase.firestore.Query import com.google.firebase.firestore.QuerySnapshot import com.google.firebase.firestore.toObject +import com.squirtles.firebase.BaseFirebaseDataSource +import com.squirtles.firebase.FirebaseCollections +import com.squirtles.firebase.FirebaseDocumentFields +import com.squirtles.firebase.model.FirebaseFavorite +import com.squirtles.firebase.model.FirebasePick +import com.squirtles.firebase.model.FirebaseUser +import com.squirtles.firebase.model.toFirebasePick +import com.squirtles.firebase.model.toPick +import com.squirtles.model.Pick import kotlinx.coroutines.suspendCancellableCoroutine import kotlinx.coroutines.tasks.await import javax.inject.Inject @@ -35,21 +29,16 @@ import kotlin.coroutines.resumeWithException @Singleton class FirebasePickDataSourceImpl @Inject constructor( private val db: FirebaseFirestore -) : FirebasePickDataSource { +) : BaseFirebaseDataSource(db), FirebasePickDataSource { /* Fetches a pick by ID from Firestore */ - override suspend fun fetchPick(pickID: String): Pick? { - return suspendCancellableCoroutine { continuation -> - db.collection("picks").document(pickID).get() - .addOnSuccessListener { document -> - val firestorePick = document.toObject()?.copy(id = pickID) - val resultPick = firestorePick?.toPick() - continuation.resume(resultPick) - } - .addOnFailureListener { exception -> - Log.e("FirebaseDataSourceImpl", "Failed to fetch a pick", exception) - continuation.resumeWithException(exception) - } + override suspend fun fetchPick(pickId: String): Result { + return runCatching { + val pickSnap = fetchDocumentSnapshot(FirebaseCollections.Picks, pickId).getOrThrow() + val firestorePick = pickSnap.toObject()?.copy(id = pickId) + firestorePick?.toPick()!! + }.onFailure { exception -> + Log.e(TAG_LOG, "Failed to fetch a pick", exception) } } @@ -58,199 +47,113 @@ class FirebasePickDataSourceImpl @Inject constructor( lat: Double, lng: Double, radiusInM: Double - ): List { + ): Result> { val center = GeoLocation(lat, lng) val bounds = GeoFireUtils.getGeoHashQueryBounds(center, radiusInM) - val queries: MutableList = ArrayList() - val tasks: MutableList> = ArrayList() - val matchingPicks: MutableList = ArrayList() - - bounds.forEach { bound -> - val query = db.collection("picks") - .orderBy("geoHash") - .startAt(bound.startHash) - .endAt(bound.endHash) - queries.add(query) - } - - try { - queries.forEach { query -> - tasks.add(query.get()) + return runCatching { + val queryResults = bounds.map { bound -> + queryDocumentsInRange( + collection = FirebaseCollections.Picks, + field = FirebaseDocumentFields.GeoHash, + start = bound.startHash, + end = bound.endHash + ) } - Tasks.whenAllComplete(tasks).await() - } catch (exception: Exception) { - Log.e("FirebaseDataSourceImpl", "Failed to fetch picks", exception) - throw exception - } - tasks.forEach { task -> - val snap = task.result - snap.documents.forEach { doc -> - if (isAccurate(doc, center, radiusInM)) { - doc.toObject()?.run { - matchingPicks.add(this.toPick().copy(id = doc.id)) + queryResults.flatMap { querySnapshot -> + querySnapshot.getOrThrow().documents + .filter { doc -> + isAccurate(doc, center, radiusInM) + }.mapNotNull { doc -> + doc.toObject()?.toPick()?.copy(id = doc.id) } - } } + }.onFailure { e -> + Log.e(TAG_LOG, "Failed to fetch picks", e) } - - return matchingPicks } /* Creates a new pick in Firestore */ - override suspend fun createPick(pick: Pick): String = - suspendCancellableCoroutine { continuation -> - val firebasePick = pick.toFirebasePick() - - // add() 메소드는 Cloud Firestore에서 ID를 자동으로 생성 - db.collection("picks").add(firebasePick) - .addOnSuccessListener { documentReference -> - val pickId = documentReference.id - // 유저의 픽 정보 업데이트 - updateCurrentUserPick(pick.createdBy.userId, pickId) - .addOnCompleteListener { task -> - if (task.isSuccessful) { - continuation.resume(pickId) - } else { - continuation.resumeWithException( - task.exception ?: Exception("Failed to updating user pick info") - ) - } - } - } - .addOnFailureListener { exception -> - Log.e("FirebaseDataSourceImpl", "Failed to create a pick", exception) - continuation.resumeWithException(exception) - } + override suspend fun createPick(pick: Pick): Result { + val firebasePick = pick.toFirebasePick() + return runCatching { + val pickRef = addDocument(FirebaseCollections.Picks, firebasePick).getOrThrow() + updateCurrentUserPick(pick.createdBy.userId, pickRef.id) + pickRef.id + }.onFailure { + Log.e(TAG_LOG, "Failed to create a pick", it) } + } - override suspend fun deletePick(pickId: String, userId: String): Boolean { - val pickDocument = db.collection(COLLECTION_PICKS).document(pickId) - val userDocument = db.collection(COLLECTION_USERS).document(userId) - val favoriteDocuments = fetchFavoriteDocuments(pickId) + override suspend fun deletePick(pickId: String, userId: String): Result { + val pickDocument = fetchDocumentReference(FirebaseCollections.Picks, pickId) + val userDocument = fetchDocumentReference(FirebaseCollections.Users, userId) + + return runCatching { + val favoriteDocuments = fetchFavoriteDocumentRefsByPick(pickId).getOrThrow() - return suspendCancellableCoroutine { continuation -> db.runTransaction { transaction -> transaction.delete(pickDocument) - - favoriteDocuments.forEach { document -> - transaction.delete(document) + favoriteDocuments.forEach { docRef -> + transaction.delete(docRef) } + transaction.update(userDocument, FirebaseDocumentFields.MyPicks.name, FieldValue.arrayRemove(pickId)) + }.await() - transaction.update(userDocument, FIELD_MY_PICKS, FieldValue.arrayRemove(pickId)) - }.addOnSuccessListener { _ -> - continuation.resume(true) - }.addOnFailureListener { e -> - Log.w(TAG_LOG, "Transaction failure.", e) - continuation.resumeWithException(e) - } + pickDocument.id + }.onFailure { + Log.e(TAG_LOG, "Failed to delete a pick", it) } } - override suspend fun fetchMyPicks(userId: String): List { - val userDocument = fetchUserDocument(userId) - if (userDocument.exists().not()) throw Exception("No user info in database") - - val tasks = mutableListOf>() - val myPicks = mutableListOf() - - try { - userDocument.toObject()?.myPicks?.forEach { pickId -> - tasks.add( - db.collection(COLLECTION_PICKS) - .document(pickId) - .get() - ) - } - Tasks.whenAllComplete(tasks).await() - } catch (exception: Exception) { - Log.e("FirebaseDataSourceImpl", "Failed to fetch my picks", exception) - throw exception + override suspend fun fetchMyPicks(userId: String): Result> { + return runCatching { + val userDocument = fetchDocumentSnapshot(FirebaseCollections.Users, userId).getOrThrow() + userDocument.toObject()?.myPicks!!.map { + fetchPick(it).getOrThrow() + }.reversed() } + } - tasks.forEach { task -> - task.result.toObject()?.run { - myPicks.add(this.toPick().copy(id = task.result.id)) + override suspend fun fetchFavoritePicks(userId: String): Result> { + return runCatching { + val favoriteDocuments = fetchFavoritesByUserId(userId) + favoriteDocuments.map { docSnap -> + fetchPick(docSnap.toObject()?.pickId.toString()).getOrThrow() } } - - return myPicks.reversed() - } - - private fun updateCurrentUserPick(userId: String, pickId: String): Task { - val userDoc = db.collection("users").document(userId) - return userDoc.update("myPicks", FieldValue.arrayUnion(pickId)) } - private suspend fun fetchFavoriteDocuments(pickId: String): List { - return suspendCancellableCoroutine { continuation -> - db.collection(COLLECTION_FAVORITES) - .whereEqualTo(FIELD_PICK_ID, pickId) - .get() - .addOnSuccessListener { querySnapShot -> - val documentIds = querySnapShot.documents.map { it.id } - val documentRefs = mutableListOf() - documentIds.forEach { id -> - documentRefs.add(db.collection(COLLECTION_FAVORITES).document(id)) - } - continuation.resume(documentRefs) - } - .addOnFailureListener { e -> - Log.w(TAG_LOG, "Failed to fetch favorite documents id", e) - continuation.resumeWithException(e) - } + private suspend fun updateCurrentUserPick(userId: String, pickId: String): Result { + return runCatching { + updateDocument( + collection = FirebaseCollections.Users, + documentId = userId, + field = FirebaseDocumentFields.MyPicks, + value = FieldValue.arrayUnion(pickId) + ).getOrThrow() + }.onFailure { e -> + Log.e(TAG_LOG, "Failed to update user picks", e) } } - private suspend fun fetchUserDocument(userId: String): DocumentSnapshot { - return suspendCancellableCoroutine { continuation -> - db.collection(COLLECTION_USERS).document(userId) - .get() - .addOnSuccessListener { document -> - continuation.resume(document) - } - .addOnFailureListener { exception -> - Log.e("FirebaseDataSourceImpl", "Failed to get user document", exception) - continuation.resumeWithException(exception) - } + private suspend fun fetchFavoriteDocumentRefsByPick(pickId: String): Result> { + return runCatching { + queryDocumentsEquals( + collection = FirebaseCollections.Favorites, + fields = listOf(FirebaseDocumentFields.PickId), + values = listOf(pickId), + ).getOrThrow().documents.map { it.reference } } } - override suspend fun fetchFavoritePicks(userId: String): List { - val favoriteDocuments = fetchFavoritesByUserId(userId) - - val tasks = mutableListOf>() - val favorites = mutableListOf() - - try { - favoriteDocuments.forEach { doc -> - tasks.add( - db.collection(COLLECTION_PICKS) - .document(doc.data[FIELD_PICK_ID].toString()) - .get() - ) - } - Tasks.whenAllComplete(tasks).await() - } catch (exception: Exception) { - Log.e("FirebaseDataSourceImpl", "Failed to get favorite picks", exception) - throw exception - } - tasks.forEach { task -> - task.result.toObject()?.run { - favorites.add(this.toPick().copy(id = task.result.id)) - } - } - - return favorites - } - /** * GeoHash의 FP 문제 - Geohash의 쿼리가 정확하지 않으며 클라이언트 측에서 거짓양성 결과를 필터링해야 합니다. * 이러한 추가 읽기로 인해 앱에 비용과 지연 시간이 추가됩니다. */ private fun isAccurate(doc: DocumentSnapshot, center: GeoLocation, radiusInM: Double): Boolean { - val location = doc.getGeoPoint("location") ?: return false + val location = doc.getGeoPoint(FirebaseDocumentFields.Location.name) ?: return false val docLocation = GeoLocation(location.latitude, location.longitude) val distanceInM = GeoFireUtils.getDistanceBetween(docLocation, center) @@ -258,12 +161,12 @@ class FirebasePickDataSourceImpl @Inject constructor( return distanceInM <= radiusInM } - private suspend fun fetchFavoritesByUserId(userId: String): QuerySnapshot { - val query = db.collection(COLLECTION_FAVORITES) - .whereEqualTo(FIELD_USER_ID, userId) - .orderBy(FIELD_ADDED_AT, Query.Direction.DESCENDING) - - return executeQuery(query) + private suspend fun fetchFavoritesByUserId(userId: String): List { + return queryDocumentsEquals( + collection = FirebaseCollections.Favorites, + fields = listOf(FirebaseDocumentFields.UserId), + values = listOf(userId) + ).getOrThrow().documents } private suspend fun executeQuery(query: Query): QuerySnapshot { @@ -273,9 +176,14 @@ class FirebasePickDataSourceImpl @Inject constructor( continuation.resume(result) } .addOnFailureListener { exception -> - Log.w("FirebaseDataSourceImpl", "Error fetching favorite documents", exception) + Log.w(TAG_LOG, "Error fetching favorite documents", exception) continuation.resumeWithException(exception) } } } + + companion object { + private const val TAG_LOG = "FirebasePickDataSourceImpl" + } } + diff --git a/data/pick/src/main/java/com/squirtles/pick/FirebasePickRepositoryImpl.kt b/data/pick/src/main/java/com/squirtles/pick/FirebasePickRepositoryImpl.kt index 8b911757..3e772dbe 100644 --- a/data/pick/src/main/java/com/squirtles/pick/FirebasePickRepositoryImpl.kt +++ b/data/pick/src/main/java/com/squirtles/pick/FirebasePickRepositoryImpl.kt @@ -12,27 +12,19 @@ class FirebasePickRepositoryImpl @Inject constructor( ) : FirebasePickRepository { override suspend fun createPick(pick: Pick): Result { - return handleResult { - pickDataSource.createPick(pick) - } + return pickDataSource.createPick(pick) } - override suspend fun deletePick(pickId: String, userId: String): Result { - return handleResult { - pickDataSource.deletePick(pickId, userId) - } + override suspend fun deletePick(pickId: String, userId: String): Result { + return pickDataSource.deletePick(pickId, userId) } override suspend fun fetchPick(pickID: String): Result { - return handleResult(FirebaseException.NoSuchPickException()) { - pickDataSource.fetchPick(pickID) - } + return pickDataSource.fetchPick(pickID) } override suspend fun fetchMyPicks(userId: String): Result> { - return handleResult { - pickDataSource.fetchMyPicks(userId) - } + return pickDataSource.fetchMyPicks(userId) } override suspend fun fetchPicksInArea( @@ -42,13 +34,11 @@ class FirebasePickRepositoryImpl @Inject constructor( ): Result> { val pickList = pickDataSource.fetchPicksInArea(lat, lng, radiusInM) return handleResult(FirebaseException.NoSuchPickInRadiusException()) { - pickList.ifEmpty { null } + pickList.getOrThrow().ifEmpty { null } } } override suspend fun fetchFavoritePicks(userId: String): Result> { - return handleResult { - pickDataSource.fetchFavoritePicks(userId) - } + return pickDataSource.fetchFavoritePicks(userId) } } diff --git a/data/src/main/java/com/squirtles/data/favorite/CloudFunctionHelper.kt b/data/src/main/java/com/squirtles/data/favorite/CloudFunctionHelper.kt index 20505a53..e1951bea 100644 --- a/data/src/main/java/com/squirtles/data/favorite/CloudFunctionHelper.kt +++ b/data/src/main/java/com/squirtles/data/favorite/CloudFunctionHelper.kt @@ -1,8 +1,10 @@ package com.squirtles.data.favorite +import android.util.Log import com.google.firebase.functions.FirebaseFunctions import com.google.firebase.functions.ktx.functions import com.google.firebase.ktx.Firebase +import com.squirtles.data.firebase.FirebaseException import com.squirtles.localproperties.LocalPropertyProvider import kotlinx.coroutines.tasks.await import javax.inject.Singleton @@ -12,7 +14,7 @@ class CloudFunctionHelper { private val functions: FirebaseFunctions = Firebase.functions suspend fun updateFavoriteCount(pickId: String): Result { - return try { + return runCatching { val data = hashMapOf("pickId" to pickId) val result = functions .getHttpsCallable(LocalPropertyProvider.httpsCallable) @@ -23,10 +25,10 @@ class CloudFunctionHelper { val message = result.getData()?.let { (it as? Map<*, *>)?.get("message") as? String ?: "Function executed successfully" } ?: "No message in response" - Result.success(message) - } catch (e: Exception) { - // 에러 처리 - Result.failure(e) + message + }.onFailure { + Log.d("CloudFunctionHelper", "Error updating favorite count: ${it.message}") + throw FirebaseException.CloudFunctionFailedException(exceptionMessage = it.message.toString()) } } } diff --git a/data/src/main/java/com/squirtles/data/favorite/FirebaseFavoriteDataSourceImpl.kt b/data/src/main/java/com/squirtles/data/favorite/FirebaseFavoriteDataSourceImpl.kt index 9b619bdf..12bd51a2 100644 --- a/data/src/main/java/com/squirtles/data/favorite/FirebaseFavoriteDataSourceImpl.kt +++ b/data/src/main/java/com/squirtles/data/favorite/FirebaseFavoriteDataSourceImpl.kt @@ -1,132 +1,71 @@ package com.squirtles.data.favorite import android.util.Log -import com.google.android.gms.tasks.Task -import com.google.android.gms.tasks.Tasks -import com.google.firebase.firestore.DocumentSnapshot import com.google.firebase.firestore.FirebaseFirestore -import com.google.firebase.firestore.Query import com.google.firebase.firestore.QuerySnapshot -import com.google.firebase.firestore.toObject import com.squirtles.data.favorite.model.FirebaseFavorite -import com.squirtles.data.firebase.FirebaseDataSourceConstants.COLLECTION_FAVORITES -import com.squirtles.data.firebase.FirebaseDataSourceConstants.COLLECTION_PICKS -import com.squirtles.data.firebase.FirebaseDataSourceConstants.FIELD_ADDED_AT -import com.squirtles.data.firebase.FirebaseDataSourceConstants.FIELD_PICK_ID -import com.squirtles.data.firebase.FirebaseDataSourceConstants.FIELD_USER_ID -import com.squirtles.data.pick.model.FirebasePick -import com.squirtles.data.pick.model.toPick +import com.squirtles.data.firebase.BaseFirebaseDataSource +import com.squirtles.data.firebase.FirebaseCollections +import com.squirtles.data.firebase.FirebaseDocumentFields import com.squirtles.domain.favorite.FirebaseFavoriteDataSource -import com.squirtles.domain.model.Pick -import kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.launch -import kotlinx.coroutines.suspendCancellableCoroutine -import kotlinx.coroutines.tasks.await import javax.inject.Inject import javax.inject.Singleton -import kotlin.coroutines.resume -import kotlin.coroutines.resumeWithException @Singleton class FirebaseFavoriteDataSourceImpl @Inject constructor( - private val db: FirebaseFirestore, + db: FirebaseFirestore, private val cloudFunctionHelper: CloudFunctionHelper -) : FirebaseFavoriteDataSource { +) : BaseFirebaseDataSource(db), FirebaseFavoriteDataSource { - override suspend fun fetchIsFavorite(pickId: String, userId: String): Boolean { - val favoriteDocument = fetchFavoriteByPickIdAndUserId(pickId, userId) - return favoriteDocument.isEmpty.not() + override suspend fun fetchIsFavorite(pickId: String, userId: String): Result { + return runCatching { + val favoriteDocument = queryFavoriteByPickIdAndUserId(pickId, userId).getOrThrow() + favoriteDocument.isEmpty.not() + } } - override suspend fun createFavorite(pickId: String, userId: String): Boolean { - return suspendCancellableCoroutine { continuation -> - val firebaseFavorite = FirebaseFavorite( - pickId = pickId, - userId = userId - ) + override suspend fun createFavorite(pickId: String, userId: String): Result { + val firebaseFavorite = FirebaseFavorite( + pickId = pickId, + userId = userId + ) + return runCatching { + val addResult = addDocument(FirebaseCollections.Favorites, firebaseFavorite) + val functionResultMessage = updateFavoriteCount(pickId).getOrThrow() + Log.d(TAG_LOG, "Function result message: $functionResultMessage") // XXX - db.collection(COLLECTION_FAVORITES) - .add(firebaseFavorite) - .addOnSuccessListener { - // favorites에 문서 생성 후 클라우드 함수가 완료됐을 때 담기 완료 - CoroutineScope(Dispatchers.IO).launch { - try { - updateFavoriteCount(pickId) // 클라우드 함수 호출 - continuation.resume(true) - } catch (e: Exception) { - continuation.resumeWithException(e) - } - } - } - .addOnFailureListener { exception -> - Log.e("FirebaseDataSourceImpl", "Failed to create favorite", exception) - continuation.resumeWithException(exception) - } + addResult.getOrThrow().id } } - override suspend fun deleteFavorite(pickId: String, userId: String): Boolean { - val favoriteDocument = fetchFavoriteByPickIdAndUserId(pickId, userId) - return suspendCancellableCoroutine { continuation -> - favoriteDocument.forEach { document -> - db.collection(COLLECTION_FAVORITES).document(document.id) - .delete() - .addOnSuccessListener { - // favorites에 문서 삭제 후 클라우드 함수가 완료됐을 때 담기 해제 완료 - CoroutineScope(Dispatchers.IO).launch { - try { - updateFavoriteCount(pickId) // 클라우드 함수 호출 - continuation.resume(true) - } catch (e: Exception) { - continuation.resumeWithException(e) - } - } - } - .addOnFailureListener { exception -> - Log.w( - "FirebaseDataSourceImpl", - "Error deleting favorite document", - exception - ) - continuation.resumeWithException(exception) - } - } + override suspend fun deleteFavorite(pickId: String, userId: String): Result { + return runCatching { + val favoriteDocRef = queryFavoriteByPickIdAndUserId(pickId, userId).getOrThrow().documents.first().reference + + deleteDocument(favoriteDocRef) + + val functionResultMessage = updateFavoriteCount(pickId).getOrThrow() + + Log.d(TAG_LOG, "Function result message: $functionResultMessage") // XXX + + favoriteDocRef.id + }.onFailure { exception -> + Log.w(TAG_LOG, "Error deleting favorite document", exception) } } - private suspend fun fetchFavoriteByPickIdAndUserId(pickId: String, userId: String): QuerySnapshot { - return suspendCancellableCoroutine { continuation -> - db.collection(COLLECTION_FAVORITES) - .whereEqualTo(FIELD_PICK_ID, pickId) - .whereEqualTo(FIELD_USER_ID, userId) - .get() - .addOnSuccessListener { result -> - continuation.resume(result) - } - .addOnFailureListener { exception -> - Log.w( - "FirebaseDataSourceImpl", - "Error at fetching favorite document", - exception - ) - continuation.resumeWithException(exception) - } - } + private suspend fun queryFavoriteByPickIdAndUserId(pickId: String, userId: String): Result = + queryDocumentsEquals( + collection = FirebaseCollections.Favorites, + fields = listOf(FirebaseDocumentFields.PickId, FirebaseDocumentFields.UserId), + values = listOf(pickId, userId) + ) + + private suspend fun updateFavoriteCount(pickId: String): Result { + return cloudFunctionHelper.updateFavoriteCount(pickId) } - private suspend fun updateFavoriteCount(pickId: String) { - try { - val result = cloudFunctionHelper.updateFavoriteCount(pickId) - result.onSuccess { - Log.d("FirebaseDataSourceImpl", "Success to update favorite count") - }.onFailure { exception -> - Log.e("FirebaseDataSourceImpl", "Failed to update favorite count", exception) - throw exception - } - } catch (e: Exception) { - Log.e("FirebaseDataSourceImpl", "Exception occurred while updating favorite count", e) - throw e - } + companion object { + private const val TAG_LOG = "FirebaseFavoriteDataSourceImpl" } } diff --git a/data/src/main/java/com/squirtles/data/favorite/FirebaseFavoriteRepositoryImpl.kt b/data/src/main/java/com/squirtles/data/favorite/FirebaseFavoriteRepositoryImpl.kt index 7e44e389..f09e3354 100644 --- a/data/src/main/java/com/squirtles/data/favorite/FirebaseFavoriteRepositoryImpl.kt +++ b/data/src/main/java/com/squirtles/data/favorite/FirebaseFavoriteRepositoryImpl.kt @@ -1,7 +1,6 @@ package com.squirtles.data.favorite import com.squirtles.domain.favorite.FirebaseFavoriteDataSource -import com.squirtles.domain.model.Pick import com.squirtles.domain.favorite.FirebaseFavoriteRepository import javax.inject.Inject import javax.inject.Singleton @@ -12,20 +11,14 @@ class FirebaseFavoriteRepositoryImpl @Inject constructor( ) : FirebaseFavoriteRepository { override suspend fun fetchIsFavorite(pickId: String, userId: String): Result { - return handleResult { - favoriteDataSource.fetchIsFavorite(pickId, userId) - } + return favoriteDataSource.fetchIsFavorite(pickId, userId) } - override suspend fun createFavorite(pickId: String, userId: String): Result { - return handleResult { - favoriteDataSource.createFavorite(pickId, userId) - } + override suspend fun createFavorite(pickId: String, userId: String): Result { + return favoriteDataSource.createFavorite(pickId, userId) } - override suspend fun deleteFavorite(pickId: String, userId: String): Result { - return handleResult { - favoriteDataSource.deleteFavorite(pickId, userId) - } + override suspend fun deleteFavorite(pickId: String, userId: String): Result { + return favoriteDataSource.deleteFavorite(pickId, userId) } } diff --git a/data/src/main/java/com/squirtles/data/firebase/BaseFirebaseDataSource.kt b/data/src/main/java/com/squirtles/data/firebase/BaseFirebaseDataSource.kt new file mode 100644 index 00000000..ec767930 --- /dev/null +++ b/data/src/main/java/com/squirtles/data/firebase/BaseFirebaseDataSource.kt @@ -0,0 +1,116 @@ +package com.squirtles.data.firebase + +import android.util.Log +import com.google.firebase.firestore.CollectionReference +import com.google.firebase.firestore.DocumentReference +import com.google.firebase.firestore.DocumentSnapshot +import com.google.firebase.firestore.FirebaseFirestore +import com.google.firebase.firestore.Query +import com.google.firebase.firestore.QuerySnapshot +import kotlinx.coroutines.tasks.await + +open class BaseFirebaseDataSource( + private val db: FirebaseFirestore +) { + protected fun fetchCollection(collection: FirebaseCollections): CollectionReference = db.collection(collection.name) + + protected fun fetchDocumentReference(collection: FirebaseCollections, documentId: String): DocumentReference = + fetchCollection(collection).document(documentId) + + protected suspend fun fetchDocumentSnapshot(collection: FirebaseCollections, documentId: String): Result { + return runCatching { + fetchCollection(collection).document(documentId).get().await().ifNotExist { + throw FirebaseException.NoSuchDocumentException(docId = documentId, collection = collection.name) + } + }.onFailure { + Log.e("FirebaseDataSource", "Failed to fetch document snapshot", it) + throw FirebaseException.FetchDocumentFailedException(collection = collection.name) + } + } + + protected suspend fun queryDocumentsEquals( + collection: FirebaseCollections, + fields: List, + values: List + ): Result { + return runCatching { + var query: Query = fetchCollection(collection) + fields.forEachIndexed { index, field -> + query = query.whereEqualTo(field.name, values[index]) + } + query.get().await() + }.onFailure { + Log.e("FirebaseDataSource", "Failed to query documents", it) + throw FirebaseException.ExecuteQueryFailedException(collection = collection.name) + } + } + + protected suspend fun queryDocumentsInRange( + collection: FirebaseCollections, + field: FirebaseDocumentFields, + start: String, + end: String + ): Result { + return runCatching { + fetchCollection(collection) + .whereGreaterThanOrEqualTo(field.name, start) + .whereLessThanOrEqualTo(field.name, end) + .get() + .await() + }.onFailure { + Log.e("FirebaseDataSource", "Failed to create query for range", it) + throw FirebaseException.ExecuteQueryFailedException(collection = collection.name) + } + } + + protected suspend fun updateDocument( + collection: FirebaseCollections, + documentId: String, + field: FirebaseDocumentFields, + value: Any + ): Result { + return runCatching { + fetchDocumentReference(collection, documentId).update(field.name, value).await() + }.onFailure { + Log.e("FirebaseDataSource", "Failed to update document", it) + throw FirebaseException.UpdateDocumentFailedException(docId = documentId, collection = collection.name) + } + } + + protected suspend fun setDocument(collection: FirebaseCollections, docId:String, value: Any): Result { + return runCatching { + fetchDocumentReference(collection, docId).set(value).await() + }.onFailure { + Log.e("FirebaseDataSource", "Failed to set document", it) + throw FirebaseException.AddDocumentFailedException(value = value, collection = collection.name) + } + } + + protected suspend fun addDocument(collection: FirebaseCollections, value: Any): Result { + return runCatching { + fetchCollection(collection).add(value).await() + }.onFailure { + Log.e("FirebaseDataSource", "Failed to add document", it) + throw FirebaseException.AddDocumentFailedException(value = value, collection = collection.name) + } + } + + protected suspend fun deleteDocument(document: DocumentReference, collectionName: String = ""): Result { + return runCatching { + document.delete().await() + }.onFailure { exception -> + Log.e("FirebaseDataSource", "Error deleting favorite document with ID: ${document.id}", exception) + throw FirebaseException.DeleteDocumentFailedException(docId = document.id, collection = collectionName) + } + } + + protected suspend fun deleteDocument(collection: FirebaseCollections, documentId: String): Result { + val doc = fetchDocumentReference(collection, documentId) + return deleteDocument(doc, collection.name) + } + + private fun DocumentSnapshot.ifNotExist(action: () -> Unit): DocumentSnapshot { + if (exists().not()) action() + return this + } +} diff --git a/data/src/main/java/com/squirtles/data/firebase/FirebaseDataSourceConstants.kt b/data/src/main/java/com/squirtles/data/firebase/FirebaseDataSourceConstants.kt index 2d86d3b3..3c24c596 100644 --- a/data/src/main/java/com/squirtles/data/firebase/FirebaseDataSourceConstants.kt +++ b/data/src/main/java/com/squirtles/data/firebase/FirebaseDataSourceConstants.kt @@ -1,12 +1,18 @@ package com.squirtles.data.firebase -object FirebaseDataSourceConstants { - const val TAG_LOG = "FirebaseDataSourceImpl" - const val COLLECTION_FAVORITES = "favorites" - const val COLLECTION_PICKS = "picks" - const val COLLECTION_USERS = "users" - const val FIELD_PICK_ID = "pickId" - const val FIELD_USER_ID = "userId" - const val FIELD_ADDED_AT = "addedAt" - const val FIELD_MY_PICKS = "myPicks" +sealed class FirebaseCollections(val name: String) { + data object Favorites: FirebaseCollections("favorites") + data object Picks: FirebaseCollections("picks") + data object Users: FirebaseCollections("users") +} + +sealed class FirebaseDocumentFields(val name: String) { + data object AddedAt: FirebaseDocumentFields("addedAt") + data object PickId: FirebaseDocumentFields("pickId") + data object UserId: FirebaseDocumentFields("userId") + data object MyPicks: FirebaseDocumentFields("myPicks") + data object Name: FirebaseDocumentFields("name") + data object Location: FirebaseDocumentFields("location") + data object GeoHash: FirebaseDocumentFields("geoHash") + data object CreatedUserName: FirebaseDocumentFields("createdBy.userName") } diff --git a/data/src/main/java/com/squirtles/data/firebase/FirebaseException.kt b/data/src/main/java/com/squirtles/data/firebase/FirebaseException.kt new file mode 100644 index 00000000..dbce9181 --- /dev/null +++ b/data/src/main/java/com/squirtles/data/firebase/FirebaseException.kt @@ -0,0 +1,49 @@ +package com.squirtles.data.firebase + +sealed class FirebaseException(override val message: String) : Exception() { + data class CreatedUserFailedException(override val message: String = "Failed to create user") : FirebaseException(message) + data class FetchUserFailedException(override val message: String = "Failed to fetch user") : FirebaseException(message) + data class UpdateUserFailedException(override val message: String = "Failed to update user info") : FirebaseException(message) + data class NoSuchPickException(override val message: String = "No such pick", val pickId: String) + : FirebaseException("message @$pickId") + data class NoSuchPickInRadiusException(override val message: String = "No such pick in area") : FirebaseException(message) + + data class NoSuchDocumentException( + override val message: String = "No such document", + val docId: String, + val collection: String = "" + ) : FirebaseException("$message @$docId in $collection") + + data class FetchDocumentFailedException( + override val message: String = "Failed to fetch document", + val collection: String = "" + ) : FirebaseException("$message in $collection") + + data class AddDocumentFailedException( + override val message: String = "Failed to add document", + val value: Any, + val collection: String = "" + ) : FirebaseException("$message $value in $collection") + + data class DeleteDocumentFailedException( + override val message: String = "Failed to delete document", + val docId: String, + val collection: String = "" + ): FirebaseException("$message @$docId in $collection") + + data class UpdateDocumentFailedException( + override val message: String = "Failed to update document", + val docId: String, + val collection: String = "" + ) : FirebaseException("$message @$docId in $collection") + + data class ExecuteQueryFailedException( + override val message: String = "Failed to execute query", + val collection: String = "" + ) : FirebaseException("$message in $collection") + + data class CloudFunctionFailedException( + override val message: String = "Failed to run cloud function", + val exceptionMessage: String + ) : FirebaseException("$message : $exceptionMessage") +} diff --git a/data/src/main/java/com/squirtles/data/firebase/FirebaseRepositoryUtils.kt b/data/src/main/java/com/squirtles/data/firebase/FirebaseRepositoryUtils.kt new file mode 100644 index 00000000..40e7c555 --- /dev/null +++ b/data/src/main/java/com/squirtles/data/firebase/FirebaseRepositoryUtils.kt @@ -0,0 +1,19 @@ +package com.squirtles.data.firebase + +suspend fun handleResult( + firebaseRepositoryException: FirebaseException, + call: suspend () -> T? +): Result { + return runCatching { + call() ?: throw firebaseRepositoryException + } +} + +suspend fun handleResult( + call: suspend () -> T +): Result { + return runCatching { + call() + } +} + diff --git a/data/src/main/java/com/squirtles/data/pick/FirebasePickDataSourceImpl.kt b/data/src/main/java/com/squirtles/data/pick/FirebasePickDataSourceImpl.kt index 70979e5c..931554f0 100644 --- a/data/src/main/java/com/squirtles/data/pick/FirebasePickDataSourceImpl.kt +++ b/data/src/main/java/com/squirtles/data/pick/FirebasePickDataSourceImpl.kt @@ -12,18 +12,14 @@ import com.google.firebase.firestore.FirebaseFirestore import com.google.firebase.firestore.Query import com.google.firebase.firestore.QuerySnapshot import com.google.firebase.firestore.toObject -import com.squirtles.data.firebase.FirebaseDataSourceConstants.COLLECTION_FAVORITES -import com.squirtles.data.firebase.FirebaseDataSourceConstants.COLLECTION_PICKS -import com.squirtles.data.firebase.FirebaseDataSourceConstants.COLLECTION_USERS -import com.squirtles.data.firebase.FirebaseDataSourceConstants.FIELD_ADDED_AT -import com.squirtles.data.firebase.FirebaseDataSourceConstants.FIELD_MY_PICKS -import com.squirtles.data.firebase.FirebaseDataSourceConstants.FIELD_PICK_ID -import com.squirtles.data.firebase.FirebaseDataSourceConstants.FIELD_USER_ID -import com.squirtles.data.firebase.FirebaseDataSourceConstants.TAG_LOG +import com.squirtles.data.favorite.model.FirebaseFavorite +import com.squirtles.data.firebase.BaseFirebaseDataSource +import com.squirtles.data.firebase.FirebaseCollections +import com.squirtles.data.firebase.FirebaseDocumentFields import com.squirtles.data.pick.model.FirebasePick -import com.squirtles.data.user.model.FirebaseUser import com.squirtles.data.pick.model.toFirebasePick import com.squirtles.data.pick.model.toPick +import com.squirtles.data.user.model.FirebaseUser import com.squirtles.domain.model.Pick import com.squirtles.domain.pick.FirebasePickDataSource import kotlinx.coroutines.suspendCancellableCoroutine @@ -36,21 +32,16 @@ import kotlin.coroutines.resumeWithException @Singleton class FirebasePickDataSourceImpl @Inject constructor( private val db: FirebaseFirestore -) : FirebasePickDataSource { +) : BaseFirebaseDataSource(db), FirebasePickDataSource { /* Fetches a pick by ID from Firestore */ - override suspend fun fetchPick(pickID: String): Pick? { - return suspendCancellableCoroutine { continuation -> - db.collection("picks").document(pickID).get() - .addOnSuccessListener { document -> - val firestorePick = document.toObject()?.copy(id = pickID) - val resultPick = firestorePick?.toPick() - continuation.resume(resultPick) - } - .addOnFailureListener { exception -> - Log.e("FirebaseDataSourceImpl", "Failed to fetch a pick", exception) - continuation.resumeWithException(exception) - } + override suspend fun fetchPick(pickId: String): Result { + return runCatching { + val pickSnap = fetchDocumentSnapshot(FirebaseCollections.Picks, pickId).getOrThrow() + val firestorePick = pickSnap.toObject()?.copy(id = pickId) + firestorePick?.toPick()!! + }.onFailure { exception -> + Log.e(TAG_LOG, "Failed to fetch a pick", exception) } } @@ -59,199 +50,113 @@ class FirebasePickDataSourceImpl @Inject constructor( lat: Double, lng: Double, radiusInM: Double - ): List { + ): Result> { val center = GeoLocation(lat, lng) val bounds = GeoFireUtils.getGeoHashQueryBounds(center, radiusInM) - val queries: MutableList = ArrayList() - val tasks: MutableList> = ArrayList() - val matchingPicks: MutableList = ArrayList() - - bounds.forEach { bound -> - val query = db.collection("picks") - .orderBy("geoHash") - .startAt(bound.startHash) - .endAt(bound.endHash) - queries.add(query) - } - - try { - queries.forEach { query -> - tasks.add(query.get()) + return runCatching { + val queryResults = bounds.map { bound -> + queryDocumentsInRange( + collection = FirebaseCollections.Picks, + field = FirebaseDocumentFields.GeoHash, + start = bound.startHash, + end = bound.endHash + ) } - Tasks.whenAllComplete(tasks).await() - } catch (exception: Exception) { - Log.e("FirebaseDataSourceImpl", "Failed to fetch picks", exception) - throw exception - } - tasks.forEach { task -> - val snap = task.result - snap.documents.forEach { doc -> - if (isAccurate(doc, center, radiusInM)) { - doc.toObject()?.run { - matchingPicks.add(this.toPick().copy(id = doc.id)) + queryResults.flatMap { querySnapshot -> + querySnapshot.getOrThrow().documents + .filter { doc -> + isAccurate(doc, center, radiusInM) + }.mapNotNull { doc -> + doc.toObject()?.toPick()?.copy(id = doc.id) } - } } + }.onFailure { e -> + Log.e(TAG_LOG, "Failed to fetch picks", e) } - - return matchingPicks } /* Creates a new pick in Firestore */ - override suspend fun createPick(pick: Pick): String = - suspendCancellableCoroutine { continuation -> - val firebasePick = pick.toFirebasePick() - - // add() 메소드는 Cloud Firestore에서 ID를 자동으로 생성 - db.collection("picks").add(firebasePick) - .addOnSuccessListener { documentReference -> - val pickId = documentReference.id - // 유저의 픽 정보 업데이트 - updateCurrentUserPick(pick.createdBy.userId, pickId) - .addOnCompleteListener { task -> - if (task.isSuccessful) { - continuation.resume(pickId) - } else { - continuation.resumeWithException( - task.exception ?: Exception("Failed to updating user pick info") - ) - } - } - } - .addOnFailureListener { exception -> - Log.e("FirebaseDataSourceImpl", "Failed to create a pick", exception) - continuation.resumeWithException(exception) - } + override suspend fun createPick(pick: Pick): Result { + val firebasePick = pick.toFirebasePick() + return runCatching { + val pickRef = addDocument(FirebaseCollections.Picks, firebasePick).getOrThrow() + updateCurrentUserPick(pick.createdBy.userId, pickRef.id) + pickRef.id + }.onFailure { + Log.e(TAG_LOG, "Failed to create a pick", it) } + } - override suspend fun deletePick(pickId: String, userId: String): Boolean { - val pickDocument = db.collection(COLLECTION_PICKS).document(pickId) - val userDocument = db.collection(COLLECTION_USERS).document(userId) - val favoriteDocuments = fetchFavoriteDocuments(pickId) + override suspend fun deletePick(pickId: String, userId: String): Result { + val pickDocument = fetchDocumentReference(FirebaseCollections.Picks, pickId) + val userDocument = fetchDocumentReference(FirebaseCollections.Users, userId) + + return runCatching { + val favoriteDocuments = fetchFavoriteDocumentRefsByPick(pickId).getOrThrow() - return suspendCancellableCoroutine { continuation -> db.runTransaction { transaction -> transaction.delete(pickDocument) - - favoriteDocuments.forEach { document -> - transaction.delete(document) + favoriteDocuments.forEach { docRef -> + transaction.delete(docRef) } + transaction.update(userDocument, FirebaseDocumentFields.MyPicks.name, FieldValue.arrayRemove(pickId)) + }.await() - transaction.update(userDocument, FIELD_MY_PICKS, FieldValue.arrayRemove(pickId)) - }.addOnSuccessListener { _ -> - continuation.resume(true) - }.addOnFailureListener { e -> - Log.w(TAG_LOG, "Transaction failure.", e) - continuation.resumeWithException(e) - } + pickDocument.id + }.onFailure { + Log.e(TAG_LOG, "Failed to delete a pick", it) } } - override suspend fun fetchMyPicks(userId: String): List { - val userDocument = fetchUserDocument(userId) - if (userDocument.exists().not()) throw Exception("No user info in database") - - val tasks = mutableListOf>() - val myPicks = mutableListOf() - - try { - userDocument.toObject()?.myPicks?.forEach { pickId -> - tasks.add( - db.collection(COLLECTION_PICKS) - .document(pickId) - .get() - ) - } - Tasks.whenAllComplete(tasks).await() - } catch (exception: Exception) { - Log.e("FirebaseDataSourceImpl", "Failed to fetch my picks", exception) - throw exception + override suspend fun fetchMyPicks(userId: String): Result> { + return runCatching { + val userDocument = fetchDocumentSnapshot(FirebaseCollections.Users, userId).getOrThrow() + userDocument.toObject()?.myPicks!!.map { + fetchPick(it).getOrThrow() + }.reversed() } + } - tasks.forEach { task -> - task.result.toObject()?.run { - myPicks.add(this.toPick().copy(id = task.result.id)) + override suspend fun fetchFavoritePicks(userId: String): Result> { + return runCatching { + val favoriteDocuments = fetchFavoritesByUserId(userId) + favoriteDocuments.map { docSnap -> + fetchPick(docSnap.toObject()?.pickId.toString()).getOrThrow() } } - - return myPicks.reversed() - } - - private fun updateCurrentUserPick(userId: String, pickId: String): Task { - val userDoc = db.collection("users").document(userId) - return userDoc.update("myPicks", FieldValue.arrayUnion(pickId)) } - private suspend fun fetchFavoriteDocuments(pickId: String): List { - return suspendCancellableCoroutine { continuation -> - db.collection(COLLECTION_FAVORITES) - .whereEqualTo(FIELD_PICK_ID, pickId) - .get() - .addOnSuccessListener { querySnapShot -> - val documentIds = querySnapShot.documents.map { it.id } - val documentRefs = mutableListOf() - documentIds.forEach { id -> - documentRefs.add(db.collection(COLLECTION_FAVORITES).document(id)) - } - continuation.resume(documentRefs) - } - .addOnFailureListener { e -> - Log.w(TAG_LOG, "Failed to fetch favorite documents id", e) - continuation.resumeWithException(e) - } + private suspend fun updateCurrentUserPick(userId: String, pickId: String): Result { + return runCatching { + updateDocument( + collection = FirebaseCollections.Users, + documentId = userId, + field = FirebaseDocumentFields.MyPicks, + value = FieldValue.arrayUnion(pickId) + ).getOrThrow() + }.onFailure { e -> + Log.e(TAG_LOG, "Failed to update user picks", e) } } - private suspend fun fetchUserDocument(userId: String): DocumentSnapshot { - return suspendCancellableCoroutine { continuation -> - db.collection(COLLECTION_USERS).document(userId) - .get() - .addOnSuccessListener { document -> - continuation.resume(document) - } - .addOnFailureListener { exception -> - Log.e("FirebaseDataSourceImpl", "Failed to get user document", exception) - continuation.resumeWithException(exception) - } + private suspend fun fetchFavoriteDocumentRefsByPick(pickId: String): Result> { + return runCatching { + queryDocumentsEquals( + collection = FirebaseCollections.Favorites, + fields = listOf(FirebaseDocumentFields.PickId), + values = listOf(pickId), + ).getOrThrow().documents.map { it.reference } } } - override suspend fun fetchFavoritePicks(userId: String): List { - val favoriteDocuments = fetchFavoritesByUserId(userId) - - val tasks = mutableListOf>() - val favorites = mutableListOf() - - try { - favoriteDocuments.forEach { doc -> - tasks.add( - db.collection(COLLECTION_PICKS) - .document(doc.data[FIELD_PICK_ID].toString()) - .get() - ) - } - Tasks.whenAllComplete(tasks).await() - } catch (exception: Exception) { - Log.e("FirebaseDataSourceImpl", "Failed to get favorite picks", exception) - throw exception - } - tasks.forEach { task -> - task.result.toObject()?.run { - favorites.add(this.toPick().copy(id = task.result.id)) - } - } - - return favorites - } - /** * GeoHash의 FP 문제 - Geohash의 쿼리가 정확하지 않으며 클라이언트 측에서 거짓양성 결과를 필터링해야 합니다. * 이러한 추가 읽기로 인해 앱에 비용과 지연 시간이 추가됩니다. */ private fun isAccurate(doc: DocumentSnapshot, center: GeoLocation, radiusInM: Double): Boolean { - val location = doc.getGeoPoint("location") ?: return false + val location = doc.getGeoPoint(FirebaseDocumentFields.Location.name) ?: return false val docLocation = GeoLocation(location.latitude, location.longitude) val distanceInM = GeoFireUtils.getDistanceBetween(docLocation, center) @@ -259,12 +164,12 @@ class FirebasePickDataSourceImpl @Inject constructor( return distanceInM <= radiusInM } - private suspend fun fetchFavoritesByUserId(userId: String): QuerySnapshot { - val query = db.collection(COLLECTION_FAVORITES) - .whereEqualTo(FIELD_USER_ID, userId) - .orderBy(FIELD_ADDED_AT, Query.Direction.DESCENDING) - - return executeQuery(query) + private suspend fun fetchFavoritesByUserId(userId: String): List { + return queryDocumentsEquals( + collection = FirebaseCollections.Favorites, + fields = listOf(FirebaseDocumentFields.UserId), + values = listOf(userId) + ).getOrThrow().documents } private suspend fun executeQuery(query: Query): QuerySnapshot { @@ -274,9 +179,14 @@ class FirebasePickDataSourceImpl @Inject constructor( continuation.resume(result) } .addOnFailureListener { exception -> - Log.w("FirebaseDataSourceImpl", "Error fetching favorite documents", exception) + Log.w(TAG_LOG, "Error fetching favorite documents", exception) continuation.resumeWithException(exception) } } } + + companion object { + private const val TAG_LOG = "FirebasePickDataSourceImpl" + } } + diff --git a/data/src/main/java/com/squirtles/data/pick/FirebasePickRepositoryImpl.kt b/data/src/main/java/com/squirtles/data/pick/FirebasePickRepositoryImpl.kt index bc56974a..ad3414a6 100644 --- a/data/src/main/java/com/squirtles/data/pick/FirebasePickRepositoryImpl.kt +++ b/data/src/main/java/com/squirtles/data/pick/FirebasePickRepositoryImpl.kt @@ -1,6 +1,7 @@ package com.squirtles.data.pick -import com.squirtles.domain.firebase.FirebaseException +import com.squirtles.data.firebase.FirebaseException +import com.squirtles.data.firebase.handleResult import com.squirtles.domain.model.Pick import com.squirtles.domain.pick.FirebasePickDataSource import com.squirtles.domain.pick.FirebasePickRepository @@ -13,27 +14,19 @@ class FirebasePickRepositoryImpl @Inject constructor( ) : FirebasePickRepository { override suspend fun createPick(pick: Pick): Result { - return handleResult { - pickDataSource.createPick(pick) - } + return pickDataSource.createPick(pick) } - override suspend fun deletePick(pickId: String, userId: String): Result { - return handleResult { - pickDataSource.deletePick(pickId, userId) - } + override suspend fun deletePick(pickId: String, userId: String): Result { + return pickDataSource.deletePick(pickId, userId) } override suspend fun fetchPick(pickID: String): Result { - return handleResult(FirebaseException.NoSuchPickException()) { - pickDataSource.fetchPick(pickID) - } + return pickDataSource.fetchPick(pickID) } override suspend fun fetchMyPicks(userId: String): Result> { - return handleResult { - pickDataSource.fetchMyPicks(userId) - } + return pickDataSource.fetchMyPicks(userId) } override suspend fun fetchPicksInArea( @@ -43,13 +36,11 @@ class FirebasePickRepositoryImpl @Inject constructor( ): Result> { val pickList = pickDataSource.fetchPicksInArea(lat, lng, radiusInM) return handleResult(FirebaseException.NoSuchPickInRadiusException()) { - pickList.ifEmpty { null } + pickList.getOrThrow().ifEmpty { null } } } override suspend fun fetchFavoritePicks(userId: String): Result> { - return handleResult { - pickDataSource.fetchFavoritePicks(userId) - } + return pickDataSource.fetchFavoritePicks(userId) } } diff --git a/data/src/main/java/com/squirtles/data/pick/model/FirebasePick.kt b/data/src/main/java/com/squirtles/data/pick/model/FirebasePick.kt index 2e236527..eeb8729c 100644 --- a/data/src/main/java/com/squirtles/data/pick/model/FirebasePick.kt +++ b/data/src/main/java/com/squirtles/data/pick/model/FirebasePick.kt @@ -1,18 +1,8 @@ package com.squirtles.data.pick.model -import androidx.core.graphics.toColorInt -import com.firebase.geofire.GeoFireUtils -import com.firebase.geofire.GeoLocation import com.google.firebase.Timestamp import com.google.firebase.firestore.GeoPoint import com.google.firebase.firestore.ServerTimestamp -import com.squirtles.domain.model.Creator -import com.squirtles.domain.model.LocationPoint -import com.squirtles.domain.model.Pick -import com.squirtles.domain.model.Song -import java.text.SimpleDateFormat -import java.util.Date -import java.util.Locale /** * Firestore에 저장된 pick document를 불러와 변환하기위한 데이터 클래스 diff --git a/data/user/src/main/java/com/squirtles/user/FirebaseUserDataSourceImpl.kt b/data/user/src/main/java/com/squirtles/user/FirebaseUserDataSourceImpl.kt index 503e0e35..55f2f824 100644 --- a/data/user/src/main/java/com/squirtles/user/FirebaseUserDataSourceImpl.kt +++ b/data/user/src/main/java/com/squirtles/user/FirebaseUserDataSourceImpl.kt @@ -6,7 +6,11 @@ import com.squirtles.firebase.model.toUser import com.squirtles.model.User import com.google.firebase.firestore.FirebaseFirestore import com.google.firebase.firestore.toObject +import com.squirtles.firebase.BaseFirebaseDataSource +import com.squirtles.firebase.FirebaseCollections +import com.squirtles.firebase.FirebaseDocumentFields import kotlinx.coroutines.suspendCancellableCoroutine +import kotlinx.coroutines.tasks.await import javax.inject.Inject import javax.inject.Singleton import kotlin.coroutines.resume @@ -15,62 +19,55 @@ import kotlin.coroutines.resumeWithException @Singleton class FirebaseUserDataSourceImpl @Inject constructor( private val db: FirebaseFirestore -) : FirebaseUserDataSource { +) : BaseFirebaseDataSource(db), FirebaseUserDataSource { - override suspend fun createGoogleIdUser(userId: String, userName: String?, userProfileImage: String?): User? { - return suspendCancellableCoroutine { continuation -> - val documentReference = db.collection("users").document(userId) - documentReference.set(FirebaseUser(name = userName, profileImage = userProfileImage)) - .addOnSuccessListener { - documentReference.get() - .addOnSuccessListener { documentSnapshot -> - val savedUser = documentSnapshot.toObject() - continuation.resume( - savedUser?.toUser()?.copy(userId = documentReference.id) - ) - } - .addOnFailureListener { exception -> - continuation.resumeWithException(exception) - } - } - .addOnFailureListener { exception -> - Log.e("FirebaseDataSourceImpl", exception.message.toString()) - continuation.resumeWithException(exception) - } + override suspend fun createGoogleIdUser( + userId: String, + userName: String?, + userProfileImage: String? + ): Result { + return runCatching { + val firebaseUser = FirebaseUser(name = userName, profileImage = userProfileImage) + setDocument(FirebaseCollections.Users, userId, firebaseUser) + + val docSnap = fetchDocumentSnapshot(FirebaseCollections.Users, userId).getOrThrow() + docSnap.toObject()?.toUser()!! + }.onFailure { e -> + Log.e(TAG_LOG, e.message.toString()) } } - override suspend fun fetchUser(userId: String): User? { - return suspendCancellableCoroutine { continuation -> - db.collection("users").document(userId).get() - .addOnSuccessListener { document -> - val firebaseUser = document.toObject() - continuation.resume(firebaseUser?.toUser()?.copy(userId = userId)) - } - .addOnFailureListener { exception -> - continuation.resumeWithException(exception) - } + override suspend fun fetchUser(userId: String): Result { + return runCatching { + val docSnap = fetchDocumentSnapshot(FirebaseCollections.Users, userId).getOrThrow() + docSnap.toObject()?.toUser()!! + }.onFailure { e -> + Log.e(TAG_LOG, "Failed to fetch a user", e) } } - override suspend fun updateUserName(userId: String, newUserName: String): Boolean { - return suspendCancellableCoroutine { continuation -> + override suspend fun updateUserName(userId: String, newUserName: String): Result { + return runCatching { + val userSnap = fetchDocumentSnapshot(FirebaseCollections.Users, userId).getOrThrow() + db.runTransaction { transaction -> - val userRef = db.collection("users").document(userId) - val userDocument = transaction.get(userRef) - transaction.update(userRef, "name", newUserName) + transaction.update(userSnap.reference, FirebaseDocumentFields.Name.name, newUserName) + val myPicks = userSnap.get(FirebaseDocumentFields.MyPicks.name)?.let { it as List } ?: emptyList() - val myPicks = userDocument.get("myPicks")?.let { it as List } ?: emptyList() + // 해당 유저의 모든 pick의 등록 유저 정보 업데이트 myPicks.forEach { pickId -> - val pickRef = db.collection("picks").document(pickId) - transaction.update(pickRef, "createdBy.userName", newUserName) + val pickRef = fetchDocumentReference(FirebaseCollections.Picks, pickId) + transaction.update(pickRef, FirebaseDocumentFields.CreatedUserName.name, newUserName) } - }.addOnSuccessListener { - continuation.resume(true) - }.addOnFailureListener { exception -> - continuation.resumeWithException(exception) - } + }.await() + + true + }.onFailure { + Log.e(TAG_LOG, "Failed to update a user name", it) } } + companion object { + private const val TAG_LOG = "FirebaseUserDataSourceImpl" + } } diff --git a/data/user/src/main/java/com/squirtles/user/FirebaseUserRepositoryImpl.kt b/data/user/src/main/java/com/squirtles/user/FirebaseUserRepositoryImpl.kt index c2d9deb7..d2da3243 100644 --- a/data/user/src/main/java/com/squirtles/user/FirebaseUserRepositoryImpl.kt +++ b/data/user/src/main/java/com/squirtles/user/FirebaseUserRepositoryImpl.kt @@ -15,20 +15,23 @@ class FirebaseUserRepositoryImpl( userName: String?, userProfileImage: String? ): Result { - return handleResult(FirebaseException.CreatedUserFailedException()) { - userDataSource.createGoogleIdUser(userId, userName, userProfileImage) - } + return userDataSource.createGoogleIdUser(userId, userName, userProfileImage) + .onFailure { + throw FirebaseException.CreatedUserFailedException() + } } override suspend fun fetchUser(userId: String): Result { - return handleResult(FirebaseException.UserNotFoundException()) { - userDataSource.fetchUser(userId) - } + return userDataSource.fetchUser(userId) + .onFailure { + throw FirebaseException.FetchUserFailedException() + } } override suspend fun updateUserName(userId: String, newUserName: String): Result { - return handleResult { - userDataSource.updateUserName(userId, newUserName) - } + return userDataSource.updateUserName(userId, newUserName) + .onFailure { + throw FirebaseException.UpdateUserFailedException() + } } } diff --git a/domain/favorite/src/main/java/com/squirtles/favorite/FirebaseFavoriteDataSource.kt b/domain/favorite/src/main/java/com/squirtles/favorite/FirebaseFavoriteDataSource.kt index d8cf0734..82a19dbc 100644 --- a/domain/favorite/src/main/java/com/squirtles/favorite/FirebaseFavoriteDataSource.kt +++ b/domain/favorite/src/main/java/com/squirtles/favorite/FirebaseFavoriteDataSource.kt @@ -1,7 +1,7 @@ package com.squirtles.favorite interface FirebaseFavoriteDataSource { - suspend fun fetchIsFavorite(pickId: String, userId: String): Boolean - suspend fun createFavorite(pickId: String, userId: String): Boolean - suspend fun deleteFavorite(pickId: String, userId: String): Boolean + suspend fun fetchIsFavorite(pickId: String, userId: String): Result + suspend fun createFavorite(pickId: String, userId: String): Result + suspend fun deleteFavorite(pickId: String, userId: String): Result } diff --git a/domain/favorite/src/main/java/com/squirtles/favorite/FirebaseFavoriteRepository.kt b/domain/favorite/src/main/java/com/squirtles/favorite/FirebaseFavoriteRepository.kt index 5618a797..7b1b809e 100644 --- a/domain/favorite/src/main/java/com/squirtles/favorite/FirebaseFavoriteRepository.kt +++ b/domain/favorite/src/main/java/com/squirtles/favorite/FirebaseFavoriteRepository.kt @@ -2,6 +2,6 @@ package com.squirtles.favorite interface FirebaseFavoriteRepository { suspend fun fetchIsFavorite(pickId: String, userId: String): Result - suspend fun createFavorite(pickId: String, userId: String): Result - suspend fun deleteFavorite(pickId: String, userId: String): Result + suspend fun createFavorite(pickId: String, userId: String): Result + suspend fun deleteFavorite(pickId: String, userId: String): Result } diff --git a/domain/favorite/src/main/java/com/squirtles/favorite/usecase/DeleteFavoriteUseCase.kt b/domain/favorite/src/main/java/com/squirtles/favorite/usecase/DeleteFavoriteUseCase.kt index 2edfd3fc..eb292313 100644 --- a/domain/favorite/src/main/java/com/squirtles/favorite/usecase/DeleteFavoriteUseCase.kt +++ b/domain/favorite/src/main/java/com/squirtles/favorite/usecase/DeleteFavoriteUseCase.kt @@ -7,6 +7,6 @@ import javax.inject.Inject class DeleteFavoriteUseCase @Inject constructor( private val favoriteRepository: FirebaseFavoriteRepository ) : RemovePickUseCaseInterface { - override suspend operator fun invoke(pickId: String, userId: String) = + override suspend operator fun invoke(pickId: String, userId: String): Result = favoriteRepository.deleteFavorite(pickId, userId) } diff --git a/domain/pick/src/main/java/com/squirtles/pick/FirebasePickDataSource.kt b/domain/pick/src/main/java/com/squirtles/pick/FirebasePickDataSource.kt index 42c4dff0..3547efd4 100644 --- a/domain/pick/src/main/java/com/squirtles/pick/FirebasePickDataSource.kt +++ b/domain/pick/src/main/java/com/squirtles/pick/FirebasePickDataSource.kt @@ -3,10 +3,10 @@ package com.squirtles.pick import com.squirtles.model.Pick interface FirebasePickDataSource { - suspend fun createPick(pick: Pick): String - suspend fun deletePick(pickId: String, userId: String): Boolean - suspend fun fetchPick(pickID: String): Pick? - suspend fun fetchPicksInArea(lat: Double, lng: Double, radiusInM: Double): List - suspend fun fetchMyPicks(userId: String): List - suspend fun fetchFavoritePicks(userId: String): List + suspend fun fetchPick(pickID: String): Result + suspend fun fetchPicksInArea(lat: Double, lng: Double, radiusInM: Double): Result> + suspend fun createPick(pick: Pick): Result + suspend fun deletePick(pickId: String, userId: String): Result + suspend fun fetchMyPicks(userId: String): Result> + suspend fun fetchFavoritePicks(userId: String): Result> } diff --git a/domain/pick/src/main/java/com/squirtles/pick/FirebasePickRepository.kt b/domain/pick/src/main/java/com/squirtles/pick/FirebasePickRepository.kt index 29edd4ee..fbce8701 100644 --- a/domain/pick/src/main/java/com/squirtles/pick/FirebasePickRepository.kt +++ b/domain/pick/src/main/java/com/squirtles/pick/FirebasePickRepository.kt @@ -4,7 +4,7 @@ import com.squirtles.model.Pick interface FirebasePickRepository { suspend fun createPick(pick: Pick): Result - suspend fun deletePick(pickId: String, userId: String): Result + suspend fun deletePick(pickId: String, userId: String): Result suspend fun fetchPick(pickID: String): Result suspend fun fetchPicksInArea(lat: Double, lng: Double, radiusInM: Double): Result> suspend fun fetchMyPicks(userId: String): Result> diff --git a/domain/pick/src/main/java/com/squirtles/pick/usecase/DeletePickUseCase.kt b/domain/pick/src/main/java/com/squirtles/pick/usecase/DeletePickUseCase.kt index d1eee8a5..1da738ae 100644 --- a/domain/pick/src/main/java/com/squirtles/pick/usecase/DeletePickUseCase.kt +++ b/domain/pick/src/main/java/com/squirtles/pick/usecase/DeletePickUseCase.kt @@ -7,6 +7,6 @@ import javax.inject.Inject class DeletePickUseCase @Inject constructor( private val pickRepository: FirebasePickRepository ) : RemovePickUseCaseInterface { - override suspend operator fun invoke(pickId: String, userId: String): Result = + override suspend operator fun invoke(pickId: String, userId: String): Result = pickRepository.deletePick(pickId, userId) } diff --git a/domain/pick/src/main/java/com/squirtles/pick/usecase/FetchMyPicksUseCase.kt b/domain/pick/src/main/java/com/squirtles/pick/usecase/FetchMyPicksUseCase.kt index 49f764bb..e80c2708 100644 --- a/domain/pick/src/main/java/com/squirtles/pick/usecase/FetchMyPicksUseCase.kt +++ b/domain/pick/src/main/java/com/squirtles/pick/usecase/FetchMyPicksUseCase.kt @@ -1,5 +1,6 @@ package com.squirtles.pick.usecase +import com.squirtles.model.Pick import com.squirtles.pick.FirebasePickRepository import com.squirtles.picklist.FetchPickListUseCaseInterface import javax.inject.Inject @@ -7,6 +8,6 @@ import javax.inject.Inject class FetchMyPicksUseCase @Inject constructor( private val pickRepository: FirebasePickRepository ) : FetchPickListUseCaseInterface { - override suspend operator fun invoke(userId: String) = + override suspend operator fun invoke(userId: String) : Result> = pickRepository.fetchMyPicks(userId) } diff --git a/domain/pick/src/main/java/com/squirtles/pick/usecase/FetchPickUseCase.kt b/domain/pick/src/main/java/com/squirtles/pick/usecase/FetchPickUseCase.kt index c9f2befa..6428cbb9 100644 --- a/domain/pick/src/main/java/com/squirtles/pick/usecase/FetchPickUseCase.kt +++ b/domain/pick/src/main/java/com/squirtles/pick/usecase/FetchPickUseCase.kt @@ -1,14 +1,15 @@ package com.squirtles.pick.usecase +import com.squirtles.model.Pick import com.squirtles.pick.FirebasePickRepository import javax.inject.Inject class FetchPickUseCase @Inject constructor( private val pickRepository: FirebasePickRepository ) { - suspend operator fun invoke(pickId: String) = + suspend operator fun invoke(pickId: String): Result = pickRepository.fetchPick(pickId) - suspend operator fun invoke(lat: Double, lng: Double, radiusInM: Double) = + suspend operator fun invoke(lat: Double, lng: Double, radiusInM: Double): Result> = pickRepository.fetchPicksInArea(lat, lng, radiusInM) } diff --git a/domain/picklist/src/main/java/com/squirtles/picklist/RemovePickUseCaseInterface.kt b/domain/picklist/src/main/java/com/squirtles/picklist/RemovePickUseCaseInterface.kt index 2475ff83..8373d410 100644 --- a/domain/picklist/src/main/java/com/squirtles/picklist/RemovePickUseCaseInterface.kt +++ b/domain/picklist/src/main/java/com/squirtles/picklist/RemovePickUseCaseInterface.kt @@ -1,5 +1,5 @@ package com.squirtles.picklist interface RemovePickUseCaseInterface { - suspend operator fun invoke(pickId: String, userId: String): Result + suspend operator fun invoke(pickId: String, userId: String): Result } diff --git a/domain/src/main/java/com/squirtles/domain/favorite/FirebaseFavoriteDataSource.kt b/domain/src/main/java/com/squirtles/domain/favorite/FirebaseFavoriteDataSource.kt index 34425956..17f04a26 100644 --- a/domain/src/main/java/com/squirtles/domain/favorite/FirebaseFavoriteDataSource.kt +++ b/domain/src/main/java/com/squirtles/domain/favorite/FirebaseFavoriteDataSource.kt @@ -1,9 +1,7 @@ package com.squirtles.domain.favorite -import com.squirtles.domain.model.Pick - interface FirebaseFavoriteDataSource { - suspend fun fetchIsFavorite(pickId: String, userId: String): Boolean - suspend fun createFavorite(pickId: String, userId: String): Boolean - suspend fun deleteFavorite(pickId: String, userId: String): Boolean + suspend fun fetchIsFavorite(pickId: String, userId: String): Result + suspend fun createFavorite(pickId: String, userId: String): Result + suspend fun deleteFavorite(pickId: String, userId: String): Result } diff --git a/domain/src/main/java/com/squirtles/domain/favorite/FirebaseFavoriteRepository.kt b/domain/src/main/java/com/squirtles/domain/favorite/FirebaseFavoriteRepository.kt index a3af14b3..fd949300 100644 --- a/domain/src/main/java/com/squirtles/domain/favorite/FirebaseFavoriteRepository.kt +++ b/domain/src/main/java/com/squirtles/domain/favorite/FirebaseFavoriteRepository.kt @@ -3,9 +3,8 @@ package com.squirtles.domain.favorite import com.squirtles.domain.firebase.FirebaseRepository import com.squirtles.domain.model.Pick -interface FirebaseFavoriteRepository : FirebaseRepository { - // Favorite +interface FirebaseFavoriteRepository { suspend fun fetchIsFavorite(pickId: String, userId: String): Result - suspend fun createFavorite(pickId: String, userId: String): Result - suspend fun deleteFavorite(pickId: String, userId: String): Result + suspend fun createFavorite(pickId: String, userId: String): Result + suspend fun deleteFavorite(pickId: String, userId: String): Result } diff --git a/domain/src/main/java/com/squirtles/domain/favorite/usecase/DeleteFavoriteUseCase.kt b/domain/src/main/java/com/squirtles/domain/favorite/usecase/DeleteFavoriteUseCase.kt index 0acf6631..fb0e19eb 100644 --- a/domain/src/main/java/com/squirtles/domain/favorite/usecase/DeleteFavoriteUseCase.kt +++ b/domain/src/main/java/com/squirtles/domain/favorite/usecase/DeleteFavoriteUseCase.kt @@ -7,6 +7,6 @@ import javax.inject.Inject class DeleteFavoriteUseCase @Inject constructor( private val favoriteRepository: FirebaseFavoriteRepository ) : RemovePickUseCaseInterface { - override suspend operator fun invoke(pickId: String, userId: String) = + override suspend operator fun invoke(pickId: String, userId: String): Result = favoriteRepository.deleteFavorite(pickId, userId) } diff --git a/domain/src/main/java/com/squirtles/domain/pick/FirebasePickDataSource.kt b/domain/src/main/java/com/squirtles/domain/pick/FirebasePickDataSource.kt index 326c9393..eb6e3bc8 100644 --- a/domain/src/main/java/com/squirtles/domain/pick/FirebasePickDataSource.kt +++ b/domain/src/main/java/com/squirtles/domain/pick/FirebasePickDataSource.kt @@ -3,10 +3,10 @@ package com.squirtles.domain.pick import com.squirtles.domain.model.Pick interface FirebasePickDataSource { - suspend fun createPick(pick: Pick): String - suspend fun deletePick(pickId: String, userId: String): Boolean - suspend fun fetchPick(pickID: String): Pick? - suspend fun fetchPicksInArea(lat: Double, lng: Double, radiusInM: Double): List - suspend fun fetchMyPicks(userId: String): List - suspend fun fetchFavoritePicks(userId: String): List + suspend fun fetchPick(pickID: String): Result + suspend fun fetchPicksInArea(lat: Double, lng: Double, radiusInM: Double): Result> + suspend fun createPick(pick: Pick): Result + suspend fun deletePick(pickId: String, userId: String): Result + suspend fun fetchMyPicks(userId: String): Result> + suspend fun fetchFavoritePicks(userId: String): Result> } diff --git a/domain/src/main/java/com/squirtles/domain/pick/FirebasePickRepository.kt b/domain/src/main/java/com/squirtles/domain/pick/FirebasePickRepository.kt index f8181254..e572597d 100644 --- a/domain/src/main/java/com/squirtles/domain/pick/FirebasePickRepository.kt +++ b/domain/src/main/java/com/squirtles/domain/pick/FirebasePickRepository.kt @@ -1,11 +1,10 @@ package com.squirtles.domain.pick import com.squirtles.domain.model.Pick -import com.squirtles.domain.firebase.FirebaseRepository -interface FirebasePickRepository : FirebaseRepository { +interface FirebasePickRepository { suspend fun createPick(pick: Pick): Result - suspend fun deletePick(pickId: String, userId: String): Result + suspend fun deletePick(pickId: String, userId: String): Result suspend fun fetchPick(pickID: String): Result suspend fun fetchPicksInArea(lat: Double, lng: Double, radiusInM: Double): Result> suspend fun fetchMyPicks(userId: String): Result> diff --git a/domain/src/main/java/com/squirtles/domain/pick/usecase/DeletePickUseCase.kt b/domain/src/main/java/com/squirtles/domain/pick/usecase/DeletePickUseCase.kt index b2bcbbc0..4600a725 100644 --- a/domain/src/main/java/com/squirtles/domain/pick/usecase/DeletePickUseCase.kt +++ b/domain/src/main/java/com/squirtles/domain/pick/usecase/DeletePickUseCase.kt @@ -7,6 +7,6 @@ import javax.inject.Inject class DeletePickUseCase @Inject constructor( private val pickRepository: FirebasePickRepository ) : RemovePickUseCaseInterface { - override suspend operator fun invoke(pickId: String, userId: String): Result = + override suspend operator fun invoke(pickId: String, userId: String): Result = pickRepository.deletePick(pickId, userId) } diff --git a/domain/src/main/java/com/squirtles/domain/pick/usecase/FetchFavoritePicksUseCase.kt b/domain/src/main/java/com/squirtles/domain/pick/usecase/FetchFavoritePicksUseCase.kt index 9ce36467..99bc60d5 100644 --- a/domain/src/main/java/com/squirtles/domain/pick/usecase/FetchFavoritePicksUseCase.kt +++ b/domain/src/main/java/com/squirtles/domain/pick/usecase/FetchFavoritePicksUseCase.kt @@ -1,5 +1,6 @@ package com.squirtles.domain.pick.usecase +import com.squirtles.domain.model.Pick import com.squirtles.domain.pick.FirebasePickRepository import com.squirtles.domain.picklist.FetchPickListUseCaseInterface import javax.inject.Inject @@ -7,5 +8,6 @@ import javax.inject.Inject class FetchFavoritePicksUseCase @Inject constructor( private val pickRepository: FirebasePickRepository ) : FetchPickListUseCaseInterface { - override suspend operator fun invoke(userId: String) = pickRepository.fetchFavoritePicks(userId) + override suspend operator fun invoke(userId: String): Result> = + pickRepository.fetchFavoritePicks(userId) } diff --git a/domain/src/main/java/com/squirtles/domain/pick/usecase/FetchMyPicksUseCase.kt b/domain/src/main/java/com/squirtles/domain/pick/usecase/FetchMyPicksUseCase.kt index 4507acab..863e0a5b 100644 --- a/domain/src/main/java/com/squirtles/domain/pick/usecase/FetchMyPicksUseCase.kt +++ b/domain/src/main/java/com/squirtles/domain/pick/usecase/FetchMyPicksUseCase.kt @@ -1,5 +1,6 @@ package com.squirtles.domain.pick.usecase +import com.squirtles.domain.model.Pick import com.squirtles.domain.pick.FirebasePickRepository import com.squirtles.domain.picklist.FetchPickListUseCaseInterface import javax.inject.Inject @@ -7,6 +8,6 @@ import javax.inject.Inject class FetchMyPicksUseCase @Inject constructor( private val pickRepository: FirebasePickRepository ) : FetchPickListUseCaseInterface { - override suspend operator fun invoke(userId: String) = + override suspend operator fun invoke(userId: String) : Result> = pickRepository.fetchMyPicks(userId) } diff --git a/domain/src/main/java/com/squirtles/domain/pick/usecase/FetchPickUseCase.kt b/domain/src/main/java/com/squirtles/domain/pick/usecase/FetchPickUseCase.kt index 6a95b697..0adbcf50 100644 --- a/domain/src/main/java/com/squirtles/domain/pick/usecase/FetchPickUseCase.kt +++ b/domain/src/main/java/com/squirtles/domain/pick/usecase/FetchPickUseCase.kt @@ -1,14 +1,15 @@ package com.squirtles.domain.pick.usecase +import com.squirtles.domain.model.Pick import com.squirtles.domain.pick.FirebasePickRepository import javax.inject.Inject class FetchPickUseCase @Inject constructor( private val pickRepository: FirebasePickRepository ) { - suspend operator fun invoke(pickId: String) = + suspend operator fun invoke(pickId: String): Result = pickRepository.fetchPick(pickId) - suspend operator fun invoke(lat: Double, lng: Double, radiusInM: Double) = + suspend operator fun invoke(lat: Double, lng: Double, radiusInM: Double): Result> = pickRepository.fetchPicksInArea(lat, lng, radiusInM) } diff --git a/domain/src/main/java/com/squirtles/domain/picklist/RemovePickUseCaseInterface.kt b/domain/src/main/java/com/squirtles/domain/picklist/RemovePickUseCaseInterface.kt index 2e3037a4..9c47a89a 100644 --- a/domain/src/main/java/com/squirtles/domain/picklist/RemovePickUseCaseInterface.kt +++ b/domain/src/main/java/com/squirtles/domain/picklist/RemovePickUseCaseInterface.kt @@ -1,5 +1,5 @@ package com.squirtles.domain.picklist interface RemovePickUseCaseInterface { - suspend operator fun invoke(pickId: String, userId: String): Result + suspend operator fun invoke(pickId: String, userId: String): Result } diff --git a/domain/user/src/main/java/com/squirtles/user/FirebaseUserDataSource.kt b/domain/user/src/main/java/com/squirtles/user/FirebaseUserDataSource.kt index 82a6bb26..3c84f465 100644 --- a/domain/user/src/main/java/com/squirtles/user/FirebaseUserDataSource.kt +++ b/domain/user/src/main/java/com/squirtles/user/FirebaseUserDataSource.kt @@ -3,7 +3,10 @@ package com.squirtles.user import com.squirtles.model.User interface FirebaseUserDataSource { - suspend fun fetchUser(userId: String): User? - suspend fun createGoogleIdUser(userId: String, userName: String?, userProfileImage: String?): User? - suspend fun updateUserName(userId: String, newUserName: String): Boolean + suspend fun fetchUser(userId: String): Result + suspend fun createGoogleIdUser( + userId: String, + userName: String?, + userProfileImage: String?): Result + suspend fun updateUserName(userId: String, newUserName: String): Result } From d945e7e9e516f3a17d0463f583373b52dd9f9126 Mon Sep 17 00:00:00 2001 From: miller198 Date: Fri, 7 Mar 2025 22:14:02 +0900 Subject: [PATCH 35/62] =?UTF-8?q?[feature]=20feature/favorite=20=EB=AA=A8?= =?UTF-8?q?=EB=93=88?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/com/squirtles/picklist/Extension.kt | 17 - .../squirtles/picklist/PickListContents.kt | 209 ------------ .../picklist/PickListScreenContents.kt | 309 ++++++++++++++++++ .../squirtles/picklist/PickListViewModel.kt | 101 ++++++ .../components/DeleteSelectedPickDialog.kt | 46 +++ .../picklist/components/EditModeAction.kt | 61 ++++ .../components/EditModeBottomButton.kt | 96 ++++++ .../{ => components}/OrderBottomSheet.kt | 7 +- .../PickItem.kt} | 32 +- core/picklist/src/main/res/values/strings.xml | 20 ++ .../pick/usecase/FetchFavoritePicksUseCase.kt | 13 + feature/favorite/.gitignore | 1 + feature/favorite/build.gradle.kts | 20 ++ feature/favorite/consumer-rules.pro | 0 .../favorite/ExampleInstrumentedTest.kt | 24 ++ .../favorite/FavoriteListViewModel.kt | 25 ++ .../com/squirtles/favorite/FavoriteScreen.kt | 47 +++ .../favorite/navigation/FavoriteNavigation.kt | 28 ++ .../com/squirtles/favorite/ExampleUnitTest.kt | 17 + 19 files changed, 839 insertions(+), 234 deletions(-) delete mode 100644 core/picklist/src/main/java/com/squirtles/picklist/Extension.kt delete mode 100644 core/picklist/src/main/java/com/squirtles/picklist/PickListContents.kt create mode 100644 core/picklist/src/main/java/com/squirtles/picklist/PickListScreenContents.kt create mode 100644 core/picklist/src/main/java/com/squirtles/picklist/PickListViewModel.kt create mode 100644 core/picklist/src/main/java/com/squirtles/picklist/components/DeleteSelectedPickDialog.kt create mode 100644 core/picklist/src/main/java/com/squirtles/picklist/components/EditModeAction.kt create mode 100644 core/picklist/src/main/java/com/squirtles/picklist/components/EditModeBottomButton.kt rename core/picklist/src/main/java/com/squirtles/picklist/{ => components}/OrderBottomSheet.kt (96%) rename core/picklist/src/main/java/com/squirtles/picklist/{PickListItem.kt => components/PickItem.kt} (75%) create mode 100644 domain/pick/src/main/java/com/squirtles/pick/usecase/FetchFavoritePicksUseCase.kt create mode 100644 feature/favorite/.gitignore create mode 100644 feature/favorite/build.gradle.kts create mode 100644 feature/favorite/consumer-rules.pro create mode 100644 feature/favorite/src/androidTest/java/com/squirtles/favorite/ExampleInstrumentedTest.kt create mode 100644 feature/favorite/src/main/java/com/squirtles/favorite/FavoriteListViewModel.kt create mode 100644 feature/favorite/src/main/java/com/squirtles/favorite/FavoriteScreen.kt create mode 100644 feature/favorite/src/main/java/com/squirtles/favorite/navigation/FavoriteNavigation.kt create mode 100644 feature/favorite/src/test/java/com/squirtles/favorite/ExampleUnitTest.kt diff --git a/core/picklist/src/main/java/com/squirtles/picklist/Extension.kt b/core/picklist/src/main/java/com/squirtles/picklist/Extension.kt deleted file mode 100644 index 1bbca774..00000000 --- a/core/picklist/src/main/java/com/squirtles/picklist/Extension.kt +++ /dev/null @@ -1,17 +0,0 @@ -package com.squirtles.picklist - -import com.squirtles.model.Order -import com.squirtles.model.Pick - -fun List.setOrderedList(order: Order): PickListUiState.Success { - return PickListUiState.Success( - pickList = when (order) { - Order.LATEST -> this - - Order.OLDEST -> this.reversed() - - Order.FAVORITE_DESC -> this.sortedByDescending { it.favoriteCount } - }, - order = order - ) -} diff --git a/core/picklist/src/main/java/com/squirtles/picklist/PickListContents.kt b/core/picklist/src/main/java/com/squirtles/picklist/PickListContents.kt deleted file mode 100644 index bc30a490..00000000 --- a/core/picklist/src/main/java/com/squirtles/picklist/PickListContents.kt +++ /dev/null @@ -1,209 +0,0 @@ -package com.squirtles.picklist - -import android.content.res.Configuration -import androidx.compose.foundation.background -import androidx.compose.foundation.clickable -import androidx.compose.foundation.layout.Arrangement -import androidx.compose.foundation.layout.Box -import androidx.compose.foundation.layout.Column -import androidx.compose.foundation.layout.Row -import androidx.compose.foundation.layout.displayCutoutPadding -import androidx.compose.foundation.layout.fillMaxSize -import androidx.compose.foundation.layout.fillMaxWidth -import androidx.compose.foundation.layout.padding -import androidx.compose.foundation.layout.size -import androidx.compose.foundation.layout.wrapContentSize -import androidx.compose.foundation.lazy.LazyColumn -import androidx.compose.foundation.shape.CircleShape -import androidx.compose.material3.MaterialTheme -import androidx.compose.material3.Scaffold -import androidx.compose.material3.Text -import androidx.compose.runtime.Composable -import androidx.compose.ui.Alignment -import androidx.compose.ui.Modifier -import androidx.compose.ui.draw.clip -import androidx.compose.ui.graphics.Brush -import androidx.compose.ui.graphics.Color.Companion.White -import androidx.compose.ui.platform.LocalConfiguration -import androidx.compose.ui.res.stringResource -import androidx.compose.ui.unit.dp -import androidx.wear.compose.material.CircularProgressIndicator -import com.squirtles.common.ui.Constants.COLOR_STOPS -import com.squirtles.common.ui.Constants.DEFAULT_PADDING -import com.squirtles.common.ui.CountText -import com.squirtles.common.ui.DefaultTopAppBar -import com.squirtles.common.ui.VerticalSpacer -import com.squirtles.common.ui.theme.Primary -import com.squirtles.model.Order - -@Composable -fun PickListContents( - showOrderBottomSheet: Boolean, - pickListType: PickListType, - uiState: PickListUiState, - onBackClick: () -> Unit, - onItemClick: (String) -> Unit, - setListOrder: (Order) -> Unit, - setOrderBottomSheetVisibility: (Boolean) -> Unit -) { - val isLandscape = LocalConfiguration.current.orientation == Configuration.ORIENTATION_LANDSCAPE - - Scaffold( - topBar = { - DefaultTopAppBar( - title = stringResource( - when (pickListType) { - PickListType.FAVORITE -> R.string.favorite_picks_top_app_bar_title - PickListType.CREATED -> R.string.my_picks_top_app_bar_title - } - ), - onBackClick = onBackClick - ) - }, - ) { innerPadding -> - Box( - modifier = Modifier - .fillMaxSize() - .background(Brush.verticalGradient(colorStops = COLOR_STOPS)) - .padding(innerPadding) - .then(if (isLandscape) Modifier.displayCutoutPadding() else Modifier) - ) { - when (uiState) { - PickListUiState.Loading -> { - CircularProgressIndicator( - modifier = Modifier - .size(36.dp) - .align(Alignment.Center), - indicatorColor = Primary - ) - } - - is PickListUiState.Success -> { - val pickList = uiState.pickList - val order = uiState.order - - Column( - modifier = Modifier.fillMaxSize() - ) { - Row( - modifier = Modifier - .fillMaxWidth() - .padding(horizontal = DEFAULT_PADDING), - horizontalArrangement = Arrangement.SpaceBetween, - verticalAlignment = Alignment.CenterVertically - ) { - CountText( - totalCount = pickList.size, - defaultColor = White, - ) - - Box( - modifier = Modifier - .wrapContentSize() - .clip(CircleShape) - .clickable { setOrderBottomSheetVisibility(true) } - ) { - Text( - text = getOrderString( - pickListType = pickListType, - order = order - ), - modifier = Modifier.padding( - horizontal = DEFAULT_PADDING / 2, - vertical = DEFAULT_PADDING / 4 - ), - color = White, - style = MaterialTheme.typography.bodyMedium - ) - } - } - - VerticalSpacer(8) - - if (pickList.isEmpty()) { - Box( - modifier = Modifier.fillMaxSize(), - contentAlignment = Alignment.Center - ) { - Text( - text = stringResource( - when (pickListType) { - PickListType.FAVORITE -> R.string.favorite_picks_empty - PickListType.CREATED -> R.string.my_picks_empty - } - ), - color = White, - style = MaterialTheme.typography.titleMedium - ) - } - } else { - LazyColumn( - modifier = Modifier.weight(1f) - ) { - items( - count = pickList.count(), - key = { pickList[it].id }, - itemContent = { - PickListItem( - song = pickList[it].song, - isCreatedByOthers = pickListType == PickListType.FAVORITE, - createUserName = pickList[it].createdBy.userName, - favoriteCount = pickList[it].favoriteCount, - comment = pickList[it].comment, - createdAt = pickList[it].createdAt, - onItemClick = { onItemClick(pickList[it].id) } - ) - } - ) - } - } - } - } - - PickListUiState.Error -> { - Text( - text = stringResource(R.string.error_loading_pick_list), - modifier = Modifier.align(Alignment.Center), - color = White, - style = MaterialTheme.typography.bodyLarge - ) - // TODO: 다시하기 버튼 같은 거 만들어서 요청 다시 하게 할 수 있도록 만드는 것도 고려해보기 - } - } - } - } - - if (showOrderBottomSheet) { - OrderBottomSheet( - isFavoritePicks = pickListType == PickListType.FAVORITE, - currentOrder = (uiState as PickListUiState.Success).order, - onDismissRequest = { setOrderBottomSheetVisibility(false) }, - onOrderClick = { order -> - setListOrder(order) - }, - ) - } -} - -@Composable -private fun getOrderString(pickListType: PickListType, order: Order): String { - return "${ - stringResource( - when (order) { - Order.LATEST -> - when (pickListType) { - PickListType.FAVORITE -> R.string.latest_favorite_order - PickListType.CREATED -> R.string.latest_create_order - } - - Order.OLDEST -> - when (pickListType) { - PickListType.FAVORITE -> R.string.oldest_favorite_order - PickListType.CREATED -> R.string.oldest_create_order - } - - Order.FAVORITE_DESC -> R.string.favorite_count_desc - } - ) - } ▼" -} diff --git a/core/picklist/src/main/java/com/squirtles/picklist/PickListScreenContents.kt b/core/picklist/src/main/java/com/squirtles/picklist/PickListScreenContents.kt new file mode 100644 index 00000000..88d7adcd --- /dev/null +++ b/core/picklist/src/main/java/com/squirtles/picklist/PickListScreenContents.kt @@ -0,0 +1,309 @@ +package com.squirtles.picklist + +import android.content.res.Configuration +import androidx.compose.foundation.background +import androidx.compose.foundation.clickable +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.displayCutoutPadding +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size +import androidx.compose.foundation.layout.wrapContentSize +import androidx.compose.foundation.lazy.LazyColumn +import androidx.compose.foundation.lazy.items +import androidx.compose.foundation.shape.CircleShape +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.Scaffold +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.saveable.rememberSaveable +import androidx.compose.runtime.setValue +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.clip +import androidx.compose.ui.graphics.Brush +import androidx.compose.ui.platform.LocalConfiguration +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.unit.dp +import androidx.wear.compose.material.CircularProgressIndicator +import com.squirtles.common.ui.Constants.COLOR_STOPS +import com.squirtles.common.ui.Constants.DEFAULT_PADDING +import com.squirtles.common.ui.CountText +import com.squirtles.common.ui.DefaultTopAppBar +import com.squirtles.common.ui.VerticalSpacer +import com.squirtles.common.ui.theme.Primary +import com.squirtles.common.ui.theme.White +import com.squirtles.model.Order +import com.squirtles.model.Pick +import com.squirtles.picklist.components.DeleteSelectedPickDialog +import com.squirtles.picklist.components.EditModeAction +import com.squirtles.picklist.components.EditModeBottomButton +import com.squirtles.picklist.components.OrderBottomSheet +import com.squirtles.picklist.components.PickItem + +@Composable +fun PickListScreenContents( + userId: String, + showOrderBottomSheet: Boolean, + selectedPicksId: Set, + pickListType: PickListType, + uiState: PickListUiState, + getUserId: () -> String, + onBackClick: () -> Unit, + onItemClick: (String) -> Unit, + setListOrder: (Order) -> Unit, + setOrderBottomSheetVisibility: (Boolean) -> Unit, + selectAllPicks: () -> Unit, + deselectAllPicks: () -> Unit, + toggleSelectedPick: (String) -> Unit, + deleteSelectedPicks: (String) -> Unit +) { + val isLandscape = LocalConfiguration.current.orientation == Configuration.ORIENTATION_LANDSCAPE + + var isEditMode by rememberSaveable { mutableStateOf(false) } + var isDeletePickDialogVisible by rememberSaveable { mutableStateOf(false) } + + val deactivateEditMode = remember { + { + isEditMode = false + deselectAllPicks() + } + } + val showDeletePickDialog = remember { + { + isDeletePickDialogVisible = true + } + } + + Scaffold( + topBar = { + DefaultTopAppBar( + title = stringResource( + when (pickListType) { + PickListType.FAVORITE -> R.string.favorite_picks_top_app_bar_title + PickListType.CREATED -> R.string.my_picks_top_app_bar_title + } + ), + onBackClick = onBackClick, + actions = { + if(getUserId() == userId){ + EditModeAction( + isEditMode = isEditMode, + enabled = uiState is PickListUiState.Success, + isSelectedEmpty = selectedPicksId.isEmpty(), + activateEditMode = { isEditMode = true }, + selectAllPicks = { selectAllPicks() }, + deselectAllPicks = { deselectAllPicks() }, + ) + } + } + ) + }, + ) { innerPadding -> + Box( + modifier = Modifier + .fillMaxSize() + .background(Brush.verticalGradient(colorStops = COLOR_STOPS)) + .padding(innerPadding) + .then(if (isLandscape) Modifier.displayCutoutPadding() else Modifier) + ) { + when (uiState) { + PickListUiState.Loading -> { + CircularProgressIndicator( + modifier = Modifier + .size(36.dp) + .align(Alignment.Center), + indicatorColor = Primary + ) + } + + is PickListUiState.Success -> { + val pickList = uiState.pickList + val order = uiState.order + + Column( + modifier = Modifier.fillMaxSize() + ) { + PickList( + modifier = Modifier + .fillMaxWidth() + .weight(1f), + isEditMode = isEditMode, + pickListType = pickListType, + pickList = pickList, + selectedPicksId = selectedPicksId, + order = order, + onOrderClick = { + setOrderBottomSheetVisibility(true) + }, + onItemClick = onItemClick, + onEditModeItemClick = toggleSelectedPick, + ) + + if (isEditMode) { + EditModeBottomButton( + enabled = selectedPicksId.isNotEmpty(), + deactivateEditMode = deactivateEditMode, + showDeletePickDialog = showDeletePickDialog, + ) + } + } + } + + PickListUiState.Error -> { + Text( + text = stringResource(R.string.error_loading_pick_list), + modifier = Modifier.align(Alignment.Center), + color = White, + style = MaterialTheme.typography.bodyLarge + ) + // TODO: 다시하기 버튼 같은 거 만들어서 요청 다시 하게 할 수 있도록 만드는 것도 고려해보기 + } + } + } + } + + if (showOrderBottomSheet) { + OrderBottomSheet( + isFavoritePicks = pickListType == PickListType.FAVORITE, + currentOrder = (uiState as PickListUiState.Success).order, + onDismissRequest = { setOrderBottomSheetVisibility(false) }, + onOrderClick = setListOrder, + ) + } + + if (isDeletePickDialogVisible) { + DeleteSelectedPickDialog( + selectedPickCount = selectedPicksId.size, + pickListType = pickListType, + onDismissRequest = { isDeletePickDialogVisible = false }, + onDeletePickClick = { + isEditMode = false + isDeletePickDialogVisible = false + deleteSelectedPicks(userId) + }, + ) + } +} + +@Composable +private fun PickList( + modifier: Modifier, + isEditMode: Boolean, + pickListType: PickListType, + pickList: List, + selectedPicksId: Set, + order: Order, + onOrderClick: () -> Unit, + onItemClick: (String) -> Unit, + onEditModeItemClick: (String) -> Unit, +) { + Row( + modifier = Modifier + .fillMaxWidth() + .padding(horizontal = DEFAULT_PADDING), + horizontalArrangement = Arrangement.SpaceBetween, + verticalAlignment = Alignment.CenterVertically + ) { + CountText( + totalCount = if (isEditMode) selectedPicksId.size else pickList.size, + countLabel = stringResource( + if (isEditMode) R.string.selected_count_text else R.string.total_count_text + ), + defaultColor = White, + ) + + Box( + modifier = Modifier + .wrapContentSize() + .clip(CircleShape) + .clickable { onOrderClick() } + ) { + Text( + text = getOrderString( + pickListType = pickListType, + order = order + ), + modifier = Modifier.padding( + horizontal = DEFAULT_PADDING / 2, + vertical = DEFAULT_PADDING / 4 + ), + color = White, + style = MaterialTheme.typography.bodyMedium + ) + } + } + + VerticalSpacer(8) + + if (pickList.isEmpty()) { + Box( + modifier = modifier, + contentAlignment = Alignment.Center + ) { + Text( + text = stringResource( + when (pickListType) { + PickListType.FAVORITE -> R.string.favorite_picks_empty + PickListType.CREATED -> R.string.my_picks_empty + } + ), + color = White, + style = MaterialTheme.typography.titleMedium + ) + } + } else { + LazyColumn( + modifier = modifier + ) { + items( + items = pickList, + key = { it.id } + ) { pick -> + PickItem( + isEditMode = isEditMode, + isSelected = selectedPicksId.contains(pick.id), + song = pick.song, + createdByOthers = pickListType == PickListType.FAVORITE, + createUserName = pick.createdBy.userName, + favoriteCount = pick.favoriteCount, + comment = pick.comment, + createdAt = pick.createdAt, + onItemClick = { + if (isEditMode) onEditModeItemClick(pick.id) else onItemClick(pick.id) + } + ) + } + } + } +} + +@Composable +private fun getOrderString(pickListType: PickListType, order: Order): String { + return "${ + stringResource( + when (order) { + Order.LATEST -> + when (pickListType) { + PickListType.FAVORITE -> R.string.latest_favorite_order + PickListType.CREATED -> R.string.latest_create_order + } + + Order.OLDEST -> + when (pickListType) { + PickListType.FAVORITE -> R.string.oldest_favorite_order + PickListType.CREATED -> R.string.oldest_create_order + } + + Order.FAVORITE_DESC -> R.string.favorite_count_desc + } + ) + } ▼" +} diff --git a/core/picklist/src/main/java/com/squirtles/picklist/PickListViewModel.kt b/core/picklist/src/main/java/com/squirtles/picklist/PickListViewModel.kt new file mode 100644 index 00000000..c5ff9a59 --- /dev/null +++ b/core/picklist/src/main/java/com/squirtles/picklist/PickListViewModel.kt @@ -0,0 +1,101 @@ +package com.squirtles.picklist + +import android.util.Log +import androidx.lifecycle.ViewModel +import androidx.lifecycle.viewModelScope +import com.squirtles.model.Order +import com.squirtles.model.Pick +import com.squirtles.user.usecase.GetCurrentUserUseCase +import kotlinx.coroutines.async +import kotlinx.coroutines.awaitAll +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.asStateFlow +import kotlinx.coroutines.launch + +abstract class PickListViewModel( + val fetchPickListUseCase: FetchPickListUseCaseInterface, + val getPickListOrderUseCase: GetPickListOrderUseCaseInterface, + val savePickListOrderUseCase: SavePickListOrderUseCaseInterface, + val removePickUseCase: RemovePickUseCaseInterface, + val getCurrentUserUseCase: GetCurrentUserUseCase +) : ViewModel() { + + private var pickList: List = emptyList() + + private val _pickListUiState = MutableStateFlow(PickListUiState.Loading) + val pickListUiState = _pickListUiState.asStateFlow() + + private val _selectedPicksId = MutableStateFlow>(emptySet()) + val selectedPicksId = _selectedPicksId.asStateFlow() + + fun fetchPickList(userId: String) { + viewModelScope.launch { + fetchPickListUseCase(userId) + .onSuccess { picks -> + pickList = picks + sortPickList(getPickListOrderUseCase()) + } + .onFailure { + _pickListUiState.emit(PickListUiState.Error) + } + } + } + + private fun sortPickList(order: Order) { + _pickListUiState.value = PickListUiState.Success( + pickList = pickList.setOrderedList(order), + order = order + ) + } + + fun setListOrder(order: Order) { + viewModelScope.launch { + savePickListOrderUseCase(order) + sortPickList(order) + } + } + + fun toggleSelectedPick(pickId: String) { + val curSelectedPicksId = _selectedPicksId.value + _selectedPicksId.value = + if (curSelectedPicksId.contains(pickId)) curSelectedPicksId - pickId else curSelectedPicksId + pickId + } + + fun selectAllPicks() { + _selectedPicksId.value = pickList.map { it.id }.toSet() + } + + fun deselectAllPicks() { + _selectedPicksId.value = emptySet() + } + + fun deleteSelectedPicks(userId: String) { + viewModelScope.launch { + _pickListUiState.value = PickListUiState.Loading + + val deleteJobList = _selectedPicksId.value.map { pickId -> + async { removePickUseCase(pickId, userId) } + }.awaitAll() + + deselectAllPicks() + + if (deleteJobList.all { it.isSuccess }) { + fetchPickList(userId) + } else { + _pickListUiState.value = PickListUiState.Error + Log.e("PickListViewModel", "[픽 목록] 다중 삭제 오류") + } + } + } + + private fun List.setOrderedList(order: Order): List { + return if (pickList.isEmpty()) this + else when (order) { + Order.LATEST -> this + Order.OLDEST -> this.reversed() + Order.FAVORITE_DESC -> this.sortedByDescending { it.favoriteCount } + } + } + + fun getUserId() = getCurrentUserUseCase()?.userId +} diff --git a/core/picklist/src/main/java/com/squirtles/picklist/components/DeleteSelectedPickDialog.kt b/core/picklist/src/main/java/com/squirtles/picklist/components/DeleteSelectedPickDialog.kt new file mode 100644 index 00000000..7fba1a31 --- /dev/null +++ b/core/picklist/src/main/java/com/squirtles/picklist/components/DeleteSelectedPickDialog.kt @@ -0,0 +1,46 @@ +package com.squirtles.picklist.components + +import androidx.compose.runtime.Composable +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.text.font.FontWeight +import com.squirtles.common.ui.DialogTextButton +import com.squirtles.common.ui.HorizontalSpacer +import com.squirtles.common.ui.MessageAlertDialog +import com.squirtles.common.ui.theme.Primary +import com.squirtles.picklist.PickListType +import com.squirtles.picklist.R + +@Composable +internal fun DeleteSelectedPickDialog( + selectedPickCount: Int, + pickListType: PickListType, + onDismissRequest: () -> Unit, + onDeletePickClick: () -> Unit, +) { + MessageAlertDialog( + onDismissRequest = onDismissRequest, + title = stringResource(R.string.delete_pick_dialog_title), + body = stringResource( + when (pickListType) { + PickListType.FAVORITE -> R.string.delete_selected_favorite_pick_dialog_body + PickListType.CREATED -> R.string.delete_selected_pick_dialog_body + }, + selectedPickCount + ), + buttons = { + DialogTextButton( + onClick = onDismissRequest, + text = stringResource(R.string.delete_pick_dialog_cancel) + ) + + HorizontalSpacer(8) + + DialogTextButton( + onClick = onDeletePickClick, + text = stringResource(R.string.delete_pick_dialog_delete), + textColor = Primary, + fontWeight = FontWeight.Bold + ) + }, + ) +} diff --git a/core/picklist/src/main/java/com/squirtles/picklist/components/EditModeAction.kt b/core/picklist/src/main/java/com/squirtles/picklist/components/EditModeAction.kt new file mode 100644 index 00000000..53a0703c --- /dev/null +++ b/core/picklist/src/main/java/com/squirtles/picklist/components/EditModeAction.kt @@ -0,0 +1,61 @@ +package com.squirtles.picklist.components + +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.filled.Edit +import androidx.compose.material3.Icon +import androidx.compose.material3.IconButton +import androidx.compose.material3.IconButtonDefaults +import androidx.compose.material3.Text +import androidx.compose.material3.TextButton +import androidx.compose.runtime.Composable +import androidx.compose.ui.res.stringResource +import com.squirtles.common.ui.theme.Gray +import com.squirtles.common.ui.theme.White +import com.squirtles.picklist.R + + +@Composable +internal fun EditModeAction( + isEditMode: Boolean, + enabled: Boolean, + isSelectedEmpty: Boolean, + activateEditMode: () -> Unit, + selectAllPicks: () -> Unit, + deselectAllPicks: () -> Unit, +) { + if (isEditMode) { + if (isSelectedEmpty) { + TextButton( + onClick = selectAllPicks + ) { + Text( + text = stringResource(R.string.pick_list_select_all_button_text), + color = White, + ) + } + } else { + TextButton( + onClick = deselectAllPicks + ) { + Text( + text = stringResource(R.string.pick_list_deselect_all_button_text), + color = White, + ) + } + } + } else { + IconButton( + onClick = activateEditMode, + enabled = enabled, + colors = IconButtonDefaults.iconButtonColors().copy( + contentColor = White, + disabledContentColor = Gray, + ) + ) { + Icon( + imageVector = Icons.Default.Edit, + contentDescription = stringResource(R.string.pick_list_activate_edit_mode_button_description), + ) + } + } +} diff --git a/core/picklist/src/main/java/com/squirtles/picklist/components/EditModeBottomButton.kt b/core/picklist/src/main/java/com/squirtles/picklist/components/EditModeBottomButton.kt new file mode 100644 index 00000000..8def40cb --- /dev/null +++ b/core/picklist/src/main/java/com/squirtles/picklist/components/EditModeBottomButton.kt @@ -0,0 +1,96 @@ +package com.squirtles.picklist.components + +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.fillMaxHeight +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.height +import androidx.compose.material3.Button +import androidx.compose.material3.ButtonDefaults +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.graphics.RectangleShape +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.dp +import com.squirtles.common.ui.theme.DarkGray +import com.squirtles.common.ui.theme.Gray +import com.squirtles.common.ui.theme.MusicRoadTheme +import com.squirtles.common.ui.theme.Primary +import com.squirtles.common.ui.theme.White +import com.squirtles.picklist.R + +@Composable +internal fun EditModeBottomButton( + enabled: Boolean, + deactivateEditMode: () -> Unit, + showDeletePickDialog: () -> Unit, +) { + Row( + modifier = Modifier + .fillMaxWidth() + .height(ButtonHeight) + ) { + EditModeButton( + text = stringResource(R.string.pick_list_deactivate_edit_mode_button_text), + onClick = deactivateEditMode, + modifier = Modifier + .fillMaxHeight() + .weight(1f), + buttonColor = Gray + ) + + EditModeButton( + text = stringResource(R.string.pick_list_delete_selection_button_text), + onClick = showDeletePickDialog, + modifier = Modifier + .fillMaxHeight() + .weight(1f), + enabled = enabled + ) + } +} + +@Composable +private fun EditModeButton( + text: String, + onClick: () -> Unit, + modifier: Modifier, + enabled: Boolean = true, + textColor: Color = White, + buttonColor: Color = Primary, +) { + Button( + onClick = onClick, + modifier = modifier, + enabled = enabled, + shape = RectangleShape, + colors = ButtonDefaults.buttonColors().copy( + containerColor = buttonColor, + contentColor = textColor, + disabledContainerColor = DarkGray, + disabledContentColor = White + ) + ) { + Text( + text = text, + style = MaterialTheme.typography.bodyLarge + ) + } +} + +@Preview +@Composable +private fun EditModeBottomButtonPreview() { + MusicRoadTheme { + EditModeBottomButton( + enabled = true, + deactivateEditMode = {}, + showDeletePickDialog = {}, + ) + } +} + +private val ButtonHeight = 48.dp diff --git a/core/picklist/src/main/java/com/squirtles/picklist/OrderBottomSheet.kt b/core/picklist/src/main/java/com/squirtles/picklist/components/OrderBottomSheet.kt similarity index 96% rename from core/picklist/src/main/java/com/squirtles/picklist/OrderBottomSheet.kt rename to core/picklist/src/main/java/com/squirtles/picklist/components/OrderBottomSheet.kt index 735f90ab..35969015 100644 --- a/core/picklist/src/main/java/com/squirtles/picklist/OrderBottomSheet.kt +++ b/core/picklist/src/main/java/com/squirtles/picklist/components/OrderBottomSheet.kt @@ -1,4 +1,4 @@ -package com.squirtles.picklist +package com.squirtles.picklist.components import androidx.compose.foundation.clickable import androidx.compose.foundation.layout.Arrangement @@ -25,11 +25,12 @@ import androidx.compose.ui.Modifier import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.text.style.TextAlign -import com.squirtles.common.ui.Constants +import com.squirtles.common.ui.Constants.DEFAULT_PADDING import com.squirtles.common.ui.theme.Dark import com.squirtles.common.ui.theme.Primary import com.squirtles.common.ui.theme.White import com.squirtles.model.Order +import com.squirtles.picklist.R import kotlinx.coroutines.launch @OptIn(ExperimentalMaterial3Api::class) @@ -111,7 +112,7 @@ private fun BottomSheetMenu( modifier = Modifier .fillMaxWidth() .clickable { onClick() } - .padding(Constants.DEFAULT_PADDING), + .padding(DEFAULT_PADDING), horizontalArrangement = Arrangement.SpaceBetween, verticalAlignment = Alignment.CenterVertically ) { diff --git a/core/picklist/src/main/java/com/squirtles/picklist/PickListItem.kt b/core/picklist/src/main/java/com/squirtles/picklist/components/PickItem.kt similarity index 75% rename from core/picklist/src/main/java/com/squirtles/picklist/PickListItem.kt rename to core/picklist/src/main/java/com/squirtles/picklist/components/PickItem.kt index 345b1703..ec7f23a8 100644 --- a/core/picklist/src/main/java/com/squirtles/picklist/PickListItem.kt +++ b/core/picklist/src/main/java/com/squirtles/picklist/components/PickItem.kt @@ -1,5 +1,6 @@ -package com.squirtles.picklist +package com.squirtles.picklist.components +import androidx.compose.foundation.background import androidx.compose.foundation.clickable import androidx.compose.foundation.interaction.MutableInteractionSource import androidx.compose.foundation.layout.Arrangement @@ -9,6 +10,9 @@ import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.size import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.outlined.CheckCircle +import androidx.compose.material3.Icon import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Text import androidx.compose.material3.ripple @@ -17,6 +21,8 @@ import androidx.compose.runtime.remember import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clip +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.unit.dp import com.squirtles.common.ui.AlbumImage @@ -27,13 +33,17 @@ import com.squirtles.common.ui.FavoriteCountText import com.squirtles.common.ui.HorizontalSpacer import com.squirtles.common.ui.SongInfoText import com.squirtles.common.ui.theme.Gray +import com.squirtles.common.ui.theme.Primary import com.squirtles.common.ui.theme.White import com.squirtles.model.Song +import com.squirtles.picklist.R @Composable -fun PickListItem( +internal fun PickItem( + isEditMode: Boolean, + isSelected: Boolean, song: Song, - isCreatedByOthers: Boolean, + createdByOthers: Boolean, createUserName: String, favoriteCount: Int, comment: String, @@ -43,6 +53,7 @@ fun PickListItem( Row( modifier = Modifier .fillMaxWidth() + .background(color = if (isSelected) White.copy(alpha = 0.2F) else Color.Transparent) .clickable( interactionSource = remember { MutableInteractionSource() }, indication = ripple(color = White), @@ -55,7 +66,10 @@ fun PickListItem( verticalAlignment = Alignment.CenterVertically ) { AlbumImage( - imageUrl = song.getImageUrlWithSize(Constants.REQUEST_IMAGE_SIZE_DEFAULT.width, Constants.REQUEST_IMAGE_SIZE_DEFAULT.height), + imageUrl = song.getImageUrlWithSize( + Constants.REQUEST_IMAGE_SIZE_DEFAULT.width, + Constants.REQUEST_IMAGE_SIZE_DEFAULT.height + ), modifier = Modifier .size(64.dp) .clip(RoundedCornerShape(8.dp)) @@ -72,7 +86,7 @@ fun PickListItem( Row( verticalAlignment = Alignment.CenterVertically ) { - if (isCreatedByOthers) { + if (createdByOthers) { CreatedByOtherUserText( userName = createUserName, modifier = Modifier.weight(weight = 1f, fill = false), @@ -103,5 +117,13 @@ fun PickListItem( color = Gray ) } + + if (isEditMode) { + Icon( + imageVector = Icons.Outlined.CheckCircle, + contentDescription = stringResource(R.string.outlined_check_circle_icon_description), + tint = if (isSelected) Primary else Gray + ) + } } } diff --git a/core/picklist/src/main/res/values/strings.xml b/core/picklist/src/main/res/values/strings.xml index 267dec2a..81080ca5 100644 --- a/core/picklist/src/main/res/values/strings.xml +++ b/core/picklist/src/main/res/values/strings.xml @@ -13,4 +13,24 @@ 담은 픽이 없습니다 등록한 픽이 없습니다 일시적인 오류가 발생했습니다. + + + 삭제하시겠습니까? + 등록하신 픽이 삭제됩니다. + 취소 + 삭제하기 + 픽 보관함에서 %d개의 픽이 삭제됩니다. + 선택하신 %d개의 픽이 삭제됩니다. + + + 취소 + 선택 삭제 + 원 윤곽선이 있는 체크 아이콘 + 전체 + 선택 + + + 픽 목록 편집 모드 활성화 버튼 + 전체 선택 + 선택 해제 diff --git a/domain/pick/src/main/java/com/squirtles/pick/usecase/FetchFavoritePicksUseCase.kt b/domain/pick/src/main/java/com/squirtles/pick/usecase/FetchFavoritePicksUseCase.kt new file mode 100644 index 00000000..1e2ca29b --- /dev/null +++ b/domain/pick/src/main/java/com/squirtles/pick/usecase/FetchFavoritePicksUseCase.kt @@ -0,0 +1,13 @@ +package com.squirtles.pick.usecase + +import com.squirtles.model.Pick +import com.squirtles.pick.FirebasePickRepository +import com.squirtles.picklist.FetchPickListUseCaseInterface +import javax.inject.Inject + +class FetchFavoritePicksUseCase @Inject constructor( + private val pickRepository: FirebasePickRepository +) : FetchPickListUseCaseInterface { + override suspend operator fun invoke(userId: String): Result> = + pickRepository.fetchFavoritePicks(userId) +} diff --git a/feature/favorite/.gitignore b/feature/favorite/.gitignore new file mode 100644 index 00000000..42afabfd --- /dev/null +++ b/feature/favorite/.gitignore @@ -0,0 +1 @@ +/build \ No newline at end of file diff --git a/feature/favorite/build.gradle.kts b/feature/favorite/build.gradle.kts new file mode 100644 index 00000000..dda46676 --- /dev/null +++ b/feature/favorite/build.gradle.kts @@ -0,0 +1,20 @@ +plugins { + alias(libs.plugins.musicroad.feature) +} + +android { + namespace = "com.squirtles.favorite" +} + +dependencies { + implementation(projects.core.common) + implementation(projects.core.picklist) + implementation(projects.domain.picklist) + implementation(projects.domain.favorite) + implementation(projects.domain.pick) + implementation(projects.domain.order) + implementation(projects.domain.user) + + testImplementation(libs.junit) + androidTestImplementation(libs.bundles.test) +} diff --git a/feature/favorite/consumer-rules.pro b/feature/favorite/consumer-rules.pro new file mode 100644 index 00000000..e69de29b diff --git a/feature/favorite/src/androidTest/java/com/squirtles/favorite/ExampleInstrumentedTest.kt b/feature/favorite/src/androidTest/java/com/squirtles/favorite/ExampleInstrumentedTest.kt new file mode 100644 index 00000000..e488b426 --- /dev/null +++ b/feature/favorite/src/androidTest/java/com/squirtles/favorite/ExampleInstrumentedTest.kt @@ -0,0 +1,24 @@ +package com.squirtles.favorite + +import androidx.test.platform.app.InstrumentationRegistry +import androidx.test.ext.junit.runners.AndroidJUnit4 + +import org.junit.Test +import org.junit.runner.RunWith + +import org.junit.Assert.* + +/** + * Instrumented test, which will execute on an Android device. + * + * See [testing documentation](http://d.android.com/tools/testing). + */ +@RunWith(AndroidJUnit4::class) +class ExampleInstrumentedTest { + @Test + fun useAppContext() { + // Context of the app under test. + val appContext = InstrumentationRegistry.getInstrumentation().targetContext + assertEquals("com.squirtles.favorite.test", appContext.packageName) + } +} diff --git a/feature/favorite/src/main/java/com/squirtles/favorite/FavoriteListViewModel.kt b/feature/favorite/src/main/java/com/squirtles/favorite/FavoriteListViewModel.kt new file mode 100644 index 00000000..ec5a6159 --- /dev/null +++ b/feature/favorite/src/main/java/com/squirtles/favorite/FavoriteListViewModel.kt @@ -0,0 +1,25 @@ +package com.squirtles.favorite + +import com.squirtles.favorite.usecase.DeleteFavoriteUseCase +import com.squirtles.order.usecase.GetFavoriteListOrderUseCase +import com.squirtles.order.usecase.SaveFavoriteListOrderUseCase +import com.squirtles.pick.usecase.FetchFavoritePicksUseCase +import com.squirtles.picklist.PickListViewModel +import com.squirtles.user.usecase.GetCurrentUserUseCase +import dagger.hilt.android.lifecycle.HiltViewModel +import javax.inject.Inject + +@HiltViewModel +class FavoriteListViewModel @Inject constructor( + fetchFavoritePicksUseCase: FetchFavoritePicksUseCase, + getFavoriteListOrderUseCase: GetFavoriteListOrderUseCase, + saveFavoriteListOrderUseCase: SaveFavoriteListOrderUseCase, + deleteFavoriteUseCase: DeleteFavoriteUseCase, + getCurrentUserUseCase: GetCurrentUserUseCase +) : PickListViewModel( + fetchPickListUseCase = fetchFavoritePicksUseCase, + getPickListOrderUseCase = getFavoriteListOrderUseCase, + savePickListOrderUseCase = saveFavoriteListOrderUseCase, + removePickUseCase = deleteFavoriteUseCase, + getCurrentUserUseCase = getCurrentUserUseCase +) diff --git a/feature/favorite/src/main/java/com/squirtles/favorite/FavoriteScreen.kt b/feature/favorite/src/main/java/com/squirtles/favorite/FavoriteScreen.kt new file mode 100644 index 00000000..1ac6245c --- /dev/null +++ b/feature/favorite/src/main/java/com/squirtles/favorite/FavoriteScreen.kt @@ -0,0 +1,47 @@ +package com.squirtles.favorite + +import androidx.compose.runtime.Composable +import androidx.compose.runtime.LaunchedEffect +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.saveable.rememberSaveable +import androidx.compose.runtime.setValue +import androidx.hilt.navigation.compose.hiltViewModel +import androidx.lifecycle.compose.collectAsStateWithLifecycle +import com.squirtles.picklist.PickListScreenContents +import com.squirtles.picklist.PickListType + +@Composable +fun FavoriteScreen( + userId: String, + onBackClick: () -> Unit, + onItemClick: (String) -> Unit, + favoriteListViewModel: FavoriteListViewModel = hiltViewModel() +) { + val uiState by favoriteListViewModel.pickListUiState.collectAsStateWithLifecycle() + val selectedPicksId by favoriteListViewModel.selectedPicksId.collectAsStateWithLifecycle() + var showOrderBottomSheet by rememberSaveable { mutableStateOf(false) } + + LaunchedEffect(Unit) { + favoriteListViewModel.fetchPickList(userId) + } + + PickListScreenContents( + userId = userId, + showOrderBottomSheet = showOrderBottomSheet, + selectedPicksId = selectedPicksId, + pickListType = PickListType.FAVORITE, + uiState = uiState, + onBackClick = onBackClick, + onItemClick = onItemClick, + setListOrder = favoriteListViewModel::setListOrder, + setOrderBottomSheetVisibility = { showOrderBottomSheet = it }, + selectAllPicks = favoriteListViewModel::selectAllPicks, + deselectAllPicks = favoriteListViewModel::deselectAllPicks, + toggleSelectedPick = favoriteListViewModel::toggleSelectedPick, + deleteSelectedPicks = favoriteListViewModel::deleteSelectedPicks, + getUserId = { + favoriteListViewModel.getUserId().toString() + } + ) +} diff --git a/feature/favorite/src/main/java/com/squirtles/favorite/navigation/FavoriteNavigation.kt b/feature/favorite/src/main/java/com/squirtles/favorite/navigation/FavoriteNavigation.kt new file mode 100644 index 00000000..ea575d87 --- /dev/null +++ b/feature/favorite/src/main/java/com/squirtles/favorite/navigation/FavoriteNavigation.kt @@ -0,0 +1,28 @@ +package com.squirtles.favorite.navigation + +import androidx.navigation.NavController +import androidx.navigation.NavGraphBuilder +import androidx.navigation.NavOptions +import androidx.navigation.compose.composable +import androidx.navigation.toRoute +import com.squirtles.favorite.FavoriteScreen +import com.squirtles.navigation.MainRoute + +fun NavController.navigateFavorite(userId: String, navOptions: NavOptions? = null) { + navigate(MainRoute.Favorite(userId), navOptions) +} + +fun NavGraphBuilder.favoriteNavGraph( + onBackClick: () -> Unit, + onItemClick: (String) -> Unit, +) { + composable { backStackEntry -> + val userId = backStackEntry.toRoute().userId + + FavoriteScreen( + userId = userId, + onBackClick = onBackClick, + onItemClick = onItemClick, + ) + } +} diff --git a/feature/favorite/src/test/java/com/squirtles/favorite/ExampleUnitTest.kt b/feature/favorite/src/test/java/com/squirtles/favorite/ExampleUnitTest.kt new file mode 100644 index 00000000..2e5010ef --- /dev/null +++ b/feature/favorite/src/test/java/com/squirtles/favorite/ExampleUnitTest.kt @@ -0,0 +1,17 @@ +package com.squirtles.favorite + +import org.junit.Test + +import org.junit.Assert.* + +/** + * Example local unit test, which will execute on the development machine (host). + * + * See [testing documentation](http://d.android.com/tools/testing). + */ +class ExampleUnitTest { + @Test + fun addition_isCorrect() { + assertEquals(4, 2 + 2) + } +} From 04135bd4b55e8a0d8f7302d46b45a1ffb7065e88 Mon Sep 17 00:00:00 2001 From: miller198 Date: Sat, 8 Mar 2025 00:50:15 +0900 Subject: [PATCH 36/62] =?UTF-8?q?[fix]=20merge=20=ED=9B=84=20=EC=A0=95?= =?UTF-8?q?=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../musicroad/account/AccountViewModel.kt | 7 ++- .../common/picklist/PickListViewModel.kt | 2 +- .../musicroad/create/CreatePickViewModel.kt | 8 +-- .../musicroad/detail/DetailViewModel.kt | 2 +- .../favorite/FavoriteListViewModel.kt | 5 +- .../squirtles/musicroad/main/MainViewModel.kt | 6 +-- .../squirtles/musicroad/map/MapViewModel.kt | 4 +- .../musicroad/mypick/MyPickListViewModel.kt | 2 +- .../musicroad/userinfo/UserInfoViewModel.kt | 2 +- .../FirebaseFavoriteDataSourceImpl.kt | 2 +- .../data/pick/FirebasePickDataSourceImpl.kt | 2 +- .../data/user/FirebaseUserDataSourceImpl.kt | 27 ++++++---- .../data/user/FirebaseUserRepositoryImpl.kt | 11 +++- .../data/user/LocalUserDataSourceImpl.kt | 2 +- .../data/user/LocalUserRepositoryImpl.kt | 4 +- .../java/com/squirtles/data/user/di/UserDi.kt | 2 +- .../squirtles/data/user/model/FirebaseUser.kt | 3 +- .../com/squirtles/data/user/model/Mapper.kt | 3 +- .../domain/pick/FirebasePickDataSource.kt | 2 +- .../domain/user/FirebaseUserDataSource.kt | 7 +-- .../domain/user/FirebaseUserRepository.kt | 3 +- .../domain/user/LocalUserDataSource.kt | 4 +- .../user/usecase/CreateGoogleIdUserUseCase.kt | 18 +++---- .../user/usecase/DeleteAccountUseCase.kt | 51 +++++++++++++++++++ .../domain/user/usecase/FetchUserUseCase.kt | 2 +- .../user/usecase/GetCurrentUidUseCase.kt | 8 +++ .../domain/user/usecase/SignOutUseCase.kt | 8 +++ 27 files changed, 136 insertions(+), 61 deletions(-) create mode 100644 domain/src/main/java/com/squirtles/domain/user/usecase/DeleteAccountUseCase.kt create mode 100644 domain/src/main/java/com/squirtles/domain/user/usecase/GetCurrentUidUseCase.kt create mode 100644 domain/src/main/java/com/squirtles/domain/user/usecase/SignOutUseCase.kt diff --git a/app/src/main/java/com/squirtles/musicroad/account/AccountViewModel.kt b/app/src/main/java/com/squirtles/musicroad/account/AccountViewModel.kt index 8f786e7c..e3c29775 100644 --- a/app/src/main/java/com/squirtles/musicroad/account/AccountViewModel.kt +++ b/app/src/main/java/com/squirtles/musicroad/account/AccountViewModel.kt @@ -4,11 +4,10 @@ import android.util.Log import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope import com.google.android.libraries.identity.googleid.GoogleIdTokenCredential -import com.squirtles.domain.user.usecase.ClearUserUseCase import com.squirtles.domain.user.usecase.CreateGoogleIdUserUseCase -import com.squirtles.domain.usecase.user.DeleteAccountUseCase -import com.squirtles.domain.usecase.user.FetchUserByIdUseCase -import com.squirtles.domain.usecase.user.SignOutUseCase +import com.squirtles.domain.user.usecase.DeleteAccountUseCase +import com.squirtles.domain.user.usecase.SignOutUseCase +import com.squirtles.domain.user.usecase.FetchUserByIdUseCase import dagger.hilt.android.lifecycle.HiltViewModel import kotlinx.coroutines.flow.MutableSharedFlow import kotlinx.coroutines.flow.asSharedFlow diff --git a/app/src/main/java/com/squirtles/musicroad/common/picklist/PickListViewModel.kt b/app/src/main/java/com/squirtles/musicroad/common/picklist/PickListViewModel.kt index 359ca5c5..aff24b4c 100644 --- a/app/src/main/java/com/squirtles/musicroad/common/picklist/PickListViewModel.kt +++ b/app/src/main/java/com/squirtles/musicroad/common/picklist/PickListViewModel.kt @@ -9,7 +9,7 @@ import com.squirtles.domain.picklist.FetchPickListUseCaseInterface import com.squirtles.domain.picklist.GetPickListOrderUseCaseInterface import com.squirtles.domain.picklist.RemovePickUseCaseInterface import com.squirtles.domain.picklist.SavePickListOrderUseCaseInterface -import com.squirtles.domain.usecase.user.GetCurrentUidUseCase +import com.squirtles.domain.user.usecase.GetCurrentUidUseCase import kotlinx.coroutines.async import kotlinx.coroutines.awaitAll import kotlinx.coroutines.flow.MutableStateFlow diff --git a/app/src/main/java/com/squirtles/musicroad/create/CreatePickViewModel.kt b/app/src/main/java/com/squirtles/musicroad/create/CreatePickViewModel.kt index 71f2a4b9..73a5c74f 100644 --- a/app/src/main/java/com/squirtles/musicroad/create/CreatePickViewModel.kt +++ b/app/src/main/java/com/squirtles/musicroad/create/CreatePickViewModel.kt @@ -6,15 +6,15 @@ import androidx.lifecycle.SavedStateHandle import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope import androidx.navigation.toRoute +import com.squirtles.domain.applemusic.usecase.FetchMusicVideoUseCase +import com.squirtles.domain.location.usecase.GetLastLocationUseCase import com.squirtles.domain.model.Creator import com.squirtles.domain.model.LocationPoint import com.squirtles.domain.model.Pick import com.squirtles.domain.model.Song -import com.squirtles.domain.location.usecase.GetLastLocationUseCase -import com.squirtles.domain.applemusic.usecase.FetchMusicVideoUseCase import com.squirtles.domain.pick.usecase.CreatePickUseCase -import com.squirtles.domain.user.usecase.GetCurrentUserUseCase -import com.squirtles.domain.usecase.user.GetCurrentUidUseCase +import com.squirtles.domain.user.usecase.FetchUserByIdUseCase +import com.squirtles.domain.user.usecase.GetCurrentUidUseCase import com.squirtles.musicroad.navigation.SearchRoute import com.squirtles.musicroad.utils.throttleFirst import dagger.hilt.android.lifecycle.HiltViewModel diff --git a/app/src/main/java/com/squirtles/musicroad/detail/DetailViewModel.kt b/app/src/main/java/com/squirtles/musicroad/detail/DetailViewModel.kt index 9f030335..5164691e 100644 --- a/app/src/main/java/com/squirtles/musicroad/detail/DetailViewModel.kt +++ b/app/src/main/java/com/squirtles/musicroad/detail/DetailViewModel.kt @@ -13,7 +13,7 @@ import com.squirtles.domain.model.Pick import com.squirtles.domain.model.Song import com.squirtles.domain.pick.usecase.DeletePickUseCase import com.squirtles.domain.pick.usecase.FetchPickUseCase -import com.squirtles.domain.usecase.user.GetCurrentUidUseCase +import com.squirtles.domain.user.usecase.GetCurrentUidUseCase import com.squirtles.musicroad.utils.throttleFirst import dagger.hilt.android.lifecycle.HiltViewModel import kotlinx.coroutines.async diff --git a/app/src/main/java/com/squirtles/musicroad/favorite/FavoriteListViewModel.kt b/app/src/main/java/com/squirtles/musicroad/favorite/FavoriteListViewModel.kt index 78ffe314..d8c02343 100644 --- a/app/src/main/java/com/squirtles/musicroad/favorite/FavoriteListViewModel.kt +++ b/app/src/main/java/com/squirtles/musicroad/favorite/FavoriteListViewModel.kt @@ -1,11 +1,10 @@ package com.squirtles.musicroad.favorite import com.squirtles.domain.favorite.usecase.DeleteFavoriteUseCase -import com.squirtles.domain.pick.usecase.FetchFavoritePicksUseCase import com.squirtles.domain.order.usecase.GetFavoriteListOrderUseCase import com.squirtles.domain.order.usecase.SaveFavoriteListOrderUseCase -import com.squirtles.domain.user.usecase.GetCurrentUserUseCase -import com.squirtles.domain.usecase.user.GetCurrentUidUseCase +import com.squirtles.domain.pick.usecase.FetchFavoritePicksUseCase +import com.squirtles.domain.user.usecase.GetCurrentUidUseCase import com.squirtles.musicroad.common.picklist.PickListViewModel import dagger.hilt.android.lifecycle.HiltViewModel import javax.inject.Inject diff --git a/app/src/main/java/com/squirtles/musicroad/main/MainViewModel.kt b/app/src/main/java/com/squirtles/musicroad/main/MainViewModel.kt index ef0ae455..6330cfc5 100644 --- a/app/src/main/java/com/squirtles/musicroad/main/MainViewModel.kt +++ b/app/src/main/java/com/squirtles/musicroad/main/MainViewModel.kt @@ -4,10 +4,8 @@ import android.util.Log import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope import com.squirtles.domain.firebase.FirebaseException -import com.squirtles.domain.user.usecase.FetchUserUseCase -import com.squirtles.domain.user.usecase.GetUserIdFromDataStoreUseCase -import com.squirtles.domain.usecase.user.FetchUserByIdUseCase -import com.squirtles.domain.usecase.user.GetCurrentUidUseCase +import com.squirtles.domain.user.usecase.FetchUserByIdUseCase +import com.squirtles.domain.user.usecase.GetCurrentUidUseCase import dagger.hilt.android.lifecycle.HiltViewModel import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.asStateFlow diff --git a/app/src/main/java/com/squirtles/musicroad/map/MapViewModel.kt b/app/src/main/java/com/squirtles/musicroad/map/MapViewModel.kt index 150d14fb..80d9a3a6 100644 --- a/app/src/main/java/com/squirtles/musicroad/map/MapViewModel.kt +++ b/app/src/main/java/com/squirtles/musicroad/map/MapViewModel.kt @@ -9,11 +9,11 @@ import com.naver.maps.geometry.LatLng import com.naver.maps.map.CameraPosition import com.naver.maps.map.clustering.Clusterer import com.naver.maps.map.overlay.Marker -import com.squirtles.domain.model.Pick import com.squirtles.domain.location.usecase.GetLastLocationUseCase import com.squirtles.domain.location.usecase.SaveLastLocationUseCase +import com.squirtles.domain.model.Pick import com.squirtles.domain.pick.usecase.FetchPickUseCase -import com.squirtles.domain.usecase.user.GetCurrentUidUseCase +import com.squirtles.domain.user.usecase.GetCurrentUidUseCase import com.squirtles.musicroad.map.marker.MarkerKey import dagger.hilt.android.lifecycle.HiltViewModel import kotlinx.coroutines.flow.MutableStateFlow diff --git a/app/src/main/java/com/squirtles/musicroad/mypick/MyPickListViewModel.kt b/app/src/main/java/com/squirtles/musicroad/mypick/MyPickListViewModel.kt index 3667bdee..aeee6a1a 100644 --- a/app/src/main/java/com/squirtles/musicroad/mypick/MyPickListViewModel.kt +++ b/app/src/main/java/com/squirtles/musicroad/mypick/MyPickListViewModel.kt @@ -4,7 +4,7 @@ import com.squirtles.domain.order.usecase.GetMyPickListOrderUseCase import com.squirtles.domain.order.usecase.SaveMyPickListOrderUseCase import com.squirtles.domain.pick.usecase.DeletePickUseCase import com.squirtles.domain.pick.usecase.FetchMyPicksUseCase -import com.squirtles.domain.usecase.user.GetCurrentUidUseCase +import com.squirtles.domain.user.usecase.GetCurrentUidUseCase import com.squirtles.musicroad.common.picklist.PickListViewModel import dagger.hilt.android.lifecycle.HiltViewModel import javax.inject.Inject diff --git a/app/src/main/java/com/squirtles/musicroad/userinfo/UserInfoViewModel.kt b/app/src/main/java/com/squirtles/musicroad/userinfo/UserInfoViewModel.kt index f9444bb7..c37719bc 100644 --- a/app/src/main/java/com/squirtles/musicroad/userinfo/UserInfoViewModel.kt +++ b/app/src/main/java/com/squirtles/musicroad/userinfo/UserInfoViewModel.kt @@ -4,7 +4,7 @@ import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope import com.squirtles.domain.model.User import com.squirtles.domain.user.usecase.FetchUserByIdUseCase -import com.squirtles.domain.usecase.user.GetCurrentUidUseCase +import com.squirtles.domain.user.usecase.GetCurrentUidUseCase import com.squirtles.domain.user.usecase.UpdateUserNameUseCase import dagger.hilt.android.lifecycle.HiltViewModel import kotlinx.coroutines.flow.MutableSharedFlow diff --git a/data/favorite/src/main/java/com/squirtles/favorite/FirebaseFavoriteDataSourceImpl.kt b/data/favorite/src/main/java/com/squirtles/favorite/FirebaseFavoriteDataSourceImpl.kt index 45a41d0e..2f0ab501 100644 --- a/data/favorite/src/main/java/com/squirtles/favorite/FirebaseFavoriteDataSourceImpl.kt +++ b/data/favorite/src/main/java/com/squirtles/favorite/FirebaseFavoriteDataSourceImpl.kt @@ -26,7 +26,7 @@ class FirebaseFavoriteDataSourceImpl @Inject constructor( override suspend fun createFavorite(pickId: String, userId: String): Result { val firebaseFavorite = FirebaseFavorite( pickId = pickId, - userId = userId + uid = userId ) return runCatching { val addResult = addDocument(FirebaseCollections.Favorites, firebaseFavorite) diff --git a/data/src/main/java/com/squirtles/data/pick/FirebasePickDataSourceImpl.kt b/data/src/main/java/com/squirtles/data/pick/FirebasePickDataSourceImpl.kt index 931554f0..204ecb34 100644 --- a/data/src/main/java/com/squirtles/data/pick/FirebasePickDataSourceImpl.kt +++ b/data/src/main/java/com/squirtles/data/pick/FirebasePickDataSourceImpl.kt @@ -82,7 +82,7 @@ class FirebasePickDataSourceImpl @Inject constructor( val firebasePick = pick.toFirebasePick() return runCatching { val pickRef = addDocument(FirebaseCollections.Picks, firebasePick).getOrThrow() - updateCurrentUserPick(pick.createdBy.userId, pickRef.id) + updateCurrentUserPick(pick.createdBy.uid, pickRef.id) pickRef.id }.onFailure { Log.e(TAG_LOG, "Failed to create a pick", it) diff --git a/data/src/main/java/com/squirtles/data/user/FirebaseUserDataSourceImpl.kt b/data/src/main/java/com/squirtles/data/user/FirebaseUserDataSourceImpl.kt index 31b8a778..6b107c57 100644 --- a/data/src/main/java/com/squirtles/data/user/FirebaseUserDataSourceImpl.kt +++ b/data/src/main/java/com/squirtles/data/user/FirebaseUserDataSourceImpl.kt @@ -18,17 +18,15 @@ class FirebaseUserDataSourceImpl @Inject constructor( private val db: FirebaseFirestore ): FirebaseUserDataSource { - override suspend fun createGoogleIdUser(userId: String, userName: String?, userProfileImage: String?): User? { + override suspend fun createGoogleIdUser(uid: String, email: String, userName: String?, userProfileImage: String?): User? { return suspendCancellableCoroutine { continuation -> - val documentReference = db.collection("users").document(userId) - documentReference.set(FirebaseUser(name = userName, profileImage = userProfileImage)) + val documentReference = db.collection("users").document(uid) + documentReference.set(FirebaseUser(email = email, name = userName, profileImage = userProfileImage)) .addOnSuccessListener { documentReference.get() .addOnSuccessListener { documentSnapshot -> val savedUser = documentSnapshot.toObject() - continuation.resume( - savedUser?.toUser()?.copy(userId = documentReference.id) - ) + continuation.resume(savedUser?.toUser()?.copy(uid = documentReference.id)) } .addOnFailureListener { exception -> continuation.resumeWithException(exception) @@ -41,12 +39,12 @@ class FirebaseUserDataSourceImpl @Inject constructor( } } - override suspend fun fetchUser(userId: String): User? { + override suspend fun fetchUser(uid: String): User? { return suspendCancellableCoroutine { continuation -> - db.collection("users").document(userId).get() + db.collection("users").document(uid).get() .addOnSuccessListener { document -> val firebaseUser = document.toObject() - continuation.resume(firebaseUser?.toUser()?.copy(userId = userId)) + continuation.resume(firebaseUser?.toUser()?.copy(uid = uid)) } .addOnFailureListener { exception -> continuation.resumeWithException(exception) @@ -54,10 +52,10 @@ class FirebaseUserDataSourceImpl @Inject constructor( } } - override suspend fun updateUserName(userId: String, newUserName: String): Boolean { + override suspend fun updateUserName(uid: String, newUserName: String): Boolean { return suspendCancellableCoroutine { continuation -> db.runTransaction { transaction -> - val userRef = db.collection("users").document(userId) + val userRef = db.collection("users").document(uid) val userDocument = transaction.get(userRef) transaction.update(userRef, "name", newUserName) @@ -74,4 +72,11 @@ class FirebaseUserDataSourceImpl @Inject constructor( } } + override suspend fun deleteUser(uid: String): Boolean { + return suspendCancellableCoroutine { continuation -> + db.collection("users").document(uid).delete() + .addOnSuccessListener { continuation.resume(true) } + .addOnFailureListener { exception -> continuation.resumeWithException(exception) } + } + } } diff --git a/data/src/main/java/com/squirtles/data/user/FirebaseUserRepositoryImpl.kt b/data/src/main/java/com/squirtles/data/user/FirebaseUserRepositoryImpl.kt index d096fbca..a9f41847 100644 --- a/data/src/main/java/com/squirtles/data/user/FirebaseUserRepositoryImpl.kt +++ b/data/src/main/java/com/squirtles/data/user/FirebaseUserRepositoryImpl.kt @@ -12,12 +12,13 @@ class FirebaseUserRepositoryImpl( ) : FirebaseUserRepository { override suspend fun createGoogleIdUser( - userId: String, + uid: String, + email: String, userName: String?, userProfileImage: String? ): Result { return handleResult(FirebaseException.CreatedUserFailedException()) { - userDataSource.createGoogleIdUser(userId, userName, userProfileImage) + userDataSource.createGoogleIdUser(uid, email, userName, userProfileImage) } } @@ -32,4 +33,10 @@ class FirebaseUserRepositoryImpl( userDataSource.updateUserName(userId, newUserName) } } + + override suspend fun deleteUser(uid: String): Result { + return handleResult(FirebaseException.UserNotFoundException()) { + userDataSource.deleteUser(uid) + } + } } diff --git a/data/src/main/java/com/squirtles/data/user/LocalUserDataSourceImpl.kt b/data/src/main/java/com/squirtles/data/user/LocalUserDataSourceImpl.kt index 77624447..c77e67fa 100644 --- a/data/src/main/java/com/squirtles/data/user/LocalUserDataSourceImpl.kt +++ b/data/src/main/java/com/squirtles/data/user/LocalUserDataSourceImpl.kt @@ -25,7 +25,7 @@ class LocalUserDataSourceImpl @Inject constructor( override val currentUser: User? get() = _currentUser - override fun readUserIdDataStore(): Flow { + override fun readUidDataStore(): Flow { val dataStoreKey = stringPreferencesKey(USER_ID_KEY) return context.dataStore.data.map { preferences -> preferences[dataStoreKey] diff --git a/data/src/main/java/com/squirtles/data/user/LocalUserRepositoryImpl.kt b/data/src/main/java/com/squirtles/data/user/LocalUserRepositoryImpl.kt index 49a96707..ced59f96 100644 --- a/data/src/main/java/com/squirtles/data/user/LocalUserRepositoryImpl.kt +++ b/data/src/main/java/com/squirtles/data/user/LocalUserRepositoryImpl.kt @@ -14,11 +14,11 @@ class LocalUserRepositoryImpl @Inject constructor( override val currentUser get() = userDataSource.currentUser override fun readUserIdDataStore(): Flow { - return userDataSource.readUserIdDataStore() + return userDataSource.readUidDataStore() } override suspend fun saveUserIdDataStore(userId: String) { - userDataSource.saveUserIdDataStore(userId) + userDataSource.saveUidDataStore(userId) } override suspend fun saveCurrentUser(user: User) { diff --git a/data/src/main/java/com/squirtles/data/user/di/UserDi.kt b/data/src/main/java/com/squirtles/data/user/di/UserDi.kt index 89f6a080..9c01ffee 100644 --- a/data/src/main/java/com/squirtles/data/user/di/UserDi.kt +++ b/data/src/main/java/com/squirtles/data/user/di/UserDi.kt @@ -2,9 +2,9 @@ package com.squirtles.data.user.di import android.content.Context import com.google.firebase.firestore.FirebaseFirestore +import com.squirtles.data.datasource.local.LocalUserDataSourceImpl import com.squirtles.data.user.FirebaseUserDataSourceImpl import com.squirtles.data.user.FirebaseUserRepositoryImpl -import com.squirtles.data.user.LocalUserDataSourceImpl import com.squirtles.data.user.LocalUserRepositoryImpl import com.squirtles.domain.user.FirebaseUserDataSource import com.squirtles.domain.user.FirebaseUserRepository diff --git a/data/src/main/java/com/squirtles/data/user/model/FirebaseUser.kt b/data/src/main/java/com/squirtles/data/user/model/FirebaseUser.kt index ff351294..b40c2b92 100644 --- a/data/src/main/java/com/squirtles/data/user/model/FirebaseUser.kt +++ b/data/src/main/java/com/squirtles/data/user/model/FirebaseUser.kt @@ -1,8 +1,7 @@ package com.squirtles.data.user.model -import com.squirtles.domain.model.User - data class FirebaseUser( + val email: String? = null, val name: String? = null, val profileImage: String? = null, val myPicks: List = emptyList() diff --git a/data/src/main/java/com/squirtles/data/user/model/Mapper.kt b/data/src/main/java/com/squirtles/data/user/model/Mapper.kt index 18267513..0edfc477 100644 --- a/data/src/main/java/com/squirtles/data/user/model/Mapper.kt +++ b/data/src/main/java/com/squirtles/data/user/model/Mapper.kt @@ -3,7 +3,8 @@ package com.squirtles.data.user.model import com.squirtles.domain.model.User internal fun FirebaseUser.toUser(): User = User( - userId = "", + uid = "", + email = email ?: "", userName = name ?: "", userProfileImage = profileImage, myPicks = myPicks diff --git a/domain/src/main/java/com/squirtles/domain/pick/FirebasePickDataSource.kt b/domain/src/main/java/com/squirtles/domain/pick/FirebasePickDataSource.kt index eb6e3bc8..a3f33764 100644 --- a/domain/src/main/java/com/squirtles/domain/pick/FirebasePickDataSource.kt +++ b/domain/src/main/java/com/squirtles/domain/pick/FirebasePickDataSource.kt @@ -3,7 +3,7 @@ package com.squirtles.domain.pick import com.squirtles.domain.model.Pick interface FirebasePickDataSource { - suspend fun fetchPick(pickID: String): Result + suspend fun fetchPick(pickId: String): Result suspend fun fetchPicksInArea(lat: Double, lng: Double, radiusInM: Double): Result> suspend fun createPick(pick: Pick): Result suspend fun deletePick(pickId: String, userId: String): Result diff --git a/domain/src/main/java/com/squirtles/domain/user/FirebaseUserDataSource.kt b/domain/src/main/java/com/squirtles/domain/user/FirebaseUserDataSource.kt index a15dba82..2dfb3990 100644 --- a/domain/src/main/java/com/squirtles/domain/user/FirebaseUserDataSource.kt +++ b/domain/src/main/java/com/squirtles/domain/user/FirebaseUserDataSource.kt @@ -3,7 +3,8 @@ package com.squirtles.domain.user import com.squirtles.domain.model.User interface FirebaseUserDataSource { - suspend fun fetchUser(userId: String): User? - suspend fun createGoogleIdUser(userId: String, userName: String?, userProfileImage: String?): User? - suspend fun updateUserName(userId: String, newUserName: String): Boolean + suspend fun fetchUser(uid: String): User? + suspend fun createGoogleIdUser(uid: String, email: String, userName: String?, userProfileImage: String?): User? + suspend fun updateUserName(uid: String, newUserName: String): Boolean + suspend fun deleteUser(uid: String): Boolean } diff --git a/domain/src/main/java/com/squirtles/domain/user/FirebaseUserRepository.kt b/domain/src/main/java/com/squirtles/domain/user/FirebaseUserRepository.kt index c64a5548..3d4844d9 100644 --- a/domain/src/main/java/com/squirtles/domain/user/FirebaseUserRepository.kt +++ b/domain/src/main/java/com/squirtles/domain/user/FirebaseUserRepository.kt @@ -5,7 +5,8 @@ import com.squirtles.domain.model.User interface FirebaseUserRepository : FirebaseRepository { // user - suspend fun createGoogleIdUser(userId: String, userName: String?, userProfileImage: String?): Result + suspend fun createGoogleIdUser(uid: String, email: String, userName: String?, userProfileImage: String?): Result suspend fun fetchUser(userId: String): Result suspend fun updateUserName(userId: String, newUserName: String): Result + suspend fun deleteUser(uid: String): Result } diff --git a/domain/src/main/java/com/squirtles/domain/user/LocalUserDataSource.kt b/domain/src/main/java/com/squirtles/domain/user/LocalUserDataSource.kt index ebc7c0ca..514a3615 100644 --- a/domain/src/main/java/com/squirtles/domain/user/LocalUserDataSource.kt +++ b/domain/src/main/java/com/squirtles/domain/user/LocalUserDataSource.kt @@ -6,8 +6,8 @@ import kotlinx.coroutines.flow.Flow interface LocalUserDataSource { val currentUser: User? - fun readUserIdDataStore(): Flow - suspend fun saveUserIdDataStore(userId: String) + fun readUidDataStore(): Flow + suspend fun saveUidDataStore(uid: String) suspend fun saveCurrentUser(user: User) suspend fun clearUser() } diff --git a/domain/src/main/java/com/squirtles/domain/user/usecase/CreateGoogleIdUserUseCase.kt b/domain/src/main/java/com/squirtles/domain/user/usecase/CreateGoogleIdUserUseCase.kt index 07cdf223..e04f8010 100644 --- a/domain/src/main/java/com/squirtles/domain/user/usecase/CreateGoogleIdUserUseCase.kt +++ b/domain/src/main/java/com/squirtles/domain/user/usecase/CreateGoogleIdUserUseCase.kt @@ -10,16 +10,14 @@ class CreateGoogleIdUserUseCase @Inject constructor( private val firebaseUserRepository: FirebaseUserRepository ) { suspend operator fun invoke( - userId: String, + uid: String, + email: String, userName: String? = null, userProfileImage: String? = null - ): Result { - val createdUser = firebaseUserRepository.createGoogleIdUser(userId, userName, userProfileImage) - .onSuccess { user -> - // 생성된 유저의 userId 저장 후 user 반환 - localUserRepository.saveUserIdDataStore(user.userId) - localUserRepository.saveCurrentUser(user) - } - return createdUser - } + ): Result = firebaseUserRepository.createGoogleIdUser( + uid, + email, + userName, + userProfileImage + ) } diff --git a/domain/src/main/java/com/squirtles/domain/user/usecase/DeleteAccountUseCase.kt b/domain/src/main/java/com/squirtles/domain/user/usecase/DeleteAccountUseCase.kt new file mode 100644 index 00000000..512fc36b --- /dev/null +++ b/domain/src/main/java/com/squirtles/domain/user/usecase/DeleteAccountUseCase.kt @@ -0,0 +1,51 @@ +package com.squirtles.domain.user.usecase + +import android.util.Log +import com.google.firebase.auth.FirebaseAuth +import com.squirtles.domain.favorite.usecase.DeleteFavoriteUseCase +import com.squirtles.domain.firebase.FirebaseRepository +import com.squirtles.domain.pick.usecase.DeletePickUseCase +import com.squirtles.domain.pick.usecase.FetchFavoritePicksUseCase +import com.squirtles.domain.pick.usecase.FetchMyPicksUseCase +import com.squirtles.domain.user.FirebaseUserRepository +import kotlinx.coroutines.async +import kotlinx.coroutines.awaitAll +import kotlinx.coroutines.coroutineScope +import javax.inject.Inject + +class DeleteAccountUseCase @Inject constructor( + private val fetchFavoritePicksUseCase: FetchFavoritePicksUseCase, + private val deleteFavoriteUseCase: DeleteFavoriteUseCase, + private val fetchMyPicksUseCase: FetchMyPicksUseCase, + private val deletePickUseCase: DeletePickUseCase, + private val firebaseUserRepository: FirebaseUserRepository +) { + suspend operator fun invoke() = coroutineScope { + FirebaseAuth.getInstance().currentUser?.let { currentUser -> + try { + // 1. 좋아한 픽 삭제 + val favoritePicks = fetchFavoritePicksUseCase(currentUser.uid).getOrNull() ?: emptyList() + val favoritePicksDeleteJobs = favoritePicks.map { + async { deleteFavoriteUseCase(it.id, currentUser.uid) } + } + + // 2. 등록한 픽 삭제 + val myPicks = fetchMyPicksUseCase(currentUser.uid).getOrNull() ?: emptyList() + val myPicksDeleteJobs = myPicks.map { + async { deletePickUseCase(it.id, currentUser.uid) } + } + + // 모든 삭제 작업이 끝날 때까지 기다림 + (favoritePicksDeleteJobs + myPicksDeleteJobs).awaitAll() + + // 3. Firebase Firestore 유저 정보 삭제 + firebaseUserRepository.deleteUser(currentUser.uid) + + // 4. Firebase Auth 유저 삭제 + currentUser.delete() + } catch (e: Exception) { + Log.e("DeleteAccount", "Error deleting user account: ${e.message}") + } + } + } +} diff --git a/domain/src/main/java/com/squirtles/domain/user/usecase/FetchUserUseCase.kt b/domain/src/main/java/com/squirtles/domain/user/usecase/FetchUserUseCase.kt index a586f560..d397d47d 100644 --- a/domain/src/main/java/com/squirtles/domain/user/usecase/FetchUserUseCase.kt +++ b/domain/src/main/java/com/squirtles/domain/user/usecase/FetchUserUseCase.kt @@ -11,7 +11,7 @@ class FetchUserUseCase @Inject constructor( suspend operator fun invoke(userId: String): Result { val user = fetchUserByIdUseCase(userId) // userId가 있으면 Firestore에서 유저 가져오기 .onSuccess { user -> - localUserRepository.saveUserIdDataStore(user.userId) + localUserRepository.saveUserIdDataStore(user.uid) localUserRepository.saveCurrentUser(user) // Firestore에서 가져온 user를 LocalDataSource에 저장 } return user diff --git a/domain/src/main/java/com/squirtles/domain/user/usecase/GetCurrentUidUseCase.kt b/domain/src/main/java/com/squirtles/domain/user/usecase/GetCurrentUidUseCase.kt new file mode 100644 index 00000000..912c969f --- /dev/null +++ b/domain/src/main/java/com/squirtles/domain/user/usecase/GetCurrentUidUseCase.kt @@ -0,0 +1,8 @@ +package com.squirtles.domain.user.usecase + +import com.google.firebase.auth.FirebaseAuth +import javax.inject.Inject + +class GetCurrentUidUseCase @Inject constructor() { + operator fun invoke() = FirebaseAuth.getInstance().currentUser?.uid +} diff --git a/domain/src/main/java/com/squirtles/domain/user/usecase/SignOutUseCase.kt b/domain/src/main/java/com/squirtles/domain/user/usecase/SignOutUseCase.kt new file mode 100644 index 00000000..f071accd --- /dev/null +++ b/domain/src/main/java/com/squirtles/domain/user/usecase/SignOutUseCase.kt @@ -0,0 +1,8 @@ +package com.squirtles.domain.user.usecase + +import com.google.firebase.auth.FirebaseAuth +import javax.inject.Inject + +class SignOutUseCase @Inject constructor() { + operator fun invoke() = FirebaseAuth.getInstance().signOut() +} From 24e1da72775d0c9ff1aa6c4bb7266885e4ddf59d Mon Sep 17 00:00:00 2001 From: miller198 Date: Sun, 9 Mar 2025 00:25:26 +0900 Subject: [PATCH 37/62] =?UTF-8?q?[refactor]=20user=20=EC=98=88=EC=99=B8?= =?UTF-8?q?=EC=B2=98=EB=A6=AC=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../com/squirtles/account/AccountViewModel.kt | 42 +++++---- .../src/main/java/com/squirtles/model/Pick.kt | 2 +- .../src/main/java/com/squirtles/model/User.kt | 3 +- .../squirtles/picklist/PickListViewModel.kt | 16 ++-- .../firebase/BaseFirebaseDataSource.kt | 2 +- .../com/squirtles/firebase/model/Mapper.kt | 7 +- .../pick/FirebasePickDataSourceImpl.kt | 2 +- .../data/firebase/BaseFirebaseDataSource.kt | 16 +++- .../data/user/FirebaseUserDataSourceImpl.kt | 86 +++++++++---------- .../data/user/FirebaseUserRepositoryImpl.kt | 18 ++-- data/user/build.gradle.kts | 1 + .../user/FirebaseUserDataSourceImpl.kt | 54 ++++++------ .../user/FirebaseUserRepositoryImpl.kt | 26 +++--- .../favorite/FirebaseFavoriteRepository.kt | 6 +- .../favorite/usecase/CreateFavoriteUseCase.kt | 4 +- .../favorite/FirebaseFavoriteRepository.kt | 1 - .../domain/firebase/FirebaseRepository.kt | 20 ----- .../domain/user/FirebaseUserDataSource.kt | 13 ++- .../domain/user/FirebaseUserRepository.kt | 6 +- .../domain/user/usecase/ClearUserUseCase.kt | 10 --- .../user/usecase/DeleteAccountUseCase.kt | 1 - .../domain/user/usecase/FetchUserUseCase.kt | 19 ---- .../user/usecase/GetCurrentUserUseCase.kt | 10 --- .../usecase/GetUserIdFromDataStoreUseCase.kt | 10 --- domain/user/build.gradle.kts | 3 + .../squirtles/user/FirebaseUserDataSource.kt | 11 ++- .../squirtles/user/FirebaseUserRepository.kt | 5 +- .../user/usecase/ClearUserUseCase.kt | 10 --- .../user/usecase/CreateGoogleIdUserUseCase.kt | 18 ++-- .../user/usecase/DeleteAccountUseCase.kt | 45 ++++++++++ .../user/usecase/FetchUserUseCase.kt | 19 ---- .../user/usecase/GetCurrentUidUseCase.kt | 10 +++ .../user/usecase/GetCurrentUserUseCase.kt | 10 --- .../usecase/GetUserIdFromDataStoreUseCase.kt | 10 --- .../squirtles/user/usecase/SignOutUseCase.kt | 10 +++ .../squirtles/create/CreatePickViewModel.kt | 59 +++++++------ .../favorite/FavoriteListViewModel.kt | 6 +- .../com/squirtles/favorite/FavoriteScreen.kt | 2 +- 38 files changed, 284 insertions(+), 309 deletions(-) delete mode 100644 domain/src/main/java/com/squirtles/domain/firebase/FirebaseRepository.kt delete mode 100644 domain/src/main/java/com/squirtles/domain/user/usecase/ClearUserUseCase.kt delete mode 100644 domain/src/main/java/com/squirtles/domain/user/usecase/FetchUserUseCase.kt delete mode 100644 domain/src/main/java/com/squirtles/domain/user/usecase/GetCurrentUserUseCase.kt delete mode 100644 domain/src/main/java/com/squirtles/domain/user/usecase/GetUserIdFromDataStoreUseCase.kt delete mode 100644 domain/user/src/main/java/com/squirtles/user/usecase/ClearUserUseCase.kt create mode 100644 domain/user/src/main/java/com/squirtles/user/usecase/DeleteAccountUseCase.kt delete mode 100644 domain/user/src/main/java/com/squirtles/user/usecase/FetchUserUseCase.kt create mode 100644 domain/user/src/main/java/com/squirtles/user/usecase/GetCurrentUidUseCase.kt delete mode 100644 domain/user/src/main/java/com/squirtles/user/usecase/GetCurrentUserUseCase.kt delete mode 100644 domain/user/src/main/java/com/squirtles/user/usecase/GetUserIdFromDataStoreUseCase.kt create mode 100644 domain/user/src/main/java/com/squirtles/user/usecase/SignOutUseCase.kt diff --git a/core/account/src/main/java/com/squirtles/account/AccountViewModel.kt b/core/account/src/main/java/com/squirtles/account/AccountViewModel.kt index 42174cd6..c286940d 100644 --- a/core/account/src/main/java/com/squirtles/account/AccountViewModel.kt +++ b/core/account/src/main/java/com/squirtles/account/AccountViewModel.kt @@ -3,10 +3,11 @@ package com.squirtles.account import android.util.Log import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope -import com.squirtles.user.usecase.ClearUserUseCase -import com.squirtles.user.usecase.CreateGoogleIdUserUseCase -import com.squirtles.user.usecase.FetchUserUseCase import com.google.android.libraries.identity.googleid.GoogleIdTokenCredential +import com.squirtles.domain.user.usecase.SignOutUseCase +import com.squirtles.user.usecase.CreateGoogleIdUserUseCase +import com.squirtles.user.usecase.DeleteAccountUseCase +import com.squirtles.user.usecase.FetchUserByIdUseCase import dagger.hilt.android.lifecycle.HiltViewModel import kotlinx.coroutines.flow.MutableSharedFlow import kotlinx.coroutines.flow.asSharedFlow @@ -15,9 +16,10 @@ import javax.inject.Inject @HiltViewModel class AccountViewModel @Inject constructor( - private val fetchUserUseCase: FetchUserUseCase, + private val fetchUserByIdUseCase: FetchUserByIdUseCase, private val createGoogleIdUserUseCase: CreateGoogleIdUserUseCase, - private val clearUserUseCase: ClearUserUseCase, + private val signOutUseCase: SignOutUseCase, + private val deleteAccountUseCase: DeleteAccountUseCase ) : ViewModel() { private val _signInSuccess = MutableSharedFlow() @@ -26,27 +28,31 @@ class AccountViewModel @Inject constructor( private val _signOutSuccess = MutableSharedFlow() val signOutSuccess = _signOutSuccess.asSharedFlow() - fun signIn(credential: GoogleIdTokenCredential) { + private val _deleteAccountSuccess = MutableSharedFlow() + val deleteAccountSuccess = _deleteAccountSuccess.asSharedFlow() + + fun signIn(uid: String, credential: GoogleIdTokenCredential) { viewModelScope.launch { - fetchUserUseCase(credential.id) + fetchUserByIdUseCase(uid) .onSuccess { - Log.d("SignIn", "기존 계정 ${it.userId} 로그인") + Log.d("SignIn", "기존 계정 로그인 email : ${it.email} 로그인") _signInSuccess.emit(true) } .onFailure { - createGoogleIdUser(credential) + createGoogleIdUser(uid, credential) } } } - private fun createGoogleIdUser(credential: GoogleIdTokenCredential) { + private fun createGoogleIdUser(uid: String, credential: GoogleIdTokenCredential) { viewModelScope.launch { createGoogleIdUserUseCase( - userId = credential.id, + uid = uid, + email = credential.id, userName = credential.displayName, userProfileImage = credential.profilePictureUri.toString() ).onSuccess { - Log.d("SignIn", "새로운 계정 ${it.userId} 로그인") + Log.d("SignIn", "새로운 계정 로그인 email : ${it.email}") _signInSuccess.emit(true) }.onFailure { _signInSuccess.emit(false) @@ -56,9 +62,15 @@ class AccountViewModel @Inject constructor( fun signOut() { viewModelScope.launch { - clearUserUseCase() - .onSuccess { _signOutSuccess.emit(true) } - .onFailure { _signOutSuccess.emit(false) } + signOutUseCase() + _signOutSuccess.emit(true) + } + } + + fun deleteAccount() { + viewModelScope.launch { + deleteAccountUseCase() + _deleteAccountSuccess.emit(true) } } } diff --git a/core/model/src/main/java/com/squirtles/model/Pick.kt b/core/model/src/main/java/com/squirtles/model/Pick.kt index 756d182a..391caca0 100644 --- a/core/model/src/main/java/com/squirtles/model/Pick.kt +++ b/core/model/src/main/java/com/squirtles/model/Pick.kt @@ -23,6 +23,6 @@ data class LocationPoint( } data class Creator( - val userId: String, + val uid: String, val userName: String, ) diff --git a/core/model/src/main/java/com/squirtles/model/User.kt b/core/model/src/main/java/com/squirtles/model/User.kt index 7060376a..0198552c 100644 --- a/core/model/src/main/java/com/squirtles/model/User.kt +++ b/core/model/src/main/java/com/squirtles/model/User.kt @@ -1,7 +1,8 @@ package com.squirtles.model data class User( - val userId: String, + val uid: String, + val email: String, val userName: String, val userProfileImage: String?, val myPicks: List, diff --git a/core/picklist/src/main/java/com/squirtles/picklist/PickListViewModel.kt b/core/picklist/src/main/java/com/squirtles/picklist/PickListViewModel.kt index c5ff9a59..82e6d5c7 100644 --- a/core/picklist/src/main/java/com/squirtles/picklist/PickListViewModel.kt +++ b/core/picklist/src/main/java/com/squirtles/picklist/PickListViewModel.kt @@ -5,7 +5,7 @@ import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope import com.squirtles.model.Order import com.squirtles.model.Pick -import com.squirtles.user.usecase.GetCurrentUserUseCase +import com.squirtles.user.usecase.GetCurrentUidUseCase import kotlinx.coroutines.async import kotlinx.coroutines.awaitAll import kotlinx.coroutines.flow.MutableStateFlow @@ -17,7 +17,7 @@ abstract class PickListViewModel( val getPickListOrderUseCase: GetPickListOrderUseCaseInterface, val savePickListOrderUseCase: SavePickListOrderUseCaseInterface, val removePickUseCase: RemovePickUseCaseInterface, - val getCurrentUserUseCase: GetCurrentUserUseCase + val getCurrentUidUseCase: GetCurrentUidUseCase ) : ViewModel() { private var pickList: List = emptyList() @@ -28,9 +28,9 @@ abstract class PickListViewModel( private val _selectedPicksId = MutableStateFlow>(emptySet()) val selectedPicksId = _selectedPicksId.asStateFlow() - fun fetchPickList(userId: String) { + fun fetchPickList(uid: String) { viewModelScope.launch { - fetchPickListUseCase(userId) + fetchPickListUseCase(uid) .onSuccess { picks -> pickList = picks sortPickList(getPickListOrderUseCase()) @@ -69,18 +69,18 @@ abstract class PickListViewModel( _selectedPicksId.value = emptySet() } - fun deleteSelectedPicks(userId: String) { + fun deleteSelectedPicks(uid: String) { viewModelScope.launch { _pickListUiState.value = PickListUiState.Loading val deleteJobList = _selectedPicksId.value.map { pickId -> - async { removePickUseCase(pickId, userId) } + async { removePickUseCase(pickId, uid) } }.awaitAll() deselectAllPicks() if (deleteJobList.all { it.isSuccess }) { - fetchPickList(userId) + fetchPickList(uid) } else { _pickListUiState.value = PickListUiState.Error Log.e("PickListViewModel", "[픽 목록] 다중 삭제 오류") @@ -97,5 +97,5 @@ abstract class PickListViewModel( } } - fun getUserId() = getCurrentUserUseCase()?.userId + fun getUid() = getCurrentUidUseCase() } diff --git a/data/firebase/src/main/java/com/squirtles/firebase/BaseFirebaseDataSource.kt b/data/firebase/src/main/java/com/squirtles/firebase/BaseFirebaseDataSource.kt index c137bc8b..3d59deb5 100644 --- a/data/firebase/src/main/java/com/squirtles/firebase/BaseFirebaseDataSource.kt +++ b/data/firebase/src/main/java/com/squirtles/firebase/BaseFirebaseDataSource.kt @@ -9,7 +9,7 @@ import com.google.firebase.firestore.Query import com.google.firebase.firestore.QuerySnapshot import kotlinx.coroutines.tasks.await -open class BaseFirebaseDataSource( +open class BaseFirebaseDataSource( private val db: FirebaseFirestore ) { protected fun fetchCollection(collection: FirebaseCollections): CollectionReference = db.collection(collection.name) diff --git a/data/firebase/src/main/java/com/squirtles/firebase/model/Mapper.kt b/data/firebase/src/main/java/com/squirtles/firebase/model/Mapper.kt index 0593e827..9a97d2e2 100644 --- a/data/firebase/src/main/java/com/squirtles/firebase/model/Mapper.kt +++ b/data/firebase/src/main/java/com/squirtles/firebase/model/Mapper.kt @@ -19,7 +19,7 @@ fun Pick.toFirebasePick(): FirebasePick = FirebasePick( artistName = song.artistName, artwork = mapOf("url" to song.imageUrl, "bgColor" to song.bgColor.toRgbString()), comment = comment, - createdBy = mapOf("userId" to createdBy.userId, "userName" to createdBy.userName), + createdBy = mapOf("userId" to createdBy.uid, "userName" to createdBy.userName), externalUrl = song.externalUrl, favoriteCount = favoriteCount, genreNames = song.genreNames, @@ -50,7 +50,7 @@ fun FirebasePick.toPick(): Pick = Pick( comment = comment.toString(), favoriteCount = favoriteCount, createdBy = Creator( - userId = createdBy?.get("userId") ?: "", + uid = createdBy?.get("userId") ?: "", userName = createdBy?.get("userName") ?: "" ), createdAt = createdAt?.toDate()?.formatTimestamp() ?: "", @@ -77,7 +77,8 @@ private fun Date.formatTimestamp(): String { } fun FirebaseUser.toUser(): User = User( - userId = "", + uid = "", + email = email ?: "", userName = name ?: "", userProfileImage = profileImage, myPicks = myPicks diff --git a/data/pick/src/main/java/com/squirtles/pick/FirebasePickDataSourceImpl.kt b/data/pick/src/main/java/com/squirtles/pick/FirebasePickDataSourceImpl.kt index d234d356..124246ff 100644 --- a/data/pick/src/main/java/com/squirtles/pick/FirebasePickDataSourceImpl.kt +++ b/data/pick/src/main/java/com/squirtles/pick/FirebasePickDataSourceImpl.kt @@ -79,7 +79,7 @@ class FirebasePickDataSourceImpl @Inject constructor( val firebasePick = pick.toFirebasePick() return runCatching { val pickRef = addDocument(FirebaseCollections.Picks, firebasePick).getOrThrow() - updateCurrentUserPick(pick.createdBy.userId, pickRef.id) + updateCurrentUserPick(pick.createdBy.uid, pickRef.id) pickRef.id }.onFailure { Log.e(TAG_LOG, "Failed to create a pick", it) diff --git a/data/src/main/java/com/squirtles/data/firebase/BaseFirebaseDataSource.kt b/data/src/main/java/com/squirtles/data/firebase/BaseFirebaseDataSource.kt index ec767930..91941c7d 100644 --- a/data/src/main/java/com/squirtles/data/firebase/BaseFirebaseDataSource.kt +++ b/data/src/main/java/com/squirtles/data/firebase/BaseFirebaseDataSource.kt @@ -9,7 +9,7 @@ import com.google.firebase.firestore.Query import com.google.firebase.firestore.QuerySnapshot import kotlinx.coroutines.tasks.await -open class BaseFirebaseDataSource( +open class BaseFirebaseDataSource( private val db: FirebaseFirestore ) { protected fun fetchCollection(collection: FirebaseCollections): CollectionReference = db.collection(collection.name) @@ -77,6 +77,20 @@ open class BaseFirebaseDataSource( } } + protected suspend fun updateDocument( + collection: FirebaseCollections, + documentReference: DocumentReference, + field: FirebaseDocumentFields, + value: Any + ): Result { + return runCatching { + documentReference.update(field.name, value).await() + }.onFailure { + Log.e("FirebaseDataSource", "Failed to update document", it) + throw FirebaseException.UpdateDocumentFailedException(docId = documentReference.id, collection = collection.name) + } + } + protected suspend fun setDocument(collection: FirebaseCollections, docId:String, value: Any): Result { return runCatching { fetchDocumentReference(collection, docId).set(value).await() diff --git a/data/src/main/java/com/squirtles/data/user/FirebaseUserDataSourceImpl.kt b/data/src/main/java/com/squirtles/data/user/FirebaseUserDataSourceImpl.kt index 6b107c57..c57e1ae8 100644 --- a/data/src/main/java/com/squirtles/data/user/FirebaseUserDataSourceImpl.kt +++ b/data/src/main/java/com/squirtles/data/user/FirebaseUserDataSourceImpl.kt @@ -3,6 +3,9 @@ package com.squirtles.data.user import android.util.Log import com.google.firebase.firestore.FirebaseFirestore import com.google.firebase.firestore.toObject +import com.squirtles.data.firebase.BaseFirebaseDataSource +import com.squirtles.data.firebase.FirebaseCollections +import com.squirtles.data.firebase.FirebaseDocumentFields import com.squirtles.data.user.model.FirebaseUser import com.squirtles.data.user.model.toUser import com.squirtles.domain.model.User @@ -16,67 +19,56 @@ import kotlin.coroutines.resumeWithException @Singleton class FirebaseUserDataSourceImpl @Inject constructor( private val db: FirebaseFirestore -): FirebaseUserDataSource { +) : BaseFirebaseDataSource(db), FirebaseUserDataSource { - override suspend fun createGoogleIdUser(uid: String, email: String, userName: String?, userProfileImage: String?): User? { - return suspendCancellableCoroutine { continuation -> - val documentReference = db.collection("users").document(uid) - documentReference.set(FirebaseUser(email = email, name = userName, profileImage = userProfileImage)) - .addOnSuccessListener { - documentReference.get() - .addOnSuccessListener { documentSnapshot -> - val savedUser = documentSnapshot.toObject() - continuation.resume(savedUser?.toUser()?.copy(uid = documentReference.id)) - } - .addOnFailureListener { exception -> - continuation.resumeWithException(exception) - } - } - .addOnFailureListener { exception -> - Log.e("FirebaseDataSourceImpl", exception.message.toString()) - continuation.resumeWithException(exception) - } + override suspend fun createGoogleIdUser( + uid: String, + email: String, + userName: String?, + userProfileImage: String? + ): Result { + return runCatching { + val newUser = FirebaseUser(email = email, name = userName, profileImage = userProfileImage) + setDocument(FirebaseCollections.Users, uid, newUser) + newUser.toUser().copy(uid = uid) + }.onFailure { e -> + Log.e(TAG_LOG, e.message.toString()) } } - override suspend fun fetchUser(uid: String): User? { - return suspendCancellableCoroutine { continuation -> - db.collection("users").document(uid).get() - .addOnSuccessListener { document -> - val firebaseUser = document.toObject() - continuation.resume(firebaseUser?.toUser()?.copy(uid = uid)) - } - .addOnFailureListener { exception -> - continuation.resumeWithException(exception) - } + override suspend fun fetchUser(uid: String): Result { + return runCatching { + val userDocSnap = fetchDocumentSnapshot(FirebaseCollections.Users, uid).getOrThrow() + userDocSnap.toObject()?.toUser()?.copy(uid = uid)!! + }.onFailure { e -> + Log.e(TAG_LOG, "Failed to fetch a user", e) } } - override suspend fun updateUserName(uid: String, newUserName: String): Boolean { - return suspendCancellableCoroutine { continuation -> + override suspend fun updateUserName(uid: String, newUserName: String): Result { + return runCatching { + val userDocSnap = fetchDocumentSnapshot(FirebaseCollections.Users, uid).getOrThrow() db.runTransaction { transaction -> - val userRef = db.collection("users").document(uid) - val userDocument = transaction.get(userRef) - transaction.update(userRef, "name", newUserName) + transaction.update(userDocSnap.reference, FirebaseDocumentFields.Name.name, newUserName) - val myPicks = userDocument.get("myPicks")?.let { it as List } ?: emptyList() + val myPicks = userDocSnap.toObject()?.myPicks + requireNotNull(myPicks) myPicks.forEach { pickId -> - val pickRef = db.collection("picks").document(pickId) - transaction.update(pickRef, "createdBy.userName", newUserName) + val pickRef = fetchDocumentReference(FirebaseCollections.Picks, pickId) + transaction.update(pickRef, FirebaseDocumentFields.CreatedUserName.name, newUserName) } - }.addOnSuccessListener { - continuation.resume(true) - }.addOnFailureListener { exception -> - continuation.resumeWithException(exception) } + true + }.onFailure { e -> + Log.e(TAG_LOG, "Failed to update a user name", e) } } - override suspend fun deleteUser(uid: String): Boolean { - return suspendCancellableCoroutine { continuation -> - db.collection("users").document(uid).delete() - .addOnSuccessListener { continuation.resume(true) } - .addOnFailureListener { exception -> continuation.resumeWithException(exception) } - } + override suspend fun deleteUser(uid: String): Result { + return deleteDocument(FirebaseCollections.Users, uid) + } + + companion object { + const val TAG_LOG = "FirebaseUserDataSourceImpl" } } diff --git a/data/src/main/java/com/squirtles/data/user/FirebaseUserRepositoryImpl.kt b/data/src/main/java/com/squirtles/data/user/FirebaseUserRepositoryImpl.kt index a9f41847..00e06a3c 100644 --- a/data/src/main/java/com/squirtles/data/user/FirebaseUserRepositoryImpl.kt +++ b/data/src/main/java/com/squirtles/data/user/FirebaseUserRepositoryImpl.kt @@ -17,26 +17,18 @@ class FirebaseUserRepositoryImpl( userName: String?, userProfileImage: String? ): Result { - return handleResult(FirebaseException.CreatedUserFailedException()) { - userDataSource.createGoogleIdUser(uid, email, userName, userProfileImage) - } + return userDataSource.createGoogleIdUser(uid, email, userName, userProfileImage) } override suspend fun fetchUser(userId: String): Result { - return handleResult(FirebaseException.UserNotFoundException()) { - userDataSource.fetchUser(userId) - } + return userDataSource.fetchUser(userId) } override suspend fun updateUserName(userId: String, newUserName: String): Result { - return handleResult { - userDataSource.updateUserName(userId, newUserName) - } + return userDataSource.updateUserName(userId, newUserName) } - override suspend fun deleteUser(uid: String): Result { - return handleResult(FirebaseException.UserNotFoundException()) { - userDataSource.deleteUser(uid) - } + override suspend fun deleteUser(uid: String): Result { + return userDataSource.deleteUser(uid) } } diff --git a/data/user/build.gradle.kts b/data/user/build.gradle.kts index a98954e3..a8c50935 100644 --- a/data/user/build.gradle.kts +++ b/data/user/build.gradle.kts @@ -15,4 +15,5 @@ dependencies { // firebase implementation(libs.firebase.firestore.ktx) + implementation(libs.firebase.auth.ktx) } diff --git a/data/user/src/main/java/com/squirtles/user/FirebaseUserDataSourceImpl.kt b/data/user/src/main/java/com/squirtles/user/FirebaseUserDataSourceImpl.kt index 55f2f824..fc59410e 100644 --- a/data/user/src/main/java/com/squirtles/user/FirebaseUserDataSourceImpl.kt +++ b/data/user/src/main/java/com/squirtles/user/FirebaseUserDataSourceImpl.kt @@ -1,20 +1,18 @@ package com.squirtles.user import android.util.Log -import com.squirtles.firebase.model.FirebaseUser -import com.squirtles.firebase.model.toUser -import com.squirtles.model.User +import com.google.firebase.auth.FirebaseAuth import com.google.firebase.firestore.FirebaseFirestore import com.google.firebase.firestore.toObject import com.squirtles.firebase.BaseFirebaseDataSource import com.squirtles.firebase.FirebaseCollections import com.squirtles.firebase.FirebaseDocumentFields -import kotlinx.coroutines.suspendCancellableCoroutine +import com.squirtles.firebase.model.FirebaseUser +import com.squirtles.firebase.model.toUser +import com.squirtles.model.User import kotlinx.coroutines.tasks.await import javax.inject.Inject import javax.inject.Singleton -import kotlin.coroutines.resume -import kotlin.coroutines.resumeWithException @Singleton class FirebaseUserDataSourceImpl @Inject constructor( @@ -22,52 +20,58 @@ class FirebaseUserDataSourceImpl @Inject constructor( ) : BaseFirebaseDataSource(db), FirebaseUserDataSource { override suspend fun createGoogleIdUser( - userId: String, + uid: String, + email: String, userName: String?, userProfileImage: String? ): Result { return runCatching { - val firebaseUser = FirebaseUser(name = userName, profileImage = userProfileImage) - setDocument(FirebaseCollections.Users, userId, firebaseUser) - - val docSnap = fetchDocumentSnapshot(FirebaseCollections.Users, userId).getOrThrow() - docSnap.toObject()?.toUser()!! + val newUser = FirebaseUser(email = email, name = userName, profileImage = userProfileImage) + setDocument(FirebaseCollections.Users, uid, newUser) + newUser.toUser().copy(uid = uid) }.onFailure { e -> Log.e(TAG_LOG, e.message.toString()) } } - override suspend fun fetchUser(userId: String): Result { + override suspend fun fetchUser(uid: String): Result { return runCatching { - val docSnap = fetchDocumentSnapshot(FirebaseCollections.Users, userId).getOrThrow() - docSnap.toObject()?.toUser()!! + val userDocSnap = fetchDocumentSnapshot(FirebaseCollections.Users, uid).getOrThrow() + userDocSnap.toObject()?.toUser()?.copy(uid = uid)!! }.onFailure { e -> Log.e(TAG_LOG, "Failed to fetch a user", e) } } - override suspend fun updateUserName(userId: String, newUserName: String): Result { + override suspend fun updateUserName(uid: String, newUserName: String): Result { return runCatching { - val userSnap = fetchDocumentSnapshot(FirebaseCollections.Users, userId).getOrThrow() - + val userDocSnap = fetchDocumentSnapshot(FirebaseCollections.Users, uid).getOrThrow() db.runTransaction { transaction -> - transaction.update(userSnap.reference, FirebaseDocumentFields.Name.name, newUserName) - val myPicks = userSnap.get(FirebaseDocumentFields.MyPicks.name)?.let { it as List } ?: emptyList() + transaction.update(userDocSnap.reference, FirebaseDocumentFields.Name.name, newUserName) - // 해당 유저의 모든 pick의 등록 유저 정보 업데이트 + val myPicks = userDocSnap.toObject()?.myPicks + requireNotNull(myPicks) myPicks.forEach { pickId -> val pickRef = fetchDocumentReference(FirebaseCollections.Picks, pickId) transaction.update(pickRef, FirebaseDocumentFields.CreatedUserName.name, newUserName) } - }.await() - + } true + }.onFailure { e -> + Log.e(TAG_LOG, "Failed to update a user name", e) + } + } + + override suspend fun deleteUser(uid: String): Result { + return runCatching { + FirebaseAuth.getInstance().currentUser?.delete()?.await() + return deleteDocument(FirebaseCollections.Users, uid) }.onFailure { - Log.e(TAG_LOG, "Failed to update a user name", it) + Log.e(TAG_LOG, "Failed to delete a user", it) } } companion object { - private const val TAG_LOG = "FirebaseUserDataSourceImpl" + const val TAG_LOG = "FirebaseUserDataSourceImpl" } } diff --git a/data/user/src/main/java/com/squirtles/user/FirebaseUserRepositoryImpl.kt b/data/user/src/main/java/com/squirtles/user/FirebaseUserRepositoryImpl.kt index d2da3243..6ec56812 100644 --- a/data/user/src/main/java/com/squirtles/user/FirebaseUserRepositoryImpl.kt +++ b/data/user/src/main/java/com/squirtles/user/FirebaseUserRepositoryImpl.kt @@ -1,8 +1,8 @@ package com.squirtles.user -import com.squirtles.firebase.FirebaseException -import com.squirtles.firebase.handleResult +import com.google.firebase.auth.FirebaseAuth import com.squirtles.model.User +import kotlinx.coroutines.tasks.await import javax.inject.Singleton @Singleton @@ -10,28 +10,28 @@ class FirebaseUserRepositoryImpl( private val userDataSource: FirebaseUserDataSource ) : FirebaseUserRepository { + override val currentUser get() = FirebaseAuth.getInstance().currentUser?.uid + + override fun signOut() = FirebaseAuth.getInstance().signOut() + override suspend fun createGoogleIdUser( - userId: String, + uid: String, + email: String, userName: String?, userProfileImage: String? ): Result { - return userDataSource.createGoogleIdUser(userId, userName, userProfileImage) - .onFailure { - throw FirebaseException.CreatedUserFailedException() - } + return userDataSource.createGoogleIdUser(uid, email, userName, userProfileImage) } override suspend fun fetchUser(userId: String): Result { return userDataSource.fetchUser(userId) - .onFailure { - throw FirebaseException.FetchUserFailedException() - } } override suspend fun updateUserName(userId: String, newUserName: String): Result { return userDataSource.updateUserName(userId, newUserName) - .onFailure { - throw FirebaseException.UpdateUserFailedException() - } + } + + override suspend fun deleteUser(uid: String): Result { + return userDataSource.deleteUser(uid) } } diff --git a/domain/favorite/src/main/java/com/squirtles/favorite/FirebaseFavoriteRepository.kt b/domain/favorite/src/main/java/com/squirtles/favorite/FirebaseFavoriteRepository.kt index 7b1b809e..dbea95c8 100644 --- a/domain/favorite/src/main/java/com/squirtles/favorite/FirebaseFavoriteRepository.kt +++ b/domain/favorite/src/main/java/com/squirtles/favorite/FirebaseFavoriteRepository.kt @@ -1,7 +1,7 @@ package com.squirtles.favorite interface FirebaseFavoriteRepository { - suspend fun fetchIsFavorite(pickId: String, userId: String): Result - suspend fun createFavorite(pickId: String, userId: String): Result - suspend fun deleteFavorite(pickId: String, userId: String): Result + suspend fun fetchIsFavorite(pickId: String, uid: String): Result + suspend fun createFavorite(pickId: String, uid: String): Result + suspend fun deleteFavorite(pickId: String, uid: String): Result } diff --git a/domain/favorite/src/main/java/com/squirtles/favorite/usecase/CreateFavoriteUseCase.kt b/domain/favorite/src/main/java/com/squirtles/favorite/usecase/CreateFavoriteUseCase.kt index d6283958..5556c97b 100644 --- a/domain/favorite/src/main/java/com/squirtles/favorite/usecase/CreateFavoriteUseCase.kt +++ b/domain/favorite/src/main/java/com/squirtles/favorite/usecase/CreateFavoriteUseCase.kt @@ -6,6 +6,6 @@ import javax.inject.Inject class CreateFavoriteUseCase @Inject constructor( private val favoriteRepository: FirebaseFavoriteRepository ) { - suspend operator fun invoke(pickId: String, userId: String) = - favoriteRepository.createFavorite(pickId, userId) + suspend operator fun invoke(pickId: String, uid: String) = + favoriteRepository.createFavorite(pickId, uid) } diff --git a/domain/src/main/java/com/squirtles/domain/favorite/FirebaseFavoriteRepository.kt b/domain/src/main/java/com/squirtles/domain/favorite/FirebaseFavoriteRepository.kt index fd949300..f5f70e64 100644 --- a/domain/src/main/java/com/squirtles/domain/favorite/FirebaseFavoriteRepository.kt +++ b/domain/src/main/java/com/squirtles/domain/favorite/FirebaseFavoriteRepository.kt @@ -1,6 +1,5 @@ package com.squirtles.domain.favorite -import com.squirtles.domain.firebase.FirebaseRepository import com.squirtles.domain.model.Pick interface FirebaseFavoriteRepository { diff --git a/domain/src/main/java/com/squirtles/domain/firebase/FirebaseRepository.kt b/domain/src/main/java/com/squirtles/domain/firebase/FirebaseRepository.kt deleted file mode 100644 index 0b771702..00000000 --- a/domain/src/main/java/com/squirtles/domain/firebase/FirebaseRepository.kt +++ /dev/null @@ -1,20 +0,0 @@ -package com.squirtles.domain.firebase - -interface FirebaseRepository { - suspend fun handleResult( - firebaseRepositoryException: FirebaseException, - call: suspend () -> T? - ): Result { - return runCatching { - call() ?: throw firebaseRepositoryException - } - } - - suspend fun handleResult( - call: suspend () -> T - ): Result { - return runCatching { - call() - } - } -} diff --git a/domain/src/main/java/com/squirtles/domain/user/FirebaseUserDataSource.kt b/domain/src/main/java/com/squirtles/domain/user/FirebaseUserDataSource.kt index 2dfb3990..3ca3b1d7 100644 --- a/domain/src/main/java/com/squirtles/domain/user/FirebaseUserDataSource.kt +++ b/domain/src/main/java/com/squirtles/domain/user/FirebaseUserDataSource.kt @@ -3,8 +3,13 @@ package com.squirtles.domain.user import com.squirtles.domain.model.User interface FirebaseUserDataSource { - suspend fun fetchUser(uid: String): User? - suspend fun createGoogleIdUser(uid: String, email: String, userName: String?, userProfileImage: String?): User? - suspend fun updateUserName(uid: String, newUserName: String): Boolean - suspend fun deleteUser(uid: String): Boolean + suspend fun fetchUser(uid: String): Result + suspend fun createGoogleIdUser( + uid: String, + email: String, + userName: String?, + userProfileImage: String? + ): Result + suspend fun updateUserName(uid: String, newUserName: String): Result + suspend fun deleteUser(uid: String): Result } diff --git a/domain/src/main/java/com/squirtles/domain/user/FirebaseUserRepository.kt b/domain/src/main/java/com/squirtles/domain/user/FirebaseUserRepository.kt index 3d4844d9..569f2633 100644 --- a/domain/src/main/java/com/squirtles/domain/user/FirebaseUserRepository.kt +++ b/domain/src/main/java/com/squirtles/domain/user/FirebaseUserRepository.kt @@ -1,12 +1,10 @@ package com.squirtles.domain.user -import com.squirtles.domain.firebase.FirebaseRepository import com.squirtles.domain.model.User -interface FirebaseUserRepository : FirebaseRepository { - // user +interface FirebaseUserRepository { suspend fun createGoogleIdUser(uid: String, email: String, userName: String?, userProfileImage: String?): Result suspend fun fetchUser(userId: String): Result suspend fun updateUserName(userId: String, newUserName: String): Result - suspend fun deleteUser(uid: String): Result + suspend fun deleteUser(uid: String): Result } diff --git a/domain/src/main/java/com/squirtles/domain/user/usecase/ClearUserUseCase.kt b/domain/src/main/java/com/squirtles/domain/user/usecase/ClearUserUseCase.kt deleted file mode 100644 index c42eb854..00000000 --- a/domain/src/main/java/com/squirtles/domain/user/usecase/ClearUserUseCase.kt +++ /dev/null @@ -1,10 +0,0 @@ -package com.squirtles.domain.user.usecase - -import com.squirtles.domain.user.LocalUserRepository -import javax.inject.Inject - -class ClearUserUseCase @Inject constructor( - private val localUserRepository: LocalUserRepository -) { - suspend operator fun invoke() = localUserRepository.clearUser() -} diff --git a/domain/src/main/java/com/squirtles/domain/user/usecase/DeleteAccountUseCase.kt b/domain/src/main/java/com/squirtles/domain/user/usecase/DeleteAccountUseCase.kt index 512fc36b..1c999df1 100644 --- a/domain/src/main/java/com/squirtles/domain/user/usecase/DeleteAccountUseCase.kt +++ b/domain/src/main/java/com/squirtles/domain/user/usecase/DeleteAccountUseCase.kt @@ -3,7 +3,6 @@ package com.squirtles.domain.user.usecase import android.util.Log import com.google.firebase.auth.FirebaseAuth import com.squirtles.domain.favorite.usecase.DeleteFavoriteUseCase -import com.squirtles.domain.firebase.FirebaseRepository import com.squirtles.domain.pick.usecase.DeletePickUseCase import com.squirtles.domain.pick.usecase.FetchFavoritePicksUseCase import com.squirtles.domain.pick.usecase.FetchMyPicksUseCase diff --git a/domain/src/main/java/com/squirtles/domain/user/usecase/FetchUserUseCase.kt b/domain/src/main/java/com/squirtles/domain/user/usecase/FetchUserUseCase.kt deleted file mode 100644 index d397d47d..00000000 --- a/domain/src/main/java/com/squirtles/domain/user/usecase/FetchUserUseCase.kt +++ /dev/null @@ -1,19 +0,0 @@ -package com.squirtles.domain.user.usecase - -import com.squirtles.domain.model.User -import com.squirtles.domain.user.LocalUserRepository -import javax.inject.Inject - -class FetchUserUseCase @Inject constructor( - private val localUserRepository: LocalUserRepository, - private val fetchUserByIdUseCase: FetchUserByIdUseCase -) { - suspend operator fun invoke(userId: String): Result { - val user = fetchUserByIdUseCase(userId) // userId가 있으면 Firestore에서 유저 가져오기 - .onSuccess { user -> - localUserRepository.saveUserIdDataStore(user.uid) - localUserRepository.saveCurrentUser(user) // Firestore에서 가져온 user를 LocalDataSource에 저장 - } - return user - } -} diff --git a/domain/src/main/java/com/squirtles/domain/user/usecase/GetCurrentUserUseCase.kt b/domain/src/main/java/com/squirtles/domain/user/usecase/GetCurrentUserUseCase.kt deleted file mode 100644 index a3d9c432..00000000 --- a/domain/src/main/java/com/squirtles/domain/user/usecase/GetCurrentUserUseCase.kt +++ /dev/null @@ -1,10 +0,0 @@ -package com.squirtles.domain.user.usecase - -import com.squirtles.domain.user.LocalUserRepository -import javax.inject.Inject - -class GetCurrentUserUseCase @Inject constructor( - private val localUserRepository: LocalUserRepository -) { - operator fun invoke() = localUserRepository.currentUser -} diff --git a/domain/src/main/java/com/squirtles/domain/user/usecase/GetUserIdFromDataStoreUseCase.kt b/domain/src/main/java/com/squirtles/domain/user/usecase/GetUserIdFromDataStoreUseCase.kt deleted file mode 100644 index c882d41a..00000000 --- a/domain/src/main/java/com/squirtles/domain/user/usecase/GetUserIdFromDataStoreUseCase.kt +++ /dev/null @@ -1,10 +0,0 @@ -package com.squirtles.domain.user.usecase - -import com.squirtles.domain.user.LocalUserRepository -import javax.inject.Inject - -class GetUserIdFromDataStoreUseCase @Inject constructor( - private val localUserRepository: LocalUserRepository -) { - operator fun invoke() = localUserRepository.readUserIdDataStore() -} diff --git a/domain/user/build.gradle.kts b/domain/user/build.gradle.kts index 8cb3df6f..b3237734 100644 --- a/domain/user/build.gradle.kts +++ b/domain/user/build.gradle.kts @@ -4,6 +4,9 @@ plugins { dependencies { implementation(projects.core.model) + implementation(projects.domain.picklist) + implementation(projects.domain.pick) + implementation(projects.domain.favorite) implementation(libs.kotlinx.coroutines.core) } diff --git a/domain/user/src/main/java/com/squirtles/user/FirebaseUserDataSource.kt b/domain/user/src/main/java/com/squirtles/user/FirebaseUserDataSource.kt index 3c84f465..ebb66803 100644 --- a/domain/user/src/main/java/com/squirtles/user/FirebaseUserDataSource.kt +++ b/domain/user/src/main/java/com/squirtles/user/FirebaseUserDataSource.kt @@ -3,10 +3,13 @@ package com.squirtles.user import com.squirtles.model.User interface FirebaseUserDataSource { - suspend fun fetchUser(userId: String): Result + suspend fun fetchUser(uid: String): Result suspend fun createGoogleIdUser( - userId: String, + uid: String, + email: String, userName: String?, - userProfileImage: String?): Result - suspend fun updateUserName(userId: String, newUserName: String): Result + userProfileImage: String? + ): Result + suspend fun updateUserName(uid: String, newUserName: String): Result + suspend fun deleteUser(uid: String): Result } diff --git a/domain/user/src/main/java/com/squirtles/user/FirebaseUserRepository.kt b/domain/user/src/main/java/com/squirtles/user/FirebaseUserRepository.kt index c2f74658..30c66e32 100644 --- a/domain/user/src/main/java/com/squirtles/user/FirebaseUserRepository.kt +++ b/domain/user/src/main/java/com/squirtles/user/FirebaseUserRepository.kt @@ -3,8 +3,11 @@ package com.squirtles.user import com.squirtles.model.User interface FirebaseUserRepository { + val currentUser: String? // user - suspend fun createGoogleIdUser(userId: String, userName: String?, userProfileImage: String?): Result + fun signOut() + suspend fun createGoogleIdUser(uid: String, email: String, userName: String?, userProfileImage: String?): Result suspend fun fetchUser(userId: String): Result suspend fun updateUserName(userId: String, newUserName: String): Result + suspend fun deleteUser(uid: String): Result } diff --git a/domain/user/src/main/java/com/squirtles/user/usecase/ClearUserUseCase.kt b/domain/user/src/main/java/com/squirtles/user/usecase/ClearUserUseCase.kt deleted file mode 100644 index 36672586..00000000 --- a/domain/user/src/main/java/com/squirtles/user/usecase/ClearUserUseCase.kt +++ /dev/null @@ -1,10 +0,0 @@ -package com.squirtles.user.usecase - -import com.squirtles.user.LocalUserRepository -import javax.inject.Inject - -class ClearUserUseCase @Inject constructor( - private val localUserRepository: LocalUserRepository -) { - suspend operator fun invoke() = localUserRepository.clearUser() -} diff --git a/domain/user/src/main/java/com/squirtles/user/usecase/CreateGoogleIdUserUseCase.kt b/domain/user/src/main/java/com/squirtles/user/usecase/CreateGoogleIdUserUseCase.kt index b2f7d805..fb87d85a 100644 --- a/domain/user/src/main/java/com/squirtles/user/usecase/CreateGoogleIdUserUseCase.kt +++ b/domain/user/src/main/java/com/squirtles/user/usecase/CreateGoogleIdUserUseCase.kt @@ -10,16 +10,14 @@ class CreateGoogleIdUserUseCase @Inject constructor( private val firebaseUserRepository: FirebaseUserRepository ) { suspend operator fun invoke( - userId: String, + uid: String, + email: String, userName: String? = null, userProfileImage: String? = null - ): Result { - val createdUser = firebaseUserRepository.createGoogleIdUser(userId, userName, userProfileImage) - .onSuccess { user -> - // 생성된 유저의 userId 저장 후 user 반환 - localUserRepository.saveUserIdDataStore(user.userId) - localUserRepository.saveCurrentUser(user) - } - return createdUser - } + ): Result = firebaseUserRepository.createGoogleIdUser( + uid, + email, + userName, + userProfileImage + ) } diff --git a/domain/user/src/main/java/com/squirtles/user/usecase/DeleteAccountUseCase.kt b/domain/user/src/main/java/com/squirtles/user/usecase/DeleteAccountUseCase.kt new file mode 100644 index 00000000..2c8b9d64 --- /dev/null +++ b/domain/user/src/main/java/com/squirtles/user/usecase/DeleteAccountUseCase.kt @@ -0,0 +1,45 @@ +package com.squirtles.user.usecase + +import com.squirtles.favorite.usecase.DeleteFavoriteUseCase +import com.squirtles.pick.usecase.DeletePickUseCase +import com.squirtles.pick.usecase.FetchFavoritePicksUseCase +import com.squirtles.pick.usecase.FetchMyPicksUseCase +import com.squirtles.user.FirebaseUserRepository +import kotlinx.coroutines.async +import kotlinx.coroutines.awaitAll +import kotlinx.coroutines.coroutineScope +import javax.inject.Inject + +class DeleteAccountUseCase @Inject constructor( + private val getCurrentUidUseCase: GetCurrentUidUseCase, + private val fetchFavoritePicksUseCase: FetchFavoritePicksUseCase, + private val deleteFavoriteUseCase: DeleteFavoriteUseCase, + private val fetchMyPicksUseCase: FetchMyPicksUseCase, + private val deletePickUseCase: DeletePickUseCase, + private val firebaseUserRepository: FirebaseUserRepository +) { + suspend operator fun invoke(): Result { + return runCatching { + val currentUid = getCurrentUidUseCase() + requireNotNull(currentUid) + coroutineScope { + // 1. 좋아한 픽 삭제 + val favoritePicks = fetchFavoritePicksUseCase(currentUid).getOrNull() ?: emptyList() + val favoritePicksDeleteJobs = favoritePicks.map { pick -> + async { deleteFavoriteUseCase(pick.id, currentUid) } + } + + // 2. 등록한 픽 삭제 + val myPicks = fetchMyPicksUseCase(currentUid).getOrNull() ?: emptyList() + val myPicksDeleteJobs = myPicks.map { pick -> + async { deletePickUseCase(pick.id, currentUid) } + } + + // 모든 삭제 작업이 끝날 때까지 기다림 + (favoritePicksDeleteJobs + myPicksDeleteJobs).awaitAll() + } + // 3. Firebase Firestore 유저 정보 삭제 + firebaseUserRepository.deleteUser(currentUid) + } + } +} diff --git a/domain/user/src/main/java/com/squirtles/user/usecase/FetchUserUseCase.kt b/domain/user/src/main/java/com/squirtles/user/usecase/FetchUserUseCase.kt deleted file mode 100644 index cd5f9ed8..00000000 --- a/domain/user/src/main/java/com/squirtles/user/usecase/FetchUserUseCase.kt +++ /dev/null @@ -1,19 +0,0 @@ -package com.squirtles.user.usecase - -import com.squirtles.model.User -import com.squirtles.user.LocalUserRepository -import javax.inject.Inject - -class FetchUserUseCase @Inject constructor( - private val localUserRepository: LocalUserRepository, - private val fetchUserByIdUseCase: FetchUserByIdUseCase -) { - suspend operator fun invoke(userId: String): Result { - val user = fetchUserByIdUseCase(userId) // userId가 있으면 Firestore에서 유저 가져오기 - .onSuccess { user -> - localUserRepository.saveUserIdDataStore(user.userId) - localUserRepository.saveCurrentUser(user) // Firestore에서 가져온 user를 LocalDataSource에 저장 - } - return user - } -} diff --git a/domain/user/src/main/java/com/squirtles/user/usecase/GetCurrentUidUseCase.kt b/domain/user/src/main/java/com/squirtles/user/usecase/GetCurrentUidUseCase.kt new file mode 100644 index 00000000..fea1ffbb --- /dev/null +++ b/domain/user/src/main/java/com/squirtles/user/usecase/GetCurrentUidUseCase.kt @@ -0,0 +1,10 @@ +package com.squirtles.user.usecase + +import com.squirtles.user.FirebaseUserRepository +import javax.inject.Inject + +class GetCurrentUidUseCase @Inject constructor( + private val firebaseUserRepository: FirebaseUserRepository +) { + operator fun invoke() = firebaseUserRepository.currentUser +} diff --git a/domain/user/src/main/java/com/squirtles/user/usecase/GetCurrentUserUseCase.kt b/domain/user/src/main/java/com/squirtles/user/usecase/GetCurrentUserUseCase.kt deleted file mode 100644 index 5ee90743..00000000 --- a/domain/user/src/main/java/com/squirtles/user/usecase/GetCurrentUserUseCase.kt +++ /dev/null @@ -1,10 +0,0 @@ -package com.squirtles.user.usecase - -import com.squirtles.user.LocalUserRepository -import javax.inject.Inject - -class GetCurrentUserUseCase @Inject constructor( - private val localUserRepository: LocalUserRepository -) { - operator fun invoke() = localUserRepository.currentUser -} diff --git a/domain/user/src/main/java/com/squirtles/user/usecase/GetUserIdFromDataStoreUseCase.kt b/domain/user/src/main/java/com/squirtles/user/usecase/GetUserIdFromDataStoreUseCase.kt deleted file mode 100644 index 3812aa9c..00000000 --- a/domain/user/src/main/java/com/squirtles/user/usecase/GetUserIdFromDataStoreUseCase.kt +++ /dev/null @@ -1,10 +0,0 @@ -package com.squirtles.user.usecase - -import com.squirtles.user.LocalUserRepository -import javax.inject.Inject - -class GetUserIdFromDataStoreUseCase @Inject constructor( - private val localUserRepository: LocalUserRepository -) { - operator fun invoke() = localUserRepository.readUserIdDataStore() -} diff --git a/domain/user/src/main/java/com/squirtles/user/usecase/SignOutUseCase.kt b/domain/user/src/main/java/com/squirtles/user/usecase/SignOutUseCase.kt new file mode 100644 index 00000000..f5ac64e6 --- /dev/null +++ b/domain/user/src/main/java/com/squirtles/user/usecase/SignOutUseCase.kt @@ -0,0 +1,10 @@ +package com.squirtles.domain.user.usecase + +import com.squirtles.user.FirebaseUserRepository +import javax.inject.Inject + +class SignOutUseCase @Inject constructor( + private val firebaseUserRepository: FirebaseUserRepository +) { + operator fun invoke() = firebaseUserRepository.signOut() +} diff --git a/feature/create/src/main/java/com/squirtles/create/CreatePickViewModel.kt b/feature/create/src/main/java/com/squirtles/create/CreatePickViewModel.kt index add9845b..6128b313 100644 --- a/feature/create/src/main/java/com/squirtles/create/CreatePickViewModel.kt +++ b/feature/create/src/main/java/com/squirtles/create/CreatePickViewModel.kt @@ -14,11 +14,11 @@ import com.squirtles.model.Pick import com.squirtles.model.Song import com.squirtles.navigation.SearchRoute import com.squirtles.pick.usecase.CreatePickUseCase -import com.squirtles.user.usecase.GetCurrentUserUseCase +import com.squirtles.user.usecase.FetchUserByIdUseCase +import com.squirtles.user.usecase.GetCurrentUidUseCase import com.squirtles.util.serializableType import com.squirtles.util.throttleFirst import dagger.hilt.android.lifecycle.HiltViewModel -import kotlinx.coroutines.FlowPreview import kotlinx.coroutines.flow.MutableSharedFlow import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.asStateFlow @@ -26,14 +26,14 @@ import kotlinx.coroutines.launch import javax.inject.Inject import kotlin.reflect.typeOf -@OptIn(FlowPreview::class) @HiltViewModel class CreatePickViewModel @Inject constructor( savedStateHandle: SavedStateHandle, getLastLocationUseCase: GetLastLocationUseCase, private val fetchMusicVideoUseCase: FetchMusicVideoUseCase, private val createPickUseCase: CreatePickUseCase, - private val getCurrentUserUseCase: GetCurrentUserUseCase + private val getCurrentUidUseCase: GetCurrentUidUseCase, + private val fetchUserByIdUseCase: FetchUserByIdUseCase ) : ViewModel() { private val typeMap = mapOf(typeOf() to serializableType()) @@ -88,32 +88,35 @@ class CreatePickViewModel @Inject constructor( } val musicVideo = fetchMusicVideoUseCase(song) - + val currentUid = getCurrentUidUseCase() /* 등록 결과 - pick ID 담긴 Result */ - getCurrentUserUseCase()?.let { user -> - val createResult = createPickUseCase( - Pick( - id = "", - song = song, - comment = _comment.value, - createdAt = "", - createdBy = Creator( - userId = user.userId, - userName = user.userName - ), - location = LocationPoint(lastLocation!!.latitude, lastLocation!!.longitude), - musicVideoUrl = musicVideo?.previewUrl ?: "", - musicVideoThumbnailUrl = musicVideo?.thumbnailUrl ?: "" - ) - ) + if (currentUid != null) { + fetchUserByIdUseCase(currentUid) + .onSuccess { user -> + val createResult = createPickUseCase( + Pick( + id = "", + song = song, + comment = _comment.value, + createdAt = "", + createdBy = Creator( + uid = user.uid, + userName = user.userName + ), + location = LocationPoint(lastLocation!!.latitude, lastLocation!!.longitude), + musicVideoUrl = musicVideo?.previewUrl ?: "", + musicVideoThumbnailUrl = musicVideo?.thumbnailUrl ?: "" + ) + ) - createResult.onSuccess { pickId -> - _createPickUiState.emit(CreateUiState.Success(pickId)) - }.onFailure { - /* TODO: Firestore 등록 실패처리 */ - _createPickUiState.emit(CreateUiState.Error) - Log.d("CreatePickViewModel", createResult.exceptionOrNull()?.message.toString()) - } + createResult.onSuccess { pickId -> + _createPickUiState.emit(CreateUiState.Success(pickId)) + }.onFailure { + /* TODO: Firestore 등록 실패처리 */ + _createPickUiState.emit(CreateUiState.Error) + Log.d("CreatePickViewModel", createResult.exceptionOrNull()?.message.toString()) + } + } } } } diff --git a/feature/favorite/src/main/java/com/squirtles/favorite/FavoriteListViewModel.kt b/feature/favorite/src/main/java/com/squirtles/favorite/FavoriteListViewModel.kt index ec5a6159..c31d6c04 100644 --- a/feature/favorite/src/main/java/com/squirtles/favorite/FavoriteListViewModel.kt +++ b/feature/favorite/src/main/java/com/squirtles/favorite/FavoriteListViewModel.kt @@ -5,7 +5,7 @@ import com.squirtles.order.usecase.GetFavoriteListOrderUseCase import com.squirtles.order.usecase.SaveFavoriteListOrderUseCase import com.squirtles.pick.usecase.FetchFavoritePicksUseCase import com.squirtles.picklist.PickListViewModel -import com.squirtles.user.usecase.GetCurrentUserUseCase +import com.squirtles.user.usecase.GetCurrentUidUseCase import dagger.hilt.android.lifecycle.HiltViewModel import javax.inject.Inject @@ -15,11 +15,11 @@ class FavoriteListViewModel @Inject constructor( getFavoriteListOrderUseCase: GetFavoriteListOrderUseCase, saveFavoriteListOrderUseCase: SaveFavoriteListOrderUseCase, deleteFavoriteUseCase: DeleteFavoriteUseCase, - getCurrentUserUseCase: GetCurrentUserUseCase + getCurrentUidUseCase: GetCurrentUidUseCase ) : PickListViewModel( fetchPickListUseCase = fetchFavoritePicksUseCase, getPickListOrderUseCase = getFavoriteListOrderUseCase, savePickListOrderUseCase = saveFavoriteListOrderUseCase, removePickUseCase = deleteFavoriteUseCase, - getCurrentUserUseCase = getCurrentUserUseCase + getCurrentUidUseCase = getCurrentUidUseCase ) diff --git a/feature/favorite/src/main/java/com/squirtles/favorite/FavoriteScreen.kt b/feature/favorite/src/main/java/com/squirtles/favorite/FavoriteScreen.kt index 1ac6245c..9c12dcef 100644 --- a/feature/favorite/src/main/java/com/squirtles/favorite/FavoriteScreen.kt +++ b/feature/favorite/src/main/java/com/squirtles/favorite/FavoriteScreen.kt @@ -41,7 +41,7 @@ fun FavoriteScreen( toggleSelectedPick = favoriteListViewModel::toggleSelectedPick, deleteSelectedPicks = favoriteListViewModel::deleteSelectedPicks, getUserId = { - favoriteListViewModel.getUserId().toString() + favoriteListViewModel.getUid().toString() } ) } From 05aca563a5627591a55d5ba7106201162c27d60e Mon Sep 17 00:00:00 2001 From: miller198 Date: Wed, 26 Mar 2025 16:23:26 +0900 Subject: [PATCH 38/62] [refactor] userId -> uid --- .../src/main/java/com/squirtles/firebase/model/Mapper.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/data/firebase/src/main/java/com/squirtles/firebase/model/Mapper.kt b/data/firebase/src/main/java/com/squirtles/firebase/model/Mapper.kt index 9a97d2e2..a7dd4cc2 100644 --- a/data/firebase/src/main/java/com/squirtles/firebase/model/Mapper.kt +++ b/data/firebase/src/main/java/com/squirtles/firebase/model/Mapper.kt @@ -19,7 +19,7 @@ fun Pick.toFirebasePick(): FirebasePick = FirebasePick( artistName = song.artistName, artwork = mapOf("url" to song.imageUrl, "bgColor" to song.bgColor.toRgbString()), comment = comment, - createdBy = mapOf("userId" to createdBy.uid, "userName" to createdBy.userName), + createdBy = mapOf("uid" to createdBy.uid, "userName" to createdBy.userName), externalUrl = song.externalUrl, favoriteCount = favoriteCount, genreNames = song.genreNames, From 4b1e2ba2677353d35ddef3792c6b22b960ae37d6 Mon Sep 17 00:00:00 2001 From: miller198 Date: Wed, 26 Mar 2025 16:31:05 +0900 Subject: [PATCH 39/62] =?UTF-8?q?[refactor]=20datasource=20=EC=9D=B8?= =?UTF-8?q?=ED=84=B0=ED=8E=98=EC=9D=B4=EC=8A=A4=20=EC=9C=84=EC=B9=98=20?= =?UTF-8?q?=EC=A1=B0=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../applemusic/AppleMusicDataSource.kt | 0 .../applemusic/di/AppleMusicModule.kt | 2 +- .../favorite/FirebaseFavoriteDataSource.kt | 0 .../squirtles/pick/FirebasePickDataSource.kt | 13 ++++++++ .../pick/FirebasePickDataSourceImpl.kt | 21 +++++-------- .../pick/FirebasePickRepositoryImpl.kt | 31 ++++++++++++++----- .../squirtles/user/FirebaseUserDataSource.kt | 10 ++---- .../user/FirebaseUserDataSourceImpl.kt | 16 +++------- .../user/FirebaseUserRepositoryImpl.kt | 14 +++++++-- .../com/squirtles/user/LocalUserDataSource.kt | 0 .../squirtles/pick/FirebasePickDataSource.kt | 12 ------- .../squirtles/pick/FirebasePickRepository.kt | 2 +- 12 files changed, 65 insertions(+), 56 deletions(-) rename {domain => data}/applemusic/src/main/java/com/squirtles/applemusic/AppleMusicDataSource.kt (100%) rename {domain => data}/favorite/src/main/java/com/squirtles/favorite/FirebaseFavoriteDataSource.kt (100%) create mode 100644 data/pick/src/main/java/com/squirtles/pick/FirebasePickDataSource.kt rename {domain => data}/user/src/main/java/com/squirtles/user/FirebaseUserDataSource.kt (52%) rename {domain => data}/user/src/main/java/com/squirtles/user/LocalUserDataSource.kt (100%) delete mode 100644 domain/pick/src/main/java/com/squirtles/pick/FirebasePickDataSource.kt diff --git a/domain/applemusic/src/main/java/com/squirtles/applemusic/AppleMusicDataSource.kt b/data/applemusic/src/main/java/com/squirtles/applemusic/AppleMusicDataSource.kt similarity index 100% rename from domain/applemusic/src/main/java/com/squirtles/applemusic/AppleMusicDataSource.kt rename to data/applemusic/src/main/java/com/squirtles/applemusic/AppleMusicDataSource.kt diff --git a/data/applemusic/src/main/java/com/squirtles/applemusic/di/AppleMusicModule.kt b/data/applemusic/src/main/java/com/squirtles/applemusic/di/AppleMusicModule.kt index 84e46391..05d119ec 100644 --- a/data/applemusic/src/main/java/com/squirtles/applemusic/di/AppleMusicModule.kt +++ b/data/applemusic/src/main/java/com/squirtles/applemusic/di/AppleMusicModule.kt @@ -1,7 +1,7 @@ package com.squirtles.applemusic.di -import com.squirtles.applemusic.AppleMusicDataSourceImpl import com.squirtles.applemusic.AppleMusicDataSource +import com.squirtles.applemusic.AppleMusicDataSourceImpl import com.squirtles.applemusic.AppleMusicRepository import com.squirtles.applemusic.AppleMusicRepositoryImpl import com.squirtles.applemusic.api.AppleMusicApi diff --git a/domain/favorite/src/main/java/com/squirtles/favorite/FirebaseFavoriteDataSource.kt b/data/favorite/src/main/java/com/squirtles/favorite/FirebaseFavoriteDataSource.kt similarity index 100% rename from domain/favorite/src/main/java/com/squirtles/favorite/FirebaseFavoriteDataSource.kt rename to data/favorite/src/main/java/com/squirtles/favorite/FirebaseFavoriteDataSource.kt diff --git a/data/pick/src/main/java/com/squirtles/pick/FirebasePickDataSource.kt b/data/pick/src/main/java/com/squirtles/pick/FirebasePickDataSource.kt new file mode 100644 index 00000000..5d94b34e --- /dev/null +++ b/data/pick/src/main/java/com/squirtles/pick/FirebasePickDataSource.kt @@ -0,0 +1,13 @@ +package com.squirtles.pick + +import com.squirtles.firebase.model.FirebasePick +import com.squirtles.model.Pick + +interface FirebasePickDataSource { + suspend fun fetchPick(pickId: String): Result + suspend fun fetchPicksInArea(lat: Double, lng: Double, radiusInM: Double): Result> + suspend fun createPick(firebasePick: FirebasePick, userId: String): Result + suspend fun deletePick(pickId: String, userId: String): Result + suspend fun fetchMyPicks(userId: String): Result> + suspend fun fetchFavoritePicks(userId: String): Result> +} diff --git a/data/pick/src/main/java/com/squirtles/pick/FirebasePickDataSourceImpl.kt b/data/pick/src/main/java/com/squirtles/pick/FirebasePickDataSourceImpl.kt index 124246ff..f79933dc 100644 --- a/data/pick/src/main/java/com/squirtles/pick/FirebasePickDataSourceImpl.kt +++ b/data/pick/src/main/java/com/squirtles/pick/FirebasePickDataSourceImpl.kt @@ -16,9 +16,6 @@ import com.squirtles.firebase.FirebaseDocumentFields import com.squirtles.firebase.model.FirebaseFavorite import com.squirtles.firebase.model.FirebasePick import com.squirtles.firebase.model.FirebaseUser -import com.squirtles.firebase.model.toFirebasePick -import com.squirtles.firebase.model.toPick -import com.squirtles.model.Pick import kotlinx.coroutines.suspendCancellableCoroutine import kotlinx.coroutines.tasks.await import javax.inject.Inject @@ -32,11 +29,10 @@ class FirebasePickDataSourceImpl @Inject constructor( ) : BaseFirebaseDataSource(db), FirebasePickDataSource { /* Fetches a pick by ID from Firestore */ - override suspend fun fetchPick(pickId: String): Result { + override suspend fun fetchPick(pickId: String): Result { return runCatching { val pickSnap = fetchDocumentSnapshot(FirebaseCollections.Picks, pickId).getOrThrow() - val firestorePick = pickSnap.toObject()?.copy(id = pickId) - firestorePick?.toPick()!! + pickSnap.toObject()?.copy(id = pickId)!! }.onFailure { exception -> Log.e(TAG_LOG, "Failed to fetch a pick", exception) } @@ -47,7 +43,7 @@ class FirebasePickDataSourceImpl @Inject constructor( lat: Double, lng: Double, radiusInM: Double - ): Result> { + ): Result> { val center = GeoLocation(lat, lng) val bounds = GeoFireUtils.getGeoHashQueryBounds(center, radiusInM) @@ -66,7 +62,7 @@ class FirebasePickDataSourceImpl @Inject constructor( .filter { doc -> isAccurate(doc, center, radiusInM) }.mapNotNull { doc -> - doc.toObject()?.toPick()?.copy(id = doc.id) + doc.toObject()!!.copy(id = doc.id) } } }.onFailure { e -> @@ -75,11 +71,10 @@ class FirebasePickDataSourceImpl @Inject constructor( } /* Creates a new pick in Firestore */ - override suspend fun createPick(pick: Pick): Result { - val firebasePick = pick.toFirebasePick() + override suspend fun createPick(firebasePick: FirebasePick, userId: String): Result { return runCatching { val pickRef = addDocument(FirebaseCollections.Picks, firebasePick).getOrThrow() - updateCurrentUserPick(pick.createdBy.uid, pickRef.id) + updateCurrentUserPick(userId, pickRef.id) pickRef.id }.onFailure { Log.e(TAG_LOG, "Failed to create a pick", it) @@ -107,7 +102,7 @@ class FirebasePickDataSourceImpl @Inject constructor( } } - override suspend fun fetchMyPicks(userId: String): Result> { + override suspend fun fetchMyPicks(userId: String): Result> { return runCatching { val userDocument = fetchDocumentSnapshot(FirebaseCollections.Users, userId).getOrThrow() userDocument.toObject()?.myPicks!!.map { @@ -116,7 +111,7 @@ class FirebasePickDataSourceImpl @Inject constructor( } } - override suspend fun fetchFavoritePicks(userId: String): Result> { + override suspend fun fetchFavoritePicks(userId: String): Result> { return runCatching { val favoriteDocuments = fetchFavoritesByUserId(userId) favoriteDocuments.map { docSnap -> diff --git a/data/pick/src/main/java/com/squirtles/pick/FirebasePickRepositoryImpl.kt b/data/pick/src/main/java/com/squirtles/pick/FirebasePickRepositoryImpl.kt index 3e772dbe..e71c8ab5 100644 --- a/data/pick/src/main/java/com/squirtles/pick/FirebasePickRepositoryImpl.kt +++ b/data/pick/src/main/java/com/squirtles/pick/FirebasePickRepositoryImpl.kt @@ -1,7 +1,10 @@ package com.squirtles.pick +import android.util.Log import com.squirtles.firebase.FirebaseException import com.squirtles.firebase.handleResult +import com.squirtles.firebase.model.toFirebasePick +import com.squirtles.firebase.model.toPick import com.squirtles.model.Pick import javax.inject.Inject import javax.inject.Singleton @@ -12,7 +15,8 @@ class FirebasePickRepositoryImpl @Inject constructor( ) : FirebasePickRepository { override suspend fun createPick(pick: Pick): Result { - return pickDataSource.createPick(pick) + val firebasePick = pick.toFirebasePick() + return pickDataSource.createPick(firebasePick, pick.createdBy.uid) } override suspend fun deletePick(pickId: String, userId: String): Result { @@ -20,11 +24,17 @@ class FirebasePickRepositoryImpl @Inject constructor( } override suspend fun fetchPick(pickID: String): Result { - return pickDataSource.fetchPick(pickID) + return runCatching { + val firebasePick = pickDataSource.fetchPick(pickID).getOrThrow() + firebasePick.toPick() + } } override suspend fun fetchMyPicks(userId: String): Result> { - return pickDataSource.fetchMyPicks(userId) + return runCatching { + val firebasePicks = pickDataSource.fetchMyPicks(userId).getOrThrow() + firebasePicks.map { it.toPick() } + } } override suspend fun fetchPicksInArea( @@ -32,13 +42,20 @@ class FirebasePickRepositoryImpl @Inject constructor( lng: Double, radiusInM: Double ): Result> { - val pickList = pickDataSource.fetchPicksInArea(lat, lng, radiusInM) - return handleResult(FirebaseException.NoSuchPickInRadiusException()) { - pickList.getOrThrow().ifEmpty { null } + return runCatching { + val firebasePicks = pickDataSource.fetchPicksInArea(lat, lng, radiusInM).getOrThrow() + firebasePicks.map { it.toPick() } } } override suspend fun fetchFavoritePicks(userId: String): Result> { - return pickDataSource.fetchFavoritePicks(userId) + return runCatching { + val firebasePicks = pickDataSource.fetchFavoritePicks(userId).getOrThrow() + firebasePicks.map { it.toPick() } + } + } + + companion object { + const val TAG_LOG = "FirebasePickRepositoryImpl" } } diff --git a/domain/user/src/main/java/com/squirtles/user/FirebaseUserDataSource.kt b/data/user/src/main/java/com/squirtles/user/FirebaseUserDataSource.kt similarity index 52% rename from domain/user/src/main/java/com/squirtles/user/FirebaseUserDataSource.kt rename to data/user/src/main/java/com/squirtles/user/FirebaseUserDataSource.kt index ebb66803..43ed496a 100644 --- a/domain/user/src/main/java/com/squirtles/user/FirebaseUserDataSource.kt +++ b/data/user/src/main/java/com/squirtles/user/FirebaseUserDataSource.kt @@ -1,15 +1,11 @@ package com.squirtles.user +import com.squirtles.firebase.model.FirebaseUser import com.squirtles.model.User interface FirebaseUserDataSource { - suspend fun fetchUser(uid: String): Result - suspend fun createGoogleIdUser( - uid: String, - email: String, - userName: String?, - userProfileImage: String? - ): Result + suspend fun fetchUser(uid: String): Result + suspend fun createGoogleIdUser(uid: String, newUser: FirebaseUser): Result suspend fun updateUserName(uid: String, newUserName: String): Result suspend fun deleteUser(uid: String): Result } diff --git a/data/user/src/main/java/com/squirtles/user/FirebaseUserDataSourceImpl.kt b/data/user/src/main/java/com/squirtles/user/FirebaseUserDataSourceImpl.kt index fc59410e..67ce2165 100644 --- a/data/user/src/main/java/com/squirtles/user/FirebaseUserDataSourceImpl.kt +++ b/data/user/src/main/java/com/squirtles/user/FirebaseUserDataSourceImpl.kt @@ -8,8 +8,6 @@ import com.squirtles.firebase.BaseFirebaseDataSource import com.squirtles.firebase.FirebaseCollections import com.squirtles.firebase.FirebaseDocumentFields import com.squirtles.firebase.model.FirebaseUser -import com.squirtles.firebase.model.toUser -import com.squirtles.model.User import kotlinx.coroutines.tasks.await import javax.inject.Inject import javax.inject.Singleton @@ -19,25 +17,19 @@ class FirebaseUserDataSourceImpl @Inject constructor( private val db: FirebaseFirestore ) : BaseFirebaseDataSource(db), FirebaseUserDataSource { - override suspend fun createGoogleIdUser( - uid: String, - email: String, - userName: String?, - userProfileImage: String? - ): Result { + override suspend fun createGoogleIdUser(uid: String, newUser: FirebaseUser): Result { return runCatching { - val newUser = FirebaseUser(email = email, name = userName, profileImage = userProfileImage) setDocument(FirebaseCollections.Users, uid, newUser) - newUser.toUser().copy(uid = uid) + newUser }.onFailure { e -> Log.e(TAG_LOG, e.message.toString()) } } - override suspend fun fetchUser(uid: String): Result { + override suspend fun fetchUser(uid: String): Result { return runCatching { val userDocSnap = fetchDocumentSnapshot(FirebaseCollections.Users, uid).getOrThrow() - userDocSnap.toObject()?.toUser()?.copy(uid = uid)!! + userDocSnap.toObject()!! }.onFailure { e -> Log.e(TAG_LOG, "Failed to fetch a user", e) } diff --git a/data/user/src/main/java/com/squirtles/user/FirebaseUserRepositoryImpl.kt b/data/user/src/main/java/com/squirtles/user/FirebaseUserRepositoryImpl.kt index 6ec56812..de0006c6 100644 --- a/data/user/src/main/java/com/squirtles/user/FirebaseUserRepositoryImpl.kt +++ b/data/user/src/main/java/com/squirtles/user/FirebaseUserRepositoryImpl.kt @@ -1,8 +1,9 @@ package com.squirtles.user import com.google.firebase.auth.FirebaseAuth +import com.squirtles.firebase.model.FirebaseUser +import com.squirtles.firebase.model.toUser import com.squirtles.model.User -import kotlinx.coroutines.tasks.await import javax.inject.Singleton @Singleton @@ -20,11 +21,18 @@ class FirebaseUserRepositoryImpl( userName: String?, userProfileImage: String? ): Result { - return userDataSource.createGoogleIdUser(uid, email, userName, userProfileImage) + return runCatching { + val newUser = FirebaseUser(email = email, name = userName, profileImage = userProfileImage) + val firebaseUser = userDataSource.createGoogleIdUser(uid, newUser).getOrThrow() + firebaseUser.toUser().copy(uid = uid) + } } override suspend fun fetchUser(userId: String): Result { - return userDataSource.fetchUser(userId) + return runCatching{ + val firebaseUser = userDataSource.fetchUser(userId).getOrThrow() + firebaseUser.toUser().copy(uid = userId) + } } override suspend fun updateUserName(userId: String, newUserName: String): Result { diff --git a/domain/user/src/main/java/com/squirtles/user/LocalUserDataSource.kt b/data/user/src/main/java/com/squirtles/user/LocalUserDataSource.kt similarity index 100% rename from domain/user/src/main/java/com/squirtles/user/LocalUserDataSource.kt rename to data/user/src/main/java/com/squirtles/user/LocalUserDataSource.kt diff --git a/domain/pick/src/main/java/com/squirtles/pick/FirebasePickDataSource.kt b/domain/pick/src/main/java/com/squirtles/pick/FirebasePickDataSource.kt deleted file mode 100644 index 3547efd4..00000000 --- a/domain/pick/src/main/java/com/squirtles/pick/FirebasePickDataSource.kt +++ /dev/null @@ -1,12 +0,0 @@ -package com.squirtles.pick - -import com.squirtles.model.Pick - -interface FirebasePickDataSource { - suspend fun fetchPick(pickID: String): Result - suspend fun fetchPicksInArea(lat: Double, lng: Double, radiusInM: Double): Result> - suspend fun createPick(pick: Pick): Result - suspend fun deletePick(pickId: String, userId: String): Result - suspend fun fetchMyPicks(userId: String): Result> - suspend fun fetchFavoritePicks(userId: String): Result> -} diff --git a/domain/pick/src/main/java/com/squirtles/pick/FirebasePickRepository.kt b/domain/pick/src/main/java/com/squirtles/pick/FirebasePickRepository.kt index fbce8701..4b6eec17 100644 --- a/domain/pick/src/main/java/com/squirtles/pick/FirebasePickRepository.kt +++ b/domain/pick/src/main/java/com/squirtles/pick/FirebasePickRepository.kt @@ -5,7 +5,7 @@ import com.squirtles.model.Pick interface FirebasePickRepository { suspend fun createPick(pick: Pick): Result suspend fun deletePick(pickId: String, userId: String): Result - suspend fun fetchPick(pickID: String): Result + suspend fun fetchPick(pickId: String): Result suspend fun fetchPicksInArea(lat: Double, lng: Double, radiusInM: Double): Result> suspend fun fetchMyPicks(userId: String): Result> suspend fun fetchFavoritePicks(userId: String): Result> From 45d131eecde94b4e91f037e5d1262107c72afbae Mon Sep 17 00:00:00 2001 From: miller198 Date: Wed, 26 Mar 2025 17:17:42 +0900 Subject: [PATCH 40/62] =?UTF-8?q?[feature]=20:feature:search=20=EB=AA=A8?= =?UTF-8?q?=EB=93=88=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/build.gradle.kts | 2 +- .../create/navigation/CreateNavigation.kt | 41 +++ .../musicroad/main/navigation/MainNavHost.kt | 5 + .../main/navigation/MainNavigator.kt | 2 +- .../search/navigation/SearchNavigation.kt | 27 -- core/picklist/src/main/AndroidManifest.xml | 4 - core/util/src/main/AndroidManifest.xml | 4 - .../com/squirtles/create/CreatePickUiState.kt | 5 - .../create/navigation/CreateNavigation.kt | 41 +++ feature/search/.gitignore | 1 + feature/search/build.gradle.kts | 17 + feature/search/consumer-rules.pro | 0 feature/search/proguard-rules.pro | 21 ++ .../search/ExampleInstrumentedTest.kt | 24 ++ .../com/squirtles/search/SearchMusicScreen.kt | 338 ++++++++++++++++++ .../com/squirtles/search/SearchUiConstants.kt | 12 + .../com/squirtles/search/SearchUiState.kt | 6 + .../com/squirtles/search/SearchViewModel.kt | 63 ++++ .../search/navigation/SearchNavigation.kt | 25 ++ .../search/src/main/res/values/strings.xml | 13 + .../com/squirtles/search/ExampleUnitTest.kt | 17 + gradle/libs.versions.toml | 9 +- settings.gradle.kts | 1 + 23 files changed, 633 insertions(+), 45 deletions(-) create mode 100644 app/src/main/java/com/squirtles/musicroad/create/navigation/CreateNavigation.kt delete mode 100644 core/picklist/src/main/AndroidManifest.xml delete mode 100644 core/util/src/main/AndroidManifest.xml create mode 100644 feature/create/src/main/java/com/squirtles/create/navigation/CreateNavigation.kt create mode 100644 feature/search/.gitignore create mode 100644 feature/search/build.gradle.kts create mode 100644 feature/search/consumer-rules.pro create mode 100644 feature/search/proguard-rules.pro create mode 100644 feature/search/src/androidTest/java/com/squirtles/search/ExampleInstrumentedTest.kt create mode 100644 feature/search/src/main/java/com/squirtles/search/SearchMusicScreen.kt create mode 100644 feature/search/src/main/java/com/squirtles/search/SearchUiConstants.kt create mode 100644 feature/search/src/main/java/com/squirtles/search/SearchUiState.kt create mode 100644 feature/search/src/main/java/com/squirtles/search/SearchViewModel.kt create mode 100644 feature/search/src/main/java/com/squirtles/search/navigation/SearchNavigation.kt create mode 100644 feature/search/src/main/res/values/strings.xml create mode 100644 feature/search/src/test/java/com/squirtles/search/ExampleUnitTest.kt diff --git a/app/build.gradle.kts b/app/build.gradle.kts index d5f79315..390efa08 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -157,7 +157,7 @@ dependencies { // Paging implementation(libs.androidx.paging.runtime) - implementation(libs.androidx.paging.compose.android) + implementation(libs.androidx.paging.compose) // Serialization implementation(libs.kotlinx.serialization.json) diff --git a/app/src/main/java/com/squirtles/musicroad/create/navigation/CreateNavigation.kt b/app/src/main/java/com/squirtles/musicroad/create/navigation/CreateNavigation.kt new file mode 100644 index 00000000..0e28c0fa --- /dev/null +++ b/app/src/main/java/com/squirtles/musicroad/create/navigation/CreateNavigation.kt @@ -0,0 +1,41 @@ +package com.squirtles.create.navigation + +import androidx.navigation.NavController +import androidx.navigation.NavGraphBuilder +import androidx.navigation.NavOptions +import androidx.navigation.compose.composable +import androidx.navigation.toRoute +import com.squirtles.domain.model.Song +import com.squirtles.musicroad.create.CreatePickScreen +import com.squirtles.musicroad.navigation.SearchRoute +import com.squirtles.musicroad.utils.serializableType +import java.net.URLEncoder +import java.nio.charset.StandardCharsets +import kotlin.reflect.typeOf + +fun NavController.navigateCreate(song: Song, navOptions: NavOptions? = null) { + val encodedSong = song.copy( + previewUrl = URLEncoder.encode(song.previewUrl, StandardCharsets.UTF_8.toString()), + externalUrl = URLEncoder.encode(song.externalUrl, StandardCharsets.UTF_8.toString()), + genreNames = song.genreNames.map { URLEncoder.encode(it, StandardCharsets.UTF_8.toString()) }, + imageUrl = URLEncoder.encode(song.imageUrl, StandardCharsets.UTF_8.toString()) + ) + navigate(SearchRoute.Create(encodedSong), navOptions) +} + +fun NavGraphBuilder.createNavGraph( + onBackClick: () -> Unit, + onCreateClick: (String) -> Unit +) { + composable( + typeMap = mapOf(typeOf() to serializableType()) + ) { backStackEntry -> + val song = backStackEntry.toRoute().song + + CreatePickScreen( + song = song, + onBackClick = onBackClick, + onCreateClick = onCreateClick, + ) + } +} diff --git a/app/src/main/java/com/squirtles/musicroad/main/navigation/MainNavHost.kt b/app/src/main/java/com/squirtles/musicroad/main/navigation/MainNavHost.kt index f546f96b..398e3cc5 100644 --- a/app/src/main/java/com/squirtles/musicroad/main/navigation/MainNavHost.kt +++ b/app/src/main/java/com/squirtles/musicroad/main/navigation/MainNavHost.kt @@ -4,6 +4,7 @@ import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier import androidx.hilt.navigation.compose.hiltViewModel import androidx.navigation.compose.NavHost +import com.squirtles.create.navigation.createNavGraph import com.squirtles.musicroad.favorite.navigation.favoriteNavGraph import com.squirtles.musicroad.map.MapViewModel import com.squirtles.musicroad.map.navigation.mapNavGraph @@ -36,6 +37,10 @@ internal fun MainNavHost( searchNavGraph( onBackClick = navigator::popBackStackIfNotMap, onItemClick = navigator::navigateCreate, + ) + + createNavGraph( + onBackClick = navigator::popBackStackIfNotMap, onCreateClick = { pickId -> navigator.navigatePickDetail(pickId, true) }, diff --git a/app/src/main/java/com/squirtles/musicroad/main/navigation/MainNavigator.kt b/app/src/main/java/com/squirtles/musicroad/main/navigation/MainNavigator.kt index e07b5442..a7f734e4 100644 --- a/app/src/main/java/com/squirtles/musicroad/main/navigation/MainNavigator.kt +++ b/app/src/main/java/com/squirtles/musicroad/main/navigation/MainNavigator.kt @@ -8,12 +8,12 @@ import androidx.navigation.NavHostController import androidx.navigation.compose.currentBackStackEntryAsState import androidx.navigation.compose.rememberNavController import androidx.navigation.navOptions +import com.squirtles.create.navigation.navigateCreate import com.squirtles.domain.model.Song import com.squirtles.musicroad.favorite.navigation.navigateFavorite import com.squirtles.musicroad.map.navigation.navigateMap import com.squirtles.musicroad.map.navigation.navigatePickDetail import com.squirtles.musicroad.navigation.Route -import com.squirtles.musicroad.search.navigation.navigateCreate import com.squirtles.musicroad.search.navigation.navigateSearch import com.squirtles.musicroad.userinfo.navigation.navigateEditNotificationSetting import com.squirtles.musicroad.userinfo.navigation.navigateEditProfile diff --git a/app/src/main/java/com/squirtles/musicroad/search/navigation/SearchNavigation.kt b/app/src/main/java/com/squirtles/musicroad/search/navigation/SearchNavigation.kt index b46e2049..b9ccfa2a 100644 --- a/app/src/main/java/com/squirtles/musicroad/search/navigation/SearchNavigation.kt +++ b/app/src/main/java/com/squirtles/musicroad/search/navigation/SearchNavigation.kt @@ -4,33 +4,17 @@ import androidx.navigation.NavController import androidx.navigation.NavGraphBuilder import androidx.navigation.NavOptions import androidx.navigation.compose.composable -import androidx.navigation.toRoute import com.squirtles.domain.model.Song -import com.squirtles.musicroad.create.CreatePickScreen import com.squirtles.musicroad.navigation.MainRoute -import com.squirtles.musicroad.navigation.SearchRoute import com.squirtles.musicroad.search.SearchMusicScreen -import java.net.URLEncoder -import java.nio.charset.StandardCharsets fun NavController.navigateSearch(navOptions: NavOptions? = null) { navigate(MainRoute.Search, navOptions) } -fun NavController.navigateCreate(song: Song, navOptions: NavOptions? = null) { - val encodedSong = song.copy( - previewUrl = URLEncoder.encode(song.previewUrl, StandardCharsets.UTF_8.toString()), - externalUrl = URLEncoder.encode(song.externalUrl, StandardCharsets.UTF_8.toString()), - genreNames = song.genreNames.map { URLEncoder.encode(it, StandardCharsets.UTF_8.toString()) }, - imageUrl = URLEncoder.encode(song.imageUrl, StandardCharsets.UTF_8.toString()) - ) - navigate(SearchRoute.Create(encodedSong), navOptions) -} - fun NavGraphBuilder.searchNavGraph( onBackClick: () -> Unit, onItemClick: (Song) -> Unit, - onCreateClick: (String) -> Unit ) { composable { SearchMusicScreen( @@ -38,15 +22,4 @@ fun NavGraphBuilder.searchNavGraph( onItemClick = onItemClick, // Create 이동 ) } - composable( - typeMap = SearchRoute.Create.typeMap - ) { backStackEntry -> - val song = backStackEntry.toRoute().song - - CreatePickScreen( - song = song, - onBackClick = onBackClick, - onCreateClick = onCreateClick, - ) - } } diff --git a/core/picklist/src/main/AndroidManifest.xml b/core/picklist/src/main/AndroidManifest.xml deleted file mode 100644 index 8bdb7e14..00000000 --- a/core/picklist/src/main/AndroidManifest.xml +++ /dev/null @@ -1,4 +0,0 @@ - - - - diff --git a/core/util/src/main/AndroidManifest.xml b/core/util/src/main/AndroidManifest.xml deleted file mode 100644 index 8bdb7e14..00000000 --- a/core/util/src/main/AndroidManifest.xml +++ /dev/null @@ -1,4 +0,0 @@ - - - - diff --git a/feature/create/src/main/java/com/squirtles/create/CreatePickUiState.kt b/feature/create/src/main/java/com/squirtles/create/CreatePickUiState.kt index a6fae3e3..ac42389e 100644 --- a/feature/create/src/main/java/com/squirtles/create/CreatePickUiState.kt +++ b/feature/create/src/main/java/com/squirtles/create/CreatePickUiState.kt @@ -1,10 +1,5 @@ package com.squirtles.create -sealed class SearchUiState { - data object HotResult : SearchUiState() - data object SearchResult : SearchUiState() -} - sealed class CreateUiState { data object Default : CreateUiState() data class Success(val data: T) : CreateUiState() diff --git a/feature/create/src/main/java/com/squirtles/create/navigation/CreateNavigation.kt b/feature/create/src/main/java/com/squirtles/create/navigation/CreateNavigation.kt new file mode 100644 index 00000000..113e275d --- /dev/null +++ b/feature/create/src/main/java/com/squirtles/create/navigation/CreateNavigation.kt @@ -0,0 +1,41 @@ +package com.squirtles.create.navigation + +import androidx.navigation.NavController +import androidx.navigation.NavGraphBuilder +import androidx.navigation.NavOptions +import androidx.navigation.compose.composable +import androidx.navigation.toRoute +import com.squirtles.create.CreatePickScreen +import com.squirtles.model.Song +import com.squirtles.navigation.SearchRoute +import com.squirtles.util.serializableType +import java.net.URLEncoder +import java.nio.charset.StandardCharsets +import kotlin.reflect.typeOf + +fun NavController.navigateCreate(song: Song, navOptions: NavOptions? = null) { + val encodedSong = song.copy( + previewUrl = URLEncoder.encode(song.previewUrl, StandardCharsets.UTF_8.toString()), + externalUrl = URLEncoder.encode(song.externalUrl, StandardCharsets.UTF_8.toString()), + genreNames = song.genreNames.map { URLEncoder.encode(it, StandardCharsets.UTF_8.toString()) }, + imageUrl = URLEncoder.encode(song.imageUrl, StandardCharsets.UTF_8.toString()) + ) + navigate(SearchRoute.Create(encodedSong), navOptions) +} + +fun NavGraphBuilder.createNavGraph( + onBackClick: () -> Unit, + onCreateClick: (String) -> Unit +) { + composable( + typeMap = mapOf(typeOf() to serializableType()) + ) { backStackEntry -> + val song = backStackEntry.toRoute().song + + CreatePickScreen( + song = song, + onBackClick = onBackClick, + onCreateClick = onCreateClick, + ) + } +} diff --git a/feature/search/.gitignore b/feature/search/.gitignore new file mode 100644 index 00000000..42afabfd --- /dev/null +++ b/feature/search/.gitignore @@ -0,0 +1 @@ +/build \ No newline at end of file diff --git a/feature/search/build.gradle.kts b/feature/search/build.gradle.kts new file mode 100644 index 00000000..ca673ee5 --- /dev/null +++ b/feature/search/build.gradle.kts @@ -0,0 +1,17 @@ +plugins { + alias(libs.plugins.musicroad.feature) +} + +android { + namespace = "com.squirtles.search" +} + +dependencies { + implementation(projects.core.common) + implementation(projects.domain.applemusic) + + implementation(libs.androidx.paging.compose) + + testImplementation(libs.junit) + androidTestImplementation(libs.bundles.test) +} diff --git a/feature/search/consumer-rules.pro b/feature/search/consumer-rules.pro new file mode 100644 index 00000000..e69de29b diff --git a/feature/search/proguard-rules.pro b/feature/search/proguard-rules.pro new file mode 100644 index 00000000..481bb434 --- /dev/null +++ b/feature/search/proguard-rules.pro @@ -0,0 +1,21 @@ +# Add project specific ProGuard rules here. +# You can control the set of applied configuration files using the +# proguardFiles setting in build.gradle. +# +# For more details, see +# http://developer.android.com/guide/developing/tools/proguard.html + +# If your project uses WebView with JS, uncomment the following +# and specify the fully qualified class name to the JavaScript interface +# class: +#-keepclassmembers class fqcn.of.javascript.interface.for.webview { +# public *; +#} + +# Uncomment this to preserve the line number information for +# debugging stack traces. +#-keepattributes SourceFile,LineNumberTable + +# If you keep the line number information, uncomment this to +# hide the original source file name. +#-renamesourcefileattribute SourceFile \ No newline at end of file diff --git a/feature/search/src/androidTest/java/com/squirtles/search/ExampleInstrumentedTest.kt b/feature/search/src/androidTest/java/com/squirtles/search/ExampleInstrumentedTest.kt new file mode 100644 index 00000000..cbce775e --- /dev/null +++ b/feature/search/src/androidTest/java/com/squirtles/search/ExampleInstrumentedTest.kt @@ -0,0 +1,24 @@ +package com.example.search + +import androidx.test.platform.app.InstrumentationRegistry +import androidx.test.ext.junit.runners.AndroidJUnit4 + +import org.junit.Test +import org.junit.runner.RunWith + +import org.junit.Assert.* + +/** + * Instrumented test, which will execute on an Android device. + * + * See [testing documentation](http://d.android.com/tools/testing). + */ +@RunWith(AndroidJUnit4::class) +class ExampleInstrumentedTest { + @Test + fun useAppContext() { + // Context of the app under test. + val appContext = InstrumentationRegistry.getInstrumentation().targetContext + assertEquals("com.example.search.test", appContext.packageName) + } +} diff --git a/feature/search/src/main/java/com/squirtles/search/SearchMusicScreen.kt b/feature/search/src/main/java/com/squirtles/search/SearchMusicScreen.kt new file mode 100644 index 00000000..275000d5 --- /dev/null +++ b/feature/search/src/main/java/com/squirtles/search/SearchMusicScreen.kt @@ -0,0 +1,338 @@ +package com.squirtles.search + +import androidx.compose.foundation.background +import androidx.compose.foundation.clickable +import androidx.compose.foundation.gestures.detectTapGestures +import androidx.compose.foundation.interaction.MutableInteractionSource +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.WindowInsets +import androidx.compose.foundation.layout.asPaddingValues +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.navigationBars +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size +import androidx.compose.foundation.layout.statusBars +import androidx.compose.foundation.lazy.LazyColumn +import androidx.compose.foundation.shape.CircleShape +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.foundation.text.KeyboardActions +import androidx.compose.foundation.text.KeyboardOptions +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.automirrored.filled.ArrowBack +import androidx.compose.material3.Icon +import androidx.compose.material3.IconButton +import androidx.compose.material3.LinearProgressIndicator +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.OutlinedTextField +import androidx.compose.material3.Scaffold +import androidx.compose.material3.Text +import androidx.compose.material3.TextFieldDefaults +import androidx.compose.material3.ripple +import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.compose.runtime.remember +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.clip +import androidx.compose.ui.focus.FocusManager +import androidx.compose.ui.graphics.Brush +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.input.pointer.pointerInput +import androidx.compose.ui.platform.LocalFocusManager +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.text.TextStyle +import androidx.compose.ui.text.input.ImeAction +import androidx.compose.ui.text.input.KeyboardType +import androidx.compose.ui.text.style.TextOverflow +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.dp +import androidx.hilt.navigation.compose.hiltViewModel +import androidx.lifecycle.compose.collectAsStateWithLifecycle +import androidx.paging.LoadState +import androidx.paging.compose.LazyPagingItems +import androidx.paging.compose.collectAsLazyPagingItems +import com.squirtles.common.ui.AlbumImage +import com.squirtles.common.ui.Constants.COLOR_STOPS +import com.squirtles.common.ui.Constants.REQUEST_IMAGE_SIZE_DEFAULT +import com.squirtles.common.ui.HorizontalSpacer +import com.squirtles.common.ui.VerticalSpacer +import com.squirtles.common.ui.theme.Gray +import com.squirtles.common.ui.theme.White +import com.squirtles.model.Song +import com.squirtles.search.SearchUiConstants.DefaultPadding +import com.squirtles.search.SearchUiConstants.ImageSize +import com.squirtles.search.SearchUiConstants.ItemSpacing +import com.squirtles.search.SearchUiConstants.SearchBarHeight + +@Composable +fun SearchMusicScreen( + onBackClick: () -> Unit, + onItemClick: (Song) -> Unit, + searchViewModel: SearchViewModel = hiltViewModel(), +) { + val focusManager = LocalFocusManager.current + val searchText by searchViewModel.searchText.collectAsStateWithLifecycle() + val searchUiState by searchViewModel.searchUiState.collectAsStateWithLifecycle() + val searchResult = searchViewModel.searchResult.collectAsLazyPagingItems() + + Scaffold( + contentWindowInsets = WindowInsets.navigationBars, + topBar = { + Box( + modifier = Modifier + .fillMaxWidth() + .padding(WindowInsets.statusBars.asPaddingValues()) + .padding(top = 16.dp) + ) { + SearchTopBar( + keyword = searchText, + onValueChange = searchViewModel::onSearchTextChange, + onBackClick = onBackClick, + focusManager = focusManager + ) + } + }, + ) { innerPadding -> + + Box( + modifier = Modifier + .fillMaxSize() + .background(Brush.verticalGradient(colorStops = COLOR_STOPS)) + .padding(innerPadding) + .addFocusCleaner(focusManager) + ) { + if (searchResult.loadState.refresh is LoadState.Loading || + searchResult.loadState.append is LoadState.Loading + ) { + LinearProgressIndicator( + modifier = Modifier + .fillMaxWidth() + .padding(top = 10.dp), + trackColor = Gray + ) + } + + // 검색 결과 + when (searchUiState) { + is SearchUiState.HotResult -> { + // TODO HOT 리스트 + } + + is SearchUiState.SearchResult -> { + SearchResult( + searchResult = searchResult, + onItemClick = { song -> + focusManager.clearFocus() + onItemClick(song) + } + ) + } + } + } + } +} + +@Composable +private fun SearchTopBar( + keyword: String, + onValueChange: (String) -> Unit, + onBackClick: () -> Unit, + focusManager: FocusManager +) { + + Row( + modifier = Modifier + .fillMaxWidth() + .height(SearchBarHeight) + .padding(end = DefaultPadding), + verticalAlignment = Alignment.CenterVertically + ) { + IconButton( + onClick = { + onBackClick() + }, + modifier = Modifier.padding(start = 4.dp) + ) { + Icon( + imageVector = Icons.AutoMirrored.Filled.ArrowBack, + contentDescription = stringResource(id = R.string.top_app_bar_back_description), + tint = White + ) + } + HorizontalSpacer(8) + + OutlinedTextField( + value = keyword, + onValueChange = onValueChange, + modifier = Modifier.weight(1f), + placeholder = { + Text(stringResource(id = R.string.search)) + }, + // 키보드 완료버튼 -> Search로 변경, 누르면 Search 동작 실행 + keyboardOptions = KeyboardOptions( + keyboardType = KeyboardType.Text, + imeAction = ImeAction.Search + ), + keyboardActions = KeyboardActions( + onSearch = { + focusManager.clearFocus() + } + ), + singleLine = true, + shape = CircleShape, + colors = TextFieldDefaults.colors( + focusedTextColor = White, + unfocusedTextColor = White, + focusedContainerColor = Color.Transparent, + unfocusedContainerColor = Color.Transparent, + cursorColor = White, + focusedIndicatorColor = White, + unfocusedIndicatorColor = White, + focusedPlaceholderColor = Gray, + unfocusedPlaceholderColor = White + ) + ) + } +} + +@Composable +private fun SearchResult( + searchResult: LazyPagingItems, + onItemClick: (Song) -> Unit +) { + Column( + modifier = Modifier.fillMaxSize() + ) { + VerticalSpacer(20) + + TextWithColorAndStyle( + text = stringResource(id = R.string.result), + textColor = White, + textStyle = MaterialTheme.typography.titleLarge, + modifier = Modifier.padding(start = DefaultPadding) + ) + VerticalSpacer(20) + + LazyColumn(modifier = Modifier.padding(bottom = DefaultPadding)) { + items(searchResult.itemCount) { index -> + searchResult[index]?.let { + SongItem(it) { + onItemClick(it) + } + } + } + } + + if (searchResult.loadState.refresh is LoadState.NotLoading) { + if (searchResult.itemCount == 0) { + Text( + text = stringResource(id = R.string.search_music_empty_description), + modifier = Modifier.padding(horizontal = DefaultPadding), + color = White + ) + } + } else if (searchResult.loadState.refresh is LoadState.Error) { + Text( + text = stringResource(id = R.string.search_music_error_description), + modifier = Modifier.padding(horizontal = DefaultPadding), + color = White + ) + } + } +} + +@Composable +private fun SongItem( + song: Song, + onItemClick: () -> Unit, +) { + Row( + modifier = Modifier + .fillMaxWidth() + .clickable( + interactionSource = remember { MutableInteractionSource() }, + indication = ripple(color = White), + ) { + onItemClick() + } + .padding(horizontal = DefaultPadding, vertical = ItemSpacing / 2), + verticalAlignment = Alignment.CenterVertically + ) { + AlbumImage( + imageUrl = song.getImageUrlWithSize(REQUEST_IMAGE_SIZE_DEFAULT.width, REQUEST_IMAGE_SIZE_DEFAULT.height), + modifier = Modifier + .size(ImageSize) + .clip(RoundedCornerShape(16.dp)) + ) + + HorizontalSpacer(16) + Column { + TextWithColorAndStyle( + text = song.songName, + textColor = White, + textStyle = MaterialTheme.typography.bodyLarge + ) + TextWithColorAndStyle( + text = song.artistName, + textColor = Gray, + textStyle = MaterialTheme.typography.bodyMedium + ) + TextWithColorAndStyle( + text = song.albumName, + textColor = Gray, + textStyle = MaterialTheme.typography.bodyMedium + ) + } + } +} + +@Composable +fun TextWithColorAndStyle( + text: String, + textColor: Color, + textStyle: TextStyle, + modifier: Modifier = Modifier +) { + Text( + text = text, + modifier = modifier, + color = textColor, + overflow = TextOverflow.Ellipsis, + maxLines = 1, + style = textStyle + ) +} + +fun Modifier.addFocusCleaner(focusManager: FocusManager, doOnClear: () -> Unit = {}): Modifier { + return this.pointerInput(Unit) { + detectTapGestures( + onTap = { + doOnClear() + focusManager.clearFocus() + } + ) + } +} + +@Preview +@Composable +fun SongItemPreview() { + val song = Song( + id = "1", + songName = "Ditto", + artistName = "String", + albumName = "Ditto", + imageUrl = "https://i.scdn.co/image/ab67616d0000b2733d98a0ae7c78a3a9babaf8af", + genreNames = listOf(), + bgColor = android.graphics.Color.RED, + externalUrl = "", + previewUrl = "", + ) + SongItem(song) { + + } +} diff --git a/feature/search/src/main/java/com/squirtles/search/SearchUiConstants.kt b/feature/search/src/main/java/com/squirtles/search/SearchUiConstants.kt new file mode 100644 index 00000000..d093c226 --- /dev/null +++ b/feature/search/src/main/java/com/squirtles/search/SearchUiConstants.kt @@ -0,0 +1,12 @@ +package com.squirtles.search + +import androidx.compose.ui.unit.dp + +object SearchUiConstants { + val SearchBarHeight = 56.dp + val DefaultPadding = 16.dp + val ItemSpacing = 24.dp + val ImageSize = 56.dp +} + + diff --git a/feature/search/src/main/java/com/squirtles/search/SearchUiState.kt b/feature/search/src/main/java/com/squirtles/search/SearchUiState.kt new file mode 100644 index 00000000..6ef6effb --- /dev/null +++ b/feature/search/src/main/java/com/squirtles/search/SearchUiState.kt @@ -0,0 +1,6 @@ +package com.squirtles.search + +sealed class SearchUiState { + data object HotResult : SearchUiState() + data object SearchResult : SearchUiState() +} diff --git a/feature/search/src/main/java/com/squirtles/search/SearchViewModel.kt b/feature/search/src/main/java/com/squirtles/search/SearchViewModel.kt new file mode 100644 index 00000000..d25dd4c5 --- /dev/null +++ b/feature/search/src/main/java/com/squirtles/search/SearchViewModel.kt @@ -0,0 +1,63 @@ +package com.squirtles.search + +import androidx.lifecycle.ViewModel +import androidx.lifecycle.viewModelScope +import androidx.paging.PagingData +import androidx.paging.cachedIn +import com.squirtles.applemusic.usecase.FetchSongsUseCase +import com.squirtles.model.Song +import dagger.hilt.android.lifecycle.HiltViewModel +import kotlinx.coroutines.FlowPreview +import kotlinx.coroutines.Job +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.asStateFlow +import kotlinx.coroutines.flow.collectLatest +import kotlinx.coroutines.flow.debounce +import kotlinx.coroutines.launch +import javax.inject.Inject + +@OptIn(FlowPreview::class) +@HiltViewModel +class SearchViewModel @Inject constructor( + private val fetchSongsUseCase: FetchSongsUseCase, +) : ViewModel() { + + private val _searchUiState = MutableStateFlow(SearchUiState.HotResult) + val searchUiState = _searchUiState.asStateFlow() + + private val _searchResult = MutableStateFlow>(PagingData.empty()) + val searchResult = _searchResult.asStateFlow() + + private val _searchText = MutableStateFlow("") + val searchText = _searchText.asStateFlow() + + private var searchJob: Job? = null + + init { + viewModelScope.launch { + _searchText + .debounce(300) + .collect { searchKeyword -> + searchJob?.cancel() + if (searchKeyword.isNotBlank()) { + searchJob = launch { searchSongs(searchKeyword) } + } else { + _searchUiState.emit(SearchUiState.HotResult) + } + } + } + } + + private suspend fun searchSongs(searchKeyword: String) { + fetchSongsUseCase(searchKeyword) + .cachedIn(viewModelScope) + .collectLatest { + _searchResult.emit(it) + _searchUiState.emit(SearchUiState.SearchResult) + } + } + + fun onSearchTextChange(text: String) { + _searchText.value = text + } +} diff --git a/feature/search/src/main/java/com/squirtles/search/navigation/SearchNavigation.kt b/feature/search/src/main/java/com/squirtles/search/navigation/SearchNavigation.kt new file mode 100644 index 00000000..2f461ce2 --- /dev/null +++ b/feature/search/src/main/java/com/squirtles/search/navigation/SearchNavigation.kt @@ -0,0 +1,25 @@ +package com.squirtles.search.navigation + +import androidx.navigation.NavController +import androidx.navigation.NavGraphBuilder +import androidx.navigation.NavOptions +import androidx.navigation.compose.composable +import com.squirtles.model.Song +import com.squirtles.navigation.MainRoute +import com.squirtles.search.SearchMusicScreen + +fun NavController.navigateSearch(navOptions: NavOptions? = null) { + navigate(MainRoute.Search, navOptions) +} + +fun NavGraphBuilder.searchNavGraph( + onBackClick: () -> Unit, + onItemClick: (Song) -> Unit, +) { + composable { + SearchMusicScreen( + onBackClick = onBackClick, + onItemClick = onItemClick, // Create 이동 + ) + } +} diff --git a/feature/search/src/main/res/values/strings.xml b/feature/search/src/main/res/values/strings.xml new file mode 100644 index 00000000..08bbf77d --- /dev/null +++ b/feature/search/src/main/res/values/strings.xml @@ -0,0 +1,13 @@ + + MusicRoad + + 상단 바 뒤로 가기 버튼 + + + 검색 + 검색 결과 + 노래 앨범 이미지 + 노래 검색 버튼 + 검색 결과가 없습니다. + 검색 결과를 불러올 수 없습니다. + diff --git a/feature/search/src/test/java/com/squirtles/search/ExampleUnitTest.kt b/feature/search/src/test/java/com/squirtles/search/ExampleUnitTest.kt new file mode 100644 index 00000000..7993c561 --- /dev/null +++ b/feature/search/src/test/java/com/squirtles/search/ExampleUnitTest.kt @@ -0,0 +1,17 @@ +package com.example.search + +import org.junit.Test + +import org.junit.Assert.* + +/** + * Example local unit test, which will execute on the development machine (host). + * + * See [testing documentation](http://d.android.com/tools/testing). + */ +class ExampleUnitTest { + @Test + fun addition_isCorrect() { + assertEquals(4, 2 + 2) + } +} diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 91cb969c..be596a3f 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -138,6 +138,7 @@ kotlinx-immutable = { group = "org.jetbrains.kotlinx", name = "kotlinx-collectio # Paging androidx-paging-runtime = { module = "androidx.paging:paging-runtime", version.ref = "pagingRuntime" } +androidx-paging-compose = { group = "androidx.paging", name = "paging-compose-android", version.ref = "pagingComposeAndroid" } # Auth androidx-credentials = { module = "androidx.credentials:credentials", version.ref = "androidx-credentials" } @@ -191,9 +192,6 @@ coil = { module = "io.coil-kt.coil3:coil", version.ref = "coil" } coil-compose = { module = "io.coil-kt.coil3:coil-compose", version.ref = "coil" } coil-network-okhttp = { module = "io.coil-kt.coil3:coil-network-okhttp", version.ref = "coil" } -# Paging -androidx-paging-compose-android = { group = "androidx.paging", name = "paging-compose-android", version.ref = "pagingComposeAndroid" } - # Build-logic android-gradlePlugin = { group = "com.android.tools.build", name = "gradle", version.ref = "agp" } android-tools-common = { group = "com.android.tools", name = "common", version.ref = "androidTools" } @@ -293,6 +291,11 @@ test = [ "androidx-espresso-core", ] +paging = [ + "androidx-paging-compose", + "androidx-paging-runtime", +] + # Plugins [plugins] android-application = { id = "com.android.application", version.ref = "agp" } diff --git a/settings.gradle.kts b/settings.gradle.kts index 66ec8ab7..8df32a6d 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -59,3 +59,4 @@ include(":core:account") include(":feature:create") include(":feature:favorite") include(":core:buildconfig") +include(":feature:search") From 6532939dcf316973fbe38a6cf27b20b634cd184c Mon Sep 17 00:00:00 2001 From: miller198 Date: Wed, 26 Mar 2025 18:09:42 +0900 Subject: [PATCH 41/62] =?UTF-8?q?[feature]=20:feature:userinfo=20=EB=AA=A8?= =?UTF-8?q?=EB=93=88=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../musicroad/main/navigation/MainNavHost.kt | 7 +- .../main/navigation/MainNavigator.kt | 2 +- .../mypick/navigation/MyPickNavigation.kt | 28 ++ .../userinfo/navigation/UserInfoNavigation.kt | 15 - .../convention/MusicRoadFeaturePlugin.kt | 2 +- .../squirtles/common/ui/MessageAlertDialog.kt | 19 +- .../com/squirtles/navigation/MainRoute.kt | 2 +- .../com/squirtles/navigation/ProfileRoute.kt | 16 - .../com/squirtles/navigation/UserInfoRoute.kt | 16 + feature/search/build.gradle.kts | 1 - feature/userinfo/.gitignore | 1 + feature/userinfo/build.gradle.kts | 18 + feature/userinfo/consumer-rules.pro | 0 feature/userinfo/proguard-rules.pro | 21 ++ .../userinfo/ExampleInstrumentedTest.kt | 24 ++ .../squirtles/userinfo/UserInfoConstants.kt | 13 + .../squirtles/userinfo/UserInfoViewModel.kt | 52 +++ .../squirtles/userinfo/components/MenuItem.kt | 13 + .../userinfo/components/UserInfoMenus.kt | 103 ++++++ .../userinfo/navigation/UserInfoNavigation.kt | 62 ++++ .../screen/EditNotificationSettingScreen.kt | 49 +++ .../userinfo/screen/EditProfileScreen.kt | 326 ++++++++++++++++++ .../userinfo/screen/UserInfoScreen.kt | 273 +++++++++++++++ .../res/drawable/img_user_default_profile.jpg | Bin 0 -> 15062 bytes .../userinfo/src/main/res/values/strings.xml | 48 +++ .../com/squirtles/userinfo/ExampleUnitTest.kt | 17 + settings.gradle.kts | 1 + 27 files changed, 1085 insertions(+), 44 deletions(-) create mode 100644 app/src/main/java/com/squirtles/musicroad/mypick/navigation/MyPickNavigation.kt delete mode 100644 core/navigation/src/main/java/com/squirtles/navigation/ProfileRoute.kt create mode 100644 core/navigation/src/main/java/com/squirtles/navigation/UserInfoRoute.kt create mode 100644 feature/userinfo/.gitignore create mode 100644 feature/userinfo/build.gradle.kts create mode 100644 feature/userinfo/consumer-rules.pro create mode 100644 feature/userinfo/proguard-rules.pro create mode 100644 feature/userinfo/src/androidTest/java/com/squirtles/userinfo/ExampleInstrumentedTest.kt create mode 100644 feature/userinfo/src/main/java/com/squirtles/userinfo/UserInfoConstants.kt create mode 100644 feature/userinfo/src/main/java/com/squirtles/userinfo/UserInfoViewModel.kt create mode 100644 feature/userinfo/src/main/java/com/squirtles/userinfo/components/MenuItem.kt create mode 100644 feature/userinfo/src/main/java/com/squirtles/userinfo/components/UserInfoMenus.kt create mode 100644 feature/userinfo/src/main/java/com/squirtles/userinfo/navigation/UserInfoNavigation.kt create mode 100644 feature/userinfo/src/main/java/com/squirtles/userinfo/screen/EditNotificationSettingScreen.kt create mode 100644 feature/userinfo/src/main/java/com/squirtles/userinfo/screen/EditProfileScreen.kt create mode 100644 feature/userinfo/src/main/java/com/squirtles/userinfo/screen/UserInfoScreen.kt create mode 100644 feature/userinfo/src/main/res/drawable/img_user_default_profile.jpg create mode 100644 feature/userinfo/src/main/res/values/strings.xml create mode 100644 feature/userinfo/src/test/java/com/squirtles/userinfo/ExampleUnitTest.kt diff --git a/app/src/main/java/com/squirtles/musicroad/main/navigation/MainNavHost.kt b/app/src/main/java/com/squirtles/musicroad/main/navigation/MainNavHost.kt index 398e3cc5..ca02089c 100644 --- a/app/src/main/java/com/squirtles/musicroad/main/navigation/MainNavHost.kt +++ b/app/src/main/java/com/squirtles/musicroad/main/navigation/MainNavHost.kt @@ -9,6 +9,7 @@ import com.squirtles.musicroad.favorite.navigation.favoriteNavGraph import com.squirtles.musicroad.map.MapViewModel import com.squirtles.musicroad.map.navigation.mapNavGraph import com.squirtles.musicroad.media.PlayerServiceViewModel +import com.squirtles.musicroad.mypick.navigation.myPickNavGraph import com.squirtles.musicroad.search.navigation.searchNavGraph import com.squirtles.musicroad.userinfo.navigation.userInfoNavGraph @@ -53,12 +54,16 @@ internal fun MainNavHost( userInfoNavGraph( onBackClick = navigator::popBackStackIfNotMap, - onItemClick = navigator::navigatePickDetail, onBackToMapClick = navigator::navigateMap, onFavoritePicksClick = navigator::navigateFavorite, onMyPicksClick = navigator::navigateMyPicks, onEditProfileClick = navigator::navigateEditProfile, onEditNotificationClick = navigator::navigateEditNotificationSetting, ) + + myPickNavGraph( + onBackClick = navigator::popBackStackIfNotMap, + onItemClick = navigator::navigatePickDetail, + ) } } diff --git a/app/src/main/java/com/squirtles/musicroad/main/navigation/MainNavigator.kt b/app/src/main/java/com/squirtles/musicroad/main/navigation/MainNavigator.kt index a7f734e4..9401f8cc 100644 --- a/app/src/main/java/com/squirtles/musicroad/main/navigation/MainNavigator.kt +++ b/app/src/main/java/com/squirtles/musicroad/main/navigation/MainNavigator.kt @@ -13,11 +13,11 @@ import com.squirtles.domain.model.Song import com.squirtles.musicroad.favorite.navigation.navigateFavorite import com.squirtles.musicroad.map.navigation.navigateMap import com.squirtles.musicroad.map.navigation.navigatePickDetail +import com.squirtles.musicroad.mypick.navigation.navigateMyPicks import com.squirtles.musicroad.navigation.Route import com.squirtles.musicroad.search.navigation.navigateSearch import com.squirtles.musicroad.userinfo.navigation.navigateEditNotificationSetting import com.squirtles.musicroad.userinfo.navigation.navigateEditProfile -import com.squirtles.musicroad.userinfo.navigation.navigateMyPicks import com.squirtles.musicroad.userinfo.navigation.navigateUserInfo internal class MainNavigator( diff --git a/app/src/main/java/com/squirtles/musicroad/mypick/navigation/MyPickNavigation.kt b/app/src/main/java/com/squirtles/musicroad/mypick/navigation/MyPickNavigation.kt new file mode 100644 index 00000000..9371ba69 --- /dev/null +++ b/app/src/main/java/com/squirtles/musicroad/mypick/navigation/MyPickNavigation.kt @@ -0,0 +1,28 @@ +package com.squirtles.musicroad.mypick.navigation + +import androidx.navigation.NavController +import androidx.navigation.NavGraphBuilder +import androidx.navigation.NavOptions +import androidx.navigation.compose.composable +import androidx.navigation.toRoute +import com.squirtles.musicroad.mypick.MyPickScreen +import com.squirtles.musicroad.navigation.UserInfoRoute + +fun NavController.navigateMyPicks(uid: String, navOptions: NavOptions) { + navigate(UserInfoRoute.MyPicks(uid), navOptions) +} + +fun NavGraphBuilder.myPickNavGraph( + onBackClick: () -> Unit, + onItemClick: (String) -> Unit, +) { + composable { backStackEntry -> + val uid = backStackEntry.toRoute().uid + + MyPickScreen( + uid = uid, + onBackClick = onBackClick, + onItemClick = onItemClick + ) + } +} diff --git a/app/src/main/java/com/squirtles/musicroad/userinfo/navigation/UserInfoNavigation.kt b/app/src/main/java/com/squirtles/musicroad/userinfo/navigation/UserInfoNavigation.kt index 5c357224..2d081930 100644 --- a/app/src/main/java/com/squirtles/musicroad/userinfo/navigation/UserInfoNavigation.kt +++ b/app/src/main/java/com/squirtles/musicroad/userinfo/navigation/UserInfoNavigation.kt @@ -24,13 +24,8 @@ fun NavController.navigateEditNotificationSetting(navOptions: NavOptions? = null navigate(UserInfoRoute.EditNotification, navOptions) } -fun NavController.navigateMyPicks(uid: String, navOptions: NavOptions) { - navigate(UserInfoRoute.MyPicks(uid), navOptions) -} - fun NavGraphBuilder.userInfoNavGraph( onBackClick: () -> Unit, - onItemClick: (String) -> Unit, onBackToMapClick: () -> Unit, onFavoritePicksClick: (String) -> Unit, onMyPicksClick: (String) -> Unit, @@ -65,14 +60,4 @@ fun NavGraphBuilder.userInfoNavGraph( onBackClick = onBackClick ) } - - composable { backStackEntry -> - val uid = backStackEntry.toRoute().uid - - MyPickScreen( - uid = uid, - onBackClick = onBackClick, - onItemClick = onItemClick - ) - } } diff --git a/build-logic/convention/src/main/java/com/squirtles/convention/MusicRoadFeaturePlugin.kt b/build-logic/convention/src/main/java/com/squirtles/convention/MusicRoadFeaturePlugin.kt index 505923e6..7c8304f6 100644 --- a/build-logic/convention/src/main/java/com/squirtles/convention/MusicRoadFeaturePlugin.kt +++ b/build-logic/convention/src/main/java/com/squirtles/convention/MusicRoadFeaturePlugin.kt @@ -19,9 +19,9 @@ class MusicRoadFeaturePlugin : Plugin { dependencies { implementation(project(":core:model")) implementation(project(":core:util")) + implementation(project(":core:common")) implementation(project(":core:navigation")) -// implementation(libs.getBundle("compose")) implementation(libs.getBundle("navigation")) } } diff --git a/core/common/src/main/java/com/squirtles/common/ui/MessageAlertDialog.kt b/core/common/src/main/java/com/squirtles/common/ui/MessageAlertDialog.kt index a8866cbf..e36bc386 100644 --- a/core/common/src/main/java/com/squirtles/common/ui/MessageAlertDialog.kt +++ b/core/common/src/main/java/com/squirtles/common/ui/MessageAlertDialog.kt @@ -31,9 +31,10 @@ import com.squirtles.common.ui.theme.White @OptIn(ExperimentalMaterial3Api::class) @Composable fun MessageAlertDialog( - onDismissRequest: () -> Unit, title: String, body: String, + onDismissRequest: () -> Unit, + showBody: Boolean = true, buttons: @Composable RowScope.() -> Unit, ) { BasicAlertDialog( @@ -55,13 +56,15 @@ fun MessageAlertDialog( style = MaterialTheme.typography.bodyLarge ) - VerticalSpacer(8) + if (showBody) { + VerticalSpacer(8) - Text( - text = body, - color = Black, - style = MaterialTheme.typography.bodyLarge - ) + Text( + text = body, + color = Black, + style = MaterialTheme.typography.bodyLarge + ) + } VerticalSpacer(24) @@ -100,7 +103,7 @@ fun DialogTextButton( @Preview(showBackground = true) @Composable -private fun DeletePickDialogPreview() { +fun DeletePickDialogPreview() { MusicRoadTheme { MessageAlertDialog( onDismissRequest = {}, diff --git a/core/navigation/src/main/java/com/squirtles/navigation/MainRoute.kt b/core/navigation/src/main/java/com/squirtles/navigation/MainRoute.kt index a6c9621a..b9f6c460 100644 --- a/core/navigation/src/main/java/com/squirtles/navigation/MainRoute.kt +++ b/core/navigation/src/main/java/com/squirtles/navigation/MainRoute.kt @@ -11,5 +11,5 @@ sealed interface MainRoute : Route { data class Favorite(val userId: String) : MainRoute @Serializable - data class Profile(val userId: String) : MainRoute + data class UserInfo(val uid: String) : MainRoute } diff --git a/core/navigation/src/main/java/com/squirtles/navigation/ProfileRoute.kt b/core/navigation/src/main/java/com/squirtles/navigation/ProfileRoute.kt deleted file mode 100644 index 132230e4..00000000 --- a/core/navigation/src/main/java/com/squirtles/navigation/ProfileRoute.kt +++ /dev/null @@ -1,16 +0,0 @@ -package com.squirtles.navigation - -import kotlinx.serialization.Serializable - -@Serializable -sealed interface ProfileRoute : Route { - @Serializable - data class MyPicks(val userId: String) : ProfileRoute - - @Serializable - data object Setting : ProfileRoute - - @Serializable - data object Notification : ProfileRoute -} - diff --git a/core/navigation/src/main/java/com/squirtles/navigation/UserInfoRoute.kt b/core/navigation/src/main/java/com/squirtles/navigation/UserInfoRoute.kt new file mode 100644 index 00000000..92ccdc66 --- /dev/null +++ b/core/navigation/src/main/java/com/squirtles/navigation/UserInfoRoute.kt @@ -0,0 +1,16 @@ +package com.squirtles.navigation + +import kotlinx.serialization.Serializable + +@Serializable +sealed interface UserInfoRoute : Route { + @Serializable + data class MyPicks(val uid: String) : UserInfoRoute + + @Serializable + data class EditProfile(val userName: String) : UserInfoRoute + + @Serializable + data object EditNotification : UserInfoRoute +} + diff --git a/feature/search/build.gradle.kts b/feature/search/build.gradle.kts index ca673ee5..03ffdc8c 100644 --- a/feature/search/build.gradle.kts +++ b/feature/search/build.gradle.kts @@ -7,7 +7,6 @@ android { } dependencies { - implementation(projects.core.common) implementation(projects.domain.applemusic) implementation(libs.androidx.paging.compose) diff --git a/feature/userinfo/.gitignore b/feature/userinfo/.gitignore new file mode 100644 index 00000000..42afabfd --- /dev/null +++ b/feature/userinfo/.gitignore @@ -0,0 +1 @@ +/build \ No newline at end of file diff --git a/feature/userinfo/build.gradle.kts b/feature/userinfo/build.gradle.kts new file mode 100644 index 00000000..f435468f --- /dev/null +++ b/feature/userinfo/build.gradle.kts @@ -0,0 +1,18 @@ +plugins { + alias(libs.plugins.musicroad.feature) +} + +android { + namespace = "com.squirtles.userinfo" +} + +dependencies { + implementation(projects.core.account) + implementation(projects.domain.user) + + implementation(libs.coil) + implementation(libs.coil.compose) + + testImplementation(libs.junit) + androidTestImplementation(libs.bundles.test) +} diff --git a/feature/userinfo/consumer-rules.pro b/feature/userinfo/consumer-rules.pro new file mode 100644 index 00000000..e69de29b diff --git a/feature/userinfo/proguard-rules.pro b/feature/userinfo/proguard-rules.pro new file mode 100644 index 00000000..481bb434 --- /dev/null +++ b/feature/userinfo/proguard-rules.pro @@ -0,0 +1,21 @@ +# Add project specific ProGuard rules here. +# You can control the set of applied configuration files using the +# proguardFiles setting in build.gradle. +# +# For more details, see +# http://developer.android.com/guide/developing/tools/proguard.html + +# If your project uses WebView with JS, uncomment the following +# and specify the fully qualified class name to the JavaScript interface +# class: +#-keepclassmembers class fqcn.of.javascript.interface.for.webview { +# public *; +#} + +# Uncomment this to preserve the line number information for +# debugging stack traces. +#-keepattributes SourceFile,LineNumberTable + +# If you keep the line number information, uncomment this to +# hide the original source file name. +#-renamesourcefileattribute SourceFile \ No newline at end of file diff --git a/feature/userinfo/src/androidTest/java/com/squirtles/userinfo/ExampleInstrumentedTest.kt b/feature/userinfo/src/androidTest/java/com/squirtles/userinfo/ExampleInstrumentedTest.kt new file mode 100644 index 00000000..8fdfeaa9 --- /dev/null +++ b/feature/userinfo/src/androidTest/java/com/squirtles/userinfo/ExampleInstrumentedTest.kt @@ -0,0 +1,24 @@ +package com.squirtles.userinfo + +import androidx.test.platform.app.InstrumentationRegistry +import androidx.test.ext.junit.runners.AndroidJUnit4 + +import org.junit.Test +import org.junit.runner.RunWith + +import org.junit.Assert.* + +/** + * Instrumented test, which will execute on an Android device. + * + * See [testing documentation](http://d.android.com/tools/testing). + */ +@RunWith(AndroidJUnit4::class) +class ExampleInstrumentedTest { + @Test + fun useAppContext() { + // Context of the app under test. + val appContext = InstrumentationRegistry.getInstrumentation().targetContext + assertEquals("com.squirtles.userinfo.test", appContext.packageName) + } +} diff --git a/feature/userinfo/src/main/java/com/squirtles/userinfo/UserInfoConstants.kt b/feature/userinfo/src/main/java/com/squirtles/userinfo/UserInfoConstants.kt new file mode 100644 index 00000000..137a25d3 --- /dev/null +++ b/feature/userinfo/src/main/java/com/squirtles/userinfo/UserInfoConstants.kt @@ -0,0 +1,13 @@ +package com.squirtles.userinfo + +import androidx.compose.ui.unit.dp +import com.squirtles.model.User + +internal object UserInfoConstants { + const val USERNAME_PATTERN = "^[ㄱ-ㅎ|ㅏ-ㅣ가-힣a-zA-Z0-9]+$" + val DEFAULT_USER = User("", "", "", null, listOf()) + + // UI + val MENU_PADDING_HORIZONTAL = 24.dp + val MENU_PADDING_VERTICAL = 8.dp +} diff --git a/feature/userinfo/src/main/java/com/squirtles/userinfo/UserInfoViewModel.kt b/feature/userinfo/src/main/java/com/squirtles/userinfo/UserInfoViewModel.kt new file mode 100644 index 00000000..e237332e --- /dev/null +++ b/feature/userinfo/src/main/java/com/squirtles/userinfo/UserInfoViewModel.kt @@ -0,0 +1,52 @@ +package com.squirtles.userinfo + +import androidx.lifecycle.ViewModel +import androidx.lifecycle.viewModelScope +import com.squirtles.model.User +import com.squirtles.user.usecase.FetchUserByIdUseCase +import com.squirtles.user.usecase.GetCurrentUidUseCase +import com.squirtles.user.usecase.UpdateUserNameUseCase +import com.squirtles.userinfo.UserInfoConstants.DEFAULT_USER +import dagger.hilt.android.lifecycle.HiltViewModel +import kotlinx.coroutines.flow.MutableSharedFlow +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.asSharedFlow +import kotlinx.coroutines.flow.asStateFlow +import kotlinx.coroutines.launch +import javax.inject.Inject + +@HiltViewModel +class UserInfoViewModel @Inject constructor( + private val getCurrentUidUseCase: GetCurrentUidUseCase, + private val fetchUserByIdUseCase: FetchUserByIdUseCase, + private val updateUserNameUseCase: UpdateUserNameUseCase +) : ViewModel() { + + private val _profileUser = MutableStateFlow(DEFAULT_USER) + val profileUser = _profileUser.asStateFlow() + + val currentUid get() = getCurrentUidUseCase() + + private val _updateSuccess = MutableSharedFlow() + val updateSuccess = _updateSuccess.asSharedFlow() + + fun getUserById(uid: String) { + viewModelScope.launch { + val user = fetchUserByIdUseCase(uid).getOrDefault(DEFAULT_USER) + _profileUser.emit(user) + } + } + + fun updateUsername(newUserName: String) { + viewModelScope.launch { + currentUid?.let { uid -> + val result = runCatching { + updateUserNameUseCase(uid, newUserName).getOrThrow() + fetchUserByIdUseCase(uid).getOrThrow() + } + _updateSuccess.emit(result.isSuccess) + } + } + } +} + diff --git a/feature/userinfo/src/main/java/com/squirtles/userinfo/components/MenuItem.kt b/feature/userinfo/src/main/java/com/squirtles/userinfo/components/MenuItem.kt new file mode 100644 index 00000000..cc25460e --- /dev/null +++ b/feature/userinfo/src/main/java/com/squirtles/userinfo/components/MenuItem.kt @@ -0,0 +1,13 @@ +package com.squirtles.userinfo.components + +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.graphics.vector.ImageVector +import com.squirtles.common.ui.theme.White + +data class MenuItem( + val imageVector: ImageVector, + val contentDescription: String, + val iconColor: Color = White, + val menuTitle: String, + val onMenuClick: () -> Unit +) diff --git a/feature/userinfo/src/main/java/com/squirtles/userinfo/components/UserInfoMenus.kt b/feature/userinfo/src/main/java/com/squirtles/userinfo/components/UserInfoMenus.kt new file mode 100644 index 00000000..073432a5 --- /dev/null +++ b/feature/userinfo/src/main/java/com/squirtles/userinfo/components/UserInfoMenus.kt @@ -0,0 +1,103 @@ +package com.squirtles.userinfo.components + +import androidx.compose.foundation.clickable +import androidx.compose.foundation.interaction.MutableInteractionSource +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.automirrored.filled.ArrowForwardIos +import androidx.compose.material3.HorizontalDivider +import androidx.compose.material3.Icon +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.Text +import androidx.compose.material3.ripple +import androidx.compose.runtime.Composable +import androidx.compose.runtime.remember +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.text.TextStyle +import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.unit.dp +import com.squirtles.common.ui.Constants.DEFAULT_PADDING +import com.squirtles.common.ui.VerticalSpacer +import com.squirtles.common.ui.theme.Gray +import com.squirtles.common.ui.theme.White +import com.squirtles.userinfo.R +import com.squirtles.userinfo.UserInfoConstants.MENU_PADDING_HORIZONTAL +import com.squirtles.userinfo.UserInfoConstants.MENU_PADDING_VERTICAL + +@Composable +internal fun UserInfoMenus( + title: String, + titleTextColor: Color = White, + titleTextStyle: TextStyle = MaterialTheme.typography.titleMedium, + menus: List, + menuTextColor: Color = White, + menuTextStyle: TextStyle = MaterialTheme.typography.bodyLarge +) { + Column { + Text( + text = title, + modifier = Modifier + .fillMaxWidth() + .padding( + horizontal = MENU_PADDING_HORIZONTAL, + vertical = MENU_PADDING_VERTICAL + ), + color = titleTextColor, + fontWeight = FontWeight.Bold, + style = titleTextStyle + ) + + HorizontalDivider( + modifier = Modifier + .fillMaxWidth() + .padding(horizontal = MENU_PADDING_HORIZONTAL), + color = Gray + ) + + VerticalSpacer(8) + + for (menu in menus) { + Row( + modifier = Modifier + .fillMaxWidth() + .padding(horizontal = MENU_PADDING_HORIZONTAL) + .clickable( + interactionSource = remember { MutableInteractionSource() }, + indication = ripple(color = White), + onClick = menu.onMenuClick + ) + .padding(vertical = MENU_PADDING_VERTICAL), + horizontalArrangement = Arrangement.spacedBy(DEFAULT_PADDING), + verticalAlignment = Alignment.CenterVertically + ) { + Icon( + imageVector = menu.imageVector, + contentDescription = menu.contentDescription, + tint = menu.iconColor + ) + Text( + text = menu.menuTitle, + modifier = Modifier.weight(1f), + color = menuTextColor, + style = menuTextStyle + ) + Icon( + imageVector = Icons.AutoMirrored.Filled.ArrowForwardIos, + contentDescription = stringResource(R.string.user_info_navigate_to_menu), + modifier = Modifier.size(16.dp), + tint = Gray + ) + } + } + + VerticalSpacer(20) + } +} diff --git a/feature/userinfo/src/main/java/com/squirtles/userinfo/navigation/UserInfoNavigation.kt b/feature/userinfo/src/main/java/com/squirtles/userinfo/navigation/UserInfoNavigation.kt new file mode 100644 index 00000000..5d926763 --- /dev/null +++ b/feature/userinfo/src/main/java/com/squirtles/userinfo/navigation/UserInfoNavigation.kt @@ -0,0 +1,62 @@ +package com.squirtles.userinfo.navigation + +import androidx.navigation.NavController +import androidx.navigation.NavGraphBuilder +import androidx.navigation.NavOptions +import androidx.navigation.compose.composable +import androidx.navigation.toRoute +import com.squirtles.navigation.MainRoute +import com.squirtles.navigation.UserInfoRoute +import com.squirtles.userinfo.screen.EditNotificationSettingScreen +import com.squirtles.userinfo.screen.EditProfileScreen +import com.squirtles.userinfo.screen.UserInfoScreen + +fun NavController.navigateUserInfo(uid: String, navOptions: NavOptions? = null) { + navigate(MainRoute.UserInfo(uid), navOptions) +} + +fun NavController.navigateEditProfile(userName: String, navOptions: NavOptions? = null) { + navigate(UserInfoRoute.EditProfile(userName), navOptions) +} + +fun NavController.navigateEditNotificationSetting(navOptions: NavOptions? = null) { + navigate(UserInfoRoute.EditNotification, navOptions) +} + +fun NavGraphBuilder.userInfoNavGraph( + onBackClick: () -> Unit, + onBackToMapClick: () -> Unit, + onFavoritePicksClick: (String) -> Unit, + onMyPicksClick: (String) -> Unit, + onEditProfileClick: (String) -> Unit, + onEditNotificationClick: () -> Unit, +) { + composable { backStackEntry -> + val uid = backStackEntry.toRoute().uid + + UserInfoScreen( + uid = uid, + onBackClick = onBackClick, + onBackToMapClick = onBackToMapClick, + onFavoritePicksClick = onFavoritePicksClick, + onMyPicksClick = onMyPicksClick, + onEditProfileClick = onEditProfileClick, + onEditNotificationClick = onEditNotificationClick, + ) + } + + composable { backStackEntry -> + val userName = backStackEntry.toRoute().userName + EditProfileScreen( + currentUserName = userName, + onBackToMapClick = onBackToMapClick, + onBackClick = onBackClick, + ) + } + + composable { + EditNotificationSettingScreen( + onBackClick = onBackClick + ) + } +} diff --git a/feature/userinfo/src/main/java/com/squirtles/userinfo/screen/EditNotificationSettingScreen.kt b/feature/userinfo/src/main/java/com/squirtles/userinfo/screen/EditNotificationSettingScreen.kt new file mode 100644 index 00000000..b9e7fc28 --- /dev/null +++ b/feature/userinfo/src/main/java/com/squirtles/userinfo/screen/EditNotificationSettingScreen.kt @@ -0,0 +1,49 @@ +package com.squirtles.userinfo.screen + +import androidx.compose.foundation.background +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.padding +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.Scaffold +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Brush +import androidx.compose.ui.res.stringResource +import androidx.wear.compose.material.Text +import com.squirtles.common.ui.Constants.COLOR_STOPS +import com.squirtles.common.ui.Constants.DEFAULT_PADDING +import com.squirtles.common.ui.DefaultTopAppBar +import com.squirtles.common.ui.theme.White +import com.squirtles.userinfo.R + +@Composable +internal fun EditNotificationSettingScreen( + onBackClick: () -> Unit +) { + Scaffold( + topBar = { + DefaultTopAppBar( + title = stringResource(id = R.string.setting_notification_title), + onBackClick = onBackClick + ) + } + ) { innerPadding -> + Box( + modifier = Modifier + .fillMaxSize() + .background(Brush.verticalGradient(colorStops = COLOR_STOPS)) + .padding(innerPadding) + ) { + Text( + text = stringResource(id = R.string.setting_notification_description), + modifier = Modifier + .padding(horizontal = DEFAULT_PADDING) + .align(Alignment.Center), + color = White, + style = MaterialTheme.typography.bodyLarge + ) + } + } +} diff --git a/feature/userinfo/src/main/java/com/squirtles/userinfo/screen/EditProfileScreen.kt b/feature/userinfo/src/main/java/com/squirtles/userinfo/screen/EditProfileScreen.kt new file mode 100644 index 00000000..7f430f0b --- /dev/null +++ b/feature/userinfo/src/main/java/com/squirtles/userinfo/screen/EditProfileScreen.kt @@ -0,0 +1,326 @@ +package com.squirtles.userinfo.screen + +import android.content.Context +import android.widget.Toast +import androidx.activity.compose.BackHandler +import androidx.compose.foundation.background +import androidx.compose.foundation.clickable +import androidx.compose.foundation.interaction.MutableInteractionSource +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.wrapContentHeight +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.automirrored.filled.ArrowBack +import androidx.compose.material.icons.filled.Check +import androidx.compose.material3.CenterAlignedTopAppBar +import androidx.compose.material3.CircularProgressIndicator +import androidx.compose.material3.ExperimentalMaterial3Api +import androidx.compose.material3.Icon +import androidx.compose.material3.IconButton +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.OutlinedTextField +import androidx.compose.material3.OutlinedTextFieldDefaults +import androidx.compose.material3.Scaffold +import androidx.compose.material3.Text +import androidx.compose.material3.TopAppBarDefaults +import androidx.compose.runtime.Composable +import androidx.compose.runtime.LaunchedEffect +import androidx.compose.runtime.MutableState +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.saveable.rememberSaveable +import androidx.compose.runtime.setValue +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Brush +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.platform.LocalContext +import androidx.compose.ui.platform.LocalFocusManager +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.text.style.TextAlign +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.dp +import androidx.compose.ui.unit.sp +import androidx.hilt.navigation.compose.hiltViewModel +import androidx.lifecycle.Lifecycle +import androidx.lifecycle.compose.LocalLifecycleOwner +import androidx.lifecycle.flowWithLifecycle +import com.squirtles.account.AccountViewModel +import com.squirtles.account.GoogleId +import com.squirtles.common.ui.Constants.COLOR_STOPS +import com.squirtles.common.ui.DialogTextButton +import com.squirtles.common.ui.HorizontalSpacer +import com.squirtles.common.ui.MessageAlertDialog +import com.squirtles.common.ui.theme.Black +import com.squirtles.common.ui.theme.DarkGray +import com.squirtles.common.ui.theme.Gray +import com.squirtles.common.ui.theme.MusicRoadTheme +import com.squirtles.common.ui.theme.Primary +import com.squirtles.common.ui.theme.White +import com.squirtles.userinfo.R +import com.squirtles.userinfo.UserInfoConstants.USERNAME_PATTERN +import com.squirtles.userinfo.UserInfoViewModel +import kotlinx.coroutines.delay +import kotlinx.coroutines.launch +import java.util.regex.Pattern + +@Composable +internal fun EditProfileScreen( + currentUserName: String, + onBackToMapClick: () -> Unit, + onBackClick: () -> Unit, + userInfoViewModel: UserInfoViewModel = hiltViewModel(), + accountViewModel: AccountViewModel = hiltViewModel() +) { + val context = LocalContext.current + val lifecycleOwner = LocalLifecycleOwner.current + val focusManager = LocalFocusManager.current + val userName = remember { mutableStateOf(currentUserName) } + val nickNameErrorMessage = remember { mutableStateOf("") } + var showLoadingIndicator by rememberSaveable { mutableStateOf(false) } + var showDeleteAccountDialog by remember { mutableStateOf(false) } + + val onDeleteAccountClick: () -> Unit = { + GoogleId(context).signOut() + accountViewModel.deleteAccount() + } + + BackHandler(enabled = showLoadingIndicator) { } + + LaunchedEffect(Unit) { + launch { + userInfoViewModel.updateSuccess + .flowWithLifecycle(lifecycleOwner.lifecycle, Lifecycle.State.STARTED) + .collect { isSuccess -> + focusManager.clearFocus() + delay(100) + if (isSuccess) { + Toast.makeText( + context, + context.getString(R.string.setting_profile_update_nickname_success), + Toast.LENGTH_SHORT + ).show() + onBackClick() + } else { + showLoadingIndicator = false + Toast.makeText( + context, + context.getString(R.string.setting_profile_update_nickname_failure), + Toast.LENGTH_SHORT + ).show() + } + } + } + + launch { + accountViewModel.deleteAccountSuccess + .flowWithLifecycle(lifecycleOwner.lifecycle, Lifecycle.State.STARTED) + .collect { isSuccess -> + if (isSuccess) { + showLoadingIndicator = false + onBackToMapClick() + } + } + } + } + + Scaffold( + topBar = { + EditProfileAppBar( + confirmEnabled = nickNameErrorMessage.value.isEmpty() && + currentUserName != userName.value, + onConfirmClick = { + showLoadingIndicator = true + userInfoViewModel.updateUsername(userName.value) + }, + onBackClick = onBackClick + ) + } + ) { innerPadding -> + Box( + modifier = Modifier + .fillMaxSize() + .background(Brush.verticalGradient(colorStops = COLOR_STOPS)) + .padding(innerPadding) + ) { + // 프로필 수정 + EditProfileContents(userName, nickNameErrorMessage) + + // 회원 탈퇴 + Text( + text = stringResource(id = R.string.user_info_setting_delete_user_account), + modifier = Modifier + .padding(vertical = 20.dp) + .clickable { showDeleteAccountDialog = true } + .align(Alignment.BottomCenter), + color = DarkGray, + textAlign = TextAlign.Center, + style = MaterialTheme.typography.bodyMedium + ) + } + } + + if (showLoadingIndicator) { + Box( + modifier = Modifier + .fillMaxSize() + .background(Black.copy(alpha = 0.5F)) + .clickable( + interactionSource = remember { MutableInteractionSource() }, + indication = null, + onClick = {} + ), + contentAlignment = Alignment.Center + ) { + CircularProgressIndicator() + } + } + + if (showDeleteAccountDialog) { + MessageAlertDialog( + onDismissRequest = { + showDeleteAccountDialog = false + }, + title = stringResource(R.string.delete_account_dialog_title), + body = stringResource(R.string.delete_account_dialog_description), + ) { + DialogTextButton( + onClick = { + showDeleteAccountDialog = false + }, + text = stringResource(R.string.delete_account_dialog_dismiss) + ) + + HorizontalSpacer(8) + + DialogTextButton( + onClick = { + showLoadingIndicator = true + showDeleteAccountDialog = false + onDeleteAccountClick() + }, + text = stringResource(R.string.delete_account_dialog_confirm), + textColor = Primary, + fontWeight = FontWeight.Bold + ) + } + } +} + +@OptIn(ExperimentalMaterial3Api::class) +@Composable +private fun EditProfileAppBar( + confirmEnabled: Boolean, + onConfirmClick: () -> Unit, + onBackClick: () -> Unit +) { + CenterAlignedTopAppBar( + title = { + Text( + text = stringResource(id = R.string.setting_profile_title), + style = MaterialTheme.typography.titleLarge.copy(fontWeight = FontWeight.Bold) + ) + }, + navigationIcon = { + IconButton( + onClick = onBackClick + ) { + Icon( + imageVector = Icons.AutoMirrored.Filled.ArrowBack, + contentDescription = stringResource(R.string.top_app_bar_back_description), + tint = White + ) + } + }, + actions = { + IconButton( + onClick = onConfirmClick, + enabled = confirmEnabled + ) { + Icon( + imageVector = Icons.Default.Check, + contentDescription = stringResource(R.string.setting_profile_confirm_icon_description), + tint = if (confirmEnabled) White else Gray + ) + } + }, + colors = TopAppBarDefaults.centerAlignedTopAppBarColors().copy( + containerColor = Color.Transparent, + titleContentColor = White + ) + ) +} + +private fun validateUserName(userName: String, context: Context) = when { + userName.length < 2 -> context.getString(R.string.setting_profile_nickname_message_length_fail_min) + userName.length > 10 -> context.getString(R.string.setting_profile_nickname_message_length_fail_max) + Pattern.matches(USERNAME_PATTERN, userName).not() -> context.getString(R.string.setting_profile_nickname_message_format_fail) + else -> "" +} + +@Composable +private fun EditProfileContents( + userName: MutableState, + nickNameErrorMessage: MutableState +) { + val context = LocalContext.current + Column( + modifier = Modifier + .wrapContentHeight() + .padding(vertical = 30.dp, horizontal = 30.dp), + verticalArrangement = Arrangement.spacedBy(12.dp) + ) { + Text( + text = stringResource(id = R.string.setting_profile_nickname), + fontSize = 20.sp, + fontWeight = FontWeight.Bold, + color = White, + style = MaterialTheme.typography.titleLarge + ) + + OutlinedTextField( + value = userName.value, + onValueChange = { + nickNameErrorMessage.value = validateUserName(it, context) + userName.value = it + }, + modifier = Modifier.fillMaxWidth(), + singleLine = true, + supportingText = { Text(nickNameErrorMessage.value) }, + isError = nickNameErrorMessage.value.isNotEmpty(), + colors = OutlinedTextFieldDefaults.colors( + focusedTextColor = White, + unfocusedTextColor = White, + errorTextColor = White, + focusedContainerColor = Color.Transparent, + unfocusedContainerColor = Color.Transparent, + cursorColor = White, + focusedBorderColor = Gray, + unfocusedBorderColor = Gray, + ) + ) + } +} + +@Preview +@Composable +private fun EditProfileAppBarPreview() { + EditProfileAppBar(false, {}, {}) +} + +@Preview +@Composable +private fun EditProfileContentPreview() { + MusicRoadTheme { + EditProfileContents( + remember { mutableStateOf("짱구") }, + remember { mutableStateOf("") } + ) + } +} diff --git a/feature/userinfo/src/main/java/com/squirtles/userinfo/screen/UserInfoScreen.kt b/feature/userinfo/src/main/java/com/squirtles/userinfo/screen/UserInfoScreen.kt new file mode 100644 index 00000000..d8ed99cf --- /dev/null +++ b/feature/userinfo/src/main/java/com/squirtles/userinfo/screen/UserInfoScreen.kt @@ -0,0 +1,273 @@ +package com.squirtles.userinfo.screen + +import androidx.activity.compose.BackHandler +import androidx.compose.foundation.background +import androidx.compose.foundation.clickable +import androidx.compose.foundation.interaction.MutableInteractionSource +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size +import androidx.compose.foundation.layout.wrapContentWidth +import androidx.compose.foundation.rememberScrollState +import androidx.compose.foundation.shape.CircleShape +import androidx.compose.foundation.verticalScroll +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.automirrored.outlined.Logout +import androidx.compose.material.icons.filled.MusicNote +import androidx.compose.material.icons.outlined.Archive +import androidx.compose.material.icons.outlined.Map +import androidx.compose.material.icons.outlined.Notifications +import androidx.compose.material.icons.outlined.SwitchAccount +import androidx.compose.material3.CircularProgressIndicator +import androidx.compose.material3.ExtendedFloatingActionButton +import androidx.compose.material3.Icon +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.Scaffold +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.runtime.LaunchedEffect +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.saveable.rememberSaveable +import androidx.compose.runtime.setValue +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.clip +import androidx.compose.ui.graphics.Brush +import androidx.compose.ui.layout.ContentScale +import androidx.compose.ui.platform.LocalContext +import androidx.compose.ui.res.painterResource +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.unit.dp +import androidx.hilt.navigation.compose.hiltViewModel +import androidx.lifecycle.Lifecycle +import androidx.lifecycle.compose.LocalLifecycleOwner +import androidx.lifecycle.compose.collectAsStateWithLifecycle +import androidx.lifecycle.flowWithLifecycle +import coil3.compose.AsyncImage +import coil3.request.ImageRequest +import coil3.request.crossfade +import com.squirtles.account.AccountViewModel +import com.squirtles.account.GoogleId +import com.squirtles.common.ui.Constants.COLOR_STOPS +import com.squirtles.common.ui.DefaultTopAppBar +import com.squirtles.common.ui.DialogTextButton +import com.squirtles.common.ui.HorizontalSpacer +import com.squirtles.common.ui.MessageAlertDialog +import com.squirtles.common.ui.VerticalSpacer +import com.squirtles.common.ui.theme.Black +import com.squirtles.common.ui.theme.Primary +import com.squirtles.common.ui.theme.White +import com.squirtles.userinfo.R +import com.squirtles.userinfo.UserInfoViewModel +import com.squirtles.userinfo.components.MenuItem +import com.squirtles.userinfo.components.UserInfoMenus +import kotlinx.coroutines.launch + +@Composable +fun UserInfoScreen( + uid: String, + onBackClick: () -> Unit, + onBackToMapClick: () -> Unit, + onFavoritePicksClick: (String) -> Unit, + onMyPicksClick: (String) -> Unit, + onEditProfileClick: (String) -> Unit, + onEditNotificationClick: () -> Unit, + userInfoViewModel: UserInfoViewModel = hiltViewModel(), + accountViewModel: AccountViewModel = hiltViewModel() +) { + val context = LocalContext.current + val lifecycleOwner = LocalLifecycleOwner.current + val scrollState = rememberScrollState() + val user by userInfoViewModel.profileUser.collectAsStateWithLifecycle() + + var showLogOutDialog by remember { mutableStateOf(false) } + var showLoadingIndicator by rememberSaveable { mutableStateOf(false) } + + val onSignOutClick: () -> Unit = { + GoogleId(context).signOut() + accountViewModel.signOut() + } + + BackHandler(enabled = showLoadingIndicator) { } + + LaunchedEffect(Unit) { + uid.let { + userInfoViewModel.getUserById(uid) + } + + launch { + accountViewModel.signOutSuccess + .flowWithLifecycle(lifecycleOwner.lifecycle, Lifecycle.State.STARTED) + .collect { isSuccess -> + if (isSuccess) { + showLoadingIndicator = false + onBackToMapClick() + } + } + } + } + + Scaffold( + topBar = { + DefaultTopAppBar( + title = user.userName, + onBackClick = onBackClick + ) + } + ) { innerPadding -> + Box( + modifier = Modifier + .fillMaxSize() + .background(Brush.verticalGradient(colorStops = COLOR_STOPS)) + .padding(innerPadding), + ) { + Column( + modifier = Modifier + .fillMaxSize() + .verticalScroll(scrollState) + .padding(bottom = 96.dp), + horizontalAlignment = Alignment.CenterHorizontally + ) { + VerticalSpacer(16) + + AsyncImage( + model = ImageRequest.Builder(LocalContext.current) + .data(user.userProfileImage) + .crossfade(true) + .build(), + contentDescription = stringResource(R.string.user_info_profile_image), + modifier = Modifier + .size(180.dp) + .clip(CircleShape), + placeholder = painterResource(R.drawable.img_user_default_profile), + error = painterResource(R.drawable.img_user_default_profile), + contentScale = ContentScale.Crop, + ) + + VerticalSpacer(40) + + UserInfoMenus( + title = stringResource(R.string.user_info_pick_category_title), + menus = listOf( + MenuItem( + imageVector = Icons.Outlined.Archive, + contentDescription = stringResource(R.string.user_info_favorite_menu_icon_description), + menuTitle = stringResource(R.string.user_info_favorite_menu_title), + onMenuClick = { onFavoritePicksClick(uid) } + ), + MenuItem( + imageVector = Icons.Default.MusicNote, + contentDescription = stringResource(R.string.user_info_created_by_self_menu_icon_description), + menuTitle = stringResource(R.string.user_info_created_by_self_menu_title), + onMenuClick = { onMyPicksClick(uid) } + ) + ) + ) + + if (uid == userInfoViewModel.currentUid) { + UserInfoMenus( + title = stringResource(R.string.user_info_setting_category_title), + menus = listOf( + MenuItem( + imageVector = Icons.Outlined.SwitchAccount, + contentDescription = stringResource(R.string.user_info_setting_profile_menu_icon_description), + menuTitle = stringResource(R.string.user_info_setting_profile_menu_title), + onMenuClick = { onEditProfileClick(user.userName) } + ), + MenuItem( + imageVector = Icons.Outlined.Notifications, + contentDescription = stringResource(R.string.user_info_setting_notification_menu_icon_description), + menuTitle = stringResource(R.string.user_info_setting_notification_menu_title), + onMenuClick = onEditNotificationClick + ), + MenuItem( + imageVector = Icons.AutoMirrored.Outlined.Logout, + contentDescription = stringResource(R.string.user_info_setting_sign_out_menu_icon_description), + menuTitle = stringResource(R.string.user_info_setting_sign_out_menu_title), + onMenuClick = { showLogOutDialog = true } + ) + ) + ) + } + } + + ExtendedFloatingActionButton( + onClick = onBackToMapClick, + modifier = Modifier + .wrapContentWidth() + .padding(horizontal = 8.dp) + .padding(bottom = 48.dp) + .align(Alignment.BottomCenter), + shape = CircleShape, + containerColor = Primary, + contentColor = White, + ) { + Icon( + imageVector = Icons.Outlined.Map, + contentDescription = stringResource(R.string.user_info_icon_map_description), + tint = White + ) + + HorizontalSpacer(8) + + Text( + text = stringResource(R.string.user_info_back_to_map_button_text), + color = White, + style = MaterialTheme.typography.bodyLarge + ) + } + + if (showLogOutDialog) { + MessageAlertDialog( + onDismissRequest = { + showLogOutDialog = false + }, + title = stringResource(R.string.sign_out_dialog_title), + body = "", + showBody = false + ) { + DialogTextButton( + onClick = { + showLogOutDialog = false + }, + text = stringResource(R.string.sign_out_dialog_dismiss) + ) + + HorizontalSpacer(8) + + DialogTextButton( + onClick = { + showLogOutDialog = false + showLoadingIndicator = true + onSignOutClick() + }, + text = stringResource(R.string.sign_out_dialog_confirm), + textColor = Primary, + fontWeight = FontWeight.Bold + ) + } + } + + if (showLoadingIndicator) { + Box( + modifier = Modifier + .fillMaxSize() + .background(Black.copy(alpha = 0.5F)) + .clickable( + interactionSource = remember { MutableInteractionSource() }, + indication = null, + onClick = {} + ), + contentAlignment = Alignment.Center + ) { + CircularProgressIndicator() + } + } + } + } +} diff --git a/feature/userinfo/src/main/res/drawable/img_user_default_profile.jpg b/feature/userinfo/src/main/res/drawable/img_user_default_profile.jpg new file mode 100644 index 0000000000000000000000000000000000000000..569284983120bcaa638e31a1774b0ac16264603f GIT binary patch literal 15062 zcmb`uWpEu$5+!=YXfZQc%*WGf&?mU^*UDcUUdFpfNa|?hZB_=5b00992K)x#Aa~1Fl0P#NyBqRhhG!!&694rhB zEF3cYx37wfgM^Cob>Lv)VxVK-Vp0*|;So_WP<*Fg;9_Cn5*HEI(eVNKKLz;g1t3F% zY=QcNfsg<|kwL(aK|cEd?Ef+m0_0z|{_g?_3I+}V1p*E8brBTo|9J-h0SN*M4hHqP z0)Pkmx`_ma^d%I7nEUqoe+&O#I)9@-m5td<`Ts)WTKL!yQP~msZoc*Z{@C(qXN^tH zeU_xaKE5mBiI)P`P0fFO`kUCQY_`~2HvoW;I<=N@o_cuc+u~$9vP`bAwWxbeqTRl+dDY7;S>~@f6o> zaszjZKLEjlWbxFXGq!NWInws}fkOet&l$=2$A1EkvzVZy_OCxWw32}>AlgtZNv8ao zUnua-++@7AeeN$2FpL!|Ub?r!@ZS98=2z#9T(h`8F;{I>%%~?|CoWC|m;|mvjx%?=lHwbRTBA=%1JUrzWqh3FcPquyWtt*4R``t{~mf4$iJoy*vSEkrlFJkB^07 z%fIj6dOnI@#K%ZTZg1oPAaH4pQE&-fdAC*3$amva3Y(2L0=$dEFgW&}j{g6lft!`( zPw%odzx+#Hz1w*EIW46-DGK#<*8r2iUoUS|Z@Ho~mpY_1)45J!k7sqSYk$!1bRMoZ zwwXy+u?WNe8fX6~Q0NC2uk=Q037dIuw|D46$#MzvyJ#y_)90=i(R^6kl||*I^+_)7 z%Il`Aw9YyIkp5rIpTwI&eH!d!)U|GDz@fbL@;OAlVw!~cgD?4qc2l5Qa?RcC9)YBh znp{dqGvj{&!RFaSC>gUWxx4-LX6F_Kau?*eeuF80;klR;H9;+kfMwlY#s6!uPQ z`QW}R?n+>4Lb#8=8?1iZ^lM#Rckn^X`9EN>QyjAqN*7E`WhjoX&k;p>H>ZMk9)=?W za{(_)o@`GnBvG5&9sE-R z0063obew@Q`_nty;u9bmY4yEjS^NQ;z{Jm{Vyw@=4T=SkvegJK$;K3;{7+;ehLuS; zX2Q@P-d5=;J3VU1|HK0Tp!D|eJ8_1*7(m+wcCAF+92eKd@9K4wxA#ZSldha>k{r*T z^y-C8T%DLARhtDAj(5lS81$$C#umrXwJSEnbP5stq4C>`W7tdV+!8i9|J1=#40kS{ zWc`X+R;lB7<8Xs$RqNd|dONe9o)qD=P{LOmD=D?r8m>yON*YIL=4_0n?Q84Bba*!V z)3y2Jah$g@sxM3$CF5PyAQ*g{}UdrDP^@&Zy#SZ{VsfdW-qo?mv3E->dw7s^r~;vGN&lo=BybQkI#-YN75&E z{P3J#B~V}_i2E#+zciUe^zdIkBLGVFfbHhdZS~rNPvGcIvo`YZ{Ld}e8lPq5Sz<+_ zUjC{m=lt>*oKEV5SKd9=e*2+8(|?%tZ!i3amWLcv^m5Hg! zH4PJIK4$66Oby;2iclU)LiyGeg?`8l1uoX=$(dN9|ZD45z9$xQ+i~N{z z-7B5F&0b-mvMwKBD6YOVdUaysxfwEb9etp|qZ-cm4s)-NyrvkV%)`vap zccHOOq=XIUtNc2F46gP9`_X23*AmjIiRRhEtor#sae)KNl$G1C`Qg=kSf3 zM(%VhgDfC&zi{40nn-bN!u^o$uKof4|D*X8?Z_hmKtaGj!N4HkprIfkAih2hC>Q`7 z8G;xRg$Y$i0m{H3AQlOYg!zZOqGN7t4>}Q}zF}Zq9jUOAkyG3>CW|OAp%)s1)!Cm+ zY-W4sD-MGGijP190H1&ZwuMHBqDqTmiInpgri3QY_?UdCA;K~|C)=W3nbZRF=^gjw zMQq&(pxc=%Tdx`>%h3t_cS#&?x9 zwzGvOh{zQoTBJ{avBY=(_soZP*%QI*H!HpQqPc1l!9A@XXSGGHoU19%ry<{2%sn0W zRImkx2mBNp)(I8n$9F~%X6v+DY0>Qd+HF4jZv}cq>S^hyTcWB@SlQpYR*>w4rZ+1( zR{U~*4{ng=s$fmWwdh7AK_!AB))+D5(-=CDD`#`_B7`w#w1`1|mfeOM6~8wbJoDYJ z%1%uJ_zw|ePsK@WM;64+BfFUz`O_%wYT>Hbc@f1EE)08+-n*r>E!_`k^KwwKZ-@OHcorMLxFE3MbqtNqg1l@KjtC4*{x9zy%*4E7^l<<0lDiHr8 zvP9cF+13;oTU?)w%Us$Awk769m~s(IDoI*KbTaNCc%PxXrqXwX$6gR6{vb8uM@f>u zb3qWZdlN$O6x)Vqfez7+sSs*&7-jyEV2$UKngg>0zeocgSA5HQ&V8udE^Xz4H{|O2 z6kBpK=@kTbf%uo0{c4rI&>=#Sf~3aiK3$);HxxV$4Is&hzq|=1kh9aT?vhD=U>>KA z^Zh^^Z`6y5xjR}|wZ31~Q`SI`JX35M7!={VjL(Nw{F}2)@Nblr{^sT0s20T>zV{tS3zUge_85#?py5M((zalDbRl;ynzn z>N@o3a|cGwwk@GQfVE}qol085cUJLgGJuh|Aw_M9K>iZ|gI;Fe^LIB}d8SdV=}er3 zEaVKH`vtmE{P(?&Qi6=LA|v2z`y)~k{}A62ZmeAmMQeQmHR>)V>@Vl|7>VXdp^#17 z9z1;3G2r!mErUDIehHqXcJTEhJd*fu_Uzy??2w>TIl4m?*lVvN4)IY;vPR}FiC(uS z4*WZIH`gKFl12?&CCJ=qC9w6TP(*D_7xNCAr=$Y`3%VKvlkBREzVxwk?cGi68()h zuf2Wd`@DJElzm^?o;-goGjpvCP231w3sAn+=8vz{ip_8Yf)1Tcc*sUJWZ`YEl5{lV zWZbMz#Rem5`PUAed#5!ARoM~+$-+lr@?%#IbK4Q=O0wKNPg*I?8rL|)Pr>cc?4H^2 z+3PkeHdPz&99Wq^vh(TEXZKtz9cv~Pj$2n0q)_#tcLQFA%hySjXG7TbfBmhVrAd5< zi2my!;!_ml^S&G#?N;a`S1De5kp8(iJITB8m*%V`{*OiKfOmX?1=0;b&qD>IKVsx9HLnAk@ zV+ry13Dpd~d_lBuaHqS1kC-ptIuDSLr==|Egyr%*Q%jyRZQrK4O*ms9xepPqM5^BB z@qXgx<1JomYm#pF(6^0Zza_|72y$Pstv6t?2G8RXKkMqYBn1bqUufuU9M8g3T-J9Y?vj5_%cKQ{)LQl|Jb^2ZXeO?C$e+O?cYwE_?>*=QA=>DF~M zoH^z=dG!iBlBHTTMlR2cKceJJg-T4kJn~ZcS*@`=%dqoX!npFe#I?ilMfH#`%NJxb z>tj#23cm-&Z-6{Re1wPJa|BXW5L>g3O;F)`@$bK1It8Y)bBt{}c3G-n_K>ZR)sbMZ z-xQ1kM)t5(`E#7ab^vG(Wot`&upZ&4YdL0IRX8(yv#ft9bhT198xwtl#n1(uU7qJa zB>*Ur-C(8U4Rj#R!$}&u6T8vBJRkJmg5G3Y$$%MQ(z>SDlazvDOpUZWcG5W2E^;<@ zZV&^N<3A)QLU)qbkA`;>fG2E&cr&LQT|2nt2R9Z>Ql7Kg^uPJ0RAJY3s!Bo-A7&W$XEYt=y zrCtqli+uw8?WP@+m=K|7cZhx7SKXM`3|G-^^N~h0 z)EkuK!vAV)>@{i#cvG9$DK^p;-|LIubktxe8u2rS*>9^8rX+p%Yw~wK8}aldVrOGa zxGT%hVYq3==9mmXGg2L&v@PqSrncv!svcp1LtxSv*%tBGuJYc%(R8~y+$xDwI^-5` zuY5IP!&)Q7L?)~7hxCqDrf=4AJ!n25ws4P9z9OBhHa<&H%wFioa`BgY@Qz?JjExC~ zFIr|vX6-9%yO-tQ;HFG{;LN&27;!*+lA#;zP)B^%G2;0Fp zvPhCJGUzaL1t>wtZZ&+r9U8Uir)b5QCqJ<|_7|VE7-_yO4+1BczAaEt*y%>B`%GN% zd83Fty=ATSJk+>6_x_Z7vo+h`6vdr%-ag2ftQTB7An>46V{|w3_ig;oRxFZ2a8#hS zhS3Nw$1p#9OO#y2AH)jZTzl0xYv)}zYD2FMj!Z*BG|!g>8of_IUp2z%nub>ud$S8f z;r^KRy1F<@Mn?$42o(=xi9zrRmVV&(D6D+-UC|{HqJ0$tf4;s8n@;XUh99!`M}@1^ zn!djb3zUGsu-Nq6@+YG|4O#OanrFV@-+JzG;yA*@qeT01sr$O4v%9~3PNi=JR&fpy zIJ)V@D^oVlQalppdxWrC4*`sNWw)~ctbo6Gh-!x%Quym1exmx>qKKj{r&`EL{xGkW zHcjZv7fhSl(iY2WqR#nSqg;BY)i2R=2!I=p-lj)K2faP>sB*ggVC+ys(hD<@DN08_ zzUEczDuv7tIVVqij*4@o;HCNA*bG~0;R8O)hc%rcw)V4%;)HihT%ZVZttytjy?BQA zn=A-FdVzXXW|8^|Lahi_bU5L!O^Zj`r%ynL-P5AN)|dr_kL>8u1k3LpAWh)(?IG_8 z?rqeZI!L^B-;~JtPgw-2Ar6&eHE47XM{ZO_i76&sYb1M#)1!jAV9`DlNuEn>x#HKE zXA04NHL#~equcws^0k$GZ`Cw5{=jZEq!0DC&?;YHxN_}6ZJuAgtk@w}^E7Q77} znlf~p&R2XRJ4Pv@uUe8*_pbyBi)#P-&PVU&53qYMBE*B~dJwj3VrHp3BWkhi) z59l7;7g3hO{}Aja?`rKBW4-WjEZ+`PX3pPbW;T4Iaof$gi?7M;)$9ZzU-M{}yF+)$ zwQNddu$s&-7xDNw{|Xsv<<(tqqg8~7(y$8g$wd8b;TL%`I{xGv^`-3Ia*a^*W_v&V z9p+npWV!%}*hzteVm(ZR25YxW`)LUuP5Wr4PprJIGmTu?II@ZVzU*1lWPQIT- zl5^)``|@ z%y8#YxUa#j{!_GncP@jW4T1Y8-{cf1k#B>vROFi>TD2WrM#_< z$l9a`t@X-I5fi_L@YdMpEx@)=8NLMDXAw%Kip%4jcpEVt^LSxi`(|N6>&-hq3C%jL zPKgV$=m$F`92RjakHvp#BNR}1ie{u+pp<9pAvQl2S<~sK!&*ny%dZfno7lhL<{3E< zucgHrg&q?$Bph7H5lC-LMW0U@iSQmFGd^)_P*cu>Rhb(_X{f#S`oq`t=&gH1za{*=%?*?JNHNj2BtQM^K)NDQ5_h zg9Ejc)Mb}dH6iU#=FW$QsqwzYAC!B{VqK)gQydhns}!@TjtA6qBRE91Olo&N&v{dN z{{~}oxr2$3OvNg`Pxbo7=lt;2z4gjb-foB<{lgnVl=tCGYSi=b3AS;*Nug!ZDPGzS z*AbX`B#paWUo%o@Y`q1%pcmp#vk9oOHuL-zn?~l6XHmm*t^EW>q=fJ`iv$3l-=YI% z0M!=7CSeX$7?#OBoT9QkWd-&Cz7mrHU7vvGqMHz=De`EfyKz5=@(t>_YWyWK`NLKr zzk|}^tZm=~%kcWzQ1KKVy7}!%-8dY0Aj%Xl%J35aA#|OSE3{G6j2NhOeqDO5(prCx zrhm2;WYki!Av!202d1#mRwBOl$C^TvNsFja(B3iP&+UEN7l&Wz*+32T!zCgrSDL~(xIo z$}f0Ml#c^MYMuqmg2V_*b5h8j{*t+DMCfPfGs^D0(4bRMS2%Sd&?GeXP>c23DS#0` zQTB|`olcnb&vM@F#Qn?&Sl6DEIMAlLy;KyS0z{s;$w$c|`Z}n^=hU`8wVr+FA0Q7A zI(oYVQ8-SP3mmO|cj~H9nEgHiN0Q+YDPzYi4l^GFSqljQD7H*rZdVzPn2f`*9e!EsIusBSOL?Cf5~tbl(UeMjF37SDRmH z?kHgY^7?izY6)zT{cc*Do@F6FjM>fB+K9lWRum}$dHqrO6kEQf4>3F}0rd*H(vW{h zVs76Ml88Dhnb?neWx=X=Kf-2XD5uzj=FYM6Yw*K$Pz|Tp1Bz+K9-}>^oD)TB~WhnMf5fF>3%cpB=^O+F1TCgKLkF) zD2&FyW4EsPHyN$tYa;c0eKhYF_G*uQrUdY)m#`%y_U-{8ltKUdjdZYd18wb-m9~;0 zX05^MV=lXvx81&(hc$shs!im{i|!0dE;LI)nAW zPX!)FhkSPkb5Ol(Q5ukt=8#Vmi+WgFiv4I*Jcg|vL*1+GtR*8c-8e}_C~v?WF{g@^WagJjF3GUHf-RZX_(F#jSQ*+JbnD`3N{$? zn3>k265-(;uH16X`$0#&&9%UAN>=fAf?A==w>1mTt70u5IiL*cD7+&V6D<#RwAw>2 z$hqRw=r=j9jHB5jd8K6aTYNfZ0CRB&`naUmxsk(#17=ei`SB56zu*-(2i!7CR}u0_ z5*>LS3I$Myf3Fxt`#8mabi z7ltTUJ;V^Gw-b}M0wr=rOFR~-Wyc#!>^(O()^$Eq^-W`j$CeJUFP6(?x8BK@f5lfM z|KurLW)?89+PPt%MilG@>jsf1d5!%;D<#1Rh6X^BP@#3j-#erJ1cW>DAn4on-yoiB zeZ;(qp{Z65re)>e&?sDTA`HbG{NO!bKY5;k*>B2%*|wHO4G}&>FWwX`Sr+oZ>`yQw z6p0MWaL1u*SzVj>cH~tU7$`&IWClgLHsGB92LsHI(tU0QzsFj6@wj|-ibJ?0vAD6V8zqU1v;FI0sICXHWTW}nzFZB@x2#Z?g7L%0oEizofBC(a(%MldhsU7?E#HYsZbV#{3r9YLEaDO}i4 z>uKfd@uXFelj3M$ZB#0D`JhGhFEcEvS->v(fSM59R-*e_o+zkD5>zB7)UHFFx%XtQX|2IC= z)X|M7i6m}9EEY2-C$-pfC72FSGtw|Qks-mG^@wa}RIcAmL9sT5qf~o0A&@2Fr9>i6 zR$g|U*>xk%8mVmHj|*=`O&mKL9JWzn)Y7S{PeC1;UU-zDQ`235|I%~<6~5tCj8|*#fz;pyCXP7v+?qr&DwGJ%mPV z;&fPBj7q{*AWsi^{&mC17-Sib2q);ic%NPs)Zks%IdK-N`<}QYq{7_37C*SM!>^d# z6eG?lUyCqe+KB+*@r0gkoS-Tv_+?f@yd(|NB-fzNkA_vliaL5vqv}ZN*!axjWU`D4 zDw8orQpjp@l^^R4SuZFA{SvPG@Dr!ytFKzpe%{(Kv5y3W!x!=nY4eR9_ZoG5ao|%X z7@i)h{n>Le6q=ngCIjuagNNJQtK~24(HV-Cic{dSHCTJbQ>J5ANrj0;v^q;oGPKN7 zRO)|Y4++pAmH5cJ*r@;GH7KU6jc$t78(S3#rsDmkwi#iD{1 zHuNfJUNiEZ%iRLHa_tgAg{RFSo67*6fT@O_f+U{zZ=r-wz&E|?TfC}Fqv%VsH)L}M zIi%1uyV1J6w+se~Qi%Zs_Qh);T`NC1h@yjS7rB!=a2gi&uS@u8rHp=Cn}w>eiM(L9 z!BsGmf(A=-FM(%EbC`K?vkR7q`RZb$41Ch9G&~Ry7tK!Mt4Du8!JEQWTLf<<;{kl6 z@(vUC(XCdFaF(cr&ghK^mG?rQK$!eILV9hd}cppQ;m+G6WTC7pU*~Bq8~I~9esZW zA-`2v#W;ukI!ME)%14`D$-CgpQ?u$!Wq#vtVOpv>+7ie7K5DV~BAv#EOe5D(^x^NEu6Qhtk)B6B=UMn1YDfVWrI9xMoga9gB_W_f$@$KZiZ@vP2)~DFN(Ycf4>F zShj>xsa^T$43J(0xQXxEN)+Z1G@A8~_l*NB+MHwJvD3*8FTkF*_j(QTbQflETEOq{ zEcxrKEjiCc)N5-R_$D0Vo;`;#+9SsjMEQ`8#h)NAJXRqMEzPZRk?^5J@3U!9>IV}0 z5&L8+&H{CPB-OM)&O`)Uwrb7rZ<;r#L8`X%{1w@7_W*y-h>zq#`#Cjxb5CnEhlE7Z zA*vHpvMTSVqGK5g8`g{&Es?zllW8qG9m|gIg_a7nVc?A|L)}T=_pz^Bjzp!=l5WP} z%g4xU6^4LRJ@3+6KJ+wW&zIyNaepaDvd;-Q1==9Yreyxk5M!0Ep6YO~NT;`oI57Bm z5klba&EK!;ZR+aO-K6e&aCx(=KXZ$vl{$kFcvFZ!?V)_5CScgM=p^qld!1Bz_AjsE*_Kx9KIt{Z|0{O$ zo&%zmr9vA|G9;0u2bCp+^uWMWK1KN^;}Q#>^kDTqu03%cpH_80VfhK*f1ST1hiWad z_JV|PBzGES*$umcy;rGCquODQq}Im3PYogYu~|RXX(j#mJj;4p0$}jDB5ht4wgzYDm}(A>-!e=)|{o zlf9alen}oieB%Ei3V;%XI*7qS^2DjRx!s5DlaA_Zu4y&jg-%Y!*gpio!^dijWYeib)W6^kxnCMy)j zsrDXz5n#Vjoj2s-;79tSK1aFkV2U2WW6fFV*0sG~(+pvCi0v#w^XKo}2vbSu(`W#% zjGo7rcO_FlPI@?9n~7(6g`zBd+6|{?$uyN1`o&b>XFs8H;+gq0aFFl9c^YbBO3>?| zQ2w)rL^XD5SoA=^)N(A)z33TksGvhK0%+fh_X4Jz0W(nh3Ft}vD5&MwmFwkIAn<7M ze4NYJbST<>cacR1DKYy5*dAV$=A^C2PP&taenfYfUo^8T1x4&g3mFMPEP`)V zyZHo+i+re*+d^(jYa{0u6u3=ZGTh>+G&VM-Iclzog8E>^5PjfzZw_eVW1>o0adui+ zY()$*XXY`-Pt1}+#*av&rn&>)k~UB75#uJP99AtC;}9VvW1xyY=r`ffts%P`wl%@cGlU`5!0zaxD^f zv&IDBp_I?u+rl%GTb}f~UCTqTp8$iecv*4IJKetdK`r~9ylc4{?j3jYCF~Pm+QD$g z;CDjhonfDIYUAv~!63c&3HX&?4E zut#TxjrJ@D5%sT?tc7uVX1Z0tw`{P@{@>$AxGUPm9uLLM&wc&4FBTn9X5Fq;3~>}E z`NCwIF)G9DC*$#J;JhLMJPjD2}a1X-jL2tBJpi&LWrj zZ-P zC}H?6iPoN^G{kn}`doG9s=dF>!*$RhRLVU8oIg8vHw)7G(`PJ)Q;Nsvh-g3_d5>st z`^JozQYm|!j8tw!O1H1+F;SrV=hS{vGjU~#8!H^4g8BR~%Kh$yaW76Un*tT#KRMg#LrgbHzr43KEZckib59P2;jorRuHKfjID;i zy43u$9SIKYQ?z@bIVDZ0FN6&lm5ps3LgZK?FH}Ri9=V%kxnSS`?udxKdAO8@bwSEB z%t81A`WoHJ=v(P`NA-&lF3w9gH3v;13zm*8ebeI9;>0~$T3`HJP0_*rU{E*q>jMG6 z!nz1@5l8ZSh#4PXhAVP@ZYy%9@&L6D4|tBa*(vE{huk+_=7HK7r@oIJIUmvjoUQ^( zciKKjSUQ^iKJGa1EGr&+L?0$do0En6^q8K^v^st|ARl}9b0Cz~Q7%%LHHN#xE*Raa zLhMOnaO8kfwcY`*2lf#Ord7Rz~1|2#Rpvey`vsYj>dD* zwA2*gUtpjEWlDh{$mnI>w1MG_I;0^9t% z51c8BI_FDNk(4eYWv9h1?;OD6GcqOFpaxB(k?b6|>4_C`>@`-;uWa-qKNJ^l}3du>#ACHo+@O}Gq!Yq}dR z>5?$~q2Iib(r7WQl9=$^5QdQm;kX=a z@d-wu1u7!S3jQ33-Sl+1tvacwyy^Wa=iw`i4|T->*Ey82+v?=Cs-DL6+j|d6k8Eg? zDBwat1o$7js!1E$yX+ss+i^nt*Wy~yW+Y&ZV?1R(iYG>`4^ZEWTwTX7jK(wO*wvh} zP07Jh*C>w`wQ7caBuZJ+fA4x=6``2{F4yHHNYW@si5STo)&&F6fH8i6xvf^oGz|&r z@gH_u)c;!Vq`|^%+1paaAxAJx=HDx^q09Nmpho1UC`@29v4yOrOIvV@xY|Kvw35oM zY=V|+NdF@MXm6BmX=~!VhKeoMk~k+_ik^=~@zUI(A4kmGiaq$IzTrJ7ZkR3@VZ>R^ zb5sqoJnS$aw`JnLV{uXWce_~7`)G3ou?yW-@xU}jyNam=$;~j)HC48Y`HZsOTUv2vFhHyvb+R$=Q2IGRv!ZpQyaC?0}iYul7NNsMhV02M` zAXyMh`B7lmDG1w{rA~^k>?QKF6+<JT@+bZ`}*riZT!5eCL8uSvHAgZ`^U_ZeE>gNO>Z#?D}nHv z)t9>x{Bl>IUmgnVYbVM7^jBXEaOAK387NG|3JzZmi$qY!0G*LpUNImx4hju}^oMe8 z4~xjZ$c$u4M*sF%UpqVg#amRFlASO=9ig=@<#rm}IKbyG$vVT}q)%YplSZ42ZDNKI z`xYJ0=OYz6LIW;BJCp-PG0Yuxb{e5)5gy!RIhRFxT2AzXCF&;&p&+7FTxN>ZV%=3u zu&*8a$L^@Tc%kZ2S$u-tCg(olgYrG;?y-$G+xN0l-}{1M;ZK0oZt4eg+xD_+@|t0J z!`cW3f_$O&lzlKWG90lH4uwJjrw29JNHJa8Z2&yEUsL`Em&#YXwWCnjB@_8hU zmhJbJs)FgT=`NK;j(}n#Ue|mJScA%k8os?cnDBG4(4c0W?}v#!ROetEnbX#C^%=4W z2A4`u?bjEA9x5M)vcPxgclmm6n{MiVdfg1RHkuzFDtEBTtR1>zMQZ97-L)Cb5?f(n zr*e_o;*vk1a=x9lW$* zOPs^E5IqZy04xXReX}8$nn9vJ0o+_hP}MZ0x@qqOXGrRY>lJxjqzM6t=J0>Cv~8kS zY{u}9pKcH>&pGjnwdi1|XJRQ}MJA)>#~1~ zz_^@Wb<%4#TpMEkxu4$us)lH@;~`UuITx|bPgmigut9+k0?#9aBlBXPVji2`MP|8H z?NDIx$JV8X;LP&fwAj{MPSkcPEGy)1uJ6LKC5?*lej+zPezVpT@rHB=nAqp z_2J6V`RxV5F|>BcnDLfnp>w$|r&3-OuUv7z@~;@WLV`M1jsdwjJQmz^Cr;CS<@58C zuf;2z@n~H5aw>2)V0q$7WooY7wzYe-b#USsSQ_=@*annoVW2s}_sEzxAp%)UgK(?M zdxZL1JQ;1;`9rw70M%JeXedpYmGvwE^i|AD>5F{38*AcpzYGgqSL-q*~xYJrkKgm&~q5R9T-%e0TS zqE1UzV(7}*bVeA>92r?fTo4kn8TuDyrC2_IYbfC1m#LVVQXqoR1*vquR#+9x53kXO zlV+6h>To6+2G0oOvh*P7@)XmN$J{f6y{U1Y3iFF0*qrz+R=4%~=jfMA%^j)X0=3A@ z_ThaU8I~#9xYjWyvZrjTWs=*~MBn`}Up;bGkh?=&b8_7~gC}6sC%|SqVPaXUjpbc# zLeo}iw)T;fup6I{%t4an_wJ8zPmnTg&J;wOrR(dM&ULrOGWuJq z{v#pS8Py0sg?tYh@~=?Pk|V%^;QXJRL_wkC%#Yr!Ok3Q_^z&feT7=(xh3e%rR6RO2 zZZr2Le!KWlJkjHqT9qjb(5K_dB=E026nYJ~SpxlX!>p?t2b%{HR{%>b*RPZTikrcj z`tBx_I&9fRea!v>pfziOVOp|1(Q_6u&&K0dT3ki`f5<4{rJroBC*9!2t!;siFi zLNPuIn`z^Z(86--Pz&)rwvKPg39GFCjhR-`GlbxDU#RIPpzOq=u$D%e=9a~UV)fXX zQL7v;@32DL9E?~t1YYit(Ylzz7r{l%Fjw0o|F&}Dy~}^5z=0ke9nH)CYxol5Lf=3 zrL!4#6lf5H61Y7hvXk?dJ7gSF{#qhRYzmzE{{AnTGa=^r8)SuEtNF)+tHF8(A~g#s z4Ok$L`0s^P2Rju>aU*AkwLYzPx%rD!z&-hW=O@7ZB{1liQtnXRtR6z{2b<^J3Yv zxvmNcqX~r#&QEu@xcZ|l_pbx0i~Rl^r}?mrJn%L zK-SaYvsNg0F>ztwoHa@MDTHFBJ98oi=df&vQA`^8)TU_%BNbX1EIat5xYbfb5`|AM}I;2_JRdAl`ii{&VCpRoMRC#s&Txs zBMqTIC)^Y5*XFYJ71SX@lKxoFo$2q&IMQw5){MW+{5E)XY^yi!a+9?Ew$_!dM|D9s eG4^zH!4a+H_+eDkXfjCPW^irjH>>x#{67GB4nTeY literal 0 HcmV?d00001 diff --git a/feature/userinfo/src/main/res/values/strings.xml b/feature/userinfo/src/main/res/values/strings.xml new file mode 100644 index 00000000..3c6d6d9a --- /dev/null +++ b/feature/userinfo/src/main/res/values/strings.xml @@ -0,0 +1,48 @@ + + MusicRoad + + + 상단 바 뒤로 가기 버튼 + + + 메뉴로 이동하기 아이콘 + 프로필 이미지 + Pick + 픽 보관함 + 등록한 픽 + 픽 보관함 메뉴 아이콘 + 등록한 픽 메뉴 아이콘 + Settings + 프로필 + 알림 + 로그아웃 + 회원 탈퇴 + 프로필 설정 메뉴 아이콘 + 알림 메뉴 아이콘 + 로그아웃 메뉴 아이콘 + 지도 아이콘 + 지도로 돌아가기 + + + 알림 설정 + 준비 중인 기능입니다! + 프로필 설정 + 닉네임 + 닉네임에는 한글, 영문, 숫자만 사용할 수 있습니다. + 닉네임을 2글자 이상 입력해주세요. + 닉네임은 10글자 이하로 가능합니다. + 변경 사항 적용 + 변경 사항이 적용되었습니다. + 일시적인 오류가 발생했습니다. + + + 로그아웃 하시겠습니까? + 취소 + 로그아웃 + + 정말 탈퇴하시겠습니까? + 탈퇴 시 회원 정보와 픽 데이터가 삭제되며, 복구할 수 없습니다. + 취소 + 탈퇴 + + diff --git a/feature/userinfo/src/test/java/com/squirtles/userinfo/ExampleUnitTest.kt b/feature/userinfo/src/test/java/com/squirtles/userinfo/ExampleUnitTest.kt new file mode 100644 index 00000000..d2f41332 --- /dev/null +++ b/feature/userinfo/src/test/java/com/squirtles/userinfo/ExampleUnitTest.kt @@ -0,0 +1,17 @@ +package com.squirtles.userinfo + +import org.junit.Test + +import org.junit.Assert.* + +/** + * Example local unit test, which will execute on the development machine (host). + * + * See [testing documentation](http://d.android.com/tools/testing). + */ +class ExampleUnitTest { + @Test + fun addition_isCorrect() { + assertEquals(4, 2 + 2) + } +} diff --git a/settings.gradle.kts b/settings.gradle.kts index 8df32a6d..c5fd3099 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -60,3 +60,4 @@ include(":feature:create") include(":feature:favorite") include(":core:buildconfig") include(":feature:search") +include(":feature:userinfo") From e389485ce64495c2cd33a0458da1da2deed645a0 Mon Sep 17 00:00:00 2001 From: miller198 Date: Wed, 26 Mar 2025 18:12:48 +0900 Subject: [PATCH 42/62] =?UTF-8?q?[refactor]=20userid=20->=20uid=20?= =?UTF-8?q?=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/main/java/com/squirtles/navigation/MainRoute.kt | 2 +- .../src/main/java/com/squirtles/favorite/FavoriteScreen.kt | 6 +++--- .../com/squirtles/favorite/navigation/FavoriteNavigation.kt | 4 ++-- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/core/navigation/src/main/java/com/squirtles/navigation/MainRoute.kt b/core/navigation/src/main/java/com/squirtles/navigation/MainRoute.kt index b9f6c460..afcd3908 100644 --- a/core/navigation/src/main/java/com/squirtles/navigation/MainRoute.kt +++ b/core/navigation/src/main/java/com/squirtles/navigation/MainRoute.kt @@ -8,7 +8,7 @@ sealed interface MainRoute : Route { data object Search : MainRoute @Serializable - data class Favorite(val userId: String) : MainRoute + data class Favorite(val uid: String) : MainRoute @Serializable data class UserInfo(val uid: String) : MainRoute diff --git a/feature/favorite/src/main/java/com/squirtles/favorite/FavoriteScreen.kt b/feature/favorite/src/main/java/com/squirtles/favorite/FavoriteScreen.kt index 9c12dcef..a8041bf8 100644 --- a/feature/favorite/src/main/java/com/squirtles/favorite/FavoriteScreen.kt +++ b/feature/favorite/src/main/java/com/squirtles/favorite/FavoriteScreen.kt @@ -13,7 +13,7 @@ import com.squirtles.picklist.PickListType @Composable fun FavoriteScreen( - userId: String, + uid: String, onBackClick: () -> Unit, onItemClick: (String) -> Unit, favoriteListViewModel: FavoriteListViewModel = hiltViewModel() @@ -23,11 +23,11 @@ fun FavoriteScreen( var showOrderBottomSheet by rememberSaveable { mutableStateOf(false) } LaunchedEffect(Unit) { - favoriteListViewModel.fetchPickList(userId) + favoriteListViewModel.fetchPickList(uid) } PickListScreenContents( - userId = userId, + userId = uid, showOrderBottomSheet = showOrderBottomSheet, selectedPicksId = selectedPicksId, pickListType = PickListType.FAVORITE, diff --git a/feature/favorite/src/main/java/com/squirtles/favorite/navigation/FavoriteNavigation.kt b/feature/favorite/src/main/java/com/squirtles/favorite/navigation/FavoriteNavigation.kt index ea575d87..07e1f9bd 100644 --- a/feature/favorite/src/main/java/com/squirtles/favorite/navigation/FavoriteNavigation.kt +++ b/feature/favorite/src/main/java/com/squirtles/favorite/navigation/FavoriteNavigation.kt @@ -17,10 +17,10 @@ fun NavGraphBuilder.favoriteNavGraph( onItemClick: (String) -> Unit, ) { composable { backStackEntry -> - val userId = backStackEntry.toRoute().userId + val uid = backStackEntry.toRoute().uid FavoriteScreen( - userId = userId, + uid = uid, onBackClick = onBackClick, onItemClick = onItemClick, ) From 6ca21211403f7dfe9df0a8d705c819b4d9d9f1f8 Mon Sep 17 00:00:00 2001 From: miller198 Date: Wed, 26 Mar 2025 18:57:04 +0900 Subject: [PATCH 43/62] =?UTF-8?q?[feature]=20:feature:mypick=20=EB=AA=A8?= =?UTF-8?q?=EB=93=88=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- core/picklist/build.gradle.kts | 2 + .../picklist/PickListScreenContents.kt | 8 ++-- .../squirtles/picklist/PickListViewModel.kt | 15 ++++--- .../user/usecase/CreateGoogleIdUserUseCase.kt | 1 - .../com/squirtles/favorite/FavoriteScreen.kt | 4 +- feature/mypick/.gitignore | 1 + feature/mypick/build.gradle.kts | 18 ++++++++ feature/mypick/consumer-rules.pro | 0 feature/mypick/proguard-rules.pro | 21 +++++++++ .../mypick/ExampleInstrumentedTest.kt | 24 ++++++++++ .../squirtles/mypick/MyPickListViewModel.kt | 25 +++++++++++ .../java/com/squirtles/mypick/MyPickScreen.kt | 45 +++++++++++++++++++ .../mypick/navigation/MyPickNavigation.kt | 28 ++++++++++++ .../com/squirtles/mypick/ExampleUnitTest.kt | 17 +++++++ settings.gradle.kts | 1 + 15 files changed, 196 insertions(+), 14 deletions(-) create mode 100644 feature/mypick/.gitignore create mode 100644 feature/mypick/build.gradle.kts create mode 100644 feature/mypick/consumer-rules.pro create mode 100644 feature/mypick/proguard-rules.pro create mode 100644 feature/mypick/src/androidTest/java/com/squirtles/mypick/ExampleInstrumentedTest.kt create mode 100644 feature/mypick/src/main/java/com/squirtles/mypick/MyPickListViewModel.kt create mode 100644 feature/mypick/src/main/java/com/squirtles/mypick/MyPickScreen.kt create mode 100644 feature/mypick/src/main/java/com/squirtles/mypick/navigation/MyPickNavigation.kt create mode 100644 feature/mypick/src/test/java/com/squirtles/mypick/ExampleUnitTest.kt diff --git a/core/picklist/build.gradle.kts b/core/picklist/build.gradle.kts index d08bb11c..c38e44b8 100644 --- a/core/picklist/build.gradle.kts +++ b/core/picklist/build.gradle.kts @@ -12,6 +12,8 @@ dependencies { implementation(projects.domain.picklist) implementation(projects.domain.user) + implementation(libs.inject) + testImplementation(libs.junit) androidTestImplementation(libs.bundles.test) diff --git a/core/picklist/src/main/java/com/squirtles/picklist/PickListScreenContents.kt b/core/picklist/src/main/java/com/squirtles/picklist/PickListScreenContents.kt index 88d7adcd..82a4b2f6 100644 --- a/core/picklist/src/main/java/com/squirtles/picklist/PickListScreenContents.kt +++ b/core/picklist/src/main/java/com/squirtles/picklist/PickListScreenContents.kt @@ -50,12 +50,12 @@ import com.squirtles.picklist.components.PickItem @Composable fun PickListScreenContents( - userId: String, + uid: String, showOrderBottomSheet: Boolean, selectedPicksId: Set, pickListType: PickListType, uiState: PickListUiState, - getUserId: () -> String, + getUid: () -> String, onBackClick: () -> Unit, onItemClick: (String) -> Unit, setListOrder: (Order) -> Unit, @@ -93,7 +93,7 @@ fun PickListScreenContents( ), onBackClick = onBackClick, actions = { - if(getUserId() == userId){ + if(getUid() == uid){ EditModeAction( isEditMode = isEditMode, enabled = uiState is PickListUiState.Success, @@ -187,7 +187,7 @@ fun PickListScreenContents( onDeletePickClick = { isEditMode = false isDeletePickDialogVisible = false - deleteSelectedPicks(userId) + deleteSelectedPicks(uid) }, ) } diff --git a/core/picklist/src/main/java/com/squirtles/picklist/PickListViewModel.kt b/core/picklist/src/main/java/com/squirtles/picklist/PickListViewModel.kt index 82e6d5c7..1ac1d2cb 100644 --- a/core/picklist/src/main/java/com/squirtles/picklist/PickListViewModel.kt +++ b/core/picklist/src/main/java/com/squirtles/picklist/PickListViewModel.kt @@ -11,13 +11,14 @@ import kotlinx.coroutines.awaitAll import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.asStateFlow import kotlinx.coroutines.launch - -abstract class PickListViewModel( - val fetchPickListUseCase: FetchPickListUseCaseInterface, - val getPickListOrderUseCase: GetPickListOrderUseCaseInterface, - val savePickListOrderUseCase: SavePickListOrderUseCaseInterface, - val removePickUseCase: RemovePickUseCaseInterface, - val getCurrentUidUseCase: GetCurrentUidUseCase +import javax.inject.Inject + +open class PickListViewModel @Inject constructor( + private val fetchPickListUseCase: FetchPickListUseCaseInterface, + private val getPickListOrderUseCase: GetPickListOrderUseCaseInterface, + private val savePickListOrderUseCase: SavePickListOrderUseCaseInterface, + private val removePickUseCase: RemovePickUseCaseInterface, + private val getCurrentUidUseCase: GetCurrentUidUseCase ) : ViewModel() { private var pickList: List = emptyList() diff --git a/domain/user/src/main/java/com/squirtles/user/usecase/CreateGoogleIdUserUseCase.kt b/domain/user/src/main/java/com/squirtles/user/usecase/CreateGoogleIdUserUseCase.kt index fb87d85a..2c47f3d7 100644 --- a/domain/user/src/main/java/com/squirtles/user/usecase/CreateGoogleIdUserUseCase.kt +++ b/domain/user/src/main/java/com/squirtles/user/usecase/CreateGoogleIdUserUseCase.kt @@ -6,7 +6,6 @@ import com.squirtles.user.LocalUserRepository import javax.inject.Inject class CreateGoogleIdUserUseCase @Inject constructor( - private val localUserRepository: LocalUserRepository, private val firebaseUserRepository: FirebaseUserRepository ) { suspend operator fun invoke( diff --git a/feature/favorite/src/main/java/com/squirtles/favorite/FavoriteScreen.kt b/feature/favorite/src/main/java/com/squirtles/favorite/FavoriteScreen.kt index a8041bf8..430e6ac4 100644 --- a/feature/favorite/src/main/java/com/squirtles/favorite/FavoriteScreen.kt +++ b/feature/favorite/src/main/java/com/squirtles/favorite/FavoriteScreen.kt @@ -27,7 +27,7 @@ fun FavoriteScreen( } PickListScreenContents( - userId = uid, + uid = uid, showOrderBottomSheet = showOrderBottomSheet, selectedPicksId = selectedPicksId, pickListType = PickListType.FAVORITE, @@ -40,7 +40,7 @@ fun FavoriteScreen( deselectAllPicks = favoriteListViewModel::deselectAllPicks, toggleSelectedPick = favoriteListViewModel::toggleSelectedPick, deleteSelectedPicks = favoriteListViewModel::deleteSelectedPicks, - getUserId = { + getUid = { favoriteListViewModel.getUid().toString() } ) diff --git a/feature/mypick/.gitignore b/feature/mypick/.gitignore new file mode 100644 index 00000000..42afabfd --- /dev/null +++ b/feature/mypick/.gitignore @@ -0,0 +1 @@ +/build \ No newline at end of file diff --git a/feature/mypick/build.gradle.kts b/feature/mypick/build.gradle.kts new file mode 100644 index 00000000..d62b6b19 --- /dev/null +++ b/feature/mypick/build.gradle.kts @@ -0,0 +1,18 @@ +plugins { + alias(libs.plugins.musicroad.feature) +} + +android { + namespace = "com.squirtles.mypick" +} + +dependencies { + implementation(projects.core.picklist) + implementation(projects.domain.picklist) + implementation(projects.domain.pick) + implementation(projects.domain.order) + implementation(projects.domain.user) + + testImplementation(libs.junit) + androidTestImplementation(libs.bundles.test) +} diff --git a/feature/mypick/consumer-rules.pro b/feature/mypick/consumer-rules.pro new file mode 100644 index 00000000..e69de29b diff --git a/feature/mypick/proguard-rules.pro b/feature/mypick/proguard-rules.pro new file mode 100644 index 00000000..481bb434 --- /dev/null +++ b/feature/mypick/proguard-rules.pro @@ -0,0 +1,21 @@ +# Add project specific ProGuard rules here. +# You can control the set of applied configuration files using the +# proguardFiles setting in build.gradle. +# +# For more details, see +# http://developer.android.com/guide/developing/tools/proguard.html + +# If your project uses WebView with JS, uncomment the following +# and specify the fully qualified class name to the JavaScript interface +# class: +#-keepclassmembers class fqcn.of.javascript.interface.for.webview { +# public *; +#} + +# Uncomment this to preserve the line number information for +# debugging stack traces. +#-keepattributes SourceFile,LineNumberTable + +# If you keep the line number information, uncomment this to +# hide the original source file name. +#-renamesourcefileattribute SourceFile \ No newline at end of file diff --git a/feature/mypick/src/androidTest/java/com/squirtles/mypick/ExampleInstrumentedTest.kt b/feature/mypick/src/androidTest/java/com/squirtles/mypick/ExampleInstrumentedTest.kt new file mode 100644 index 00000000..dec8f396 --- /dev/null +++ b/feature/mypick/src/androidTest/java/com/squirtles/mypick/ExampleInstrumentedTest.kt @@ -0,0 +1,24 @@ +package com.example.mypick + +import androidx.test.platform.app.InstrumentationRegistry +import androidx.test.ext.junit.runners.AndroidJUnit4 + +import org.junit.Test +import org.junit.runner.RunWith + +import org.junit.Assert.* + +/** + * Instrumented test, which will execute on an Android device. + * + * See [testing documentation](http://d.android.com/tools/testing). + */ +@RunWith(AndroidJUnit4::class) +class ExampleInstrumentedTest { + @Test + fun useAppContext() { + // Context of the app under test. + val appContext = InstrumentationRegistry.getInstrumentation().targetContext + assertEquals("com.example.mypick.test", appContext.packageName) + } +} diff --git a/feature/mypick/src/main/java/com/squirtles/mypick/MyPickListViewModel.kt b/feature/mypick/src/main/java/com/squirtles/mypick/MyPickListViewModel.kt new file mode 100644 index 00000000..d10378a6 --- /dev/null +++ b/feature/mypick/src/main/java/com/squirtles/mypick/MyPickListViewModel.kt @@ -0,0 +1,25 @@ +package com.squirtles.mypick + +import com.squirtles.order.usecase.GetMyPickListOrderUseCase +import com.squirtles.order.usecase.SaveMyPickListOrderUseCase +import com.squirtles.pick.usecase.DeletePickUseCase +import com.squirtles.pick.usecase.FetchMyPicksUseCase +import com.squirtles.picklist.PickListViewModel +import com.squirtles.user.usecase.GetCurrentUidUseCase +import dagger.hilt.android.lifecycle.HiltViewModel +import javax.inject.Inject + +@HiltViewModel +class MyPickListViewModel @Inject constructor( + fetchMyPicksUseCase: FetchMyPicksUseCase, + getMyPickListOrderUseCase: GetMyPickListOrderUseCase, + saveMyPickListOrderUseCase: SaveMyPickListOrderUseCase, + deletePickUseCase: DeletePickUseCase, + getCurrentUidUseCase: GetCurrentUidUseCase +) : PickListViewModel( + fetchPickListUseCase = fetchMyPicksUseCase, + getPickListOrderUseCase = getMyPickListOrderUseCase, + savePickListOrderUseCase = saveMyPickListOrderUseCase, + removePickUseCase = deletePickUseCase, + getCurrentUidUseCase = getCurrentUidUseCase +) diff --git a/feature/mypick/src/main/java/com/squirtles/mypick/MyPickScreen.kt b/feature/mypick/src/main/java/com/squirtles/mypick/MyPickScreen.kt new file mode 100644 index 00000000..96e19d11 --- /dev/null +++ b/feature/mypick/src/main/java/com/squirtles/mypick/MyPickScreen.kt @@ -0,0 +1,45 @@ +package com.squirtles.mypick + +import androidx.compose.runtime.Composable +import androidx.compose.runtime.LaunchedEffect +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.saveable.rememberSaveable +import androidx.compose.runtime.setValue +import androidx.hilt.navigation.compose.hiltViewModel +import androidx.lifecycle.compose.collectAsStateWithLifecycle +import com.squirtles.picklist.PickListScreenContents +import com.squirtles.picklist.PickListType + +@Composable +fun MyPickScreen( + uid: String, + onBackClick: () -> Unit, + onItemClick: (String) -> Unit, + myPickListViewModel: MyPickListViewModel = hiltViewModel() +) { + val uiState by myPickListViewModel.pickListUiState.collectAsStateWithLifecycle() + val selectedPicksId by myPickListViewModel.selectedPicksId.collectAsStateWithLifecycle() + var showOrderBottomSheet by rememberSaveable { mutableStateOf(false) } + + LaunchedEffect(Unit) { + myPickListViewModel.fetchPickList(uid) + } + + PickListScreenContents( + uid = uid, + showOrderBottomSheet = showOrderBottomSheet, + selectedPicksId = selectedPicksId, + pickListType = PickListType.CREATED, + uiState = uiState, + onBackClick = onBackClick, + onItemClick = onItemClick, + setListOrder = myPickListViewModel::setListOrder, + setOrderBottomSheetVisibility = { showOrderBottomSheet = it }, + selectAllPicks = myPickListViewModel::selectAllPicks, + deselectAllPicks = myPickListViewModel::deselectAllPicks, + toggleSelectedPick = myPickListViewModel::toggleSelectedPick, + deleteSelectedPicks = myPickListViewModel::deleteSelectedPicks, + getUid = { myPickListViewModel.getUid().toString() }, + ) +} diff --git a/feature/mypick/src/main/java/com/squirtles/mypick/navigation/MyPickNavigation.kt b/feature/mypick/src/main/java/com/squirtles/mypick/navigation/MyPickNavigation.kt new file mode 100644 index 00000000..a5cd36b2 --- /dev/null +++ b/feature/mypick/src/main/java/com/squirtles/mypick/navigation/MyPickNavigation.kt @@ -0,0 +1,28 @@ +package com.squirtles.mypick.navigation + +import androidx.navigation.NavController +import androidx.navigation.NavGraphBuilder +import androidx.navigation.NavOptions +import androidx.navigation.compose.composable +import androidx.navigation.toRoute +import com.squirtles.mypick.MyPickScreen +import com.squirtles.navigation.UserInfoRoute + +fun NavController.navigateMyPicks(uid: String, navOptions: NavOptions) { + navigate(UserInfoRoute.MyPicks(uid), navOptions) +} + +fun NavGraphBuilder.myPickNavGraph( + onBackClick: () -> Unit, + onItemClick: (String) -> Unit, +) { + composable { backStackEntry -> + val uid = backStackEntry.toRoute().uid + + MyPickScreen( + uid = uid, + onBackClick = onBackClick, + onItemClick = onItemClick + ) + } +} diff --git a/feature/mypick/src/test/java/com/squirtles/mypick/ExampleUnitTest.kt b/feature/mypick/src/test/java/com/squirtles/mypick/ExampleUnitTest.kt new file mode 100644 index 00000000..7dec7030 --- /dev/null +++ b/feature/mypick/src/test/java/com/squirtles/mypick/ExampleUnitTest.kt @@ -0,0 +1,17 @@ +package com.example.mypick + +import org.junit.Test + +import org.junit.Assert.* + +/** + * Example local unit test, which will execute on the development machine (host). + * + * See [testing documentation](http://d.android.com/tools/testing). + */ +class ExampleUnitTest { + @Test + fun addition_isCorrect() { + assertEquals(4, 2 + 2) + } +} diff --git a/settings.gradle.kts b/settings.gradle.kts index c5fd3099..9430897a 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -61,3 +61,4 @@ include(":feature:favorite") include(":core:buildconfig") include(":feature:search") include(":feature:userinfo") +include(":feature:mypick") From 6f939732ed4060f5fd06cc5fdb65040ab718fb08 Mon Sep 17 00:00:00 2001 From: miller198 Date: Tue, 22 Apr 2025 19:16:13 +0900 Subject: [PATCH 44/62] =?UTF-8?q?[refactor]=20Domain,=20Data=20=EB=AA=A8?= =?UTF-8?q?=EB=93=88=20=EC=A0=81=EC=9A=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/build.gradle.kts | 35 +- .../musicroad/account/AccountViewModel.kt | 8 +- .../common/picklist/PickListScreenContents.kt | 310 ---------- .../musicroad/common/picklist/PickListType.kt | 5 - .../common/picklist/PickListUiState.kt | 10 - .../common/picklist/PickListViewModel.kt | 105 ---- .../components/DeleteSelectedPickDialog.kt | 46 -- .../picklist/components/EditModeAction.kt | 60 -- .../components/EditModeBottomButton.kt | 96 ---- .../picklist/components/OrderBottomSheet.kt | 135 ----- .../common/picklist/components/PickItem.kt | 129 ----- .../musicroad/create/CreatePickScreen.kt | 2 +- .../musicroad/create/CreatePickViewModel.kt | 20 +- .../create/navigation/CreateNavigation.kt | 4 +- .../musicroad/detail/DetailViewModel.kt | 18 +- .../musicroad/detail/PickDetailScreen.kt | 10 +- .../musicroad/detail/PickDetailUiState.kt | 2 +- .../detail/components/CircleAlbumCover.kt | 30 +- .../musicroad/detail/components/SongInfo.kt | 2 +- .../detail/components/music/MusicPlayer.kt | 4 +- .../music/visualizer/BaseVisualizer.kt | 74 --- .../visualizer/CanvasCircleVisualizer.kt | 122 ---- .../music/visualizer/CircleVisualizer.kt | 108 ---- .../detail/navigation/PickDetailNavigation.kt | 34 ++ .../detail/videoplayer/MusicVideoPlayer.kt | 5 +- .../detail/videoplayer/MusicVideoScreen.kt | 4 +- .../detail/videoplayer/VideoPlayerOverlay.kt | 8 +- .../favorite/FavoriteListViewModel.kt | 10 +- .../musicroad/favorite/FavoriteScreen.kt | 4 +- .../squirtles/musicroad/main/MainViewModel.kt | 6 +- .../musicroad/main/navigation/MainNavHost.kt | 12 +- .../main/navigation/MainNavigator.kt | 4 +- .../com/squirtles/musicroad/map/MapScreen.kt | 2 +- .../squirtles/musicroad/map/MapViewModel.kt | 8 +- .../map/components/ClusterBottomSheet.kt | 6 +- .../map/components/InfoWindowCard.kt | 8 +- .../map/components/PickNotificationBanner.kt | 2 +- .../musicroad/map/marker/Clusterer.kt | 8 +- .../map/marker/LeafMarkerIconView.kt | 7 +- .../musicroad/map/marker/MarkerKey.kt | 2 +- .../musicroad/map/navigation/MapNavigation.kt | 24 +- .../musicroad/media/PlayerServiceViewModel.kt | 226 ++++---- .../musicroad/media/PlayerUiState.kt | 24 +- .../musicroad/media/PlayerViewModel.kt | 456 +++++++-------- .../musicroad/mypick/MyPickListViewModel.kt | 8 +- .../musicroad/mypick/MyPickScreen.kt | 4 +- .../musicroad/navigation/SearchRoute.kt | 4 +- .../musicroad/search/SearchMusicScreen.kt | 2 +- .../musicroad/search/SearchViewModel.kt | 4 +- .../search/navigation/SearchNavigation.kt | 2 +- .../musicroad/userinfo/UserInfoViewModel.kt | 11 +- .../userinfo/navigation/UserInfoNavigation.kt | 1 - .../musicroad/utils/SerializableType.kt | 22 - .../musicroad/utils/ThrottleFirst.kt | 18 - {data => audio_visualizer}/.gitignore | 0 audio_visualizer/build.gradle.kts | 51 ++ {data => audio_visualizer}/consumer-rules.pro | 0 {data => audio_visualizer}/proguard-rules.pro | 0 .../ExampleInstrumentedTest.kt | 24 + .../src/main/AndroidManifest.xml | 2 +- .../audiovisualizer/BaseVisualizer.kt | 113 ++++ .../audiovisualizer/FftDataProcessor.kt | 217 +++++++ .../audiovisualizer/FrequencyScale.kt | 12 + .../audiovisualizer/configs/Configs.kt | 212 +++++++ .../configs/VisualizerCallbacks.kt | 30 + .../soundeffect/DrawSoundBar.kt | 112 ++++ .../soundeffect/DrawSoundEffectConfigs.kt | 99 ++++ .../soundeffect/DrawSoundEffectUtils.kt | 90 +++ .../soundeffect/DrawSoundWaveFill.kt | 119 ++++ .../soundeffect/DrawSoundWaveStroke.kt | 83 +++ .../soundeffect/SoundEffects.kt | 37 ++ .../audiovisualizer/ui/CircleVisualizer.kt | 112 ++++ .../audiovisualizer/ExampleUnitTest.kt | 17 + .../convention/extensions/ComposeAndroid.kt | 2 + core/account/build.gradle.kts | 6 + .../com/squirtles/account/AccountViewModel.kt | 2 +- .../java/com/squirtles/account/GoogleId.kt | 40 +- .../squirtles/common/ui/SignInAlertDialog.kt | 136 +++++ .../com/squirtles/common/ui/theme/Color.kt | 4 + .../src/main/res/drawable/img_google_logo.png | Bin 0 -> 16590 bytes core/common/src/main/res/values/strings.xml | 15 + .../squirtles/picklist/PickListViewModel.kt | 4 + data/applemusic/build.gradle.kts | 3 + ...leMusicModule.kt => AppleMusicDiModule.kt} | 2 +- data/build.gradle.kts | 33 -- data/favorite/build.gradle.kts | 4 + .../squirtles/favorite/CloudFunctionHelper.kt | 2 +- .../FirebaseFavoriteRepositoryImpl.kt | 12 +- ...eFavoriteModule.kt => FavoriteDiModule.kt} | 2 +- data/firebase/build.gradle.kts | 5 + .../firebase/BaseFirebaseDataSource.kt | 3 +- .../squirtles/firebase/FirebaseException.kt | 49 -- .../firebase/FirebaseRepositoryUtils.kt | 2 + data/location/build.gradle.kts | 3 + ...{LocationModule.kt => LocationDiModule.kt} | 2 +- data/order/build.gradle.kts | 3 + .../di/{OrderModule.kt => OrderDiModule.kt} | 2 +- data/pick/build.gradle.kts | 3 + .../pick/FirebasePickRepositoryImpl.kt | 8 +- .../di/{PickModule.kt => PickDiModule.kt} | 4 +- .../applemusic/AppleMusicDataSourceImpl.kt | 62 -- .../applemusic/AppleMusicRepositoryImpl.kt | 36 -- .../applemusic/SearchSongsPagingSource.kt | 64 --- .../data/applemusic/api/AppleMusicApi.kt | 18 - .../data/applemusic/di/AppleMusicDi.kt | 46 -- .../data/applemusic/model/AppleMusicMapper.kt | 36 -- .../data/applemusic/model/Artwork.kt | 11 - .../data/applemusic/model/Attributes.kt | 16 - .../squirtles/data/applemusic/model/Data.kt | 9 - .../applemusic/model/MusicVideoResponse.kt | 9 - .../data/applemusic/model/Preview.kt | 10 - .../data/applemusic/model/Results.kt | 10 - .../data/applemusic/model/SearchResponse.kt | 8 - .../squirtles/data/applemusic/model/Songs.kt | 9 - .../data/favorite/CloudFunctionHelper.kt | 34 -- .../FirebaseFavoriteDataSourceImpl.kt | 71 --- .../FirebaseFavoriteRepositoryImpl.kt | 24 - .../data/favorite/di/FirebaseFavoriteDi.kt | 32 -- .../data/favorite/model/FirebaseFavorite.kt | 10 - .../data/firebase/BaseFirebaseDataSource.kt | 130 ----- .../firebase/FirebaseDataSourceConstants.kt | 18 - .../squirtles/data/firebase/FirebaseModule.kt | 20 - .../data/firebase/FirebaseRepositoryUtils.kt | 19 - .../location/LocalLocationRepositoryImpl.kt | 18 - .../squirtles/data/location/di/LocationDi.kt | 18 - .../squirtles/data/network/NetworkModule.kt | 50 -- .../order/LocalPickListOrderRepositoryImpl.kt | 22 - .../com/squirtles/data/order/di/OrderDi.kt | 18 - .../data/pick/FirebasePickDataSourceImpl.kt | 192 ------- .../data/pick/FirebasePickRepositoryImpl.kt | 46 -- .../java/com/squirtles/data/pick/di/PickDi.kt | 27 - .../squirtles/data/pick/model/FirebasePick.kt | 28 - .../com/squirtles/data/pick/model/Mapper.kt | 82 --- .../data/user/FirebaseUserDataSourceImpl.kt | 74 --- .../data/user/FirebaseUserRepositoryImpl.kt | 34 -- .../data/user/LocalUserDataSourceImpl.kt | 58 -- .../data/user/LocalUserRepositoryImpl.kt | 36 -- .../java/com/squirtles/data/user/di/UserDi.kt | 42 -- .../squirtles/data/user/model/FirebaseUser.kt | 8 - .../com/squirtles/data/user/model/Mapper.kt | 11 - data/user/build.gradle.kts | 3 + .../user/FirebaseUserRepositoryImpl.kt | 3 +- .../user/di/{UserDi.kt => UserDiModule.kt} | 4 +- domain/applemusic/build.gradle.kts | 3 + domain/build.gradle.kts | 27 - domain/favorite/build.gradle.kts | 2 + .../favorite/usecase/DeleteFavoriteUseCase.kt | 6 +- domain/{ => firebase}/.gitignore | 0 domain/firebase/build.gradle.kts | 9 + .../domain}/firebase/FirebaseException.kt | 2 +- domain/location/build.gradle.kts | 3 + .../usecase/GetFavoriteListOrderUseCase.kt | 2 +- .../usecase/GetMyPickListOrderUseCase.kt | 2 +- .../usecase/SaveFavoriteListOrderUseCase.kt | 2 +- .../usecase/SaveMyPickListOrderUseCase.kt | 2 +- .../pick/FirebasePickRepository.kt | 2 +- .../domain/pick/usecase/CreatePickUseCase.kt | 2 +- .../domain/pick/usecase/DeletePickUseCase.kt | 0 .../pick/usecase/FetchFavoritePicksUseCase.kt | 2 +- .../pick/usecase/FetchMyPicksUseCase.kt | 2 +- .../domain/pick/usecase/FetchPickUseCase.kt | 2 +- .../pick/usecase/CreatePickUseCase.kt | 11 - .../pick/usecase/DeletePickUseCase.kt | 12 - .../pick/usecase/FetchFavoritePicksUseCase.kt | 13 - .../pick/usecase/FetchMyPicksUseCase.kt | 13 - .../pick/usecase/FetchPickUseCase.kt | 15 - .../picklist/FetchPickListUseCaseInterface.kt | 2 +- .../GetPickListOrderUseCaseInterface.kt | 2 +- .../picklist/RemovePickUseCaseInterface.kt | 0 .../SavePickListOrderUseCaseInterface.kt | 2 +- .../picklist/RemovePickUseCaseInterface.kt | 5 - domain/player/build.gradle.kts | 3 + domain/src/main/AndroidManifest.xml | 4 - .../domain/applemusic/AppleMusicException.kt | 12 - .../applemusic/AppleMusicRemoteDataSource.kt | 11 - .../domain/applemusic/AppleMusicRepository.kt | 12 - .../usecase/FetchMusicVideoUseCase.kt | 20 - .../applemusic/usecase/FetchSongsUseCase.kt | 11 - .../favorite/FirebaseFavoriteDataSource.kt | 7 - .../favorite/FirebaseFavoriteRepository.kt | 9 - .../favorite/usecase/CreateFavoriteUseCase.kt | 11 - .../favorite/usecase/DeleteFavoriteUseCase.kt | 12 - .../usecase/FetchIsFavoriteUseCase.kt | 11 - .../domain/firebase/FirebaseException.kt | 13 - .../location/LocalLocationRepository.kt | 10 - .../usecase/GetLastLocationUseCase.kt | 10 - .../usecase/SaveLastLocationUseCase.kt | 11 - .../com/squirtles/domain/model/GeoLocation.kt | 11 - .../com/squirtles/domain/model/MusicVideo.kt | 13 - .../java/com/squirtles/domain/model/Order.kt | 7 - .../java/com/squirtles/domain/model/Pick.kt | 28 - .../com/squirtles/domain/model/PlayerState.kt | 11 - .../java/com/squirtles/domain/model/Song.kt | 25 - .../java/com/squirtles/domain/model/User.kt | 9 - .../order/LocalPickListOrderRepository.kt | 11 - .../usecase/GetFavoriteListOrderUseCase.kt | 11 - .../usecase/GetMyPickListOrderUseCase.kt | 11 - .../usecase/SaveFavoriteListOrderUseCase.kt | 12 - .../usecase/SaveMyPickListOrderUseCase.kt | 12 - .../domain/pick/FirebasePickDataSource.kt | 12 - .../domain/pick/FirebasePickRepository.kt | 12 - .../picklist/FetchPickListUseCaseInterface.kt | 7 - .../GetPickListOrderUseCaseInterface.kt | 7 - .../SavePickListOrderUseCaseInterface.kt | 7 - .../player/MediaPlayerListenerUseCase.kt | 141 ----- .../domain/player/MediaPlayerUseCase.kt | 132 ----- .../domain/user/FirebaseUserDataSource.kt | 15 - .../domain/user/FirebaseUserRepository.kt | 10 - .../domain/user/LocalUserDataSource.kt | 13 - .../domain/user/LocalUserRepository.kt | 13 - .../user/usecase/CreateGoogleIdUserUseCase.kt | 23 - .../user/usecase/DeleteAccountUseCase.kt | 50 -- .../user/usecase/FetchUserByIdUseCase.kt | 11 - .../user/usecase/GetCurrentUidUseCase.kt | 8 - .../domain/user/usecase/SignOutUseCase.kt | 8 - .../user/usecase/UpdateUserNameUseCase.kt | 11 - .../user/usecase/DeleteAccountUseCase.kt | 6 +- .../squirtles/user/usecase/SignOutUseCase.kt | 2 +- .../squirtles/create/CreatePickViewModel.kt | 2 +- feature/detail/.gitignore | 1 + feature/detail/build.gradle.kts | 28 + {domain => feature/detail}/consumer-rules.pro | 0 {domain => feature/detail}/proguard-rules.pro | 0 .../detail}/ExampleInstrumentedTest.kt | 6 +- .../com/squirtles/detail/DetailViewModel.kt | 174 ++++++ .../com/squirtles/detail/FavoriteAction.kt | 5 + .../com/squirtles/detail/PickDetailScreen.kt | 541 ++++++++++++++++++ .../com/squirtles/detail/PickDetailUiState.kt | 10 + .../detail/components/CircleAlbumCover.kt | 82 +++ .../detail/components/DetailPickTopAppBar.kt | 104 ++++ .../detail/components/MusicVideoKnob.kt | 80 +++ .../detail/components/PickCommentText.kt | 56 ++ .../detail/components/PickInformation.kt | 39 ++ .../PlayCircularProgressIndicator.kt | 65 +++ .../squirtles/detail/components/SongInfo.kt | 39 ++ .../detail/components/SwipeUpIcon.kt | 32 ++ .../detail/components/music/MusicPlayer.kt | 55 ++ .../detail/components/music/PlayBar.kt | 99 ++++ .../components/music/PlayProgressIndicator.kt | 55 ++ .../detail/components/music/PlayerControls.kt | 101 ++++ .../detail/navigation/PickDetailNavigation.kt | 34 ++ .../detail/videoplayer/MusicVideoPlayer.kt | 113 ++++ .../detail/videoplayer/MusicVideoScreen.kt | 37 ++ .../detail/videoplayer/VideoPlayerOverlay.kt | 234 ++++++++ .../detail/videoplayer/VideoPlayerState.kt | 5 + .../videoplayer/VideoPlayerViewModel.kt | 135 +++++ .../src/main/res/drawable/ic_delete.xml | 10 + .../src/main/res/drawable/ic_favorite.xml | 10 + .../main/res/drawable/ic_favorite_false.xml | 10 + .../main/res/drawable/ic_favorite_true.xml | 10 + .../detail/src/main/res/drawable/ic_swipe.xml | 13 + .../detail/src/main/res/values/strings.xml | 39 ++ .../com/squirtles/detail/ExampleUnitTest.kt | 17 + feature/favorite/build.gradle.kts | 1 - .../favorite/FavoriteListViewModel.kt | 2 +- .../favorite/navigation/FavoriteNavigation.kt | 4 +- feature/main/.gitignore | 1 + feature/main/build.gradle.kts | 30 + feature/main/consumer-rules.pro | 0 feature/main/proguard-rules.pro | 21 + .../main}/ExampleInstrumentedTest.kt | 6 +- feature/main/src/main/AndroidManifest.xml | 23 + .../java/com/squirtles/main/LoadingState.kt | 9 + .../java/com/squirtles/main/MainActivity.kt | 196 +++++++ .../java/com/squirtles/main/MainViewModel.kt | 61 ++ .../squirtles/main/NeedPermissionDialog.kt | 95 +++ .../java/com/squirtles/main/PermissionBar.kt | 80 +++ .../squirtles/main/navigation/MainNavHost.kt | 78 +++ .../main/navigation/MainNavigator.kt | 127 ++++ feature/main/src/main/res/values/colors.xml | 11 + feature/main/src/main/res/values/strings.xml | 15 + feature/main/src/main/res/values/themes.xml | 11 + .../com/squirtles/main}/ExampleUnitTest.kt | 4 +- feature/map/.gitignore | 1 + feature/map/build.gradle.kts | 24 + feature/map/consumer-rules.pro | 0 feature/map/proguard-rules.pro | 21 + .../squirtles/map/ExampleInstrumentedTest.kt | 24 + feature/map/src/main/AndroidManifest.xml | 10 + .../main/java/com/squirtles/map/Constants.kt | 16 + .../main/java/com/squirtles/map/MapScreen.kt | 274 +++++++++ .../java/com/squirtles/map/MapViewModel.kt | 195 +++++++ .../main/java/com/squirtles/map/NaverMap.kt | 286 +++++++++ .../map/components/ClusterBottomSheet.kt | 158 +++++ .../map/components/InfoWindowCard.kt | 157 +++++ .../squirtles/map/components/LoadingDialog.kt | 87 +++ .../map/components/MapBottomNavBar.kt | 140 +++++ .../map/components/PickNotificationBanner.kt | 106 ++++ .../map/marker/ClusterMarkerIconView.kt | 55 ++ .../com/squirtles/map/marker/Clusterer.kt | 143 +++++ .../com/squirtles/map/marker/DensityType.kt | 12 + .../map/marker/LeafMarkerIconView.kt | 106 ++++ .../com/squirtles/map/marker/MarkerKey.kt | 9 + .../squirtles/map/navigation/MapNavigation.kt | 36 ++ .../com/squirtles/map/navigation/NavTab.kt | 34 ++ .../map/src/main/res/drawable/ic_location.xml | 12 + .../main/res/drawable/ic_musical_note_64.png | Bin 0 -> 1067 bytes feature/map/src/main/res/values/strings.xml | 22 + .../com/squirtles/map}/ExampleUnitTest.kt | 4 +- .../squirtles/mypick/MyPickListViewModel.kt | 4 +- gradle.properties | 4 +- gradle/libs.versions.toml | 17 +- .../CustomMediaSessionCallback.kt | 192 +++---- .../mediaservice/MediaControllerProvider.kt | 116 ++-- .../mediaservice/MediaNotificationProvider.kt | 240 ++++---- .../mediaservice/MediaPlayerService.kt | 124 ++-- .../squirtles/mediaservice/PlayerCommands.kt | 80 +-- .../mediaservice/di/MediaDiModule.kt | 202 +++---- settings.gradle.kts | 5 + 309 files changed, 7578 insertions(+), 4743 deletions(-) delete mode 100644 app/src/main/java/com/squirtles/musicroad/common/picklist/PickListScreenContents.kt delete mode 100644 app/src/main/java/com/squirtles/musicroad/common/picklist/PickListType.kt delete mode 100644 app/src/main/java/com/squirtles/musicroad/common/picklist/PickListUiState.kt delete mode 100644 app/src/main/java/com/squirtles/musicroad/common/picklist/PickListViewModel.kt delete mode 100644 app/src/main/java/com/squirtles/musicroad/common/picklist/components/DeleteSelectedPickDialog.kt delete mode 100644 app/src/main/java/com/squirtles/musicroad/common/picklist/components/EditModeAction.kt delete mode 100644 app/src/main/java/com/squirtles/musicroad/common/picklist/components/EditModeBottomButton.kt delete mode 100644 app/src/main/java/com/squirtles/musicroad/common/picklist/components/OrderBottomSheet.kt delete mode 100644 app/src/main/java/com/squirtles/musicroad/common/picklist/components/PickItem.kt delete mode 100644 app/src/main/java/com/squirtles/musicroad/detail/components/music/visualizer/BaseVisualizer.kt delete mode 100644 app/src/main/java/com/squirtles/musicroad/detail/components/music/visualizer/CanvasCircleVisualizer.kt delete mode 100644 app/src/main/java/com/squirtles/musicroad/detail/components/music/visualizer/CircleVisualizer.kt create mode 100644 app/src/main/java/com/squirtles/musicroad/detail/navigation/PickDetailNavigation.kt delete mode 100644 app/src/main/java/com/squirtles/musicroad/utils/SerializableType.kt delete mode 100644 app/src/main/java/com/squirtles/musicroad/utils/ThrottleFirst.kt rename {data => audio_visualizer}/.gitignore (100%) create mode 100644 audio_visualizer/build.gradle.kts rename {data => audio_visualizer}/consumer-rules.pro (100%) rename {data => audio_visualizer}/proguard-rules.pro (100%) create mode 100644 audio_visualizer/src/androidTest/java/com/miller198/audiovisualizer/ExampleInstrumentedTest.kt rename {data => audio_visualizer}/src/main/AndroidManifest.xml (90%) create mode 100644 audio_visualizer/src/main/java/com/miller198/audiovisualizer/BaseVisualizer.kt create mode 100644 audio_visualizer/src/main/java/com/miller198/audiovisualizer/FftDataProcessor.kt create mode 100644 audio_visualizer/src/main/java/com/miller198/audiovisualizer/FrequencyScale.kt create mode 100644 audio_visualizer/src/main/java/com/miller198/audiovisualizer/configs/Configs.kt create mode 100644 audio_visualizer/src/main/java/com/miller198/audiovisualizer/configs/VisualizerCallbacks.kt create mode 100644 audio_visualizer/src/main/java/com/miller198/audiovisualizer/soundeffect/DrawSoundBar.kt create mode 100644 audio_visualizer/src/main/java/com/miller198/audiovisualizer/soundeffect/DrawSoundEffectConfigs.kt create mode 100644 audio_visualizer/src/main/java/com/miller198/audiovisualizer/soundeffect/DrawSoundEffectUtils.kt create mode 100644 audio_visualizer/src/main/java/com/miller198/audiovisualizer/soundeffect/DrawSoundWaveFill.kt create mode 100644 audio_visualizer/src/main/java/com/miller198/audiovisualizer/soundeffect/DrawSoundWaveStroke.kt create mode 100644 audio_visualizer/src/main/java/com/miller198/audiovisualizer/soundeffect/SoundEffects.kt create mode 100644 audio_visualizer/src/main/java/com/miller198/audiovisualizer/ui/CircleVisualizer.kt create mode 100644 audio_visualizer/src/test/java/com/miller198/audiovisualizer/ExampleUnitTest.kt create mode 100644 core/common/src/main/java/com/squirtles/common/ui/SignInAlertDialog.kt create mode 100644 core/common/src/main/res/drawable/img_google_logo.png rename data/applemusic/src/main/java/com/squirtles/applemusic/di/{AppleMusicModule.kt => AppleMusicDiModule.kt} (98%) delete mode 100644 data/build.gradle.kts rename data/favorite/src/main/java/com/squirtles/favorite/di/{FirebaseFavoriteModule.kt => FavoriteDiModule.kt} (97%) delete mode 100644 data/firebase/src/main/java/com/squirtles/firebase/FirebaseException.kt rename data/location/src/main/java/com/squirtles/location/di/{LocationModule.kt => LocationDiModule.kt} (94%) rename data/order/src/main/java/com/squirtles/order/di/{OrderModule.kt => OrderDiModule.kt} (95%) rename data/pick/src/main/java/com/squirtles/pick/di/{PickModule.kt => PickDiModule.kt} (91%) delete mode 100644 data/src/main/java/com/squirtles/data/applemusic/AppleMusicDataSourceImpl.kt delete mode 100644 data/src/main/java/com/squirtles/data/applemusic/AppleMusicRepositoryImpl.kt delete mode 100644 data/src/main/java/com/squirtles/data/applemusic/SearchSongsPagingSource.kt delete mode 100644 data/src/main/java/com/squirtles/data/applemusic/api/AppleMusicApi.kt delete mode 100644 data/src/main/java/com/squirtles/data/applemusic/di/AppleMusicDi.kt delete mode 100644 data/src/main/java/com/squirtles/data/applemusic/model/AppleMusicMapper.kt delete mode 100644 data/src/main/java/com/squirtles/data/applemusic/model/Artwork.kt delete mode 100644 data/src/main/java/com/squirtles/data/applemusic/model/Attributes.kt delete mode 100644 data/src/main/java/com/squirtles/data/applemusic/model/Data.kt delete mode 100644 data/src/main/java/com/squirtles/data/applemusic/model/MusicVideoResponse.kt delete mode 100644 data/src/main/java/com/squirtles/data/applemusic/model/Preview.kt delete mode 100644 data/src/main/java/com/squirtles/data/applemusic/model/Results.kt delete mode 100644 data/src/main/java/com/squirtles/data/applemusic/model/SearchResponse.kt delete mode 100644 data/src/main/java/com/squirtles/data/applemusic/model/Songs.kt delete mode 100644 data/src/main/java/com/squirtles/data/favorite/CloudFunctionHelper.kt delete mode 100644 data/src/main/java/com/squirtles/data/favorite/FirebaseFavoriteDataSourceImpl.kt delete mode 100644 data/src/main/java/com/squirtles/data/favorite/FirebaseFavoriteRepositoryImpl.kt delete mode 100644 data/src/main/java/com/squirtles/data/favorite/di/FirebaseFavoriteDi.kt delete mode 100644 data/src/main/java/com/squirtles/data/favorite/model/FirebaseFavorite.kt delete mode 100644 data/src/main/java/com/squirtles/data/firebase/BaseFirebaseDataSource.kt delete mode 100644 data/src/main/java/com/squirtles/data/firebase/FirebaseDataSourceConstants.kt delete mode 100644 data/src/main/java/com/squirtles/data/firebase/FirebaseModule.kt delete mode 100644 data/src/main/java/com/squirtles/data/firebase/FirebaseRepositoryUtils.kt delete mode 100644 data/src/main/java/com/squirtles/data/location/LocalLocationRepositoryImpl.kt delete mode 100644 data/src/main/java/com/squirtles/data/location/di/LocationDi.kt delete mode 100644 data/src/main/java/com/squirtles/data/network/NetworkModule.kt delete mode 100644 data/src/main/java/com/squirtles/data/order/LocalPickListOrderRepositoryImpl.kt delete mode 100644 data/src/main/java/com/squirtles/data/order/di/OrderDi.kt delete mode 100644 data/src/main/java/com/squirtles/data/pick/FirebasePickDataSourceImpl.kt delete mode 100644 data/src/main/java/com/squirtles/data/pick/FirebasePickRepositoryImpl.kt delete mode 100644 data/src/main/java/com/squirtles/data/pick/di/PickDi.kt delete mode 100644 data/src/main/java/com/squirtles/data/pick/model/FirebasePick.kt delete mode 100644 data/src/main/java/com/squirtles/data/pick/model/Mapper.kt delete mode 100644 data/src/main/java/com/squirtles/data/user/FirebaseUserDataSourceImpl.kt delete mode 100644 data/src/main/java/com/squirtles/data/user/FirebaseUserRepositoryImpl.kt delete mode 100644 data/src/main/java/com/squirtles/data/user/LocalUserDataSourceImpl.kt delete mode 100644 data/src/main/java/com/squirtles/data/user/LocalUserRepositoryImpl.kt delete mode 100644 data/src/main/java/com/squirtles/data/user/di/UserDi.kt delete mode 100644 data/src/main/java/com/squirtles/data/user/model/FirebaseUser.kt delete mode 100644 data/src/main/java/com/squirtles/data/user/model/Mapper.kt rename data/user/src/main/java/com/squirtles/user/di/{UserDi.kt => UserDiModule.kt} (98%) delete mode 100644 domain/build.gradle.kts rename domain/{ => firebase}/.gitignore (100%) create mode 100644 domain/firebase/build.gradle.kts rename {data/src/main/java/com/squirtles/data => domain/firebase/src/main/java/com/squirtles/domain}/firebase/FirebaseException.kt (98%) rename domain/pick/src/main/java/com/squirtles/{ => domain}/pick/FirebasePickRepository.kt (93%) rename domain/{ => pick}/src/main/java/com/squirtles/domain/pick/usecase/CreatePickUseCase.kt (89%) rename domain/{ => pick}/src/main/java/com/squirtles/domain/pick/usecase/DeletePickUseCase.kt (100%) rename domain/{ => pick}/src/main/java/com/squirtles/domain/pick/usecase/FetchFavoritePicksUseCase.kt (92%) rename domain/{ => pick}/src/main/java/com/squirtles/domain/pick/usecase/FetchMyPicksUseCase.kt (92%) rename domain/{ => pick}/src/main/java/com/squirtles/domain/pick/usecase/FetchPickUseCase.kt (92%) delete mode 100644 domain/pick/src/main/java/com/squirtles/pick/usecase/CreatePickUseCase.kt delete mode 100644 domain/pick/src/main/java/com/squirtles/pick/usecase/DeletePickUseCase.kt delete mode 100644 domain/pick/src/main/java/com/squirtles/pick/usecase/FetchFavoritePicksUseCase.kt delete mode 100644 domain/pick/src/main/java/com/squirtles/pick/usecase/FetchMyPicksUseCase.kt delete mode 100644 domain/pick/src/main/java/com/squirtles/pick/usecase/FetchPickUseCase.kt rename domain/picklist/src/main/java/com/squirtles/{ => domain}/picklist/FetchPickListUseCaseInterface.kt (79%) rename domain/picklist/src/main/java/com/squirtles/{ => domain}/picklist/GetPickListOrderUseCaseInterface.kt (76%) rename domain/{ => picklist}/src/main/java/com/squirtles/domain/picklist/RemovePickUseCaseInterface.kt (100%) rename domain/picklist/src/main/java/com/squirtles/{ => domain}/picklist/SavePickListOrderUseCaseInterface.kt (77%) delete mode 100644 domain/picklist/src/main/java/com/squirtles/picklist/RemovePickUseCaseInterface.kt delete mode 100644 domain/src/main/AndroidManifest.xml delete mode 100644 domain/src/main/java/com/squirtles/domain/applemusic/AppleMusicException.kt delete mode 100644 domain/src/main/java/com/squirtles/domain/applemusic/AppleMusicRemoteDataSource.kt delete mode 100644 domain/src/main/java/com/squirtles/domain/applemusic/AppleMusicRepository.kt delete mode 100644 domain/src/main/java/com/squirtles/domain/applemusic/usecase/FetchMusicVideoUseCase.kt delete mode 100644 domain/src/main/java/com/squirtles/domain/applemusic/usecase/FetchSongsUseCase.kt delete mode 100644 domain/src/main/java/com/squirtles/domain/favorite/FirebaseFavoriteDataSource.kt delete mode 100644 domain/src/main/java/com/squirtles/domain/favorite/FirebaseFavoriteRepository.kt delete mode 100644 domain/src/main/java/com/squirtles/domain/favorite/usecase/CreateFavoriteUseCase.kt delete mode 100644 domain/src/main/java/com/squirtles/domain/favorite/usecase/DeleteFavoriteUseCase.kt delete mode 100644 domain/src/main/java/com/squirtles/domain/favorite/usecase/FetchIsFavoriteUseCase.kt delete mode 100644 domain/src/main/java/com/squirtles/domain/firebase/FirebaseException.kt delete mode 100644 domain/src/main/java/com/squirtles/domain/location/LocalLocationRepository.kt delete mode 100644 domain/src/main/java/com/squirtles/domain/location/usecase/GetLastLocationUseCase.kt delete mode 100644 domain/src/main/java/com/squirtles/domain/location/usecase/SaveLastLocationUseCase.kt delete mode 100644 domain/src/main/java/com/squirtles/domain/model/GeoLocation.kt delete mode 100644 domain/src/main/java/com/squirtles/domain/model/MusicVideo.kt delete mode 100644 domain/src/main/java/com/squirtles/domain/model/Order.kt delete mode 100644 domain/src/main/java/com/squirtles/domain/model/Pick.kt delete mode 100644 domain/src/main/java/com/squirtles/domain/model/PlayerState.kt delete mode 100644 domain/src/main/java/com/squirtles/domain/model/Song.kt delete mode 100644 domain/src/main/java/com/squirtles/domain/model/User.kt delete mode 100644 domain/src/main/java/com/squirtles/domain/order/LocalPickListOrderRepository.kt delete mode 100644 domain/src/main/java/com/squirtles/domain/order/usecase/GetFavoriteListOrderUseCase.kt delete mode 100644 domain/src/main/java/com/squirtles/domain/order/usecase/GetMyPickListOrderUseCase.kt delete mode 100644 domain/src/main/java/com/squirtles/domain/order/usecase/SaveFavoriteListOrderUseCase.kt delete mode 100644 domain/src/main/java/com/squirtles/domain/order/usecase/SaveMyPickListOrderUseCase.kt delete mode 100644 domain/src/main/java/com/squirtles/domain/pick/FirebasePickDataSource.kt delete mode 100644 domain/src/main/java/com/squirtles/domain/pick/FirebasePickRepository.kt delete mode 100644 domain/src/main/java/com/squirtles/domain/picklist/FetchPickListUseCaseInterface.kt delete mode 100644 domain/src/main/java/com/squirtles/domain/picklist/GetPickListOrderUseCaseInterface.kt delete mode 100644 domain/src/main/java/com/squirtles/domain/picklist/SavePickListOrderUseCaseInterface.kt delete mode 100644 domain/src/main/java/com/squirtles/domain/player/MediaPlayerListenerUseCase.kt delete mode 100644 domain/src/main/java/com/squirtles/domain/player/MediaPlayerUseCase.kt delete mode 100644 domain/src/main/java/com/squirtles/domain/user/FirebaseUserDataSource.kt delete mode 100644 domain/src/main/java/com/squirtles/domain/user/FirebaseUserRepository.kt delete mode 100644 domain/src/main/java/com/squirtles/domain/user/LocalUserDataSource.kt delete mode 100644 domain/src/main/java/com/squirtles/domain/user/LocalUserRepository.kt delete mode 100644 domain/src/main/java/com/squirtles/domain/user/usecase/CreateGoogleIdUserUseCase.kt delete mode 100644 domain/src/main/java/com/squirtles/domain/user/usecase/DeleteAccountUseCase.kt delete mode 100644 domain/src/main/java/com/squirtles/domain/user/usecase/FetchUserByIdUseCase.kt delete mode 100644 domain/src/main/java/com/squirtles/domain/user/usecase/GetCurrentUidUseCase.kt delete mode 100644 domain/src/main/java/com/squirtles/domain/user/usecase/SignOutUseCase.kt delete mode 100644 domain/src/main/java/com/squirtles/domain/user/usecase/UpdateUserNameUseCase.kt create mode 100644 feature/detail/.gitignore create mode 100644 feature/detail/build.gradle.kts rename {domain => feature/detail}/consumer-rules.pro (100%) rename {domain => feature/detail}/proguard-rules.pro (100%) rename {domain/src/androidTest/java/com/squirtles/domain => feature/detail/src/androidTest/java/com/squirtles/detail}/ExampleInstrumentedTest.kt (85%) create mode 100644 feature/detail/src/main/java/com/squirtles/detail/DetailViewModel.kt create mode 100644 feature/detail/src/main/java/com/squirtles/detail/FavoriteAction.kt create mode 100644 feature/detail/src/main/java/com/squirtles/detail/PickDetailScreen.kt create mode 100644 feature/detail/src/main/java/com/squirtles/detail/PickDetailUiState.kt create mode 100644 feature/detail/src/main/java/com/squirtles/detail/components/CircleAlbumCover.kt create mode 100644 feature/detail/src/main/java/com/squirtles/detail/components/DetailPickTopAppBar.kt create mode 100644 feature/detail/src/main/java/com/squirtles/detail/components/MusicVideoKnob.kt create mode 100644 feature/detail/src/main/java/com/squirtles/detail/components/PickCommentText.kt create mode 100644 feature/detail/src/main/java/com/squirtles/detail/components/PickInformation.kt create mode 100644 feature/detail/src/main/java/com/squirtles/detail/components/PlayCircularProgressIndicator.kt create mode 100644 feature/detail/src/main/java/com/squirtles/detail/components/SongInfo.kt create mode 100644 feature/detail/src/main/java/com/squirtles/detail/components/SwipeUpIcon.kt create mode 100644 feature/detail/src/main/java/com/squirtles/detail/components/music/MusicPlayer.kt create mode 100644 feature/detail/src/main/java/com/squirtles/detail/components/music/PlayBar.kt create mode 100644 feature/detail/src/main/java/com/squirtles/detail/components/music/PlayProgressIndicator.kt create mode 100644 feature/detail/src/main/java/com/squirtles/detail/components/music/PlayerControls.kt create mode 100644 feature/detail/src/main/java/com/squirtles/detail/navigation/PickDetailNavigation.kt create mode 100644 feature/detail/src/main/java/com/squirtles/detail/videoplayer/MusicVideoPlayer.kt create mode 100644 feature/detail/src/main/java/com/squirtles/detail/videoplayer/MusicVideoScreen.kt create mode 100644 feature/detail/src/main/java/com/squirtles/detail/videoplayer/VideoPlayerOverlay.kt create mode 100644 feature/detail/src/main/java/com/squirtles/detail/videoplayer/VideoPlayerState.kt create mode 100644 feature/detail/src/main/java/com/squirtles/detail/videoplayer/VideoPlayerViewModel.kt create mode 100644 feature/detail/src/main/res/drawable/ic_delete.xml create mode 100644 feature/detail/src/main/res/drawable/ic_favorite.xml create mode 100644 feature/detail/src/main/res/drawable/ic_favorite_false.xml create mode 100644 feature/detail/src/main/res/drawable/ic_favorite_true.xml create mode 100644 feature/detail/src/main/res/drawable/ic_swipe.xml create mode 100644 feature/detail/src/main/res/values/strings.xml create mode 100644 feature/detail/src/test/java/com/squirtles/detail/ExampleUnitTest.kt create mode 100644 feature/main/.gitignore create mode 100644 feature/main/build.gradle.kts create mode 100644 feature/main/consumer-rules.pro create mode 100644 feature/main/proguard-rules.pro rename {data/src/androidTest/java/com/squirtles/data => feature/main/src/androidTest/java/com/squirtles/main}/ExampleInstrumentedTest.kt (86%) create mode 100644 feature/main/src/main/AndroidManifest.xml create mode 100644 feature/main/src/main/java/com/squirtles/main/LoadingState.kt create mode 100644 feature/main/src/main/java/com/squirtles/main/MainActivity.kt create mode 100644 feature/main/src/main/java/com/squirtles/main/MainViewModel.kt create mode 100644 feature/main/src/main/java/com/squirtles/main/NeedPermissionDialog.kt create mode 100644 feature/main/src/main/java/com/squirtles/main/PermissionBar.kt create mode 100644 feature/main/src/main/java/com/squirtles/main/navigation/MainNavHost.kt create mode 100644 feature/main/src/main/java/com/squirtles/main/navigation/MainNavigator.kt create mode 100644 feature/main/src/main/res/values/colors.xml create mode 100644 feature/main/src/main/res/values/strings.xml create mode 100644 feature/main/src/main/res/values/themes.xml rename {domain/src/test/java/com/squirtles/domain => feature/main/src/test/java/com/squirtles/main}/ExampleUnitTest.kt (91%) create mode 100644 feature/map/.gitignore create mode 100644 feature/map/build.gradle.kts create mode 100644 feature/map/consumer-rules.pro create mode 100644 feature/map/proguard-rules.pro create mode 100644 feature/map/src/androidTest/java/com/squirtles/map/ExampleInstrumentedTest.kt create mode 100644 feature/map/src/main/AndroidManifest.xml create mode 100644 feature/map/src/main/java/com/squirtles/map/Constants.kt create mode 100644 feature/map/src/main/java/com/squirtles/map/MapScreen.kt create mode 100644 feature/map/src/main/java/com/squirtles/map/MapViewModel.kt create mode 100644 feature/map/src/main/java/com/squirtles/map/NaverMap.kt create mode 100644 feature/map/src/main/java/com/squirtles/map/components/ClusterBottomSheet.kt create mode 100644 feature/map/src/main/java/com/squirtles/map/components/InfoWindowCard.kt create mode 100644 feature/map/src/main/java/com/squirtles/map/components/LoadingDialog.kt create mode 100644 feature/map/src/main/java/com/squirtles/map/components/MapBottomNavBar.kt create mode 100644 feature/map/src/main/java/com/squirtles/map/components/PickNotificationBanner.kt create mode 100644 feature/map/src/main/java/com/squirtles/map/marker/ClusterMarkerIconView.kt create mode 100644 feature/map/src/main/java/com/squirtles/map/marker/Clusterer.kt create mode 100644 feature/map/src/main/java/com/squirtles/map/marker/DensityType.kt create mode 100644 feature/map/src/main/java/com/squirtles/map/marker/LeafMarkerIconView.kt create mode 100644 feature/map/src/main/java/com/squirtles/map/marker/MarkerKey.kt create mode 100644 feature/map/src/main/java/com/squirtles/map/navigation/MapNavigation.kt create mode 100644 feature/map/src/main/java/com/squirtles/map/navigation/NavTab.kt create mode 100644 feature/map/src/main/res/drawable/ic_location.xml create mode 100644 feature/map/src/main/res/drawable/ic_musical_note_64.png create mode 100644 feature/map/src/main/res/values/strings.xml rename {data/src/test/java/com/squirtles/data => feature/map/src/test/java/com/squirtles/map}/ExampleUnitTest.kt (91%) diff --git a/app/build.gradle.kts b/app/build.gradle.kts index 390efa08..ae7b57fd 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -29,7 +29,7 @@ android { versionCode = 10100 versionName = "1.1.0" - testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner" +// testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner" vectorDrawables { useSupportLibrary = true } @@ -98,9 +98,31 @@ android { } dependencies { - implementation(projects.domain) - implementation(projects.data) - implementation(projects.mediaservice) +// implementation(projects.domain) +// implementation(projects.data) +// implementation(projects.mediaservice) + implementation(projects.audioVisualizer) + implementation(projects.core.musicplayer) + implementation(projects.core.model) + implementation(projects.core.common) + implementation(projects.core.picklist) + implementation(projects.core.navigation) + implementation(projects.core.util) + implementation(projects.domain.applemusic) + implementation(projects.domain.firebase) + implementation(projects.domain.user) + implementation(projects.domain.pick) + implementation(projects.domain.picklist) + implementation(projects.domain.favorite) + implementation(projects.domain.order) + implementation(projects.domain.location) + implementation(projects.data.applemusic) + implementation(projects.data.firebase) + implementation(projects.data.user) + implementation(projects.data.pick) + implementation(projects.data.favorite) + implementation(projects.data.location) + implementation(projects.data.order) implementation(libs.androidx.core.ktx) implementation(libs.androidx.lifecycle.runtime.ktx) @@ -150,10 +172,7 @@ dependencies { implementation(libs.coil.network.okhttp) // ExoPlayer - implementation(libs.androidx.media3.exoplayer) - implementation(libs.androidx.media3.exoplayer.dash) - implementation(libs.androidx.media3.ui) - implementation(libs.androidx.media3.session) + implementation(libs.bundles.exoplayer) // Paging implementation(libs.androidx.paging.runtime) diff --git a/app/src/main/java/com/squirtles/musicroad/account/AccountViewModel.kt b/app/src/main/java/com/squirtles/musicroad/account/AccountViewModel.kt index e3c29775..6f58f37a 100644 --- a/app/src/main/java/com/squirtles/musicroad/account/AccountViewModel.kt +++ b/app/src/main/java/com/squirtles/musicroad/account/AccountViewModel.kt @@ -4,10 +4,10 @@ import android.util.Log import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope import com.google.android.libraries.identity.googleid.GoogleIdTokenCredential -import com.squirtles.domain.user.usecase.CreateGoogleIdUserUseCase -import com.squirtles.domain.user.usecase.DeleteAccountUseCase -import com.squirtles.domain.user.usecase.SignOutUseCase -import com.squirtles.domain.user.usecase.FetchUserByIdUseCase +import com.squirtles.user.usecase.SignOutUseCase +import com.squirtles.user.usecase.CreateGoogleIdUserUseCase +import com.squirtles.user.usecase.DeleteAccountUseCase +import com.squirtles.user.usecase.FetchUserByIdUseCase import dagger.hilt.android.lifecycle.HiltViewModel import kotlinx.coroutines.flow.MutableSharedFlow import kotlinx.coroutines.flow.asSharedFlow diff --git a/app/src/main/java/com/squirtles/musicroad/common/picklist/PickListScreenContents.kt b/app/src/main/java/com/squirtles/musicroad/common/picklist/PickListScreenContents.kt deleted file mode 100644 index c2a5ea1d..00000000 --- a/app/src/main/java/com/squirtles/musicroad/common/picklist/PickListScreenContents.kt +++ /dev/null @@ -1,310 +0,0 @@ -package com.squirtles.musicroad.common.picklist - -import android.content.res.Configuration -import androidx.compose.foundation.background -import androidx.compose.foundation.clickable -import androidx.compose.foundation.layout.Arrangement -import androidx.compose.foundation.layout.Box -import androidx.compose.foundation.layout.Column -import androidx.compose.foundation.layout.Row -import androidx.compose.foundation.layout.displayCutoutPadding -import androidx.compose.foundation.layout.fillMaxSize -import androidx.compose.foundation.layout.fillMaxWidth -import androidx.compose.foundation.layout.padding -import androidx.compose.foundation.layout.size -import androidx.compose.foundation.layout.wrapContentSize -import androidx.compose.foundation.lazy.LazyColumn -import androidx.compose.foundation.lazy.items -import androidx.compose.foundation.shape.CircleShape -import androidx.compose.material3.MaterialTheme -import androidx.compose.material3.Scaffold -import androidx.compose.material3.Text -import androidx.compose.runtime.Composable -import androidx.compose.runtime.getValue -import androidx.compose.runtime.mutableStateOf -import androidx.compose.runtime.remember -import androidx.compose.runtime.saveable.rememberSaveable -import androidx.compose.runtime.setValue -import androidx.compose.ui.Alignment -import androidx.compose.ui.Modifier -import androidx.compose.ui.draw.clip -import androidx.compose.ui.graphics.Brush -import androidx.compose.ui.platform.LocalConfiguration -import androidx.compose.ui.res.stringResource -import androidx.compose.ui.unit.dp -import androidx.wear.compose.material.CircularProgressIndicator -import com.squirtles.domain.model.Order -import com.squirtles.domain.model.Pick -import com.squirtles.musicroad.R -import com.squirtles.musicroad.common.Constants.COLOR_STOPS -import com.squirtles.musicroad.common.Constants.DEFAULT_PADDING -import com.squirtles.musicroad.common.CountText -import com.squirtles.musicroad.common.DefaultTopAppBar -import com.squirtles.musicroad.common.VerticalSpacer -import com.squirtles.musicroad.common.picklist.components.DeleteSelectedPickDialog -import com.squirtles.musicroad.common.picklist.components.EditModeAction -import com.squirtles.musicroad.common.picklist.components.EditModeBottomButton -import com.squirtles.musicroad.common.picklist.components.OrderBottomSheet -import com.squirtles.musicroad.common.picklist.components.PickItem -import com.squirtles.musicroad.ui.theme.Primary -import com.squirtles.musicroad.ui.theme.White - -@Composable -fun PickListScreenContents( - uid: String, - showOrderBottomSheet: Boolean, - selectedPicksId: Set, - pickListType: PickListType, - uiState: PickListUiState, - getUid: () -> String, - onBackClick: () -> Unit, - onItemClick: (String) -> Unit, - setListOrder: (Order) -> Unit, - setOrderBottomSheetVisibility: (Boolean) -> Unit, - selectAllPicks: () -> Unit, - deselectAllPicks: () -> Unit, - toggleSelectedPick: (String) -> Unit, - deleteSelectedPicks: (String) -> Unit -) { - val isLandscape = LocalConfiguration.current.orientation == Configuration.ORIENTATION_LANDSCAPE - - var isEditMode by rememberSaveable { mutableStateOf(false) } - var isDeletePickDialogVisible by rememberSaveable { mutableStateOf(false) } - - val deactivateEditMode = remember { - { - isEditMode = false - deselectAllPicks() - } - } - val showDeletePickDialog = remember { - { - isDeletePickDialogVisible = true - } - } - - Scaffold( - topBar = { - DefaultTopAppBar( - title = stringResource( - when (pickListType) { - PickListType.FAVORITE -> R.string.favorite_picks_top_app_bar_title - PickListType.CREATED -> R.string.my_picks_top_app_bar_title - } - ), - onBackClick = onBackClick, - actions = { - if (getUid() == uid) { - EditModeAction( - isEditMode = isEditMode, - enabled = uiState is PickListUiState.Success, - isSelectedEmpty = selectedPicksId.isEmpty(), - activateEditMode = { isEditMode = true }, - selectAllPicks = { selectAllPicks() }, - deselectAllPicks = { deselectAllPicks() }, - ) - } - } - ) - }, - ) { innerPadding -> - Box( - modifier = Modifier - .fillMaxSize() - .background(Brush.verticalGradient(colorStops = COLOR_STOPS)) - .padding(innerPadding) - .then(if (isLandscape) Modifier.displayCutoutPadding() else Modifier) - ) { - when (uiState) { - PickListUiState.Loading -> { - CircularProgressIndicator( - modifier = Modifier - .size(36.dp) - .align(Alignment.Center), - indicatorColor = Primary - ) - } - - is PickListUiState.Success -> { - val pickList = uiState.pickList - val order = uiState.order - - Column( - modifier = Modifier.fillMaxSize() - ) { - PickList( - modifier = Modifier - .fillMaxWidth() - .weight(1f), - isEditMode = isEditMode, - pickListType = pickListType, - pickList = pickList, - selectedPicksId = selectedPicksId, - order = order, - onOrderClick = { - setOrderBottomSheetVisibility(true) - }, - onItemClick = onItemClick, - onEditModeItemClick = toggleSelectedPick, - ) - - if (isEditMode) { - EditModeBottomButton( - enabled = selectedPicksId.isNotEmpty(), - deactivateEditMode = deactivateEditMode, - showDeletePickDialog = showDeletePickDialog, - ) - } - } - } - - PickListUiState.Error -> { - Text( - text = stringResource(R.string.error_loading_pick_list), - modifier = Modifier.align(Alignment.Center), - color = White, - style = MaterialTheme.typography.bodyLarge - ) - // TODO: 다시하기 버튼 같은 거 만들어서 요청 다시 하게 할 수 있도록 만드는 것도 고려해보기 - } - } - } - } - - if (showOrderBottomSheet) { - OrderBottomSheet( - isFavoritePicks = pickListType == PickListType.FAVORITE, - currentOrder = (uiState as PickListUiState.Success).order, - onDismissRequest = { setOrderBottomSheetVisibility(false) }, - onOrderClick = setListOrder, - ) - } - - if (isDeletePickDialogVisible) { - DeleteSelectedPickDialog( - selectedPickCount = selectedPicksId.size, - pickListType = pickListType, - onDismissRequest = { isDeletePickDialogVisible = false }, - onDeletePickClick = { - isEditMode = false - isDeletePickDialogVisible = false - deleteSelectedPicks(uid) - }, - ) - } -} - -@Composable -private fun PickList( - modifier: Modifier, - isEditMode: Boolean, - pickListType: PickListType, - pickList: List, - selectedPicksId: Set, - order: Order, - onOrderClick: () -> Unit, - onItemClick: (String) -> Unit, - onEditModeItemClick: (String) -> Unit, -) { - Row( - modifier = Modifier - .fillMaxWidth() - .padding(horizontal = DEFAULT_PADDING), - horizontalArrangement = Arrangement.SpaceBetween, - verticalAlignment = Alignment.CenterVertically - ) { - CountText( - totalCount = if (isEditMode) selectedPicksId.size else pickList.size, - countLabel = stringResource( - if (isEditMode) R.string.selected_count_text else R.string.total_count_text - ), - defaultColor = White, - ) - - Box( - modifier = Modifier - .wrapContentSize() - .clip(CircleShape) - .clickable { onOrderClick() } - ) { - Text( - text = getOrderString( - pickListType = pickListType, - order = order - ), - modifier = Modifier.padding( - horizontal = DEFAULT_PADDING / 2, - vertical = DEFAULT_PADDING / 4 - ), - color = White, - style = MaterialTheme.typography.bodyMedium - ) - } - } - - VerticalSpacer(8) - - if (pickList.isEmpty()) { - Box( - modifier = modifier, - contentAlignment = Alignment.Center - ) { - Text( - text = stringResource( - when (pickListType) { - PickListType.FAVORITE -> R.string.favorite_picks_empty - PickListType.CREATED -> R.string.my_picks_empty - } - ), - color = White, - style = MaterialTheme.typography.titleMedium - ) - } - } else { - LazyColumn( - modifier = modifier - ) { - items( - items = pickList, - key = { it.id } - ) { pick -> - PickItem( - isEditMode = isEditMode, - isSelected = selectedPicksId.contains(pick.id), - song = pick.song, - createdByOthers = pickListType == PickListType.FAVORITE, - createUserName = pick.createdBy.userName, - favoriteCount = pick.favoriteCount, - comment = pick.comment, - createdAt = pick.createdAt, - onItemClick = { - if (isEditMode) onEditModeItemClick(pick.id) else onItemClick(pick.id) - } - ) - } - } - } -} - -@Composable -private fun getOrderString(pickListType: PickListType, order: Order): String { - return "${ - stringResource( - when (order) { - Order.LATEST -> - when (pickListType) { - PickListType.FAVORITE -> R.string.latest_favorite_order - PickListType.CREATED -> R.string.latest_create_order - } - - Order.OLDEST -> - when (pickListType) { - PickListType.FAVORITE -> R.string.oldest_favorite_order - PickListType.CREATED -> R.string.oldest_create_order - } - - Order.FAVORITE_DESC -> R.string.favorite_count_desc - } - ) - } ▼" -} diff --git a/app/src/main/java/com/squirtles/musicroad/common/picklist/PickListType.kt b/app/src/main/java/com/squirtles/musicroad/common/picklist/PickListType.kt deleted file mode 100644 index e357f48e..00000000 --- a/app/src/main/java/com/squirtles/musicroad/common/picklist/PickListType.kt +++ /dev/null @@ -1,5 +0,0 @@ -package com.squirtles.musicroad.common.picklist - -enum class PickListType { - FAVORITE, CREATED -} diff --git a/app/src/main/java/com/squirtles/musicroad/common/picklist/PickListUiState.kt b/app/src/main/java/com/squirtles/musicroad/common/picklist/PickListUiState.kt deleted file mode 100644 index f0df5a8c..00000000 --- a/app/src/main/java/com/squirtles/musicroad/common/picklist/PickListUiState.kt +++ /dev/null @@ -1,10 +0,0 @@ -package com.squirtles.musicroad.common.picklist - -import com.squirtles.domain.model.Order -import com.squirtles.domain.model.Pick - -sealed class PickListUiState { - data object Loading : PickListUiState() - data class Success(val pickList: List, val order: Order) : PickListUiState() - data object Error : PickListUiState() -} diff --git a/app/src/main/java/com/squirtles/musicroad/common/picklist/PickListViewModel.kt b/app/src/main/java/com/squirtles/musicroad/common/picklist/PickListViewModel.kt deleted file mode 100644 index aff24b4c..00000000 --- a/app/src/main/java/com/squirtles/musicroad/common/picklist/PickListViewModel.kt +++ /dev/null @@ -1,105 +0,0 @@ -package com.squirtles.musicroad.common.picklist - -import android.util.Log -import androidx.lifecycle.ViewModel -import androidx.lifecycle.viewModelScope -import com.squirtles.domain.model.Order -import com.squirtles.domain.model.Pick -import com.squirtles.domain.picklist.FetchPickListUseCaseInterface -import com.squirtles.domain.picklist.GetPickListOrderUseCaseInterface -import com.squirtles.domain.picklist.RemovePickUseCaseInterface -import com.squirtles.domain.picklist.SavePickListOrderUseCaseInterface -import com.squirtles.domain.user.usecase.GetCurrentUidUseCase -import kotlinx.coroutines.async -import kotlinx.coroutines.awaitAll -import kotlinx.coroutines.flow.MutableStateFlow -import kotlinx.coroutines.flow.asStateFlow -import kotlinx.coroutines.launch - -abstract class PickListViewModel( - val fetchPickListUseCase: FetchPickListUseCaseInterface, - val getPickListOrderUseCase: GetPickListOrderUseCaseInterface, - val savePickListOrderUseCase: SavePickListOrderUseCaseInterface, - val removePickUseCase: RemovePickUseCaseInterface, - val getCurrentUidUseCase: GetCurrentUidUseCase -) : ViewModel() { - - private var pickList: List = emptyList() - - private val _pickListUiState = MutableStateFlow(PickListUiState.Loading) - val pickListUiState = _pickListUiState.asStateFlow() - - private val _selectedPicksId = MutableStateFlow>(emptySet()) - val selectedPicksId = _selectedPicksId.asStateFlow() - - fun fetchPickList(uid: String) { - viewModelScope.launch { - fetchPickListUseCase(uid) - .onSuccess { picks -> - pickList = picks - sortPickList(getPickListOrderUseCase()) - } - .onFailure { - _pickListUiState.emit(PickListUiState.Error) - } - } - } - - private fun sortPickList(order: Order) { - _pickListUiState.value = PickListUiState.Success( - pickList = pickList.setOrderedList(order), - order = order - ) - } - - fun setListOrder(order: Order) { - viewModelScope.launch { - savePickListOrderUseCase(order) - sortPickList(order) - } - } - - fun toggleSelectedPick(pickId: String) { - val curSelectedPicksId = _selectedPicksId.value - _selectedPicksId.value = - if (curSelectedPicksId.contains(pickId)) curSelectedPicksId - pickId else curSelectedPicksId + pickId - } - - fun selectAllPicks() { - _selectedPicksId.value = pickList.map { it.id }.toSet() - } - - fun deselectAllPicks() { - _selectedPicksId.value = emptySet() - } - - fun deleteSelectedPicks(uid: String) { - viewModelScope.launch { - _pickListUiState.value = PickListUiState.Loading - - val deleteJobList = _selectedPicksId.value.map { pickId -> - async { removePickUseCase(pickId, uid) } - }.awaitAll() - - deselectAllPicks() - - if (deleteJobList.all { it.isSuccess }) { - fetchPickList(uid) - } else { - _pickListUiState.value = PickListUiState.Error - Log.e("PickListViewModel", "[픽 목록] 다중 삭제 오류") - } - } - } - - private fun List.setOrderedList(order: Order): List { - return if (pickList.isEmpty()) this - else when (order) { - Order.LATEST -> this - Order.OLDEST -> this.reversed() - Order.FAVORITE_DESC -> this.sortedByDescending { it.favoriteCount } - } - } - - fun getUid() = getCurrentUidUseCase() -} diff --git a/app/src/main/java/com/squirtles/musicroad/common/picklist/components/DeleteSelectedPickDialog.kt b/app/src/main/java/com/squirtles/musicroad/common/picklist/components/DeleteSelectedPickDialog.kt deleted file mode 100644 index bd83d6b7..00000000 --- a/app/src/main/java/com/squirtles/musicroad/common/picklist/components/DeleteSelectedPickDialog.kt +++ /dev/null @@ -1,46 +0,0 @@ -package com.squirtles.musicroad.common.picklist.components - -import androidx.compose.runtime.Composable -import androidx.compose.ui.res.stringResource -import androidx.compose.ui.text.font.FontWeight -import com.squirtles.musicroad.R -import com.squirtles.musicroad.common.DialogTextButton -import com.squirtles.musicroad.common.HorizontalSpacer -import com.squirtles.musicroad.common.MessageAlertDialog -import com.squirtles.musicroad.common.picklist.PickListType -import com.squirtles.musicroad.ui.theme.Primary - -@Composable -internal fun DeleteSelectedPickDialog( - selectedPickCount: Int, - pickListType: PickListType, - onDismissRequest: () -> Unit, - onDeletePickClick: () -> Unit, -) { - MessageAlertDialog( - onDismissRequest = onDismissRequest, - title = stringResource(R.string.delete_pick_dialog_title), - body = stringResource( - when (pickListType) { - PickListType.FAVORITE -> R.string.delete_selected_favorite_pick_dialog_body - PickListType.CREATED -> R.string.delete_selected_pick_dialog_body - }, - selectedPickCount - ), - buttons = { - DialogTextButton( - onClick = onDismissRequest, - text = stringResource(R.string.delete_pick_dialog_cancel) - ) - - HorizontalSpacer(8) - - DialogTextButton( - onClick = onDeletePickClick, - text = stringResource(R.string.delete_pick_dialog_delete), - textColor = Primary, - fontWeight = FontWeight.Bold - ) - }, - ) -} diff --git a/app/src/main/java/com/squirtles/musicroad/common/picklist/components/EditModeAction.kt b/app/src/main/java/com/squirtles/musicroad/common/picklist/components/EditModeAction.kt deleted file mode 100644 index e904a8dd..00000000 --- a/app/src/main/java/com/squirtles/musicroad/common/picklist/components/EditModeAction.kt +++ /dev/null @@ -1,60 +0,0 @@ -package com.squirtles.musicroad.common.picklist.components - -import androidx.compose.material.icons.Icons -import androidx.compose.material.icons.filled.Edit -import androidx.compose.material3.Icon -import androidx.compose.material3.IconButton -import androidx.compose.material3.IconButtonDefaults -import androidx.compose.material3.Text -import androidx.compose.material3.TextButton -import androidx.compose.runtime.Composable -import androidx.compose.ui.res.stringResource -import com.squirtles.musicroad.R -import com.squirtles.musicroad.ui.theme.Gray -import com.squirtles.musicroad.ui.theme.White - -@Composable -internal fun EditModeAction( - isEditMode: Boolean, - enabled: Boolean, - isSelectedEmpty: Boolean, - activateEditMode: () -> Unit, - selectAllPicks: () -> Unit, - deselectAllPicks: () -> Unit, -) { - if (isEditMode) { - if (isSelectedEmpty) { - TextButton( - onClick = selectAllPicks - ) { - Text( - text = stringResource(R.string.pick_list_select_all_button_text), - color = White, - ) - } - } else { - TextButton( - onClick = deselectAllPicks - ) { - Text( - text = stringResource(R.string.pick_list_deselect_all_button_text), - color = White, - ) - } - } - } else { - IconButton( - onClick = activateEditMode, - enabled = enabled, - colors = IconButtonDefaults.iconButtonColors().copy( - contentColor = White, - disabledContentColor = Gray, - ) - ) { - Icon( - imageVector = Icons.Default.Edit, - contentDescription = stringResource(R.string.pick_list_activate_edit_mode_button_description), - ) - } - } -} diff --git a/app/src/main/java/com/squirtles/musicroad/common/picklist/components/EditModeBottomButton.kt b/app/src/main/java/com/squirtles/musicroad/common/picklist/components/EditModeBottomButton.kt deleted file mode 100644 index a8014244..00000000 --- a/app/src/main/java/com/squirtles/musicroad/common/picklist/components/EditModeBottomButton.kt +++ /dev/null @@ -1,96 +0,0 @@ -package com.squirtles.musicroad.common.picklist.components - -import androidx.compose.foundation.layout.Row -import androidx.compose.foundation.layout.fillMaxHeight -import androidx.compose.foundation.layout.fillMaxWidth -import androidx.compose.foundation.layout.height -import androidx.compose.material3.Button -import androidx.compose.material3.ButtonDefaults -import androidx.compose.material3.MaterialTheme -import androidx.compose.material3.Text -import androidx.compose.runtime.Composable -import androidx.compose.ui.Modifier -import androidx.compose.ui.graphics.Color -import androidx.compose.ui.graphics.RectangleShape -import androidx.compose.ui.res.stringResource -import androidx.compose.ui.tooling.preview.Preview -import androidx.compose.ui.unit.dp -import com.squirtles.musicroad.R -import com.squirtles.musicroad.ui.theme.DarkGray -import com.squirtles.musicroad.ui.theme.Gray -import com.squirtles.musicroad.ui.theme.MusicRoadTheme -import com.squirtles.musicroad.ui.theme.Primary -import com.squirtles.musicroad.ui.theme.White - -@Composable -internal fun EditModeBottomButton( - enabled: Boolean, - deactivateEditMode: () -> Unit, - showDeletePickDialog: () -> Unit, -) { - Row( - modifier = Modifier - .fillMaxWidth() - .height(ButtonHeight) - ) { - EditModeButton( - text = stringResource(R.string.pick_list_deactivate_edit_mode_button_text), - onClick = deactivateEditMode, - modifier = Modifier - .fillMaxHeight() - .weight(1f), - buttonColor = Gray - ) - - EditModeButton( - text = stringResource(R.string.pick_list_delete_selection_button_text), - onClick = showDeletePickDialog, - modifier = Modifier - .fillMaxHeight() - .weight(1f), - enabled = enabled - ) - } -} - -@Composable -private fun EditModeButton( - text: String, - onClick: () -> Unit, - modifier: Modifier, - enabled: Boolean = true, - textColor: Color = White, - buttonColor: Color = Primary, -) { - Button( - onClick = onClick, - modifier = modifier, - enabled = enabled, - shape = RectangleShape, - colors = ButtonDefaults.buttonColors().copy( - containerColor = buttonColor, - contentColor = textColor, - disabledContainerColor = DarkGray, - disabledContentColor = White - ) - ) { - Text( - text = text, - style = MaterialTheme.typography.bodyLarge - ) - } -} - -@Preview -@Composable -private fun EditModeBottomButtonPreview() { - MusicRoadTheme { - EditModeBottomButton( - enabled = true, - deactivateEditMode = {}, - showDeletePickDialog = {}, - ) - } -} - -private val ButtonHeight = 48.dp diff --git a/app/src/main/java/com/squirtles/musicroad/common/picklist/components/OrderBottomSheet.kt b/app/src/main/java/com/squirtles/musicroad/common/picklist/components/OrderBottomSheet.kt deleted file mode 100644 index 401c0997..00000000 --- a/app/src/main/java/com/squirtles/musicroad/common/picklist/components/OrderBottomSheet.kt +++ /dev/null @@ -1,135 +0,0 @@ -package com.squirtles.musicroad.common.picklist.components - -import androidx.compose.foundation.clickable -import androidx.compose.foundation.layout.Arrangement -import androidx.compose.foundation.layout.Column -import androidx.compose.foundation.layout.Row -import androidx.compose.foundation.layout.displayCutoutPadding -import androidx.compose.foundation.layout.fillMaxWidth -import androidx.compose.foundation.layout.navigationBarsPadding -import androidx.compose.foundation.layout.padding -import androidx.compose.foundation.layout.wrapContentHeight -import androidx.compose.material.icons.Icons -import androidx.compose.material.icons.filled.Check -import androidx.compose.material3.ExperimentalMaterial3Api -import androidx.compose.material3.HorizontalDivider -import androidx.compose.material3.Icon -import androidx.compose.material3.MaterialTheme -import androidx.compose.material3.ModalBottomSheet -import androidx.compose.material3.Text -import androidx.compose.material3.rememberModalBottomSheetState -import androidx.compose.runtime.Composable -import androidx.compose.runtime.rememberCoroutineScope -import androidx.compose.ui.Alignment -import androidx.compose.ui.Modifier -import androidx.compose.ui.res.stringResource -import androidx.compose.ui.text.font.FontWeight -import androidx.compose.ui.text.style.TextAlign -import com.squirtles.domain.model.Order -import com.squirtles.musicroad.R -import com.squirtles.musicroad.common.Constants.DEFAULT_PADDING -import com.squirtles.musicroad.ui.theme.Dark -import com.squirtles.musicroad.ui.theme.Primary -import com.squirtles.musicroad.ui.theme.White -import kotlinx.coroutines.launch - -@OptIn(ExperimentalMaterial3Api::class) -@Composable -fun OrderBottomSheet( - isFavoritePicks: Boolean, - currentOrder: Order, - onDismissRequest: () -> Unit, - onOrderClick: (Order) -> Unit, -) { - val sheetState = rememberModalBottomSheetState() - val scope = rememberCoroutineScope() - val orderList = listOf( - stringResource(if (isFavoritePicks) R.string.latest_favorite_order else R.string.latest_create_order) to Order.LATEST, - stringResource(if (isFavoritePicks) R.string.oldest_favorite_order else R.string.oldest_create_order) to Order.OLDEST, - stringResource(R.string.favorite_count_desc) to Order.FAVORITE_DESC, - ) - - ModalBottomSheet( - onDismissRequest = onDismissRequest, - modifier = Modifier - .fillMaxWidth() - .wrapContentHeight() - .displayCutoutPadding() - .navigationBarsPadding(), - sheetState = sheetState, - containerColor = Dark, - scrimColor = MaterialTheme.colorScheme.surface.copy(alpha = 0.32f), - ) { - Column( - modifier = Modifier.fillMaxWidth(), - horizontalAlignment = Alignment.CenterHorizontally - ) { - orderList.forEach { (orderText, order) -> - BottomSheetMenu( - text = orderText, - isSelected = currentOrder == order, - ) { - scope - .launch { - if (currentOrder != order) { - onOrderClick(order) - } - sheetState.hide() - } - .invokeOnCompletion { - if (!sheetState.isVisible) { - onDismissRequest() - } - } - } - } - - HorizontalDivider(color = White) - BottomSheetMenu( - text = stringResource(R.string.close_button_text), - textAlign = TextAlign.Center, - ) { - scope - .launch { sheetState.hide() } - .invokeOnCompletion { - if (!sheetState.isVisible) { - onDismissRequest() - } - } - } - } - } -} - -@Composable -private fun BottomSheetMenu( - text: String, - textAlign: TextAlign = TextAlign.Start, - isSelected: Boolean = false, - onClick: () -> Unit, -) { - Row( - modifier = Modifier - .fillMaxWidth() - .clickable { onClick() } - .padding(DEFAULT_PADDING), - horizontalArrangement = Arrangement.SpaceBetween, - verticalAlignment = Alignment.CenterVertically - ) { - Text( - text = text, - modifier = Modifier.weight(1f), - color = if (isSelected) Primary else White, - fontWeight = if (isSelected) FontWeight.Bold else FontWeight.Normal, - textAlign = textAlign, - ) - - if (isSelected) { - Icon( - imageVector = Icons.Default.Check, - contentDescription = stringResource(R.string.selected_icon_description), - tint = Primary - ) - } - } -} diff --git a/app/src/main/java/com/squirtles/musicroad/common/picklist/components/PickItem.kt b/app/src/main/java/com/squirtles/musicroad/common/picklist/components/PickItem.kt deleted file mode 100644 index 54812bad..00000000 --- a/app/src/main/java/com/squirtles/musicroad/common/picklist/components/PickItem.kt +++ /dev/null @@ -1,129 +0,0 @@ -package com.squirtles.musicroad.common.picklist.components - -import androidx.compose.foundation.background -import androidx.compose.foundation.clickable -import androidx.compose.foundation.interaction.MutableInteractionSource -import androidx.compose.foundation.layout.Arrangement -import androidx.compose.foundation.layout.Column -import androidx.compose.foundation.layout.Row -import androidx.compose.foundation.layout.fillMaxWidth -import androidx.compose.foundation.layout.padding -import androidx.compose.foundation.layout.size -import androidx.compose.foundation.shape.RoundedCornerShape -import androidx.compose.material.icons.Icons -import androidx.compose.material.icons.outlined.CheckCircle -import androidx.compose.material3.Icon -import androidx.compose.material3.MaterialTheme -import androidx.compose.material3.Text -import androidx.compose.material3.ripple -import androidx.compose.runtime.Composable -import androidx.compose.runtime.remember -import androidx.compose.ui.Alignment -import androidx.compose.ui.Modifier -import androidx.compose.ui.draw.clip -import androidx.compose.ui.graphics.Color -import androidx.compose.ui.res.stringResource -import androidx.compose.ui.text.style.TextOverflow -import androidx.compose.ui.unit.dp -import com.squirtles.domain.model.Song -import com.squirtles.musicroad.R -import com.squirtles.musicroad.common.AlbumImage -import com.squirtles.musicroad.common.CommentText -import com.squirtles.musicroad.common.Constants -import com.squirtles.musicroad.common.CreatedByOtherUserText -import com.squirtles.musicroad.common.FavoriteCountText -import com.squirtles.musicroad.common.HorizontalSpacer -import com.squirtles.musicroad.common.SongInfoText -import com.squirtles.musicroad.ui.theme.Gray -import com.squirtles.musicroad.ui.theme.Primary -import com.squirtles.musicroad.ui.theme.White - -@Composable -internal fun PickItem( - isEditMode: Boolean, - isSelected: Boolean, - song: Song, - createdByOthers: Boolean, - createUserName: String, - favoriteCount: Int, - comment: String, - createdAt: String, - onItemClick: () -> Unit, -) { - Row( - modifier = Modifier - .fillMaxWidth() - .background(color = if (isSelected) White.copy(alpha = 0.2F) else Color.Transparent) - .clickable( - interactionSource = remember { MutableInteractionSource() }, - indication = ripple(color = White), - ) { onItemClick() } - .padding( - horizontal = Constants.DEFAULT_PADDING, - vertical = Constants.DEFAULT_PADDING / 2 - ), - horizontalArrangement = Arrangement.spacedBy(Constants.DEFAULT_PADDING), - verticalAlignment = Alignment.CenterVertically - ) { - AlbumImage( - imageUrl = song.getImageUrlWithSize( - Constants.REQUEST_IMAGE_SIZE_DEFAULT.width, - Constants.REQUEST_IMAGE_SIZE_DEFAULT.height - ), - modifier = Modifier - .size(64.dp) - .clip(RoundedCornerShape(8.dp)) - ) - - Column( - modifier = Modifier.weight(1f) - ) { - SongInfoText( - songInfo = "${song.songName} - ${song.artistName}", - color = White - ) - - Row( - verticalAlignment = Alignment.CenterVertically - ) { - if (createdByOthers) { - CreatedByOtherUserText( - userName = createUserName, - modifier = Modifier.weight(weight = 1f, fill = false), - color = Gray - ) - } else { - Text( - text = createdAt, - modifier = Modifier.weight(weight = 1f, fill = false), - color = Gray, - overflow = TextOverflow.Ellipsis, - maxLines = 1, - style = MaterialTheme.typography.bodyMedium, - ) - } - - HorizontalSpacer(8) - - FavoriteCountText( - favoriteCount = favoriteCount, - iconTint = Gray, - color = Gray - ) - } - - CommentText( - comment = comment, - color = Gray - ) - } - - if (isEditMode) { - Icon( - imageVector = Icons.Outlined.CheckCircle, - contentDescription = stringResource(R.string.outlined_check_circle_icon_description), - tint = if (isSelected) Primary else Gray - ) - } - } -} diff --git a/app/src/main/java/com/squirtles/musicroad/create/CreatePickScreen.kt b/app/src/main/java/com/squirtles/musicroad/create/CreatePickScreen.kt index 21c7bbb6..f622810f 100644 --- a/app/src/main/java/com/squirtles/musicroad/create/CreatePickScreen.kt +++ b/app/src/main/java/com/squirtles/musicroad/create/CreatePickScreen.kt @@ -56,7 +56,7 @@ import androidx.core.graphics.toColorInt import androidx.core.view.WindowInsetsControllerCompat import androidx.hilt.navigation.compose.hiltViewModel import androidx.lifecycle.compose.collectAsStateWithLifecycle -import com.squirtles.domain.model.Song +import com.squirtles.model.Song import com.squirtles.musicroad.R import com.squirtles.musicroad.common.AlbumImage import com.squirtles.musicroad.common.VerticalSpacer diff --git a/app/src/main/java/com/squirtles/musicroad/create/CreatePickViewModel.kt b/app/src/main/java/com/squirtles/musicroad/create/CreatePickViewModel.kt index 73a5c74f..80655dae 100644 --- a/app/src/main/java/com/squirtles/musicroad/create/CreatePickViewModel.kt +++ b/app/src/main/java/com/squirtles/musicroad/create/CreatePickViewModel.kt @@ -6,26 +6,24 @@ import androidx.lifecycle.SavedStateHandle import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope import androidx.navigation.toRoute -import com.squirtles.domain.applemusic.usecase.FetchMusicVideoUseCase -import com.squirtles.domain.location.usecase.GetLastLocationUseCase -import com.squirtles.domain.model.Creator -import com.squirtles.domain.model.LocationPoint -import com.squirtles.domain.model.Pick -import com.squirtles.domain.model.Song +import com.squirtles.applemusic.usecase.FetchMusicVideoUseCase import com.squirtles.domain.pick.usecase.CreatePickUseCase -import com.squirtles.domain.user.usecase.FetchUserByIdUseCase -import com.squirtles.domain.user.usecase.GetCurrentUidUseCase +import com.squirtles.location.usecase.GetLastLocationUseCase +import com.squirtles.model.Creator +import com.squirtles.model.LocationPoint +import com.squirtles.model.Pick +import com.squirtles.model.Song import com.squirtles.musicroad.navigation.SearchRoute -import com.squirtles.musicroad.utils.throttleFirst +import com.squirtles.user.usecase.FetchUserByIdUseCase +import com.squirtles.user.usecase.GetCurrentUidUseCase +import com.squirtles.util.throttleFirst import dagger.hilt.android.lifecycle.HiltViewModel -import kotlinx.coroutines.FlowPreview import kotlinx.coroutines.flow.MutableSharedFlow import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.asStateFlow import kotlinx.coroutines.launch import javax.inject.Inject -@OptIn(FlowPreview::class) @HiltViewModel class CreatePickViewModel @Inject constructor( savedStateHandle: SavedStateHandle, diff --git a/app/src/main/java/com/squirtles/musicroad/create/navigation/CreateNavigation.kt b/app/src/main/java/com/squirtles/musicroad/create/navigation/CreateNavigation.kt index 0e28c0fa..f26c761e 100644 --- a/app/src/main/java/com/squirtles/musicroad/create/navigation/CreateNavigation.kt +++ b/app/src/main/java/com/squirtles/musicroad/create/navigation/CreateNavigation.kt @@ -5,10 +5,10 @@ import androidx.navigation.NavGraphBuilder import androidx.navigation.NavOptions import androidx.navigation.compose.composable import androidx.navigation.toRoute -import com.squirtles.domain.model.Song +import com.squirtles.model.Song import com.squirtles.musicroad.create.CreatePickScreen import com.squirtles.musicroad.navigation.SearchRoute -import com.squirtles.musicroad.utils.serializableType +import com.squirtles.util.serializableType import java.net.URLEncoder import java.nio.charset.StandardCharsets import kotlin.reflect.typeOf diff --git a/app/src/main/java/com/squirtles/musicroad/detail/DetailViewModel.kt b/app/src/main/java/com/squirtles/musicroad/detail/DetailViewModel.kt index 5164691e..f0068c7a 100644 --- a/app/src/main/java/com/squirtles/musicroad/detail/DetailViewModel.kt +++ b/app/src/main/java/com/squirtles/musicroad/detail/DetailViewModel.kt @@ -4,17 +4,17 @@ import androidx.core.graphics.toColorInt import androidx.lifecycle.SavedStateHandle import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope -import com.squirtles.domain.favorite.usecase.CreateFavoriteUseCase -import com.squirtles.domain.favorite.usecase.DeleteFavoriteUseCase -import com.squirtles.domain.favorite.usecase.FetchIsFavoriteUseCase -import com.squirtles.domain.model.Creator -import com.squirtles.domain.model.LocationPoint -import com.squirtles.domain.model.Pick -import com.squirtles.domain.model.Song import com.squirtles.domain.pick.usecase.DeletePickUseCase import com.squirtles.domain.pick.usecase.FetchPickUseCase -import com.squirtles.domain.user.usecase.GetCurrentUidUseCase -import com.squirtles.musicroad.utils.throttleFirst +import com.squirtles.favorite.usecase.CreateFavoriteUseCase +import com.squirtles.favorite.usecase.DeleteFavoriteUseCase +import com.squirtles.favorite.usecase.FetchIsFavoriteUseCase +import com.squirtles.model.Creator +import com.squirtles.model.LocationPoint +import com.squirtles.model.Pick +import com.squirtles.model.Song +import com.squirtles.user.usecase.GetCurrentUidUseCase +import com.squirtles.util.throttleFirst import dagger.hilt.android.lifecycle.HiltViewModel import kotlinx.coroutines.async import kotlinx.coroutines.flow.MutableSharedFlow diff --git a/app/src/main/java/com/squirtles/musicroad/detail/PickDetailScreen.kt b/app/src/main/java/com/squirtles/musicroad/detail/PickDetailScreen.kt index 9fba3d88..f3acfe07 100644 --- a/app/src/main/java/com/squirtles/musicroad/detail/PickDetailScreen.kt +++ b/app/src/main/java/com/squirtles/musicroad/detail/PickDetailScreen.kt @@ -54,7 +54,8 @@ import androidx.lifecycle.Lifecycle import androidx.lifecycle.compose.LocalLifecycleOwner import androidx.lifecycle.compose.collectAsStateWithLifecycle import androidx.lifecycle.flowWithLifecycle -import com.squirtles.domain.model.Pick +import com.squirtles.model.Pick +import com.squirtles.musicplayer.PlayerServiceViewModel import com.squirtles.musicroad.R import com.squirtles.musicroad.account.AccountViewModel import com.squirtles.musicroad.account.GoogleId @@ -71,12 +72,10 @@ import com.squirtles.musicroad.detail.components.MusicVideoKnob import com.squirtles.musicroad.detail.components.PickInformation import com.squirtles.musicroad.detail.components.SongInfo import com.squirtles.musicroad.detail.components.music.MusicPlayer -import com.squirtles.musicroad.detail.components.music.visualizer.BaseVisualizer -import com.squirtles.musicroad.media.PlayerServiceViewModel +import com.squirtles.musicroad.detail.videoplayer.MusicVideoScreen import com.squirtles.musicroad.ui.theme.Black import com.squirtles.musicroad.ui.theme.Primary import com.squirtles.musicroad.ui.theme.White -import com.squirtles.musicroad.detail.videoplayer.MusicVideoScreen import kotlinx.coroutines.launch import kotlin.math.absoluteValue @@ -386,8 +385,6 @@ private fun PickDetailContents( val onDynamicBackgroundColor = if (dynamicBackgroundColor.luminance() >= 0.5f) Black else White val view = LocalView.current - val baseVisualizer = remember { BaseVisualizer() } - val audioEffectColor = dynamicBackgroundColor.copy( red = (dynamicBackgroundColor.red + 0.2f).coerceAtMost(1.0f), green = (dynamicBackgroundColor.green + 0.2f).coerceAtMost(1.0f), @@ -471,7 +468,6 @@ private fun PickDetailContents( currentPosition = { playerUiState.currentPosition }, duration = { playerUiState.duration }, audioEffectColor = audioEffectColor, - baseVisualizer = { baseVisualizer }, audioSessionId = audioSessionId, onSeekChanged = { timeMs -> playerServiceViewModel.onSeekingFinished(timeMs) diff --git a/app/src/main/java/com/squirtles/musicroad/detail/PickDetailUiState.kt b/app/src/main/java/com/squirtles/musicroad/detail/PickDetailUiState.kt index a55c5b86..0292db04 100644 --- a/app/src/main/java/com/squirtles/musicroad/detail/PickDetailUiState.kt +++ b/app/src/main/java/com/squirtles/musicroad/detail/PickDetailUiState.kt @@ -1,6 +1,6 @@ package com.squirtles.musicroad.detail -import com.squirtles.domain.model.Pick +import com.squirtles.model.Pick sealed class PickDetailUiState { data object Loading : PickDetailUiState() diff --git a/app/src/main/java/com/squirtles/musicroad/detail/components/CircleAlbumCover.kt b/app/src/main/java/com/squirtles/musicroad/detail/components/CircleAlbumCover.kt index ab5a4868..c0b3cc86 100644 --- a/app/src/main/java/com/squirtles/musicroad/detail/components/CircleAlbumCover.kt +++ b/app/src/main/java/com/squirtles/musicroad/detail/components/CircleAlbumCover.kt @@ -15,19 +15,20 @@ import androidx.compose.ui.layout.ContentScale import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.dp import coil3.compose.AsyncImage -import com.squirtles.domain.model.Song +import com.miller198.audiovisualizer.configs.GradientConfig +import com.miller198.audiovisualizer.configs.VisualizerConfig +import com.miller198.audiovisualizer.soundeffect.SoundEffects +import com.miller198.audiovisualizer.ui.CircleVisualizer +import com.squirtles.model.Song import com.squirtles.musicroad.R -import com.squirtles.musicroad.detail.components.music.visualizer.BaseVisualizer -import com.squirtles.musicroad.detail.components.music.visualizer.CircleVisualizer @Composable internal fun CircleAlbumCover( + audioSessionId: Int, song: Song, currentPosition: () -> Long, duration: () -> Long, audioEffectColor: Color, - baseVisualizer: () -> BaseVisualizer, - audioSessionId: Int, onSeekChanged: (Long) -> Unit, modifier: Modifier = Modifier, ) { @@ -35,15 +36,19 @@ internal fun CircleAlbumCover( modifier = modifier ) { CircleVisualizer( - baseVisualizer = baseVisualizer, audioSessionId = audioSessionId, + soundEffects = SoundEffects.BAR, + visualizerConfig = VisualizerConfig.FftCaptureConfig.Default, + gradientConfig = GradientConfig.Enabled( + color = audioEffectColor.mixedWhite(), + duration = 2500 + ), color = audioEffectColor, - sizeRatio = 0.5f, - modifier = Modifier.align(Alignment.Center) + modifier = modifier.align(Alignment.Center) ) PlayCircularProgressIndicator( - modifier = Modifier + modifier = modifier .fillMaxSize() .padding(10.dp) .align(Alignment.Center), @@ -68,3 +73,10 @@ internal fun CircleAlbumCover( ) } } + +private fun Color.mixedWhite(): Color = Color( + red = (Color.White.red + this.red) / 2, + green = (Color.White.green + this.green) / 2, + blue = (Color.White.blue + this.blue) / 2, + alpha = 0.9f +) diff --git a/app/src/main/java/com/squirtles/musicroad/detail/components/SongInfo.kt b/app/src/main/java/com/squirtles/musicroad/detail/components/SongInfo.kt index 149104a5..2fad5d8f 100644 --- a/app/src/main/java/com/squirtles/musicroad/detail/components/SongInfo.kt +++ b/app/src/main/java/com/squirtles/musicroad/detail/components/SongInfo.kt @@ -10,7 +10,7 @@ import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.text.style.TextAlign -import com.squirtles.domain.model.Song +import com.squirtles.model.Song @Composable internal fun SongInfo( diff --git a/app/src/main/java/com/squirtles/musicroad/detail/components/music/MusicPlayer.kt b/app/src/main/java/com/squirtles/musicroad/detail/components/music/MusicPlayer.kt index 8f49325b..376f4c7f 100644 --- a/app/src/main/java/com/squirtles/musicroad/detail/components/music/MusicPlayer.kt +++ b/app/src/main/java/com/squirtles/musicroad/detail/components/music/MusicPlayer.kt @@ -9,8 +9,8 @@ import androidx.compose.runtime.Composable import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.unit.dp -import com.squirtles.domain.model.PlayerState -import com.squirtles.domain.model.Song +import com.squirtles.model.PlayerState +import com.squirtles.model.Song import com.squirtles.musicroad.common.Constants.DEFAULT_PADDING import com.squirtles.musicroad.ui.theme.PlayerBackground diff --git a/app/src/main/java/com/squirtles/musicroad/detail/components/music/visualizer/BaseVisualizer.kt b/app/src/main/java/com/squirtles/musicroad/detail/components/music/visualizer/BaseVisualizer.kt deleted file mode 100644 index 341d7e77..00000000 --- a/app/src/main/java/com/squirtles/musicroad/detail/components/music/visualizer/BaseVisualizer.kt +++ /dev/null @@ -1,74 +0,0 @@ -package com.squirtles.musicroad.detail.components.music.visualizer - -import android.media.audiofx.Visualizer -import kotlinx.coroutines.flow.MutableSharedFlow -import kotlinx.coroutines.flow.SharedFlow -import kotlinx.coroutines.flow.asSharedFlow -import kotlin.math.sqrt - -class BaseVisualizer { - private var visualizer: Visualizer? = null - - private val _fftFlow = MutableSharedFlow>(replay = 1) - val fftFlow: SharedFlow> = _fftFlow.asSharedFlow() - - private val validRange = getSignificantFftIndexRange() - - fun setVisualizer(audioSessionId: Int) { - val visualizer = Visualizer(audioSessionId) - this.visualizer = visualizer - } - - fun setVisualizerListener() { - visualizer?.run { - enabled = false - captureSize = CAPTURE_SIZE - setDataCaptureListener(object : Visualizer.OnDataCaptureListener { - override fun onWaveFormDataCapture(visualizer: Visualizer, bytes: ByteArray, samplingRate: Int) { - // NOT USED - } - - override fun onFftDataCapture(visualizer: Visualizer, bytes: ByteArray, samplingRate: Int) { - // bytes = [실수부,허수부,실수부,허수부 ....] - val size = bytes.size / 4 // 각 복소수당 2바이트 - val magnitudes = FloatArray(size) - - for (i in 0 until size) { - val real = bytes[2 * i].toInt() - val imaginary = bytes[2 * i + 1].toInt() - magnitudes[i] = sqrt((real * real + imaginary * imaginary).toDouble()).toFloat() - } - - val filteredMagnitudes = magnitudes.copyOfRange(validRange.first, validRange.last + 1) - _fftFlow.tryEmit(filteredMagnitudes.toList()) - } - }, Visualizer.getMaxCaptureRate() / 2, false, true) - enabled = true - } - } - - fun release() { - visualizer?.release() - visualizer = null - } - - // 20 ~ 4000 Hz 사이만 필터링 - private fun getSignificantFftIndexRange( - samplingRate: Int = SAMPLING_RATE, - captureSize: Int = CAPTURE_SIZE, - minFreq: Int = MIN_FREQ, - maxFreq: Int = MAX_FREQ - ): IntRange { - val resolution = samplingRate.toDouble() / captureSize - val startIndex = (minFreq / resolution).toInt() - val endIndex = (maxFreq / resolution).toInt() - return startIndex..endIndex - } - - companion object { - const val CAPTURE_SIZE = 512 - const val SAMPLING_RATE = 22000 - const val MIN_FREQ = 20 - const val MAX_FREQ = 4500 - } -} diff --git a/app/src/main/java/com/squirtles/musicroad/detail/components/music/visualizer/CanvasCircleVisualizer.kt b/app/src/main/java/com/squirtles/musicroad/detail/components/music/visualizer/CanvasCircleVisualizer.kt deleted file mode 100644 index 12d9b634..00000000 --- a/app/src/main/java/com/squirtles/musicroad/detail/components/music/visualizer/CanvasCircleVisualizer.kt +++ /dev/null @@ -1,122 +0,0 @@ -package com.squirtles.musicroad.detail.components.music.visualizer - -import androidx.compose.foundation.Canvas -import androidx.compose.foundation.layout.fillMaxSize -import androidx.compose.runtime.Composable -import androidx.compose.ui.Modifier -import androidx.compose.ui.geometry.Offset -import androidx.compose.ui.graphics.Color -import androidx.compose.ui.graphics.Path -import androidx.compose.ui.graphics.PointMode -import androidx.compose.ui.graphics.drawscope.Stroke -import kotlin.math.cos -import kotlin.math.max -import kotlin.math.sin - -@Composable -internal fun CanvasCircle( - color: Color, - modifier: Modifier = Modifier -) { - Canvas(modifier = modifier.fillMaxSize()) { - val width = size.width - val height = size.height - val radius = max(width, height) * 0.4f - - drawCircle( - color = color, - radius = radius, - style = Stroke(width = 4f) - ) - } -} - -/* Bar 형태 원형 시각화 */ -@Composable -internal fun CanvasSoundEffectBar( - audioData: List, - color: Color, - sizeRatio: Float, - modifier: Modifier = Modifier -) { - - val offsetAngle = Math.toRadians(-90.0) - val angleStep = 360f / audioData.size - val points = mutableListOf() - - Canvas(modifier = modifier.fillMaxSize()) { - val width = size.width - val height = size.height - val radius = max(width, height) * sizeRatio - - audioData.forEachIndexed { i, magnitude -> - // 현재 오디오데이터가 그려질 각도 - val angle = Math.toRadians((i * angleStep).toDouble()) + offsetAngle - val cosValue = cos(angle) - val sinValue = sin(angle) - val barHeight = magnitude * height / 5 - - // 현재 오디오데이터가 그려질 xy 좌표 - val startX = (width / 2 + radius * cosValue).toFloat() - val startY = (height / 2 + radius * sinValue).toFloat() - val endX = (width / 2 + (radius + barHeight) * cosValue).toFloat() - val endY = (height / 2 + (radius + barHeight) * sinValue).toFloat() - - points.add(Offset(startX, startY)) - points.add(Offset(endX, endY)) - } - - drawPoints( - points = points, - pointMode = PointMode.Lines, - color = color, - strokeWidth = 24f - ) - } -} - -/* Wave 형태 원형 시각화 */ -@Composable -internal fun CanvasSoundEffectWave( - audioData: List, - color: Color, - modifier: Modifier = Modifier -) { - - val offsetAngle = Math.toRadians(-90.0) - val angleStep = 360f / audioData.size - val path = Path() - - Canvas(modifier = modifier.fillMaxSize()) { - val width = size.width - val height = size.height - val radius = max(width, height) * 0.45f - - audioData.forEachIndexed { i, magnitude -> - val angle = Math.toRadians((i * angleStep).toDouble()) + offsetAngle - val cosValue = cos(angle) - val sinValue = sin(angle) - val barHeight = magnitude * height / 5 - - val startX = (width / 2 + radius * cosValue).toFloat() - val startY = (height / 2 + radius * sinValue).toFloat() - val endX = (width / 2 + (radius + barHeight) * cosValue).toFloat() - val endY = (height / 2 + (radius + barHeight) * sinValue).toFloat() - - if (i == 0) { - path.moveTo(startX, startY) - } else { - - path.lineTo(endX, endY) - } - } - - drawPath( - path = path, - color = color, - style = Stroke(width = 4f) - ) - } -} - -private const val STROKE_WIDTH = 18f diff --git a/app/src/main/java/com/squirtles/musicroad/detail/components/music/visualizer/CircleVisualizer.kt b/app/src/main/java/com/squirtles/musicroad/detail/components/music/visualizer/CircleVisualizer.kt deleted file mode 100644 index 39dd6a9b..00000000 --- a/app/src/main/java/com/squirtles/musicroad/detail/components/music/visualizer/CircleVisualizer.kt +++ /dev/null @@ -1,108 +0,0 @@ -package com.squirtles.musicroad.detail.components.music.visualizer - -import androidx.compose.animation.core.Animatable -import androidx.compose.animation.core.AnimationVector1D -import androidx.compose.animation.core.FastOutSlowInEasing -import androidx.compose.animation.core.tween -import androidx.compose.runtime.Composable -import androidx.compose.runtime.DisposableEffect -import androidx.compose.runtime.LaunchedEffect -import androidx.compose.runtime.mutableStateOf -import androidx.compose.runtime.remember -import androidx.compose.ui.Modifier -import androidx.compose.ui.graphics.Color -import com.squirtles.musicroad.ui.theme.White -import kotlinx.coroutines.launch - -@Composable -fun CircleVisualizer( - baseVisualizer: () -> BaseVisualizer, - audioSessionId: Int, - color: Color = White, - sizeRatio: Float, - modifier: Modifier = Modifier -) { - val magnitudes = remember { mutableStateOf>>(emptyList()) } - val visualizer = baseVisualizer() - - LaunchedEffect(Unit) { - visualizer.setVisualizer(audioSessionId) - visualizer.setVisualizerListener() - visualizer.fftFlow.collect { fftArray -> - val normalizedData = processAudioData(fftArray) - - if (magnitudes.value.isEmpty()) { - magnitudes.value = normalizedData.map { Animatable(it) } - } else { - normalizedData.forEachIndexed { i, magnitude -> - launch { - magnitudes.value[i].animateTo( - targetValue = magnitude, - animationSpec = tween( - durationMillis = 120, - easing = FastOutSlowInEasing - ) - ) - } - } - } - } - } - - DisposableEffect(Unit) { - onDispose { - visualizer.release() - } - } - - CanvasSoundEffectBar( - audioData = magnitudes.value.map { it.value }, - color = color, - sizeRatio = sizeRatio, - modifier = modifier - ) -} - -private fun processAudioData(audioData: List): List { - val scaledData = scaleAudioData(audioData) - return normalizeAudioData(scaledData) -} - -/* 주파수 대역별 가중치 */ -private fun scaleAudioData(audioData: List): List { - val size = audioData.size - return audioData.mapIndexed { index, value -> - val scaleFactor = when { - index < size / 8 -> 0.4f - index < size / 4 -> 1.0f // 저주파 대역 - index < size / 2 -> 2.0f // 중간 대역 - index < size / 1.33 -> 3.0f // 고주파 대역 - else -> 5.0f // 고주파 대역 - } - value * scaleFactor - } -} - -private fun applyLogScale(audioData: List): List { - val epsilon = 1e-6f // 0 방지용 작은 값 - val minValue = audioData.minOrNull() ?: 0f - val offset = if (minValue < 1f) 1f - minValue else 0f // 최소값을 1로 이동 - - return audioData.map { value -> - val shiftedValue = value + offset + epsilon // 데이터를 양수 범위로 이동 - 20 * kotlin.math.log10(shiftedValue) // 로그 변환 - } -} - -/* 다이나믹 레인지 압축 */ -private fun compressDynamicRangeRoot(audioData: List): List { - return audioData.map { kotlin.math.sqrt(it) } -} - -/* 0.0 ~ 1.0 사이 정규화 */ -private fun normalizeAudioData(audioData: List): List { - val max = audioData.maxOrNull() ?: 1f // 데이터 최대값 - val min = audioData.minOrNull() ?: 0f // 데이터 최소값 - return if (max - min <= 1f) List(audioData.size) { 0f } else audioData.map { (it - min) / (max - min) } -} - diff --git a/app/src/main/java/com/squirtles/musicroad/detail/navigation/PickDetailNavigation.kt b/app/src/main/java/com/squirtles/musicroad/detail/navigation/PickDetailNavigation.kt new file mode 100644 index 00000000..6cc347ed --- /dev/null +++ b/app/src/main/java/com/squirtles/musicroad/detail/navigation/PickDetailNavigation.kt @@ -0,0 +1,34 @@ +package com.squirtles.detail.navigation + +import android.content.Context +import androidx.navigation.NavController +import androidx.navigation.NavGraphBuilder +import androidx.navigation.NavOptions +import androidx.navigation.compose.composable +import androidx.navigation.toRoute +import com.squirtles.musicplayer.PlayerServiceViewModel +import com.squirtles.musicroad.detail.PickDetailScreen +import com.squirtles.musicroad.navigation.MapRoute + +fun NavController.navigatePickDetail(pickId: String, navOptions: NavOptions? = null) { + navigate(MapRoute.PickDetail(pickId), navOptions) +} + +fun NavGraphBuilder.detailNavGraph( + playerServiceViewModel: PlayerServiceViewModel, + onUserInfoClick: (String) -> Unit, + onBackClick: () -> Unit, + onDeleted: (Context) -> Unit, +) { + composable { backStackEntry -> + val pickId = backStackEntry.toRoute().pickId + + PickDetailScreen( + pickId = pickId, + playerServiceViewModel = playerServiceViewModel, + onUserInfoClick = onUserInfoClick, + onBackClick = onBackClick, + onDeleted = onDeleted, + ) + } +} diff --git a/app/src/main/java/com/squirtles/musicroad/detail/videoplayer/MusicVideoPlayer.kt b/app/src/main/java/com/squirtles/musicroad/detail/videoplayer/MusicVideoPlayer.kt index 82295f3e..f2d63795 100644 --- a/app/src/main/java/com/squirtles/musicroad/detail/videoplayer/MusicVideoPlayer.kt +++ b/app/src/main/java/com/squirtles/musicroad/detail/videoplayer/MusicVideoPlayer.kt @@ -21,13 +21,12 @@ import androidx.lifecycle.compose.LocalLifecycleOwner import androidx.lifecycle.compose.collectAsStateWithLifecycle import androidx.lifecycle.flowWithLifecycle import androidx.media3.common.util.UnstableApi -import com.squirtles.domain.model.Pick import kotlinx.coroutines.launch @OptIn(UnstableApi::class) @Composable fun MusicVideoPlayer( - pick: Pick, + musicVideoUrl: String, videoPlayerViewModel: VideoPlayerViewModel = hiltViewModel() ) { val context = LocalContext.current @@ -56,7 +55,7 @@ fun MusicVideoPlayer( AndroidView( factory = { - videoPlayerViewModel.initializePlayer(context, pick.musicVideoUrl) + videoPlayerViewModel.initializePlayer(context, musicVideoUrl) textureView.apply { surfaceTextureListener = object : TextureView.SurfaceTextureListener { override fun onSurfaceTextureAvailable(surfaceTexture: SurfaceTexture, width: Int, height: Int) { diff --git a/app/src/main/java/com/squirtles/musicroad/detail/videoplayer/MusicVideoScreen.kt b/app/src/main/java/com/squirtles/musicroad/detail/videoplayer/MusicVideoScreen.kt index 75f4073f..ebd1d74d 100644 --- a/app/src/main/java/com/squirtles/musicroad/detail/videoplayer/MusicVideoScreen.kt +++ b/app/src/main/java/com/squirtles/musicroad/detail/videoplayer/MusicVideoScreen.kt @@ -12,7 +12,7 @@ import androidx.compose.ui.Modifier import androidx.hilt.navigation.compose.hiltViewModel import androidx.lifecycle.compose.collectAsStateWithLifecycle import androidx.media3.common.util.UnstableApi -import com.squirtles.domain.model.Pick +import com.squirtles.model.Pick @OptIn(UnstableApi::class) @Composable @@ -27,7 +27,7 @@ fun MusicVideoScreen( BackHandler { onBackClick() } Box(modifier = modifier.fillMaxSize()) { - MusicVideoPlayer(pick) + MusicVideoPlayer(pick.musicVideoUrl) VideoPlayerOverlay(pick, onBackClick) if (isLoading) { diff --git a/app/src/main/java/com/squirtles/musicroad/detail/videoplayer/VideoPlayerOverlay.kt b/app/src/main/java/com/squirtles/musicroad/detail/videoplayer/VideoPlayerOverlay.kt index fdb2897b..cccca824 100644 --- a/app/src/main/java/com/squirtles/musicroad/detail/videoplayer/VideoPlayerOverlay.kt +++ b/app/src/main/java/com/squirtles/musicroad/detail/videoplayer/VideoPlayerOverlay.kt @@ -47,10 +47,10 @@ import androidx.compose.ui.unit.sp import androidx.core.graphics.toColorInt import androidx.hilt.navigation.compose.hiltViewModel import androidx.lifecycle.compose.collectAsStateWithLifecycle -import com.squirtles.domain.model.Creator -import com.squirtles.domain.model.LocationPoint -import com.squirtles.domain.model.Pick -import com.squirtles.domain.model.Song +import com.squirtles.model.Creator +import com.squirtles.model.LocationPoint +import com.squirtles.model.Pick +import com.squirtles.model.Song import com.squirtles.musicroad.R import com.squirtles.musicroad.common.VerticalSpacer import com.squirtles.musicroad.ui.theme.Black diff --git a/app/src/main/java/com/squirtles/musicroad/favorite/FavoriteListViewModel.kt b/app/src/main/java/com/squirtles/musicroad/favorite/FavoriteListViewModel.kt index d8c02343..d77453e4 100644 --- a/app/src/main/java/com/squirtles/musicroad/favorite/FavoriteListViewModel.kt +++ b/app/src/main/java/com/squirtles/musicroad/favorite/FavoriteListViewModel.kt @@ -1,11 +1,11 @@ package com.squirtles.musicroad.favorite -import com.squirtles.domain.favorite.usecase.DeleteFavoriteUseCase -import com.squirtles.domain.order.usecase.GetFavoriteListOrderUseCase -import com.squirtles.domain.order.usecase.SaveFavoriteListOrderUseCase import com.squirtles.domain.pick.usecase.FetchFavoritePicksUseCase -import com.squirtles.domain.user.usecase.GetCurrentUidUseCase -import com.squirtles.musicroad.common.picklist.PickListViewModel +import com.squirtles.favorite.usecase.DeleteFavoriteUseCase +import com.squirtles.order.usecase.GetFavoriteListOrderUseCase +import com.squirtles.order.usecase.SaveFavoriteListOrderUseCase +import com.squirtles.picklist.PickListViewModel +import com.squirtles.user.usecase.GetCurrentUidUseCase import dagger.hilt.android.lifecycle.HiltViewModel import javax.inject.Inject diff --git a/app/src/main/java/com/squirtles/musicroad/favorite/FavoriteScreen.kt b/app/src/main/java/com/squirtles/musicroad/favorite/FavoriteScreen.kt index c2e9c2e6..0926803f 100644 --- a/app/src/main/java/com/squirtles/musicroad/favorite/FavoriteScreen.kt +++ b/app/src/main/java/com/squirtles/musicroad/favorite/FavoriteScreen.kt @@ -8,8 +8,8 @@ import androidx.compose.runtime.saveable.rememberSaveable import androidx.compose.runtime.setValue import androidx.hilt.navigation.compose.hiltViewModel import androidx.lifecycle.compose.collectAsStateWithLifecycle -import com.squirtles.musicroad.common.picklist.PickListType -import com.squirtles.musicroad.common.picklist.PickListScreenContents +import com.squirtles.picklist.PickListScreenContents +import com.squirtles.picklist.PickListType @Composable fun FavoriteScreen( diff --git a/app/src/main/java/com/squirtles/musicroad/main/MainViewModel.kt b/app/src/main/java/com/squirtles/musicroad/main/MainViewModel.kt index 6330cfc5..233dbf79 100644 --- a/app/src/main/java/com/squirtles/musicroad/main/MainViewModel.kt +++ b/app/src/main/java/com/squirtles/musicroad/main/MainViewModel.kt @@ -4,8 +4,8 @@ import android.util.Log import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope import com.squirtles.domain.firebase.FirebaseException -import com.squirtles.domain.user.usecase.FetchUserByIdUseCase -import com.squirtles.domain.user.usecase.GetCurrentUidUseCase +import com.squirtles.user.usecase.FetchUserByIdUseCase +import com.squirtles.user.usecase.GetCurrentUidUseCase import dagger.hilt.android.lifecycle.HiltViewModel import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.asStateFlow @@ -48,7 +48,7 @@ class MainViewModel @Inject constructor( } .onFailure { exception -> when (exception) { - is FirebaseException.UserNotFoundException -> { + is FirebaseException.FetchDocumentFailedException -> { _loadingState.emit(LoadingState.UserNotFoundError(exception.message)) } diff --git a/app/src/main/java/com/squirtles/musicroad/main/navigation/MainNavHost.kt b/app/src/main/java/com/squirtles/musicroad/main/navigation/MainNavHost.kt index ca02089c..725acfaf 100644 --- a/app/src/main/java/com/squirtles/musicroad/main/navigation/MainNavHost.kt +++ b/app/src/main/java/com/squirtles/musicroad/main/navigation/MainNavHost.kt @@ -5,10 +5,11 @@ import androidx.compose.ui.Modifier import androidx.hilt.navigation.compose.hiltViewModel import androidx.navigation.compose.NavHost import com.squirtles.create.navigation.createNavGraph +import com.squirtles.detail.navigation.detailNavGraph +import com.squirtles.musicplayer.PlayerServiceViewModel import com.squirtles.musicroad.favorite.navigation.favoriteNavGraph import com.squirtles.musicroad.map.MapViewModel import com.squirtles.musicroad.map.navigation.mapNavGraph -import com.squirtles.musicroad.media.PlayerServiceViewModel import com.squirtles.musicroad.mypick.navigation.myPickNavGraph import com.squirtles.musicroad.search.navigation.searchNavGraph import com.squirtles.musicroad.userinfo.navigation.userInfoNavGraph @@ -31,8 +32,6 @@ internal fun MainNavHost( onCenterClick = navigator::navigateSearch, onUserInfoClick = navigator::navigateUserInfo, onPickSummaryClick = navigator::navigatePickDetail, - onBackClick = navigator::popBackStackIfNotMap, - onDeleted = mapViewModel::resetClickedMarkerState ) searchNavGraph( @@ -40,6 +39,13 @@ internal fun MainNavHost( onItemClick = navigator::navigateCreate, ) + detailNavGraph( + playerServiceViewModel = playerServiceViewModel, + onUserInfoClick = navigator::navigateUserInfo, + onBackClick = navigator::popBackStackIfNotMap, + onDeleted = mapViewModel::resetClickedMarkerState + ) + createNavGraph( onBackClick = navigator::popBackStackIfNotMap, onCreateClick = { pickId -> diff --git a/app/src/main/java/com/squirtles/musicroad/main/navigation/MainNavigator.kt b/app/src/main/java/com/squirtles/musicroad/main/navigation/MainNavigator.kt index 9401f8cc..46ff641e 100644 --- a/app/src/main/java/com/squirtles/musicroad/main/navigation/MainNavigator.kt +++ b/app/src/main/java/com/squirtles/musicroad/main/navigation/MainNavigator.kt @@ -9,10 +9,10 @@ import androidx.navigation.compose.currentBackStackEntryAsState import androidx.navigation.compose.rememberNavController import androidx.navigation.navOptions import com.squirtles.create.navigation.navigateCreate -import com.squirtles.domain.model.Song +import com.squirtles.detail.navigation.navigatePickDetail +import com.squirtles.model.Song import com.squirtles.musicroad.favorite.navigation.navigateFavorite import com.squirtles.musicroad.map.navigation.navigateMap -import com.squirtles.musicroad.map.navigation.navigatePickDetail import com.squirtles.musicroad.mypick.navigation.navigateMyPicks import com.squirtles.musicroad.navigation.Route import com.squirtles.musicroad.search.navigation.navigateSearch diff --git a/app/src/main/java/com/squirtles/musicroad/map/MapScreen.kt b/app/src/main/java/com/squirtles/musicroad/map/MapScreen.kt index e6514612..c7c46739 100644 --- a/app/src/main/java/com/squirtles/musicroad/map/MapScreen.kt +++ b/app/src/main/java/com/squirtles/musicroad/map/MapScreen.kt @@ -33,6 +33,7 @@ import androidx.lifecycle.Lifecycle import androidx.lifecycle.compose.LocalLifecycleOwner import androidx.lifecycle.compose.collectAsStateWithLifecycle import androidx.lifecycle.flowWithLifecycle +import com.squirtles.musicplayer.PlayerServiceViewModel import com.squirtles.musicroad.R import com.squirtles.musicroad.account.AccountViewModel import com.squirtles.musicroad.account.GoogleId @@ -44,7 +45,6 @@ import com.squirtles.musicroad.map.components.InfoWindow import com.squirtles.musicroad.map.components.LoadingDialog import com.squirtles.musicroad.map.components.MapBottomNavBar import com.squirtles.musicroad.map.components.PickNotificationBanner -import com.squirtles.musicroad.media.PlayerServiceViewModel import com.squirtles.musicroad.ui.theme.Black import kotlinx.coroutines.launch diff --git a/app/src/main/java/com/squirtles/musicroad/map/MapViewModel.kt b/app/src/main/java/com/squirtles/musicroad/map/MapViewModel.kt index 80d9a3a6..0f33e2fd 100644 --- a/app/src/main/java/com/squirtles/musicroad/map/MapViewModel.kt +++ b/app/src/main/java/com/squirtles/musicroad/map/MapViewModel.kt @@ -9,12 +9,12 @@ import com.naver.maps.geometry.LatLng import com.naver.maps.map.CameraPosition import com.naver.maps.map.clustering.Clusterer import com.naver.maps.map.overlay.Marker -import com.squirtles.domain.location.usecase.GetLastLocationUseCase -import com.squirtles.domain.location.usecase.SaveLastLocationUseCase -import com.squirtles.domain.model.Pick import com.squirtles.domain.pick.usecase.FetchPickUseCase -import com.squirtles.domain.user.usecase.GetCurrentUidUseCase +import com.squirtles.location.usecase.GetLastLocationUseCase +import com.squirtles.location.usecase.SaveLastLocationUseCase +import com.squirtles.model.Pick import com.squirtles.musicroad.map.marker.MarkerKey +import com.squirtles.user.usecase.GetCurrentUidUseCase import dagger.hilt.android.lifecycle.HiltViewModel import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.StateFlow diff --git a/app/src/main/java/com/squirtles/musicroad/map/components/ClusterBottomSheet.kt b/app/src/main/java/com/squirtles/musicroad/map/components/ClusterBottomSheet.kt index 9d0e5c08..716bfdff 100644 --- a/app/src/main/java/com/squirtles/musicroad/map/components/ClusterBottomSheet.kt +++ b/app/src/main/java/com/squirtles/musicroad/map/components/ClusterBottomSheet.kt @@ -21,9 +21,9 @@ import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clip import androidx.compose.ui.unit.dp -import com.squirtles.domain.model.LocationPoint -import com.squirtles.domain.model.Pick -import com.squirtles.domain.model.Song +import com.squirtles.model.LocationPoint +import com.squirtles.model.Pick +import com.squirtles.model.Song import com.squirtles.musicroad.common.AlbumImage import com.squirtles.musicroad.common.CommentText import com.squirtles.musicroad.common.Constants.DEFAULT_PADDING diff --git a/app/src/main/java/com/squirtles/musicroad/map/components/InfoWindowCard.kt b/app/src/main/java/com/squirtles/musicroad/map/components/InfoWindowCard.kt index f83525aa..564d6c36 100644 --- a/app/src/main/java/com/squirtles/musicroad/map/components/InfoWindowCard.kt +++ b/app/src/main/java/com/squirtles/musicroad/map/components/InfoWindowCard.kt @@ -24,10 +24,10 @@ import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp import androidx.core.graphics.toColorInt -import com.squirtles.domain.model.Creator -import com.squirtles.domain.model.LocationPoint -import com.squirtles.domain.model.Pick -import com.squirtles.domain.model.Song +import com.squirtles.model.Creator +import com.squirtles.model.LocationPoint +import com.squirtles.model.Pick +import com.squirtles.model.Song import com.squirtles.musicroad.common.AlbumImage import com.squirtles.musicroad.common.CreatedByOtherUserText import com.squirtles.musicroad.common.CreatedBySelfText diff --git a/app/src/main/java/com/squirtles/musicroad/map/components/PickNotificationBanner.kt b/app/src/main/java/com/squirtles/musicroad/map/components/PickNotificationBanner.kt index ed665a9d..af3b953f 100644 --- a/app/src/main/java/com/squirtles/musicroad/map/components/PickNotificationBanner.kt +++ b/app/src/main/java/com/squirtles/musicroad/map/components/PickNotificationBanner.kt @@ -23,7 +23,7 @@ import androidx.compose.ui.platform.LocalConfiguration import androidx.compose.ui.res.stringResource import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp -import com.squirtles.domain.model.Pick +import com.squirtles.model.Pick import com.squirtles.musicroad.R import com.squirtles.musicroad.detail.DetailViewModel.Companion.DEFAULT_PICK import com.squirtles.musicroad.ui.theme.Black diff --git a/app/src/main/java/com/squirtles/musicroad/map/marker/Clusterer.kt b/app/src/main/java/com/squirtles/musicroad/map/marker/Clusterer.kt index c6437abb..1a530bcf 100644 --- a/app/src/main/java/com/squirtles/musicroad/map/marker/Clusterer.kt +++ b/app/src/main/java/com/squirtles/musicroad/map/marker/Clusterer.kt @@ -15,6 +15,7 @@ import com.naver.maps.map.overlay.Align import com.naver.maps.map.overlay.Marker import com.naver.maps.map.overlay.Overlay import com.naver.maps.map.overlay.OverlayImage +import com.squirtles.musicroad.common.Constants.REQUEST_IMAGE_SIZE_DEFAULT import com.squirtles.musicroad.map.DEFAULT_MARKER_Z_INDEX import com.squirtles.musicroad.map.MapViewModel import com.squirtles.musicroad.map.setCameraToMarker @@ -115,7 +116,12 @@ internal fun buildClusterer( val color = if (pick.createdBy.uid == mapViewModel.getUid()) Blue else Primary setPaintColor(color.toArgb()) } - leafMarkerIconView.setLeafMarkerIcon(pick) { + leafMarkerIconView.setLeafMarkerIcon( + pick.song.getImageUrlWithSize( + REQUEST_IMAGE_SIZE_DEFAULT.width, + REQUEST_IMAGE_SIZE_DEFAULT.height + ) + ) { marker.icon = OverlayImage.fromView(leafMarkerIconView) marker.setOnClickListener { marker.map?.let { map -> diff --git a/app/src/main/java/com/squirtles/musicroad/map/marker/LeafMarkerIconView.kt b/app/src/main/java/com/squirtles/musicroad/map/marker/LeafMarkerIconView.kt index 159f19a0..26735017 100644 --- a/app/src/main/java/com/squirtles/musicroad/map/marker/LeafMarkerIconView.kt +++ b/app/src/main/java/com/squirtles/musicroad/map/marker/LeafMarkerIconView.kt @@ -14,8 +14,6 @@ import coil3.request.allowHardware import coil3.request.transformations import coil3.toBitmap import coil3.transform.CircleCropTransformation -import com.squirtles.domain.model.Pick -import com.squirtles.musicroad.common.Constants.REQUEST_IMAGE_SIZE_DEFAULT class LeafMarkerIconView( context: Context, @@ -70,9 +68,8 @@ class LeafMarkerIconView( strokePaint.color = color } - fun setLeafMarkerIcon(pick: Pick, onImageLoaded: () -> Unit) { - val song = pick.song - loadImage(song.getImageUrlWithSize(REQUEST_IMAGE_SIZE_DEFAULT.width, REQUEST_IMAGE_SIZE_DEFAULT.height)) { + fun setLeafMarkerIcon(imgUrl: String?, onImageLoaded: () -> Unit) { + loadImage(imgUrl) { onImageLoaded() } } diff --git a/app/src/main/java/com/squirtles/musicroad/map/marker/MarkerKey.kt b/app/src/main/java/com/squirtles/musicroad/map/marker/MarkerKey.kt index 41b308a3..7969b5a9 100644 --- a/app/src/main/java/com/squirtles/musicroad/map/marker/MarkerKey.kt +++ b/app/src/main/java/com/squirtles/musicroad/map/marker/MarkerKey.kt @@ -2,7 +2,7 @@ package com.squirtles.musicroad.map.marker import com.naver.maps.geometry.LatLng import com.naver.maps.map.clustering.ClusteringKey -import com.squirtles.domain.model.Pick +import com.squirtles.model.Pick data class MarkerKey(val pick: Pick) : ClusteringKey { override fun getPosition() = LatLng(pick.location.latitude, pick.location.longitude) diff --git a/app/src/main/java/com/squirtles/musicroad/map/navigation/MapNavigation.kt b/app/src/main/java/com/squirtles/musicroad/map/navigation/MapNavigation.kt index 507103d6..2a387990 100644 --- a/app/src/main/java/com/squirtles/musicroad/map/navigation/MapNavigation.kt +++ b/app/src/main/java/com/squirtles/musicroad/map/navigation/MapNavigation.kt @@ -1,26 +1,18 @@ package com.squirtles.musicroad.map.navigation -import android.content.Context import androidx.navigation.NavController import androidx.navigation.NavGraphBuilder import androidx.navigation.NavOptions import androidx.navigation.compose.composable -import androidx.navigation.toRoute +import com.squirtles.musicplayer.PlayerServiceViewModel import com.squirtles.musicroad.map.MapScreen import com.squirtles.musicroad.map.MapViewModel -import com.squirtles.musicroad.media.PlayerServiceViewModel -import com.squirtles.musicroad.navigation.MapRoute import com.squirtles.musicroad.navigation.Route -import com.squirtles.musicroad.detail.PickDetailScreen fun NavController.navigateMap(navOptions: NavOptions? = null) { navigate(Route.Map, navOptions) } -fun NavController.navigatePickDetail(pickId: String, navOptions: NavOptions? = null) { - navigate(MapRoute.PickDetail(pickId), navOptions) -} - fun NavGraphBuilder.mapNavGraph( mapViewModel: MapViewModel, playerServiceViewModel: PlayerServiceViewModel, @@ -28,8 +20,6 @@ fun NavGraphBuilder.mapNavGraph( onCenterClick: () -> Unit, onUserInfoClick: (String) -> Unit, onPickSummaryClick: (String) -> Unit, - onBackClick: () -> Unit, - onDeleted: (Context) -> Unit, ) { composable { MapScreen( @@ -41,16 +31,4 @@ fun NavGraphBuilder.mapNavGraph( onPickSummaryClick = onPickSummaryClick, ) } - - composable { backStackEntry -> - val pickId = backStackEntry.toRoute().pickId - - PickDetailScreen( - pickId = pickId, - playerServiceViewModel = playerServiceViewModel, - onUserInfoClick = onUserInfoClick, - onBackClick = onBackClick, - onDeleted = onDeleted, - ) - } } diff --git a/app/src/main/java/com/squirtles/musicroad/media/PlayerServiceViewModel.kt b/app/src/main/java/com/squirtles/musicroad/media/PlayerServiceViewModel.kt index e0727803..189a4c0b 100644 --- a/app/src/main/java/com/squirtles/musicroad/media/PlayerServiceViewModel.kt +++ b/app/src/main/java/com/squirtles/musicroad/media/PlayerServiceViewModel.kt @@ -1,113 +1,113 @@ -package com.squirtles.musicroad.media - -import androidx.lifecycle.ViewModel -import androidx.lifecycle.viewModelScope -import com.squirtles.domain.model.Pick -import com.squirtles.domain.model.PlayerState -import com.squirtles.domain.model.Song -import com.squirtles.domain.player.MediaPlayerListenerUseCase -import com.squirtles.domain.player.MediaPlayerUseCase -import dagger.hilt.android.lifecycle.HiltViewModel -import kotlinx.coroutines.async -import kotlinx.coroutines.flow.SharingStarted -import kotlinx.coroutines.flow.StateFlow -import kotlinx.coroutines.flow.stateIn -import kotlinx.coroutines.launch -import javax.inject.Inject - -@HiltViewModel -class PlayerServiceViewModel @Inject constructor( - private val mediaPlayerUseCase: MediaPlayerUseCase, - private val mediaPlayerListenerUseCase: MediaPlayerListenerUseCase, -) : ViewModel() { - - val playerState: StateFlow = mediaPlayerListenerUseCase.playerStateFlow() - .stateIn( - scope = viewModelScope, - started = SharingStarted.WhileSubscribed(5_000), - initialValue = PlayerState() - ) - - val audioSessionId get() = mediaPlayerUseCase.audioSessionId - - suspend fun readyPlayer() { - viewModelScope.async { - mediaPlayerUseCase.readyPlayer() - }.await() - } - - fun setMediaItem(pick: Pick) { - viewModelScope.launch { - if (playerState.value.id == pick.id) { - mediaPlayerUseCase.changeRepeatMode(false) - } else { - mediaPlayerUseCase.setMediaItem(pick) - } - } - } - - fun setMediaItems(picks: List) { - mediaPlayerUseCase.setMediaItems(picks) - } - - private fun onPlay() { - mediaPlayerUseCase.play() - } - - fun onPause() { - mediaPlayerUseCase.pause() - } - - fun onStop() { - mediaPlayerUseCase.stop() - } - - fun onRelease() { - mediaPlayerUseCase.release() - } - - fun onPrevious() { - mediaPlayerUseCase.previous() - } - - fun onNext() { - mediaPlayerUseCase.next() - } - - fun onAdvanceBy() { - mediaPlayerUseCase.advanceBy() - } - - fun shuffleNext(pick: Pick) { - if (playerState.value.isPlaying) { - onPause() - } else { - setMediaItem(pick) - onPlay() - } - } - - fun togglePlayPause(song: Song) { - if (playerState.value.isPlaying) { - onPause() - } else { - onPlay() - } - } - - fun onRewindBy() { - mediaPlayerUseCase.rewindBy() - } - - fun onSeekingStarted() { - mediaPlayerUseCase.onSeekingStarted() - } - - fun onSeekingFinished(time: Long) { - mediaPlayerUseCase.onSeekingFinished(time) - } - - fun onAddToQueue(pick: Pick) { - mediaPlayerUseCase.addMediaItem(pick) - } -} +//package com.squirtles.musicroad.media +// +//import androidx.lifecycle.ViewModel +//import androidx.lifecycle.viewModelScope +//import com.squirtles.domain.model.Pick +//import com.squirtles.domain.model.PlayerState +//import com.squirtles.domain.model.Song +//import com.squirtles.domain.player.MediaPlayerListenerUseCase +//import com.squirtles.domain.player.MediaPlayerUseCase +//import dagger.hilt.android.lifecycle.HiltViewModel +//import kotlinx.coroutines.async +//import kotlinx.coroutines.flow.SharingStarted +//import kotlinx.coroutines.flow.StateFlow +//import kotlinx.coroutines.flow.stateIn +//import kotlinx.coroutines.launch +//import javax.inject.Inject +// +//@HiltViewModel +//class PlayerServiceViewModel @Inject constructor( +// private val mediaPlayerUseCase: MediaPlayerUseCase, +// private val mediaPlayerListenerUseCase: MediaPlayerListenerUseCase, +//) : ViewModel() { +// +// val playerState: StateFlow = mediaPlayerListenerUseCase.playerStateFlow() +// .stateIn( +// scope = viewModelScope, +// started = SharingStarted.WhileSubscribed(5_000), +// initialValue = PlayerState() +// ) +// +// val audioSessionId get() = mediaPlayerUseCase.audioSessionId +// +// suspend fun readyPlayer() { +// viewModelScope.async { +// mediaPlayerUseCase.readyPlayer() +// }.await() +// } +// +// fun setMediaItem(pick: Pick) { +// viewModelScope.launch { +// if (playerState.value.id == pick.id) { +// mediaPlayerUseCase.changeRepeatMode(false) +// } else { +// mediaPlayerUseCase.setMediaItem(pick) +// } +// } +// } +// +// fun setMediaItems(picks: List) { +// mediaPlayerUseCase.setMediaItems(picks) +// } +// +// private fun onPlay() { +// mediaPlayerUseCase.play() +// } +// +// fun onPause() { +// mediaPlayerUseCase.pause() +// } +// +// fun onStop() { +// mediaPlayerUseCase.stop() +// } +// +// fun onRelease() { +// mediaPlayerUseCase.release() +// } +// +// fun onPrevious() { +// mediaPlayerUseCase.previous() +// } +// +// fun onNext() { +// mediaPlayerUseCase.next() +// } +// +// fun onAdvanceBy() { +// mediaPlayerUseCase.advanceBy() +// } +// +// fun shuffleNext(pick: Pick) { +// if (playerState.value.isPlaying) { +// onPause() +// } else { +// setMediaItem(pick) +// onPlay() +// } +// } +// +// fun togglePlayPause(song: Song) { +// if (playerState.value.isPlaying) { +// onPause() +// } else { +// onPlay() +// } +// } +// +// fun onRewindBy() { +// mediaPlayerUseCase.rewindBy() +// } +// +// fun onSeekingStarted() { +// mediaPlayerUseCase.onSeekingStarted() +// } +// +// fun onSeekingFinished(time: Long) { +// mediaPlayerUseCase.onSeekingFinished(time) +// } +// +// fun onAddToQueue(pick: Pick) { +// mediaPlayerUseCase.addMediaItem(pick) +// } +//} diff --git a/app/src/main/java/com/squirtles/musicroad/media/PlayerUiState.kt b/app/src/main/java/com/squirtles/musicroad/media/PlayerUiState.kt index 41953fbf..76525f22 100644 --- a/app/src/main/java/com/squirtles/musicroad/media/PlayerUiState.kt +++ b/app/src/main/java/com/squirtles/musicroad/media/PlayerUiState.kt @@ -1,12 +1,12 @@ -package com.squirtles.musicroad.media - -data class PlayerUiState( - val isReady: Boolean = true, - val isPlaying: Boolean = false, - val currentPosition: Long = 0L, -) { - companion object { - val PLAYER_STATE_INITIAL = PlayerUiState(isReady = false) - val PLAYER_STATE_STOP = PlayerUiState(isReady = true, isPlaying = false) - } -} +//package com.squirtles.musicroad.media +// +//data class PlayerUiState( +// val isReady: Boolean = true, +// val isPlaying: Boolean = false, +// val currentPosition: Long = 0L, +//) { +// companion object { +// val PLAYER_STATE_INITIAL = PlayerUiState(isReady = false) +// val PLAYER_STATE_STOP = PlayerUiState(isReady = true, isPlaying = false) +// } +//} diff --git a/app/src/main/java/com/squirtles/musicroad/media/PlayerViewModel.kt b/app/src/main/java/com/squirtles/musicroad/media/PlayerViewModel.kt index 617b1891..4b5c0ab0 100644 --- a/app/src/main/java/com/squirtles/musicroad/media/PlayerViewModel.kt +++ b/app/src/main/java/com/squirtles/musicroad/media/PlayerViewModel.kt @@ -1,228 +1,228 @@ -package com.squirtles.musicroad.media - -import android.content.Context -import android.util.Log -import androidx.annotation.OptIn -import androidx.lifecycle.ViewModel -import androidx.lifecycle.viewModelScope -import androidx.media3.common.C.TIME_UNSET -import androidx.media3.common.MediaItem -import androidx.media3.common.PlaybackException -import androidx.media3.common.Player -import androidx.media3.common.util.UnstableApi -import androidx.media3.exoplayer.ExoPlayer -import com.squirtles.musicroad.media.PlayerUiState.Companion.PLAYER_STATE_INITIAL -import com.squirtles.musicroad.media.PlayerUiState.Companion.PLAYER_STATE_STOP -import dagger.hilt.android.lifecycle.HiltViewModel -import kotlinx.coroutines.delay -import kotlinx.coroutines.flow.MutableStateFlow -import kotlinx.coroutines.flow.StateFlow -import kotlinx.coroutines.launch -import javax.inject.Inject - -@HiltViewModel -class PlayerViewModel @Inject constructor( -) : ViewModel() { - - private var player: ExoPlayer? = null - - private var _audioSessionId = 0 - val audioSessionId get() = _audioSessionId - - private val _playerState = MutableStateFlow(PLAYER_STATE_INITIAL) - val playerUiState: StateFlow = _playerState - - private val _bufferPercentage = MutableStateFlow(0) - val bufferPercentage: StateFlow = _bufferPercentage - - private val _duration = MutableStateFlow(30_000L) - val duration: StateFlow = _duration - - @OptIn(UnstableApi::class) - private fun initializePlayer(context: Context) { - val exoPlayer = ExoPlayer.Builder(context).build().also { - it.addListener(object : Player.Listener { - override fun onPlayerError(error: PlaybackException) { - handleError(error) - } - - override fun onPlaybackStateChanged(playbackState: Int) { - if (playbackState == Player.STATE_ENDED) { - it.seekTo(0) - it.pause() - } - } - }) - it.volume = 0.8f - } - this.player = exoPlayer - _audioSessionId = exoPlayer.audioSessionId - } - - fun readyPlayer(context: Context, sourceUrl: String) { - if (player != null) return - - initializePlayer(context) - - player?.let { - val mediaItem = MediaItem.fromUri(sourceUrl) - it.setMediaItem(mediaItem) - it.prepare() - it.playWhenReady = false - it.seekTo(_playerState.value.currentPosition) - - _playerState.value = - PlayerUiState(isReady = true, currentPosition = _playerState.value.currentPosition) - - _duration.value = - if (it.duration == TIME_UNSET) 30_000L else it.duration - - updatePlayerStatePeriodically(it) - } - } - - fun readyPlayerSetList(context: Context, sourceUrls: List) { - if (player != null) return - - initializePlayer(context) - - player?.let { - it.setMediaItems(sourceUrls.map { url -> - MediaItem.fromUri(url) - }) - it.prepare() - it.playWhenReady = false - it.repeatMode = Player.REPEAT_MODE_ALL - } - } - - private fun updatePlayerStatePeriodically(exoPlayer: ExoPlayer) { - viewModelScope.launch { - while (_playerState.value.isReady) { - _playerState.value = _playerState.value.copy( - isPlaying = exoPlayer.isPlaying, - currentPosition = exoPlayer.currentPosition, - ) - _bufferPercentage.value = exoPlayer.bufferedPercentage - delay(1000) - } - } - } - - fun shuffleNextItem() { - viewModelScope.launch { - player?.let { - if (!it.isPlaying) it.seekToNextMediaItem() - togglePlayPause(it) - } - } - } - - fun replayForward(sec: Long) { - player?.let { - it.seekTo(it.currentPosition + sec) - viewModelScope.launch { - _playerState.value = - _playerState.value.copy(currentPosition = it.currentPosition) - } - } - } - - fun togglePlayPause() { - player?.let { - togglePlayPause(it) - } - } - - private fun togglePlayPause(exoPlayer: ExoPlayer) { - if (exoPlayer.isPlaying) pause(exoPlayer) - else play(exoPlayer) - } - - fun play() { - player?.let { - play(it) - } - } - - private fun play(exoPlayer: ExoPlayer) { - viewModelScope.launch { - _playerState.value = _playerState.value.copy(isPlaying = true) - } - exoPlayer.play() - } - - fun pause() { - player?.let { - pause(it) - } - } - - private fun pause(exoPlayer: ExoPlayer) { - viewModelScope.launch { - _playerState.value = _playerState.value.copy(isPlaying = false) - } - exoPlayer.pause() - } - - fun stop() { - player?.let { - viewModelScope.launch { - _playerState.value = PLAYER_STATE_STOP - } - it.stop() - } - } - - fun playerSeekTo(sec: Long) { - viewModelScope.launch { - player?.let { - _playerState.value = _playerState.value.copy(currentPosition = sec) - it.seekTo(sec) - } - } - } - - fun savePlayerState() { - player?.let { - _playerState.value = _playerState.value.copy( - isReady = false, - isPlaying = false, - currentPosition = it.currentPosition, - ) - } - } - - private fun releasePlayer() { - player?.release() - } - - private fun handleError(error: PlaybackException) { - when (error.errorCode) { - PlaybackException.ERROR_CODE_IO_NETWORK_CONNECTION_FAILED -> { - // TODO: Handle network connection error - Log.d("PlayerViewModel", "Network connection error") - } - - PlaybackException.ERROR_CODE_IO_FILE_NOT_FOUND -> { - // TODO: Handle file not found error - Log.d("PlayerViewModel", "File not found") - } - - PlaybackException.ERROR_CODE_DECODER_INIT_FAILED -> { - // TODO: Handle decoder initialization error - Log.d("PlayerViewModel", "Decoder initialization error") - } - - else -> { - // TODO: Handle other types of errors - Log.d("PlayerViewModel", "${error.message}") - } - } - } - - override fun onCleared() { - releasePlayer() - super.onCleared() - } -} +//package com.squirtles.musicroad.media +// +//import android.content.Context +//import android.util.Log +//import androidx.annotation.OptIn +//import androidx.lifecycle.ViewModel +//import androidx.lifecycle.viewModelScope +//import androidx.media3.common.C.TIME_UNSET +//import androidx.media3.common.MediaItem +//import androidx.media3.common.PlaybackException +//import androidx.media3.common.Player +//import androidx.media3.common.util.UnstableApi +//import androidx.media3.exoplayer.ExoPlayer +//import com.squirtles.musicroad.media.PlayerUiState.Companion.PLAYER_STATE_INITIAL +//import com.squirtles.musicroad.media.PlayerUiState.Companion.PLAYER_STATE_STOP +//import dagger.hilt.android.lifecycle.HiltViewModel +//import kotlinx.coroutines.delay +//import kotlinx.coroutines.flow.MutableStateFlow +//import kotlinx.coroutines.flow.StateFlow +//import kotlinx.coroutines.launch +//import javax.inject.Inject +// +//@HiltViewModel +//class PlayerViewModel @Inject constructor( +//) : ViewModel() { +// +// private var player: ExoPlayer? = null +// +// private var _audioSessionId = 0 +// val audioSessionId get() = _audioSessionId +// +// private val _playerState = MutableStateFlow(PLAYER_STATE_INITIAL) +// val playerUiState: StateFlow = _playerState +// +// private val _bufferPercentage = MutableStateFlow(0) +// val bufferPercentage: StateFlow = _bufferPercentage +// +// private val _duration = MutableStateFlow(30_000L) +// val duration: StateFlow = _duration +// +// @OptIn(UnstableApi::class) +// private fun initializePlayer(context: Context) { +// val exoPlayer = ExoPlayer.Builder(context).build().also { +// it.addListener(object : Player.Listener { +// override fun onPlayerError(error: PlaybackException) { +// handleError(error) +// } +// +// override fun onPlaybackStateChanged(playbackState: Int) { +// if (playbackState == Player.STATE_ENDED) { +// it.seekTo(0) +// it.pause() +// } +// } +// }) +// it.volume = 0.8f +// } +// this.player = exoPlayer +// _audioSessionId = exoPlayer.audioSessionId +// } +// +// fun readyPlayer(context: Context, sourceUrl: String) { +// if (player != null) return +// +// initializePlayer(context) +// +// player?.let { +// val mediaItem = MediaItem.fromUri(sourceUrl) +// it.setMediaItem(mediaItem) +// it.prepare() +// it.playWhenReady = false +// it.seekTo(_playerState.value.currentPosition) +// +// _playerState.value = +// PlayerUiState(isReady = true, currentPosition = _playerState.value.currentPosition) +// +// _duration.value = +// if (it.duration == TIME_UNSET) 30_000L else it.duration +// +// updatePlayerStatePeriodically(it) +// } +// } +// +// fun readyPlayerSetList(context: Context, sourceUrls: List) { +// if (player != null) return +// +// initializePlayer(context) +// +// player?.let { +// it.setMediaItems(sourceUrls.map { url -> +// MediaItem.fromUri(url) +// }) +// it.prepare() +// it.playWhenReady = false +// it.repeatMode = Player.REPEAT_MODE_ALL +// } +// } +// +// private fun updatePlayerStatePeriodically(exoPlayer: ExoPlayer) { +// viewModelScope.launch { +// while (_playerState.value.isReady) { +// _playerState.value = _playerState.value.copy( +// isPlaying = exoPlayer.isPlaying, +// currentPosition = exoPlayer.currentPosition, +// ) +// _bufferPercentage.value = exoPlayer.bufferedPercentage +// delay(1000) +// } +// } +// } +// +// fun shuffleNextItem() { +// viewModelScope.launch { +// player?.let { +// if (!it.isPlaying) it.seekToNextMediaItem() +// togglePlayPause(it) +// } +// } +// } +// +// fun replayForward(sec: Long) { +// player?.let { +// it.seekTo(it.currentPosition + sec) +// viewModelScope.launch { +// _playerState.value = +// _playerState.value.copy(currentPosition = it.currentPosition) +// } +// } +// } +// +// fun togglePlayPause() { +// player?.let { +// togglePlayPause(it) +// } +// } +// +// private fun togglePlayPause(exoPlayer: ExoPlayer) { +// if (exoPlayer.isPlaying) pause(exoPlayer) +// else play(exoPlayer) +// } +// +// fun play() { +// player?.let { +// play(it) +// } +// } +// +// private fun play(exoPlayer: ExoPlayer) { +// viewModelScope.launch { +// _playerState.value = _playerState.value.copy(isPlaying = true) +// } +// exoPlayer.play() +// } +// +// fun pause() { +// player?.let { +// pause(it) +// } +// } +// +// private fun pause(exoPlayer: ExoPlayer) { +// viewModelScope.launch { +// _playerState.value = _playerState.value.copy(isPlaying = false) +// } +// exoPlayer.pause() +// } +// +// fun stop() { +// player?.let { +// viewModelScope.launch { +// _playerState.value = PLAYER_STATE_STOP +// } +// it.stop() +// } +// } +// +// fun playerSeekTo(sec: Long) { +// viewModelScope.launch { +// player?.let { +// _playerState.value = _playerState.value.copy(currentPosition = sec) +// it.seekTo(sec) +// } +// } +// } +// +// fun savePlayerState() { +// player?.let { +// _playerState.value = _playerState.value.copy( +// isReady = false, +// isPlaying = false, +// currentPosition = it.currentPosition, +// ) +// } +// } +// +// private fun releasePlayer() { +// player?.release() +// } +// +// private fun handleError(error: PlaybackException) { +// when (error.errorCode) { +// PlaybackException.ERROR_CODE_IO_NETWORK_CONNECTION_FAILED -> { +// // TODO: Handle network connection error +// Log.d("PlayerViewModel", "Network connection error") +// } +// +// PlaybackException.ERROR_CODE_IO_FILE_NOT_FOUND -> { +// // TODO: Handle file not found error +// Log.d("PlayerViewModel", "File not found") +// } +// +// PlaybackException.ERROR_CODE_DECODER_INIT_FAILED -> { +// // TODO: Handle decoder initialization error +// Log.d("PlayerViewModel", "Decoder initialization error") +// } +// +// else -> { +// // TODO: Handle other types of errors +// Log.d("PlayerViewModel", "${error.message}") +// } +// } +// } +// +// override fun onCleared() { +// releasePlayer() +// super.onCleared() +// } +//} diff --git a/app/src/main/java/com/squirtles/musicroad/mypick/MyPickListViewModel.kt b/app/src/main/java/com/squirtles/musicroad/mypick/MyPickListViewModel.kt index aeee6a1a..a5e7cc8a 100644 --- a/app/src/main/java/com/squirtles/musicroad/mypick/MyPickListViewModel.kt +++ b/app/src/main/java/com/squirtles/musicroad/mypick/MyPickListViewModel.kt @@ -1,11 +1,11 @@ package com.squirtles.musicroad.mypick -import com.squirtles.domain.order.usecase.GetMyPickListOrderUseCase -import com.squirtles.domain.order.usecase.SaveMyPickListOrderUseCase import com.squirtles.domain.pick.usecase.DeletePickUseCase import com.squirtles.domain.pick.usecase.FetchMyPicksUseCase -import com.squirtles.domain.user.usecase.GetCurrentUidUseCase -import com.squirtles.musicroad.common.picklist.PickListViewModel +import com.squirtles.order.usecase.GetMyPickListOrderUseCase +import com.squirtles.order.usecase.SaveMyPickListOrderUseCase +import com.squirtles.picklist.PickListViewModel +import com.squirtles.user.usecase.GetCurrentUidUseCase import dagger.hilt.android.lifecycle.HiltViewModel import javax.inject.Inject diff --git a/app/src/main/java/com/squirtles/musicroad/mypick/MyPickScreen.kt b/app/src/main/java/com/squirtles/musicroad/mypick/MyPickScreen.kt index c1ecbab2..34a45159 100644 --- a/app/src/main/java/com/squirtles/musicroad/mypick/MyPickScreen.kt +++ b/app/src/main/java/com/squirtles/musicroad/mypick/MyPickScreen.kt @@ -8,8 +8,8 @@ import androidx.compose.runtime.saveable.rememberSaveable import androidx.compose.runtime.setValue import androidx.hilt.navigation.compose.hiltViewModel import androidx.lifecycle.compose.collectAsStateWithLifecycle -import com.squirtles.musicroad.common.picklist.PickListScreenContents -import com.squirtles.musicroad.common.picklist.PickListType +import com.squirtles.picklist.PickListScreenContents +import com.squirtles.picklist.PickListType @Composable fun MyPickScreen( diff --git a/app/src/main/java/com/squirtles/musicroad/navigation/SearchRoute.kt b/app/src/main/java/com/squirtles/musicroad/navigation/SearchRoute.kt index 1765ecea..a689b2c7 100644 --- a/app/src/main/java/com/squirtles/musicroad/navigation/SearchRoute.kt +++ b/app/src/main/java/com/squirtles/musicroad/navigation/SearchRoute.kt @@ -2,8 +2,8 @@ package com.squirtles.musicroad.navigation import androidx.lifecycle.SavedStateHandle import androidx.navigation.toRoute -import com.squirtles.domain.model.Song -import com.squirtles.musicroad.utils.serializableType +import com.squirtles.model.Song +import com.squirtles.util.serializableType import kotlinx.serialization.Serializable import kotlin.reflect.typeOf diff --git a/app/src/main/java/com/squirtles/musicroad/search/SearchMusicScreen.kt b/app/src/main/java/com/squirtles/musicroad/search/SearchMusicScreen.kt index d76a66eb..e81cbea1 100644 --- a/app/src/main/java/com/squirtles/musicroad/search/SearchMusicScreen.kt +++ b/app/src/main/java/com/squirtles/musicroad/search/SearchMusicScreen.kt @@ -55,7 +55,7 @@ import androidx.lifecycle.compose.collectAsStateWithLifecycle import androidx.paging.LoadState import androidx.paging.compose.LazyPagingItems import androidx.paging.compose.collectAsLazyPagingItems -import com.squirtles.domain.model.Song +import com.squirtles.model.Song import com.squirtles.musicroad.R import com.squirtles.musicroad.common.AlbumImage import com.squirtles.musicroad.common.Constants.COLOR_STOPS diff --git a/app/src/main/java/com/squirtles/musicroad/search/SearchViewModel.kt b/app/src/main/java/com/squirtles/musicroad/search/SearchViewModel.kt index dc6a92db..ce6670bf 100644 --- a/app/src/main/java/com/squirtles/musicroad/search/SearchViewModel.kt +++ b/app/src/main/java/com/squirtles/musicroad/search/SearchViewModel.kt @@ -4,8 +4,8 @@ import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope import androidx.paging.PagingData import androidx.paging.cachedIn -import com.squirtles.domain.model.Song -import com.squirtles.domain.applemusic.usecase.FetchSongsUseCase +import com.squirtles.applemusic.usecase.FetchSongsUseCase +import com.squirtles.model.Song import com.squirtles.musicroad.create.SearchUiState import dagger.hilt.android.lifecycle.HiltViewModel import kotlinx.coroutines.FlowPreview diff --git a/app/src/main/java/com/squirtles/musicroad/search/navigation/SearchNavigation.kt b/app/src/main/java/com/squirtles/musicroad/search/navigation/SearchNavigation.kt index b9ccfa2a..23da197a 100644 --- a/app/src/main/java/com/squirtles/musicroad/search/navigation/SearchNavigation.kt +++ b/app/src/main/java/com/squirtles/musicroad/search/navigation/SearchNavigation.kt @@ -4,7 +4,7 @@ import androidx.navigation.NavController import androidx.navigation.NavGraphBuilder import androidx.navigation.NavOptions import androidx.navigation.compose.composable -import com.squirtles.domain.model.Song +import com.squirtles.model.Song import com.squirtles.musicroad.navigation.MainRoute import com.squirtles.musicroad.search.SearchMusicScreen diff --git a/app/src/main/java/com/squirtles/musicroad/userinfo/UserInfoViewModel.kt b/app/src/main/java/com/squirtles/musicroad/userinfo/UserInfoViewModel.kt index c37719bc..f18152f8 100644 --- a/app/src/main/java/com/squirtles/musicroad/userinfo/UserInfoViewModel.kt +++ b/app/src/main/java/com/squirtles/musicroad/userinfo/UserInfoViewModel.kt @@ -2,13 +2,14 @@ package com.squirtles.musicroad.userinfo import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope -import com.squirtles.domain.model.User -import com.squirtles.domain.user.usecase.FetchUserByIdUseCase -import com.squirtles.domain.user.usecase.GetCurrentUidUseCase -import com.squirtles.domain.user.usecase.UpdateUserNameUseCase +import com.squirtles.model.User +import com.squirtles.user.usecase.FetchUserByIdUseCase +import com.squirtles.user.usecase.GetCurrentUidUseCase +import com.squirtles.user.usecase.UpdateUserNameUseCase import dagger.hilt.android.lifecycle.HiltViewModel import kotlinx.coroutines.flow.MutableSharedFlow import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.SharedFlow import kotlinx.coroutines.flow.asSharedFlow import kotlinx.coroutines.flow.asStateFlow import kotlinx.coroutines.launch @@ -18,7 +19,7 @@ import javax.inject.Inject class UserInfoViewModel @Inject constructor( private val getCurrentUidUseCase: GetCurrentUidUseCase, private val fetchUserByIdUseCase: FetchUserByIdUseCase, - private val updateUserNameUseCase: UpdateUserNameUseCase + private val updateUserNameUseCase: UpdateUserNameUseCase, ) : ViewModel() { private val _profileUser = MutableStateFlow(DEFAULT_USER) diff --git a/app/src/main/java/com/squirtles/musicroad/userinfo/navigation/UserInfoNavigation.kt b/app/src/main/java/com/squirtles/musicroad/userinfo/navigation/UserInfoNavigation.kt index 2d081930..ea1be161 100644 --- a/app/src/main/java/com/squirtles/musicroad/userinfo/navigation/UserInfoNavigation.kt +++ b/app/src/main/java/com/squirtles/musicroad/userinfo/navigation/UserInfoNavigation.kt @@ -5,7 +5,6 @@ import androidx.navigation.NavGraphBuilder import androidx.navigation.NavOptions import androidx.navigation.compose.composable import androidx.navigation.toRoute -import com.squirtles.musicroad.mypick.MyPickScreen import com.squirtles.musicroad.navigation.MainRoute import com.squirtles.musicroad.navigation.UserInfoRoute import com.squirtles.musicroad.userinfo.screen.EditNotificationSettingScreen diff --git a/app/src/main/java/com/squirtles/musicroad/utils/SerializableType.kt b/app/src/main/java/com/squirtles/musicroad/utils/SerializableType.kt deleted file mode 100644 index 3741f31c..00000000 --- a/app/src/main/java/com/squirtles/musicroad/utils/SerializableType.kt +++ /dev/null @@ -1,22 +0,0 @@ -package com.squirtles.musicroad.utils - -import android.os.Bundle -import androidx.navigation.NavType -import kotlinx.serialization.encodeToString -import kotlinx.serialization.json.Json - -inline fun serializableType( - isNullableAllowed: Boolean = false, - json: Json = Json, -) = object : NavType(isNullableAllowed = isNullableAllowed) { - override fun get(bundle: Bundle, key: String) = - bundle.getString(key)?.let(json::decodeFromString) - - override fun parseValue(value: String): T = json.decodeFromString(value) - - override fun serializeAsValue(value: T): String = json.encodeToString(value) - - override fun put(bundle: Bundle, key: String, value: T) { - bundle.putString(key, json.encodeToString(value)) - } -} diff --git a/app/src/main/java/com/squirtles/musicroad/utils/ThrottleFirst.kt b/app/src/main/java/com/squirtles/musicroad/utils/ThrottleFirst.kt deleted file mode 100644 index cc768826..00000000 --- a/app/src/main/java/com/squirtles/musicroad/utils/ThrottleFirst.kt +++ /dev/null @@ -1,18 +0,0 @@ -package com.squirtles.musicroad.utils - -import kotlinx.coroutines.flow.Flow -import kotlinx.coroutines.flow.flow - -internal fun Flow.throttleFirst(periodMillis: Long): Flow { - require(periodMillis > 0) { "period should be positive" } - return flow { - var lastTime = 0L - collect { value -> - val currentTime = System.currentTimeMillis() - if (currentTime - lastTime >= periodMillis) { - lastTime = currentTime - emit(value) - } - } - } -} diff --git a/data/.gitignore b/audio_visualizer/.gitignore similarity index 100% rename from data/.gitignore rename to audio_visualizer/.gitignore diff --git a/audio_visualizer/build.gradle.kts b/audio_visualizer/build.gradle.kts new file mode 100644 index 00000000..ef6a7a98 --- /dev/null +++ b/audio_visualizer/build.gradle.kts @@ -0,0 +1,51 @@ +plugins { + alias(libs.plugins.android.library) + alias(libs.plugins.jetbrains.kotlin.android) +} + +android { + namespace = "com.miller198.audiovisualizer" + compileSdk = 34 + + defaultConfig { + minSdk = 26 + + testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner" + consumerProguardFiles("consumer-rules.pro") + } + buildFeatures { + compose = true + } + buildTypes { + release { + isMinifyEnabled = false + proguardFiles(getDefaultProguardFile("proguard-android-optimize.txt"), "proguard-rules.pro") + } + } + compileOptions { + sourceCompatibility = JavaVersion.VERSION_17 + targetCompatibility = JavaVersion.VERSION_17 + } + composeOptions { + kotlinCompilerExtensionVersion = "1.5.14" + } + kotlinOptions { + jvmTarget = "17" + } +} + +dependencies { + + implementation(libs.androidx.core.ktx) + implementation(libs.androidx.appcompat) + implementation(libs.material) + implementation(libs.androidx.foundation.android) + testImplementation(libs.junit) + androidTestImplementation(libs.androidx.junit) + androidTestImplementation(libs.androidx.espresso.core) + + implementation(platform(libs.compose.bom)) + implementation(libs.bundles.compose) + implementation(libs.androidx.foundation.layout.android) + implementation(libs.androidx.animation.core.android) +} diff --git a/data/consumer-rules.pro b/audio_visualizer/consumer-rules.pro similarity index 100% rename from data/consumer-rules.pro rename to audio_visualizer/consumer-rules.pro diff --git a/data/proguard-rules.pro b/audio_visualizer/proguard-rules.pro similarity index 100% rename from data/proguard-rules.pro rename to audio_visualizer/proguard-rules.pro diff --git a/audio_visualizer/src/androidTest/java/com/miller198/audiovisualizer/ExampleInstrumentedTest.kt b/audio_visualizer/src/androidTest/java/com/miller198/audiovisualizer/ExampleInstrumentedTest.kt new file mode 100644 index 00000000..bb044e31 --- /dev/null +++ b/audio_visualizer/src/androidTest/java/com/miller198/audiovisualizer/ExampleInstrumentedTest.kt @@ -0,0 +1,24 @@ +package com.miller198.audiovisualizer + +import androidx.test.platform.app.InstrumentationRegistry +import androidx.test.ext.junit.runners.AndroidJUnit4 + +import org.junit.Test +import org.junit.runner.RunWith + +import org.junit.Assert.* + +/** + * Instrumented test, which will execute on an Android device. + * + * See [testing documentation](http://d.android.com/tools/testing). + */ +@RunWith(AndroidJUnit4::class) +class ExampleInstrumentedTest { + @Test + fun useAppContext() { + // Context of the app under test. + val appContext = InstrumentationRegistry.getInstrumentation().targetContext + assertEquals("com.miller198.audiovisualizer.test", appContext.packageName) + } +} diff --git a/data/src/main/AndroidManifest.xml b/audio_visualizer/src/main/AndroidManifest.xml similarity index 90% rename from data/src/main/AndroidManifest.xml rename to audio_visualizer/src/main/AndroidManifest.xml index a5918e68..8bdb7e14 100644 --- a/data/src/main/AndroidManifest.xml +++ b/audio_visualizer/src/main/AndroidManifest.xml @@ -1,4 +1,4 @@ - \ No newline at end of file + diff --git a/audio_visualizer/src/main/java/com/miller198/audiovisualizer/BaseVisualizer.kt b/audio_visualizer/src/main/java/com/miller198/audiovisualizer/BaseVisualizer.kt new file mode 100644 index 00000000..c7b6db50 --- /dev/null +++ b/audio_visualizer/src/main/java/com/miller198/audiovisualizer/BaseVisualizer.kt @@ -0,0 +1,113 @@ +package com.miller198.audiovisualizer + +import android.media.audiofx.Visualizer +import android.util.Log +import com.miller198.audiovisualizer.configs.VisualizerCallbacks + +/** + * Core class for capturing audio data using Android's [Visualizer] API. + * + * This class handles setup, configuration, and lifecycle management of a [Visualizer] instance. + */ +class BaseVisualizer { + private var visualizer: Visualizer? = null + + /** + * Initializes and starts the [Visualizer] with the given configuration. + * + * @param audioSessionId The audio session ID to capture audio from. + * @param captureSize The number of bytes to capture. Must be a power of two and within supported range. + * @param useWaveCapture Whether to enable waveform data capture. + * @param useFftCapture Whether to enable FFT (frequency) data capture. + * @param visualizerCallbacks Callbacks to receive waveform and/or FFT data. + * @param captureRate Capture rate in milliseconds. Defaults to [Visualizer.getMaxCaptureRate]. + */ + fun start( + audioSessionId: Int, + captureSize: Int, + useWaveCapture: Boolean, + useFftCapture: Boolean, + visualizerCallbacks: VisualizerCallbacks, + captureRate: Int = Visualizer.getMaxCaptureRate(), + ) { + stop() + setVisualizer(audioSessionId) + + // Validate capture size and fallback to max size if needed + visualizer?.captureSize = + if (!isPowerOfTwo(captureSize) || !isValidCaptureSize(captureSize)) { + Visualizer.getCaptureSizeRange()[1].also { + Log.w("BaseVisualizer", "Invalid capture size, fallback to max: $it") + } + } else { + captureSize + } + + setVisualizerListener( + useWaveCapture, + useFftCapture, + captureRate, + visualizerCallbacks.provideVisualizerCallbacks() + ) + } + + /** + * Returns whether the visualizer is currently running. + */ + fun isRunning(): Boolean = visualizer != null + + /** + * Stops and releases the [Visualizer] + */ + fun stop() { + visualizer?.release() + visualizer = null + } + + /** + * Creates and assigns a [Visualizer] instance for the given audio session. + */ + private fun setVisualizer(audioSessionId: Int) { + try { + visualizer = Visualizer(audioSessionId) + } catch (e: RuntimeException) { + Log.e("BaseVisualizer", "Failed to create Visualizer", e) + } + } + + /** + * Registers a [Visualizer.OnDataCaptureListener] to receive waveform and/or FFT data. + */ + private fun setVisualizerListener( + isWaveCapture: Boolean, + isFftCapture: Boolean, + captureRate: Int, + dataCaptureListener: Visualizer.OnDataCaptureListener, + ) { + visualizer?.run { + enabled = false + setDataCaptureListener( + dataCaptureListener, + captureRate, + isWaveCapture, + isFftCapture + ) + enabled = true + } + } + + /** + * Checks whether the provided [captureSize] is a power of two. + */ + private fun isPowerOfTwo(captureSize: Int): Boolean { + return captureSize > 0 && (captureSize and (captureSize - 1)) == 0 + } + + /** + * Checks whether the provided [captureSize] is within the valid range supported by [Visualizer]. + */ + private fun isValidCaptureSize(captureSize: Int): Boolean { + val range = Visualizer.getCaptureSizeRange() + return captureSize in range[0]..range[1] + } +} diff --git a/audio_visualizer/src/main/java/com/miller198/audiovisualizer/FftDataProcessor.kt b/audio_visualizer/src/main/java/com/miller198/audiovisualizer/FftDataProcessor.kt new file mode 100644 index 00000000..50f202be --- /dev/null +++ b/audio_visualizer/src/main/java/com/miller198/audiovisualizer/FftDataProcessor.kt @@ -0,0 +1,217 @@ +package com.miller198.audiovisualizer + +import android.media.audiofx.Visualizer +import kotlin.math.hypot +import kotlin.math.log +import kotlin.math.pow +import kotlin.math.sqrt + +/** + * Class for processing FFT (Fast Fourier Transform) data of the result of Visualizer capture. + * + * @param rawFftBytes The raw FFT data as a byte array captured by [Visualizer.OnDataCaptureListener.onFftDataCapture]. + */ +class FftDataProcessor(rawFftBytes: ByteArray) { + // The Fft data applied pre-processing function + private var processedData: List = emptyList() + + init { + processedData = calculateFftMagnitude(rawFftBytes) + } + + /** + * **This function must be used before applying any other preprocessing functions.** + * + * Calculates the FFT (Fast Fourier Transform) magnitude spectrum from raw FFT byte data. + * + * @param bytes The audio data as a byte array captured by [Visualizer.OnDataCaptureListener.onFftDataCapture]. + * The array alternates real and imaginary parts: [real0, imag0, real1, imag1, ...]. + * + * @return A list of FFT magnitudes (in linear scale), excluding the first two values (DC and Nyquist). + * + * Note: The first two bytes represent the DC component and the Nyquist frequency and are excluded. + * If you need more information about the result of FFT capture, see [Visualizer.getFft]. + */ + fun calculateFftMagnitude(bytes: ByteArray): List { + val audioData = bytes.drop(2).map { it.toDouble() } + + val size = audioData.size / 2 + val magnitudes = FloatArray(size) + + for (i in 0 until size) { + val real = audioData.getOrNull(2 * i) ?: 0.0 + val imaginary = audioData.getOrNull(2 * i + 1) ?: 0.0 + magnitudes[i] = hypot(real, imaginary).toFloat() + } + + return magnitudes.toList() + } + + /** + * Filters the given frequency spectrum data to include only the components within a specified frequency range. + * + * @param samplingRate The sampling rate of the original audio signal (in Hz). + * @param captureSize The size of the FFT window used when capturing the audio data. + * @param minFreq The minimum frequency (in Hz) to include in the result. + * @param maxFreq The maximum frequency (in Hz) to include in the result. + * + * @return A list of magnitudes corresponding to the frequency components within [minFreq, maxFreq]. + * + * The frequency resolution is calculated as `samplingRate / captureSize`, and the corresponding index + * range is computed to extract the subset of the frequency data. + */ + fun filterFrequency( + samplingRate: Int, + captureSize: Int, + minFreq: Int, + maxFreq: Int + ): FftDataProcessor { + val resolution = ((samplingRate / 2.0) / (captureSize / 2 - 1)) + val startIndex = (minFreq / resolution).toInt().coerceIn(0, processedData.lastIndex) + val endIndex = (maxFreq / resolution).toInt().coerceIn(startIndex, processedData.lastIndex) + + processedData = processedData.slice(startIndex..endIndex) + + return this + } + + /** + * Applies scaling weights to audio frequency data based on predefined frequency range ratios. + * + * Each element in the audio data is scaled according to which ratio range it falls into, + * as defined by the provided list of [FrequencyScale]s. + * + * @param frequencyScales A list of [FrequencyScale] objects defining the ratio ranges and their associated weights. + * + * @return A new list of Float values with each element scaled according to its frequency range. + */ + fun scaleFrequencies( + frequencyScales: List + ): FftDataProcessor { + val size = processedData.size + + processedData = processedData.mapIndexed { index, value -> + val ratio = index.toFloat() / size + val scale = frequencyScales.find { ratio in it.rangeRatio }?.weight ?: 1.0f + value * scale + } + + return this + } + + /** + * Applies a logarithmic scale transformation to the given audio magnitude data. + * for compressing large dynamic ranges in audio signals, + * + * A small epsilon is added to avoid log(0), and an offset is used to ensure + * that all input values are positive before applying the logarithm. + * + * @param base The base of the logarithm to apply (must be > 0 and ≠ 1; default is 10). + * @param scaleFactor A multiplier applied after the logarithm for additional scaling (default is 1). + * @return A list of audio magnitudes scaled logarithmically. + */ + fun applyLogScale( + base: Float = 10f, + scaleFactor: Float = 1f + ): FftDataProcessor { + require(base > 0f && base != 1f) { "Logarithm base must be greater than 0 and not equal to 1." } + + val epsilon = 1e-6f // to avoid log(0) + val minValue = processedData.minOrNull() ?: 0f + val offset = if (minValue < 1f) 1f - minValue else 0f // move the minimum value to 1 + + processedData = processedData.map { value -> + val shifted = value + offset + epsilon + val safeValue = if (shifted <= 0f || shifted.isNaN()) epsilon else shifted + scaleFactor * log(safeValue, base) + } + + return this + } + + /** + * Applies dynamic range compression using square root scaling. + * + * This method is useful for reducing the impact of high-magnitude peaks + * and enhancing lower-magnitude values, making the overall data more perceptually uniform. + * + * @return A list of compressed audio data using sqrt scaling. + * + * Note: Negative input values are clamped to 0 to avoid sqrt domain errors. + */ + fun compressDynamicRangeRoot(): FftDataProcessor { + processedData = processedData.map { value -> + sqrt(value.coerceAtLeast(0f)) + } + return this + } + + /** + * Applies Z-Score normalization to the given audio data. + * + * This method standardizes the data to have zero mean and unit variance, + * making it easier to compare values across different datasets or features. + * + * @return A list of normalized values centered around 0 with unit variance. + * If input is empty or standard deviation is zero, returns a list of zeros. + */ + fun normalizeByZScore(): FftDataProcessor { + if (processedData.isEmpty()) return this + + val mean = processedData.average().toFloat() + val stdDev = sqrt(processedData.map { (it - mean).pow(2) }.average()).toFloat() + + processedData = if (stdDev == 0f || stdDev.isNaN()) { + List(processedData.size) { 0f } + } else { + processedData.map { (it - mean) / stdDev } + } + + return this + } + + /** + * Applies Max-Min Normalization to the given audio data. + * + * @return A list of normalized values between 0 and 1. + */ + fun normalize(): FftDataProcessor { + val max = processedData.maxOrNull() ?: 1f + val min = processedData.minOrNull() ?: 0f + processedData = if (max - min <= 1f) List(processedData.size) { 0f } else processedData.map { (it - min) / (max - min) } + + return this + } + + fun result(): List = processedData +} + +/** + * Default pre-processing for FFT data. + * + * @param bytes The input byte array containing FFT data. + * @param captureSize The size of the FFT capture. + * @param minFreq The minimum frequency to include in the FFT data. + * @param maxFreq The maximum frequency to include in the FFT data. + * @param samplingRate The sampling rate of the audio signal. + * @return A list of pre-processed FFT data. + */ +fun defaultPreProcessFftData( + bytes: ByteArray, + captureSize: Int, + minFreq: Int, + maxFreq: Int, + samplingRate: Int +): List { + return FftDataProcessor(bytes) + .filterFrequency( + samplingRate / 1000, + captureSize, + minFreq, + maxFreq + ) + .applyLogScale() + .normalizeByZScore() + .normalize() + .result() +} diff --git a/audio_visualizer/src/main/java/com/miller198/audiovisualizer/FrequencyScale.kt b/audio_visualizer/src/main/java/com/miller198/audiovisualizer/FrequencyScale.kt new file mode 100644 index 00000000..ee703742 --- /dev/null +++ b/audio_visualizer/src/main/java/com/miller198/audiovisualizer/FrequencyScale.kt @@ -0,0 +1,12 @@ +package com.miller198.audiovisualizer + +/** + * Represents a frequency range (as a ratio of the full signal) and its corresponding scaling weight. + * + * @property rangeRatio A ratio-based range from 0.0 to 1.0 indicating the portion of the frequency spectrum. + * @property weight The scaling factor to apply to values within this range. + */ +data class FrequencyScale( + val rangeRatio: ClosedFloatingPointRange, + val weight: Float +) diff --git a/audio_visualizer/src/main/java/com/miller198/audiovisualizer/configs/Configs.kt b/audio_visualizer/src/main/java/com/miller198/audiovisualizer/configs/Configs.kt new file mode 100644 index 00000000..44adaaeb --- /dev/null +++ b/audio_visualizer/src/main/java/com/miller198/audiovisualizer/configs/Configs.kt @@ -0,0 +1,212 @@ +package com.miller198.audiovisualizer.configs + +import android.media.audiofx.Visualizer +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.graphics.Color.Companion.White +import androidx.compose.ui.unit.Dp +import androidx.compose.ui.unit.dp +import com.miller198.audiovisualizer.defaultPreProcessFftData + +/** + * Interface for configuring the audio visualizer. + * Supports custom, FFT, and waveform capture configurations. + */ +sealed interface VisualizerConfig { + + /** Whether to use waveform (time domain) capture */ + val useWaveCapture: Boolean + + /** Whether to use FFT (frequency domain) capture */ + val useFftCapture: Boolean + + /** Buffer size for audio capture (e.g., 512, 1024) */ + val captureSize: Int + + /** Optional FFT data processing function */ + val processFftData: ((Visualizer, ByteArray, Int) -> List)? + + /** Optional waveform data processing function */ + val processWaveData: ((Visualizer, ByteArray, Int) -> List)? + + /** Fully custom configuration defined by the user. */ + data class Custom( + override val useWaveCapture: Boolean, + override val useFftCapture: Boolean, + override val captureSize: Int, + override val processFftData: ((Visualizer, ByteArray, Int) -> List)?, + override val processWaveData: ((Visualizer, ByteArray, Int) -> List)? + ) : VisualizerConfig + + /** FFT-based visualizer configuration. */ + data class FftCaptureConfig( + val minFrequency: Int, // Minimum frequency to analyze (Hz) + val maxFrequency: Int, // Maximum frequency to analyze (Hz) + override val captureSize: Int, + override val processFftData: (Visualizer, ByteArray, Int) -> List, + ) : VisualizerConfig { + override val useWaveCapture: Boolean = false + override val useFftCapture: Boolean = true + override val processWaveData: ((Visualizer, ByteArray, Int) -> List)? = null + + init { + require(minFrequency >= 0 && maxFrequency >= 0) { + throw IllegalArgumentException("Minimum and maximum frequencies must be non-negative.") + } + require(minFrequency <= maxFrequency) { + throw IllegalArgumentException("Minimum frequency must be less than or equal to maximum frequency.") + } + } + + /** Default FFT configuration. */ + companion object { + const val DEFAULT_MIN_FREQ = 40 + const val DEFAULT_MAX_FREQ = 4000 + const val DEFAULT_CAPTURE_SIZE = 1024 + + val Default = FftCaptureConfig( + minFrequency = DEFAULT_MIN_FREQ, + maxFrequency = DEFAULT_MAX_FREQ, + captureSize = DEFAULT_CAPTURE_SIZE, + processFftData = { _, byteArray, samplingRate -> + // Default FFT preprocessing (custom implementation required) + defaultPreProcessFftData(byteArray, DEFAULT_CAPTURE_SIZE, DEFAULT_MIN_FREQ, DEFAULT_MAX_FREQ, samplingRate) + } + ) + } + } + + /** + * Waveform-based visualizer configuration. + */ + open class WaveCaptureConfig( + override val captureSize: Int, + override val processWaveData: (Visualizer, ByteArray, Int) -> List + ) : VisualizerConfig { + override val useWaveCapture: Boolean = true + override val useFftCapture: Boolean = false + override val processFftData: ((Visualizer, ByteArray, Int) -> List)? = null + + /** Default waveform configuration. */ + data object Default : WaveCaptureConfig( + captureSize = 1024, + processWaveData = { _, byteArray, _ -> + // TODO: Implement default waveform preprocessing logic + emptyList() + } + ) + } +} + +/** + * Interface for gradient animation configuration. + */ +sealed interface GradientConfig { + val useGradient: Boolean // Whether gradient is enabled + val duration: Int // Animation duration in milliseconds + val color: Color // Base color of the gradient + + /** Default gradient config (enabled). */ + data object Default : GradientConfig { + override val useGradient: Boolean = true + override val duration: Int = 2500 + override val color: Color = White + } + + /** Enabled gradient config with custom options. */ + data class Enabled( + override val duration: Int, + override val color: Color, + ) : GradientConfig { + override val useGradient: Boolean = true + } + + /** Disabled gradient config (no animation, transparent color) */ + data object Disabled : GradientConfig { + override val useGradient: Boolean = false + override val duration: Int = 0 + override val color: Color = Color.Transparent + } +} + +/** + * Represents configuration options for determining the inner clipping radius of a visual element + * (such as the inner circle in an audio visualization). + * + * Implementations specify either a fixed radius in Dp, a relative ratio of the canvas radius, + * or a default value. + * + * @property dp The fixed radius in density-independent pixels (Dp). If greater than 0, this takes priority over [ratio]. + * @property ratio A relative ratio (0.0 to 1.0) used when [dp] is zero. Represents a proportion of the canvas radius. + */ +sealed interface ClippingRadiusConfig { + val dp: Dp + val ratio: Float + + /** + * Fixed radius configuration using a specific dp value. + * If this is used, the ratio is ignored. + * + * @param dp Must be >= 0.0 + */ + data class Fixed(override val dp: Dp) : ClippingRadiusConfig { + override val ratio: Float = 0f + + init { + require(dp.value >= 0f) { + throw IllegalArgumentException("dp must be >= 0, but was ${dp.value}") + } + } + } + + /** + * Ratio-based radius configuration using a value between 0.0 and 1.0. + * Represents a percentage of the canvas radius. + * + * @param ratio Must be in the range [0.0, 1.0] + */ + data class Ratio(override val ratio: Float) : ClippingRadiusConfig { + override val dp: Dp = 0.dp + + init { + require(ratio in 0f..1f) { + throw IllegalArgumentException("ratio must be in the range [0, 1], but was $ratio") + } + } + } + + /** + * Default configuration: ratio = 1.0 (full canvas radius), dp = 0.dp. + * This means inner clipping is applied maximally. + */ + data object FullClip : ClippingRadiusConfig { + override val dp: Dp = 0.dp + override val ratio: Float = 1f + } + + /** + * No clipping applied: ratio = 0.0, dp = 0.dp. + * The content starts from the edge of the canvas, without an inner gap. + */ + data object NoClip : ClippingRadiusConfig { + override val dp: Dp = 0.dp + override val ratio: Float = 0f + } + + /** + * Small inner clipping applied: ratio = 0.3, dp = 0.dp. + * Leaves a small circular gap in the center. + */ + data object Small : ClippingRadiusConfig { + override val dp: Dp = 0.dp + override val ratio: Float = 0.3f + } + + /** + * Medium inner clipping applied: ratio = 0.7, dp = 0.dp. + * Leaves a medium-sized circular gap in the center. + */ + data object Medium : ClippingRadiusConfig { + override val dp: Dp = 0.dp + override val ratio: Float = 0.7f + } +} diff --git a/audio_visualizer/src/main/java/com/miller198/audiovisualizer/configs/VisualizerCallbacks.kt b/audio_visualizer/src/main/java/com/miller198/audiovisualizer/configs/VisualizerCallbacks.kt new file mode 100644 index 00000000..2c137d2d --- /dev/null +++ b/audio_visualizer/src/main/java/com/miller198/audiovisualizer/configs/VisualizerCallbacks.kt @@ -0,0 +1,30 @@ +package com.miller198.audiovisualizer.configs + +import android.media.audiofx.Visualizer + +/** + * A container for user-defined callback functions for [Visualizer.OnDataCaptureListener]. + * + * @param onWaveCaptured Callback invoked when waveform (raw audio) data is captured. + * @param onFftCaptured Callback invoked when FFT (frequency domain) data is captured. + */ +data class VisualizerCallbacks( + val onWaveCaptured: (Visualizer, ByteArray, Int) -> Unit = { _, _, _ -> }, + val onFftCaptured: (Visualizer, ByteArray, Int) -> Unit = { _, _, _ -> }, +) { + /** + * Provides an implementation of [Visualizer.OnDataCaptureListener] that delegates + * waveform and FFT capture events to the corresponding user-defined callbacks. + * + * @return An instance of [Visualizer.OnDataCaptureListener] to be used with the Visualizer. + */ + fun provideVisualizerCallbacks() = object : Visualizer.OnDataCaptureListener { + override fun onWaveFormDataCapture(visualizer: Visualizer, bytes: ByteArray, samplingRate: Int) { + onWaveCaptured(visualizer, bytes, samplingRate) + } + + override fun onFftDataCapture(visualizer: Visualizer, bytes: ByteArray, samplingRate: Int) { + onFftCaptured(visualizer, bytes, samplingRate) + } + } +} diff --git a/audio_visualizer/src/main/java/com/miller198/audiovisualizer/soundeffect/DrawSoundBar.kt b/audio_visualizer/src/main/java/com/miller198/audiovisualizer/soundeffect/DrawSoundBar.kt new file mode 100644 index 00000000..13b28ee0 --- /dev/null +++ b/audio_visualizer/src/main/java/com/miller198/audiovisualizer/soundeffect/DrawSoundBar.kt @@ -0,0 +1,112 @@ +package com.miller198.audiovisualizer.soundeffect + +import androidx.compose.animation.core.LinearEasing +import androidx.compose.foundation.Canvas +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableFloatStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.setValue +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Brush +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.layout.onSizeChanged +import com.miller198.audiovisualizer.soundeffect.DrawSoundEffectConfigs.GRADIENT_RADIUS_RATIO +import com.miller198.audiovisualizer.soundeffect.DrawSoundEffectConfigs.animatedGradientRadius +import com.miller198.audiovisualizer.soundeffect.DrawSoundEffectConfigs.gradientConfig +import com.miller198.audiovisualizer.soundeffect.DrawSoundEffectConfigs.onCanvasSizeChanged + +/** + * Composable function that draws a radial sound bar visualizer effect. + * + * Each audio data point is visualized as a bar (line) extending outward from the center + * in a circular pattern, with height proportional to the audio magnitude. + * + * @param audioData The list of normalized audio magnitudes (range: 0.0f to 1.0f). + * @param color The primary color used to draw the bars. + * @param modifier Modifier to apply to the Canvas layout. + */ +@Composable +internal fun DrawSoundBar( + audioData: List, + color: Color, + modifier: Modifier = Modifier, +) { + /** The radius from the center to the start of the bars */ + var adjustedRadius by remember { mutableFloatStateOf(0f) } + + /** The maximum possible bar height based on canvas size */ + var maxEffectHeight by remember { mutableFloatStateOf(0f) } + + /** Animated gradient radius for dynamic glow effects */ + val animatedGradientRadius = animatedGradientRadius(LinearEasing) + + /** Angle between each bar in the 360° circle */ + val angleStep = 360f / audioData.size + + /** Main canvas for drawing the sound bars */ + Canvas( + modifier = modifier + .fillMaxSize() + .onSizeChanged { canvasSize -> + // Recalculate radius and effect height when the canvas size changes + onCanvasSizeChanged( + width = canvasSize.width, + height = canvasSize.height, + onRadiusCalculated = { adjustedRadius = it }, + onMaxEffectHeightCalculated = { maxEffectHeight = it } + ) + } + ) { + val width = size.width + val height = size.height + + // Draw each audio data bar + audioData.forEachIndexed { idx, magnitude -> + val angle = getAudioDataAngle(idx, angleStep) + val barHeight = maxEffectHeight * magnitude + + // Starting point of the bar (on the inner radius) + val startOffset = getOffset( + centerX = width / 2, + centerY = height / 2, + angle = angle, + innerRadius = adjustedRadius, + extraLength = 0f + ) + // End point of the bar (based on magnitude) + val endOffset = getOffset( + centerX = width / 2, + centerY = height / 2, + angle = angle, + innerRadius = adjustedRadius, + extraLength = barHeight + ) + + // Draw the line with optional radial gradient + if (gradientConfig.useGradient) { + drawLine( + brush = Brush.radialGradient( + colors = listOf( + gradientConfig.color, + color + ), + center = startOffset, + radius = animatedGradientRadius * maxEffectHeight * GRADIENT_RADIUS_RATIO, + ), + start = startOffset, + end = endOffset, + strokeWidth = DrawSoundBarConstants.STROKE_WIDTH + ) + } else { + drawLine( + color = color, + start = startOffset, + end = endOffset, + strokeWidth = DrawSoundBarConstants.STROKE_WIDTH + ) + } + } + } +} diff --git a/audio_visualizer/src/main/java/com/miller198/audiovisualizer/soundeffect/DrawSoundEffectConfigs.kt b/audio_visualizer/src/main/java/com/miller198/audiovisualizer/soundeffect/DrawSoundEffectConfigs.kt new file mode 100644 index 00000000..6e67b3aa --- /dev/null +++ b/audio_visualizer/src/main/java/com/miller198/audiovisualizer/soundeffect/DrawSoundEffectConfigs.kt @@ -0,0 +1,99 @@ +package com.miller198.audiovisualizer.soundeffect + +import androidx.compose.animation.core.Easing +import androidx.compose.animation.core.RepeatMode +import androidx.compose.animation.core.animateFloat +import androidx.compose.animation.core.infiniteRepeatable +import androidx.compose.animation.core.rememberInfiniteTransition +import androidx.compose.animation.core.tween +import androidx.compose.runtime.Composable +import com.miller198.audiovisualizer.configs.ClippingRadiusConfig +import com.miller198.audiovisualizer.configs.GradientConfig +import kotlin.math.min + +/** + * Configuration and utility object for controlling the sound effect drawing behavior. + */ +object DrawSoundEffectConfigs { + + /** Ratio used to calculate the gradient radius relative to the maximum effect radius. */ + const val GRADIENT_RADIUS_RATIO = 0.8f + + /** Divisor used to determine the maximum height of the visual wave effect. */ + const val EFFECT_HEIGHT_DIVISOR = 5f + + /** Current gradient configuration. Default is [GradientConfig.Default] */ + internal var gradientConfig: GradientConfig = GradientConfig.Default + + /** + * Current clipping radius configuration. Controls the inner radius of the sound wave. + * Default is [ClippingRadiusConfig.FullClip] + */ + internal var clippingRadiusConfig: ClippingRadiusConfig = ClippingRadiusConfig.FullClip + + /** + * Animated radius value for a radial gradient brush. + * Only animates if [GradientConfig.useGradient] of [gradientConfig] is true. + * + * @param easing Easing function used in the animation. + * @return Current animated radius value. + */ + val animatedGradientRadius: @Composable (Easing) -> Float = { easing -> + if (gradientConfig.useGradient) { + val transition = rememberInfiniteTransition(label = "GradientRadiusTransition") + transition.animateFloat( + initialValue = 0.01f, + targetValue = 1.0f, + animationSpec = infiniteRepeatable( + animation = tween(durationMillis = gradientConfig.duration, easing = easing), + repeatMode = RepeatMode.Reverse + ), + label = "AnimatedGradientRadius" + ).value + } else { + 1f + } + } + + /** + * Called when the canvas size changes to update core dimensions like inner radius and maximum effect height. + * + * @param width New width of the canvas. + * @param height New height of the canvas. + * @param onRadiusCalculated Callback to provide the computed inner radius. + * @param onMaxEffectHeightCalculated Callback to provide the computed max wave height. + */ + internal fun onCanvasSizeChanged( + width: Int, + height: Int, + onRadiusCalculated: (Float) -> Unit, + onMaxEffectHeightCalculated: (Float) -> Unit + ) { + val clippingRadius = clippingRadiusConfig.dp.value + val clippingRadiusRatio = clippingRadiusConfig.ratio + + onRadiusCalculated( + if (clippingRadius > 0f) clippingRadius else (min(width, height) / 2) * clippingRadiusRatio + ) + + onMaxEffectHeightCalculated( + min(width, height) / EFFECT_HEIGHT_DIVISOR + ) + } +} + +/** + * Constants related to drawing the sound bars (e.g., circular bars). + */ +internal object DrawSoundBarConstants { + /** Default stroke width for drawing sound bars. */ + const val STROKE_WIDTH = 25f +} + +/** + * Constants related to drawing stroke-style sound waves. + */ +internal object DrawSoundWaveStrokeConstants { + /** Default stroke width for drawing wave outlines. */ + const val STROKE_WIDTH = 6f +} diff --git a/audio_visualizer/src/main/java/com/miller198/audiovisualizer/soundeffect/DrawSoundEffectUtils.kt b/audio_visualizer/src/main/java/com/miller198/audiovisualizer/soundeffect/DrawSoundEffectUtils.kt new file mode 100644 index 00000000..88a4e7f8 --- /dev/null +++ b/audio_visualizer/src/main/java/com/miller198/audiovisualizer/soundeffect/DrawSoundEffectUtils.kt @@ -0,0 +1,90 @@ +package com.miller198.audiovisualizer.soundeffect + +import androidx.compose.ui.geometry.Offset +import androidx.compose.ui.graphics.Path +import kotlin.math.cos +import kotlin.math.sin + +private val DEFAULT_OFFSET_ANGLE = Math.toRadians(-90.0) + +/** + * Converts an audio data index to an angle (in radians) for circular visualizations. + * + * @param index The index of the audio data. + * @param angleStep The step size between each index in degrees. + * @param offset Optional angle offset in radians. Defaults to top. + * @return The angle in radians as a [Float]. + */ +internal fun getAudioDataAngle( + index: Int, + angleStep: Float, + offset: Double = DEFAULT_OFFSET_ANGLE +): Float = (offset + Math.toRadians((index * angleStep).toDouble())).toFloat() + +/** + * Computes the screen position ([Offset]) for a point on a circle or arc. + * + * @param centerX The X coordinate of the center. + * @param centerY The Y coordinate of the center. + * @param angle The angle in radians. + * @param innerRadius The base radius from the center. + * @param extraLength Additional radial offset (e.g. for waveform height). + * @return The [Offset] position on the canvas. + */ +internal fun getOffset( + centerX: Float, + centerY: Float, + angle: Float, + innerRadius: Float, + extraLength: Float, +): Offset { + return Offset( + (centerX + (innerRadius + extraLength) * cos(angle)), + (centerY + (innerRadius + extraLength) * sin(angle)) + ) +} + +/** + * Draws a smooth, closed curve through a given list of [points] using the Catmull-Rom spline algorithm. + * + * This extension function generates a smooth path by interpolating between points using a Catmull-Rom spline. + * The path automatically wraps around to create a closed loop, making it useful for circular or looping shapes. + * + * @param points The list of [Offset] points to interpolate. + * @param steps The number of interpolation steps between each pair of points. Higher values create smoother curves. + */ +internal fun Path.catmullRomSpline(points: List, steps: Int = 10) { + if (points.size < 2) return + + val paddedPoints = listOf(points.last()) + points + listOf(points.first(), points[1]) + + for (i in 0 until paddedPoints.size - 3) { + val p0 = paddedPoints[i] + val p1 = paddedPoints[i + 1] + val p2 = paddedPoints[i + 2] + val p3 = paddedPoints[i + 3] + + for (t in 0..steps) { + val s = t / steps.toFloat() + val s2 = s * s + val s3 = s2 * s + + val x = 0.5f * ((2 * p1.x) + + (-p0.x + p2.x) * s + + (2 * p0.x - 5 * p1.x + 4 * p2.x - p3.x) * s2 + + (-p0.x + 3 * p1.x - 3 * p2.x + p3.x) * s3) + + val y = 0.5f * ((2 * p1.y) + + (-p0.y + p2.y) * s + + (2 * p0.y - 5 * p1.y + 4 * p2.y - p3.y) * s2 + + (-p0.y + 3 * p1.y - 3 * p2.y + p3.y) * s3) + + if (i == 0 && t == 0) { + moveTo(x, y) + } else { + lineTo(x, y) + } + } + } + close() +} diff --git a/audio_visualizer/src/main/java/com/miller198/audiovisualizer/soundeffect/DrawSoundWaveFill.kt b/audio_visualizer/src/main/java/com/miller198/audiovisualizer/soundeffect/DrawSoundWaveFill.kt new file mode 100644 index 00000000..e40e87dc --- /dev/null +++ b/audio_visualizer/src/main/java/com/miller198/audiovisualizer/soundeffect/DrawSoundWaveFill.kt @@ -0,0 +1,119 @@ +package com.miller198.audiovisualizer.soundeffect + +import androidx.compose.animation.core.LinearOutSlowInEasing +import androidx.compose.foundation.Canvas +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableFloatStateOf +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.setValue +import androidx.compose.ui.Modifier +import androidx.compose.ui.geometry.Offset +import androidx.compose.ui.geometry.Rect +import androidx.compose.ui.graphics.Brush +import androidx.compose.ui.graphics.ClipOp +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.graphics.Path +import androidx.compose.ui.graphics.drawscope.clipPath +import androidx.compose.ui.layout.onSizeChanged +import com.miller198.audiovisualizer.soundeffect.DrawSoundEffectConfigs.animatedGradientRadius +import com.miller198.audiovisualizer.soundeffect.DrawSoundEffectConfigs.gradientConfig +import com.miller198.audiovisualizer.soundeffect.DrawSoundEffectConfigs.onCanvasSizeChanged + +/** + * Composable function that draws a smooth radial waveform (filled area). + * + * This effect visualizes the waveform in a circular path using Catmull-Rom splines. + * + * @param audioData The list of normalized audio magnitudes (range: 0.0f to 1.0f). + * @param color The primary color used to fill the waveform. + * @param modifier Modifier to apply to the Canvas layout. + */ +@Composable +internal fun DrawSoundWaveFill( + audioData: List, + color: Color, + modifier: Modifier = Modifier, +) { + /** The radius from the center to the start of the bars */ + var adjustedRadius by remember { mutableFloatStateOf(0f) } + + /** The maximum possible bar height based on canvas size */ + var maxEffectHeight by remember { mutableFloatStateOf(0f) } + + /** Path used to define the circular center hole (to clip out) */ + var holePath by remember { mutableStateOf(Path()) } + + /** Angle between each bar in the 360° circle */ + val angleStep = 360f / audioData.size + + /** The path that represents the full waveform */ + val path = Path() + + /** Animated gradient radius for visual effect */ + val animatedGradientRadius = animatedGradientRadius(LinearOutSlowInEasing) + + /** Main canvas for drawing the sound wave */ + Canvas( + modifier = modifier + .fillMaxSize() + .onSizeChanged { canvasSize -> + // Recalculate radius and effect height when the canvas size changes + onCanvasSizeChanged( + width = canvasSize.width, + height = canvasSize.height, + onRadiusCalculated = { adjustedRadius = it }, + onMaxEffectHeightCalculated = { maxEffectHeight = it } + ) + holePath = holePath.apply { + addOval( + Rect( + center = Offset(x = canvasSize.width / 2f, y = canvasSize.height / 2f), + radius = adjustedRadius + 1f + ) + ) + } + } + ) { + val width = size.width + val height = size.height + + // Calculate wave points positioned radially + val points = audioData.mapIndexed { idx, magnitude -> + val angle = getAudioDataAngle(idx, angleStep) + val waveHeight = maxEffectHeight * magnitude + getOffset( + centerX = width / 2, + centerY = height / 2, + angle = angle, + innerRadius = adjustedRadius, + extraLength = waveHeight, + ) + } + + // Apply Catmull-Rom spline for smooth curve interpolation + path.catmullRomSpline(points) + + // Clip the central hole area and draw the wave around it + clipPath(holePath, clipOp = ClipOp.Difference) { + if (gradientConfig.useGradient) { + drawPath( + path = path, + brush = Brush.radialGradient( + colors = listOf(gradientConfig.color, color), + center = Offset(width / 2, height / 2), + radius = (adjustedRadius + maxEffectHeight).coerceAtLeast(0.01f) + * animatedGradientRadius + ) + ) + } else { + drawPath( + path = path, + color = color + ) + } + } + } +} diff --git a/audio_visualizer/src/main/java/com/miller198/audiovisualizer/soundeffect/DrawSoundWaveStroke.kt b/audio_visualizer/src/main/java/com/miller198/audiovisualizer/soundeffect/DrawSoundWaveStroke.kt new file mode 100644 index 00000000..ac131a79 --- /dev/null +++ b/audio_visualizer/src/main/java/com/miller198/audiovisualizer/soundeffect/DrawSoundWaveStroke.kt @@ -0,0 +1,83 @@ +package com.miller198.audiovisualizer.soundeffect + +import androidx.compose.foundation.Canvas +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableFloatStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.setValue +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.graphics.Path +import androidx.compose.ui.graphics.drawscope.Stroke +import androidx.compose.ui.layout.onSizeChanged +import com.miller198.audiovisualizer.soundeffect.DrawSoundEffectConfigs.onCanvasSizeChanged + +/** + * Composable function that draws a smooth radial waveform (outline only). + * + * This effect visualizes the waveform in a circular path using Catmull-Rom splines. + * + * @param audioData The list of normalized audio magnitudes (range: 0.0f to 1.0f). + * @param color The primary color used to fill the waveform. + * @param modifier Modifier to apply to the Canvas layout. + */ +@Composable +internal fun DrawSoundWaveStroke( + audioData: List, + color: Color, + modifier: Modifier = Modifier, +) { + /** The radius from the center to the start of the bars */ + var adjustedRadius by remember { mutableFloatStateOf(0f) } + + /** The maximum possible bar height based on canvas size */ + var maxEffectHeight by remember { mutableFloatStateOf(0f) } + + /** Angle between each bar in the 360° circle */ + val angleStep = 360f / audioData.size + + /** The path that represents the full waveform */ + val path = Path() + + /** Main canvas for drawing the sound bars */ + Canvas( + modifier = modifier + .fillMaxSize() + .onSizeChanged { canvasSize -> + // Recalculate radius and effect height when the canvas size changes + onCanvasSizeChanged( + width = canvasSize.width, + height = canvasSize.height, + onRadiusCalculated = { adjustedRadius = it }, + onMaxEffectHeightCalculated = { maxEffectHeight = it } + ) + } + ) { + val width = size.width + val height = size.height + + // Calculate wave points positioned radially + val points = audioData.mapIndexed { idx, magnitude -> + val angle = getAudioDataAngle(idx, angleStep) + val waveHeight = maxEffectHeight * magnitude + getOffset( + centerX = width / 2, + centerY = height / 2, + angle = angle, + innerRadius = adjustedRadius, + extraLength = waveHeight, + ) + } + + // Apply Catmull-Rom spline for smooth curve interpolation + path.catmullRomSpline(points) + + drawPath( + path = path, + color = color, + style = Stroke(width = DrawSoundWaveStrokeConstants.STROKE_WIDTH) + ) + } +} diff --git a/audio_visualizer/src/main/java/com/miller198/audiovisualizer/soundeffect/SoundEffects.kt b/audio_visualizer/src/main/java/com/miller198/audiovisualizer/soundeffect/SoundEffects.kt new file mode 100644 index 00000000..043a5410 --- /dev/null +++ b/audio_visualizer/src/main/java/com/miller198/audiovisualizer/soundeffect/SoundEffects.kt @@ -0,0 +1,37 @@ +package com.miller198.audiovisualizer.soundeffect + +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color + +/** + * Enum representing different sound visualization effects. + * Each effect defines a Composable function that draws the audio data using a specific visual style. + * + * @property drawEffect A Composable lambda that renders the corresponding sound effect. + */ +enum class SoundEffects( + val drawEffect: @Composable ( + audioData: List, + color: Color, + modifier: Modifier, + ) -> Unit +) { + /** No effect. This does not render any audio visualization. */ + NONE({ _, _, _ -> }), + + /** A vertical bar graph representation of the audio data. */ + BAR({ audioData, color, modifier -> + DrawSoundBar(audioData, color, modifier) + }), + + /** A waveform rendered using stroke (outline only). */ + WAVE_STROKE({ audioData, color, modifier -> + DrawSoundWaveStroke(audioData, color, modifier) + }), + + /** A waveform rendered as a filled shape. */ + WAVE_FILL({ audioData, color, modifier -> + DrawSoundWaveFill(audioData, color, modifier) + }) +} diff --git a/audio_visualizer/src/main/java/com/miller198/audiovisualizer/ui/CircleVisualizer.kt b/audio_visualizer/src/main/java/com/miller198/audiovisualizer/ui/CircleVisualizer.kt new file mode 100644 index 00000000..58214934 --- /dev/null +++ b/audio_visualizer/src/main/java/com/miller198/audiovisualizer/ui/CircleVisualizer.kt @@ -0,0 +1,112 @@ +package com.miller198.audiovisualizer.ui + +import androidx.compose.animation.core.Animatable +import androidx.compose.animation.core.AnimationVector1D +import androidx.compose.animation.core.FastOutSlowInEasing +import androidx.compose.animation.core.tween +import androidx.compose.runtime.Composable +import androidx.compose.runtime.DisposableEffect +import androidx.compose.runtime.LaunchedEffect +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.graphics.Color.Companion.White +import com.miller198.audiovisualizer.BaseVisualizer +import com.miller198.audiovisualizer.configs.VisualizerConfig +import com.miller198.audiovisualizer.soundeffect.DrawSoundEffectConfigs +import com.miller198.audiovisualizer.configs.GradientConfig +import com.miller198.audiovisualizer.configs.ClippingRadiusConfig +import com.miller198.audiovisualizer.configs.VisualizerCallbacks +import com.miller198.audiovisualizer.soundeffect.SoundEffects +import kotlinx.coroutines.launch + +/** + * Composable that visualizes audio data as a circular sound effect (e.g., wave or bar), + * using a provided audio session ID. + * + * @param audioSessionId The audio session ID from the audio output source (e.g., MediaPlayer). + * @param soundEffects Object that defines how to draw the sound effect. use [SoundEffects] (waveform, bars, etc.). + * @param visualizerConfig Configuration for how the visualizer captures and processes audio data [VisualizerConfig]. + * @param modifier Modifier to apply to the visualizer's layout. + * @param color The primary color to use for drawing the sound visualization. + * @param clippingRadiusConfig Configuration for the inner clipping radius of the circle visualization. Default is [ClippingRadiusConfig.FullClip]. + * @param gradientConfig Configuration for gradient animation within the visualizer (optional). Default is [GradientConfig.Default] which uses gradient animation. + */ +@Composable +fun CircleVisualizer( + audioSessionId: Int, + soundEffects: SoundEffects, + visualizerConfig: VisualizerConfig, + modifier: Modifier = Modifier, + color: Color = White, + clippingRadiusConfig: ClippingRadiusConfig = ClippingRadiusConfig.FullClip, + gradientConfig: GradientConfig = GradientConfig.Default, +) { + /** Holds the current list of magnitude values */ + val magnitudes = remember { mutableStateOf>(emptyList()) } + + /** Animated version of the magnitudes for smooth transitions in visualization */ + val animateMagnitudes = remember { mutableStateOf>>(emptyList()) } + + val visualizer = remember { BaseVisualizer() } + + // Set global visual configuration (used in other rendering composable functions) + DrawSoundEffectConfigs.gradientConfig = gradientConfig + DrawSoundEffectConfigs.clippingRadiusConfig = clippingRadiusConfig + + // Start the visualizer when the composable is composed with the given audio session ID + LaunchedEffect(audioSessionId) { + visualizer.start( + audioSessionId = audioSessionId, + captureSize = visualizerConfig.captureSize, + useWaveCapture = visualizerConfig.useWaveCapture, + useFftCapture = visualizerConfig.useFftCapture, + visualizerCallbacks = VisualizerCallbacks( + // Callback for waveform audio data (optional processing) + onWaveCaptured = { visualizer, bytes, samplingRate -> + magnitudes.value = visualizerConfig.processWaveData?.invoke(visualizer, bytes, samplingRate) ?: emptyList() + }, + // Callback for FFT audio data (optional processing) + onFftCaptured = { visualizer, bytes, samplingRate -> + magnitudes.value = visualizerConfig.processFftData?.invoke(visualizer, bytes, samplingRate) ?: emptyList() + }, + ) + ) + } + + // Animate changes in magnitude values for smoother rendering transitions + LaunchedEffect(magnitudes.value) { + if (animateMagnitudes.value.isEmpty()) { + // Initialize the animatable list if not already done + animateMagnitudes.value = magnitudes.value.map { Animatable(it) } + } else { + // Animate each value to its new magnitude + magnitudes.value.forEachIndexed { i, magnitude -> + launch { + animateMagnitudes.value[i].animateTo( + targetValue = magnitude, + animationSpec = tween( + durationMillis = 120, + easing = FastOutSlowInEasing + ) + ) + } + } + } + } + + // Release the visualizer when composable leaves the composition + DisposableEffect(audioSessionId) { + onDispose { + visualizer.stop() + } + } + + // Draw the sound effect using the provided drawEffect lambda + soundEffects.drawEffect.invoke( + animateMagnitudes.value.map { it.value }, + color, + modifier, + ) +} diff --git a/audio_visualizer/src/test/java/com/miller198/audiovisualizer/ExampleUnitTest.kt b/audio_visualizer/src/test/java/com/miller198/audiovisualizer/ExampleUnitTest.kt new file mode 100644 index 00000000..1f3676f6 --- /dev/null +++ b/audio_visualizer/src/test/java/com/miller198/audiovisualizer/ExampleUnitTest.kt @@ -0,0 +1,17 @@ +package com.miller198.audiovisualizer + +import org.junit.Test + +import org.junit.Assert.* + +/** + * Example local unit test, which will execute on the development machine (host). + * + * See [testing documentation](http://d.android.com/tools/testing). + */ +class ExampleUnitTest { + @Test + fun addition_isCorrect() { + assertEquals(4, 2 + 2) + } +} diff --git a/build-logic/convention/src/main/java/com/squirtles/convention/extensions/ComposeAndroid.kt b/build-logic/convention/src/main/java/com/squirtles/convention/extensions/ComposeAndroid.kt index 76494981..35498430 100644 --- a/build-logic/convention/src/main/java/com/squirtles/convention/extensions/ComposeAndroid.kt +++ b/build-logic/convention/src/main/java/com/squirtles/convention/extensions/ComposeAndroid.kt @@ -17,9 +17,11 @@ internal fun Project.configureComposeAndroid(commonExtension: CommonExtension<*, dependencies { val composeBom = libs.getLibrary("compose.bom") implementation(platform(composeBom)) + androidTestImplementation(platform(composeBom)) implementation(libs.getBundle("compose")) implementation(libs.getBundle("material")) debugImplementation(libs.getBundle("compose-debug")) + androidTestImplementation(libs.getBundle("compose-debug")) } } } diff --git a/core/account/build.gradle.kts b/core/account/build.gradle.kts index 9d05faf5..f5244594 100644 --- a/core/account/build.gradle.kts +++ b/core/account/build.gradle.kts @@ -12,6 +12,12 @@ dependencies { implementation(projects.core.model) implementation(projects.core.buildconfig) + implementation(libs.bundles.auth) + implementation(libs.firebase.auth.ktx) + + testImplementation(libs.junit) + androidTestImplementation(libs.bundles.test) + // Credentials implementation(libs.bundles.auth) } diff --git a/core/account/src/main/java/com/squirtles/account/AccountViewModel.kt b/core/account/src/main/java/com/squirtles/account/AccountViewModel.kt index c286940d..37e7d3f5 100644 --- a/core/account/src/main/java/com/squirtles/account/AccountViewModel.kt +++ b/core/account/src/main/java/com/squirtles/account/AccountViewModel.kt @@ -4,7 +4,7 @@ import android.util.Log import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope import com.google.android.libraries.identity.googleid.GoogleIdTokenCredential -import com.squirtles.domain.user.usecase.SignOutUseCase +import com.squirtles.user.usecase.SignOutUseCase import com.squirtles.user.usecase.CreateGoogleIdUserUseCase import com.squirtles.user.usecase.DeleteAccountUseCase import com.squirtles.user.usecase.FetchUserByIdUseCase diff --git a/core/account/src/main/java/com/squirtles/account/GoogleId.kt b/core/account/src/main/java/com/squirtles/account/GoogleId.kt index 69abfe90..11485d39 100644 --- a/core/account/src/main/java/com/squirtles/account/GoogleId.kt +++ b/core/account/src/main/java/com/squirtles/account/GoogleId.kt @@ -11,7 +11,9 @@ import androidx.credentials.GetCredentialResponse import androidx.credentials.exceptions.NoCredentialException import com.google.android.libraries.identity.googleid.GetGoogleIdOption import com.google.android.libraries.identity.googleid.GoogleIdTokenCredential -import com.squirtles.localproperties.LocalPropertyProvider +import com.google.firebase.auth.FirebaseAuth +import com.google.firebase.auth.GoogleAuthProvider +import com.squirtles.localproperties.BuildConfig import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.launch @@ -21,7 +23,7 @@ class GoogleId(private val context: Context) { private val googleIdOption: GetGoogleIdOption = GetGoogleIdOption.Builder() .setFilterByAuthorizedAccounts(false) - .setServerClientId(LocalPropertyProvider.googleClientId) + .setServerClientId(BuildConfig.GOOGLE_CLIENT_ID) .setAutoSelectEnabled(true) .build() @@ -29,34 +31,42 @@ class GoogleId(private val context: Context) { .addCredentialOption(googleIdOption) .build() - private fun handleSignIn(result: GetCredentialResponse, onSuccess: (GoogleIdTokenCredential) -> Unit) { + private fun signInWithGoogle(result: GetCredentialResponse, onSuccess: (String, GoogleIdTokenCredential) -> Unit) { when (val data = result.credential) { is CustomCredential -> { if (data.type == GoogleIdTokenCredential.TYPE_GOOGLE_ID_TOKEN_CREDENTIAL) { val googleIdTokenCredential = GoogleIdTokenCredential.createFrom(data.data) - Log.d("GoogleId", "data.type : ${googleIdTokenCredential.id}") - Log.d("GoogleId", "data.type : ${googleIdTokenCredential.displayName}") - Log.d("GoogleId", "data.type : ${googleIdTokenCredential.profilePictureUri.toString()}") - onSuccess(googleIdTokenCredential) + Log.d("SignIn", "token : ${googleIdTokenCredential.idToken}") + signInWithFirebase(googleIdTokenCredential, onSuccess) } } } } - fun signIn(onSuccess: (GoogleIdTokenCredential) -> Unit) { + private fun signInWithFirebase(googleIdTokenCredential: GoogleIdTokenCredential, onSuccess: (String, GoogleIdTokenCredential) -> Unit) { + val credential = GoogleAuthProvider.getCredential(googleIdTokenCredential.idToken, null) + FirebaseAuth.getInstance().signInWithCredential(credential) + .addOnSuccessListener { authResult -> + authResult.user?.uid?.let { uid -> + Log.d("SignIn", "Firebase 인증 uid : $uid") + onSuccess(uid, googleIdTokenCredential) + } + } + .addOnFailureListener { exception -> + Log.e("SignIn", "Firebase 인증 실패", exception) + } + } + + fun signIn(onSuccess: (String, GoogleIdTokenCredential) -> Unit) { CoroutineScope(Dispatchers.Main).launch { runCatching { val result = credentialManager.getCredential(context, request) - handleSignIn(result, onSuccess) + signInWithGoogle(result, onSuccess) }.onFailure { exception -> when (exception) { - is NoCredentialException -> Toast.makeText( - context, - context.getString(R.string.google_id_no_credential_exception_message), - Toast.LENGTH_SHORT - ).show() + is NoCredentialException -> Toast.makeText(context, context.getString(R.string.google_id_no_credential_exception_message), Toast.LENGTH_SHORT).show() } - Log.e("GoogleId", "Google SignIn Error : $exception") + Log.e("SignIn", "Google SignIn Error : $exception") } } } diff --git a/core/common/src/main/java/com/squirtles/common/ui/SignInAlertDialog.kt b/core/common/src/main/java/com/squirtles/common/ui/SignInAlertDialog.kt new file mode 100644 index 00000000..c0deec96 --- /dev/null +++ b/core/common/src/main/java/com/squirtles/common/ui/SignInAlertDialog.kt @@ -0,0 +1,136 @@ +package com.squirtles.common.ui + +import android.content.res.Configuration.UI_MODE_NIGHT_YES +import androidx.compose.foundation.BorderStroke +import androidx.compose.foundation.Image +import androidx.compose.foundation.clickable +import androidx.compose.foundation.isSystemInDarkTheme +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.PaddingValues +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.material3.BasicAlertDialog +import androidx.compose.material3.Button +import androidx.compose.material3.ButtonDefaults +import androidx.compose.material3.ExperimentalMaterial3Api +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.Surface +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.res.painterResource +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.text.style.TextAlign +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.dp +import com.squirtles.common.R +import com.squirtles.common.ui.theme.Black +import com.squirtles.common.ui.theme.DarkGray +import com.squirtles.common.ui.theme.MusicRoadTheme +import com.squirtles.common.ui.theme.SignInButtonDarkBackground +import com.squirtles.common.ui.theme.SignInButtonDarkStroke +import com.squirtles.common.ui.theme.SignInButtonLightStroke +import com.squirtles.common.ui.theme.White + + +@OptIn(ExperimentalMaterial3Api::class) +@Composable +fun SignInAlertDialog( + onDismissRequest: () -> Unit, + onGoogleSignInClick: () -> Unit, + description: String +) { + BasicAlertDialog( + onDismissRequest = onDismissRequest, + ) { + Surface( + modifier = Modifier.fillMaxWidth(), + shape = RoundedCornerShape(16.dp), + color = MaterialTheme.colorScheme.surface + ) { + Column( + modifier = Modifier.padding(24.dp), + horizontalAlignment = Alignment.CenterHorizontally, + verticalArrangement = Arrangement.Center + ) { + Text( + text = description, + color = MaterialTheme.colorScheme.onSurface, + fontWeight = FontWeight.Bold, + textAlign = TextAlign.Center, + style = MaterialTheme.typography.bodyLarge + ) + + VerticalSpacer(height = 40) + + GoogleSignInButton(onClick = onGoogleSignInClick) + + VerticalSpacer(height = 20) + + Text( + text = stringResource(R.string.sign_in_dialog_dismiss), + modifier = Modifier.clickable(onClick = onDismissRequest), + color = DarkGray, + textAlign = TextAlign.Center, + style = MaterialTheme.typography.bodyMedium + ) + } + } + } +} + +@Composable +fun GoogleSignInButton( + onClick: () -> Unit, + modifier: Modifier = Modifier +) { + Button( + onClick = onClick, + modifier = modifier.height(40.dp), + shape = RoundedCornerShape(50), + colors = ButtonDefaults.buttonColors(containerColor = if (isSystemInDarkTheme()) SignInButtonDarkBackground else White), + border = BorderStroke(1.dp, if (isSystemInDarkTheme()) SignInButtonDarkStroke else SignInButtonLightStroke), + contentPadding = PaddingValues(vertical = 10.dp, horizontal = 12.dp) + ) { + Row(verticalAlignment = Alignment.CenterVertically) { + Image( + painter = painterResource(id = R.drawable.img_google_logo), + contentDescription = stringResource(id = R.string.profile_google_icon), + modifier = Modifier.size(20.dp) + ) + HorizontalSpacer(10) + Text( + stringResource( + id = R.string.profile_sign_in_google + ), + color = if (isSystemInDarkTheme()) White else Black + ) + } + } +} + +@Preview(name = "Light") +@Preview(name = "Dark", uiMode = UI_MODE_NIGHT_YES) +@Composable +private fun LoginAlertDialogPreview() { + MusicRoadTheme { + SignInAlertDialog({}, {}, stringResource(id = R.string.sign_in_dialog_title_default)) + } +} + + +@Preview(name = "Light") +@Preview(name = "Dark", uiMode = UI_MODE_NIGHT_YES) +@Composable +private fun PreviewGoogleSignInButton() { + MusicRoadTheme { + GoogleSignInButton({}) + } +} diff --git a/core/common/src/main/java/com/squirtles/common/ui/theme/Color.kt b/core/common/src/main/java/com/squirtles/common/ui/theme/Color.kt index daf81b42..ae0135f6 100644 --- a/core/common/src/main/java/com/squirtles/common/ui/theme/Color.kt +++ b/core/common/src/main/java/com/squirtles/common/ui/theme/Color.kt @@ -19,3 +19,7 @@ val Gray = Color(0xFFAAAAAA) val White = Color(0xFFFFFFFF) val PlayerBackground = Color(0xFF353535) + +val SignInButtonDarkBackground = Color(0xFF131314) +val SignInButtonLightStroke = Color(0xFF747775) +val SignInButtonDarkStroke = Color(0xFF8E918F) diff --git a/core/common/src/main/res/drawable/img_google_logo.png b/core/common/src/main/res/drawable/img_google_logo.png new file mode 100644 index 0000000000000000000000000000000000000000..a4a9918dad10832bc8aaa5574d71fe15f089c8cb GIT binary patch literal 16590 zcmZvEc|6o#^!J^y?<5gfk|j&XzSAPco+XupgzU=N*aoFSVMf+$8Dv+4tRqRvI=1Z7 zrtHeT&U5Gcd!Eer_c`Z%&RtGV%uV%K7 zr-gs52M_H6AOuhs&shb(Sejx8ozy!Z{|#EkPYJwWD$rGvKKi-&i^OMIHe+ZX;m9e z$hp$ic-;w!;5%9-mn!R)j{n6M+H#eMM9Jz`zZ2+d1-U=z0TBEo{(W|8M|yAEIk_+Y zMg$85$C_BWCQ0C$X%_H97o$iu$MZoL!g&rbru)@kKAkCUqY# zbDU;C0*9!7Pqs&}tSSEev{Z+l;VqJdGqgxDHo3a86HYKUe+dyEYi~|ag%gzAGd+C8 zaV zQC7+6jW(hO7#7%%JBrRg-P9ruKWgOu^63kQTG43<36Gltp35|&eAM4A0r2}5=ih65 z%-cQK+JI)0RmSQG5-X~1;&{|sMW*);0O;)!|E{!|bzr*~=eqs%2oCYr8&!vDP|E1h zYJ6Bp7n%ToDTLKhG)rjiD9R_LhD$4fA@~MyMfBht@c`nK_VHprI}ob*_oTinJ1B#H zJt4_T-zT5ixbS_Z=+rGiF6Mzx^hhLl5IcqQp*|(s0rgF7 z4i&nYCKF<#2)|Tu%FTytY3Y&nhP!%-nIVr^BWGauiT|DiXN9FiJi~LU_$8kq(akbP z=Zra!^w8Adx&WgKXW=KBB{gS^niveBPvb!GW4-0#=<~;J2=YHRJP9x>5QjNzZ`I1C z(>l-jTkq0264%@BC~uw4ioJIVV0?aY-mSJF-$Jb<4(Xbm{9ybna>bxQBBN(xaNsl@ z5^Ni4bL8AE+M4mP{D+h|+ejaNdM*Sne5sfN5V)bNs+5pIxOoOimpVn`?xofzf`CrUpDJjS1u< zC<&D9V3e*V&*jB^C9()Sxt>_W-a_mO-vOgyRC`;u$UU6g*?0PA@vx9!zs$wIS5Y_|mC!03?J8hiBj`4adhU8HBpSoqFemk`npo!K(ev z#mN`lTLCDB>omSsQm3XpYFSv1u{P-|80cD-3*Z zXmjWcT-?P51~i`m!OZE^G$LU#&}Pu?9CxU}v(Mt+C(@7R1={NX&t6OHG$#~fqI*=x zfgYSlHv7<*;(zAaK8SCBYJ;!UL9x?%bRwE`A4!u5ujU0TgEjMP$L6`a*GzW{sU(jeur& zIYT*^9-NoXd7McTK9gMYe8CHmdwbVw8nXarLf>=Z1T{Db#i>hL=SLO2n?43U&$2cu zW7;1gqkzzDC=Ci5E+R3HwgsmfMV0C6vkJF#keEcs3LDrKp*ahRm>ucW7qvN{P5)se z3j=yY$ZenN7Tp`QfW#-})~^B88t4B+$GUMEoJ%$PU@NNiiNr>5WmRd-g#Bm!;fc6K zE0ha;9M|(Xpalv@*y{e#Wd=?BWW#42dVo9)_3hNRG(8HtWao)^KE&HIn=KRNIEnUaUGGJX&c>I}6j+fmTv6&hZ@fn5^B zHh13Qo$P#=K%V2XQ)He6q~s$&_zeX*i5(YRqD+`RYhtMZGwkoR^O}~>x%loNNMW0b zzL?Xb1rsWKsIFeWpN3dHxz|Mga(jb{^!PazJK!l}^eB}Lf$4+vT#OADofCohU@VQt zNn6m9a)?X6&H>UX7<^P9&yIIeASx>rvFjRmU!V_2oX`;QJx$icz=}E=@O$cxE7h)@ z&tnT=u!_i>>O$_tuQcY4&`}n&{He-Ao$HcZXHkv$cqntI$%-h&nv`2#0Q- z{Vy1h`vAh;hLy(#(l41Hb?=_tPGv{{h9WtS1;yrMv4t`JXc4*Fu<2uvOHKJ~68??{ zxu5>`Zz^Ya?4Yz-d9+2tgr`26fxgh;POHqyr!0+h)WKsI7!DC&;qQhd|DS`B3{k zvn~RPI}$h^;Z2ee0Fu#dGp=a}o+1bUisMK7b+ z491<;D*^DtwCOlprB;m%6`mHEyFwcQcsOavKp1>|JhdDYjlZDv9AKWF-KIN=z>NOF zPXCgi0iN+~I)tlKRgsW5-{5CBA!Ns3ixUXUbEwzaxWlV|*+6C|Nf6mI7#6Uq5`EYM zXJ4hqe0cI(C~)9DIItp~FcSq17y9J3AL*Abun5ilz)mkyy1sKjBFmQ_VKuEpKsh@N z8gS|C;Hxw(nf;zJfKN0G&{r*nEEyh{<=~%VuXwG_SLg7Z!d~(5AH|6dkM*4j{OF6t zmoIuW92fw8Z#d+ypQoVeyjeGQl%jRczi0lwCZZ)ZCyHWP@;Latb4Pj9w#2rFlFN8G zMPvu2DEdW@%4{mNuW@rw!>?z5GV5w{v>FnNG`TRXXRzyuczqEqi7x36X!(quGh2HP zC$f}7pv#88aMD@sKxMXckOc4b(g5gycThoRy%=&7q7SC90yWWx7of6GU)6iE8}BCK z)A03SJ%M3iST?zB4MMTt8)#l{@evbqD1wW zY>OGiObyq12Pos=KLpF*8wf z^$XZTmqFv1BQRwkj&wlX_SAm(;U(vA1Z~1Jc<`Jfx;6T|ASE-W-Rt#;fM4*=UORk> z9f!QmI9{*0*teT0dXR(1i3~UT&giWQa$bbgtrywK86U@lAShwAXNy7Xf*VQ)x9*q9 zy}F|ZGd_k`(^UTyI~~kGdRwa+M412WvAkpR05X%Hw~cl6FSdeyS5Ct)os6ET3ru^s zFzVnW{Fd+c-yRS~nHmICmQPJ__|`uwB&Sn$YPdczA^kfWYhxzK7{$G2kk}^pjTN*= z&RLkznAhtV?Pg5Xb}}AMTed~{FeKkICYU;lG zZYFWvnKm4bst_Ig7C(5tpNIL7$NCQK=TPCB9%iN!eU=QKX~+xb>7*$aZ1VlG}~TtQg!VVmHOV_%PhkoTkc&+scBw$pB~|Aqmz$W0|VE{kP5 z7QglU-N5nv%ZWmL;KmnH!qYVAa6!41k39RAy#tj==TYKDk~Z=Zv=K+86BR(<-<}*z zF5j_cY%i3j@1GsU5GK(IB9O1{iZ&gkjW9MKxd6|PtsYujzQa}cJ`|cNT4!Gv>ND9t zO3TKfo;no*!N4?00fJnGUx2+0#BJJ8Y)-M(nM=e_ z)S%XLUz0}s)|*PfbCAb-ybCgWDv}v)F6)S9eIO;=q8~qnDdpMEQW2TUpG{V#ji9Dv zh7JOjsNDf-88xY52t>z`wBr<1C&UgMf3-IL!EtWp$-a#W%$fY;MNrNjU3b7aKzn!A~?caRsiS!03YG=eFQ>m(UL=9 zak0)#yKKe>Q@p710ncX-%;?`jF?$4LUwkELL*3jvmxdrswT9f3TZ!k{|AuTVAz$R9 ze%vPb>-z#_Mu*2xb!bU;mjnYet_M%6dJ0ZSuQ%0j_gJ*f=e9b8ZiK$mMoDFF*e!ge zdu`DXN9QZsIq$Vy!D%fW9c1uca-D3B-IP_V;`r)Gg@gM@m=h&GlHD5K3R28Se`0Ut zGu(WkU7L#cI?uprhEUFp_%qvi`08tOY~a_A(aRe>SXRXxD-+v-E?4dHj~CEd_7BXr zEAUJZGxjM4N*h4J@g3%s1nQcIYnFeKD7CiM_`Y+CdPE5;0|ED!1j*?u#}J!AiUP88 ztY#0DYHoXMOumVZNx^Zcl3I@T%xW*APXXP6?Yp!jMzbzj_gecr30IxU7`20Jb!u8D zuT@&53)t1*wU?a}%#Jnr6XKv!-ea5+L*@7CB%YG67t~MztCSfRpgk)w79VpVXK+X% zwFJ#*OGEX2Z&Nw~=sw(;E%%Gi&qr;q-99<0IH_gr;WLGz>KjGRllWSb*S)XoQDJnp z-DpYe4{1e)lVtR!)yzyD;j{*eym9LSFfmwVv+oA$L5ki|b9yL~?9X?3U)13m$W_lT z?Ox?3ac&u^KbxEdB-!wEM12)cbn9h}kjTR9M)S85)iMOSStGO*oXNIX$BY<6Uis?} z`~2+B3R?ExN?M<$9Pa$=I^*RT%j2|bl>RMc) za&Nrfj2t?;ZuC`3ZFx1!`oU2KD^%bT-mbtEOs_~tB$X5jm}m!NXH0*!TMqH>D#vR7 zplF5N%9A#?HUF*$N|xJWgB;APU6+pzuX`Iay0anoK^JXIiJ^oHOs@SY0$^ zKD@N#9jCa_L{pO0)qoMc=3KXTcAH99*XpP7)Zw#B3TwXk_;Ri1&KAS{rMqD^s4$#T zePRHD)Bh4=HrL!008UsIAIGQ|jt^R1cd>p;<*pvyBJ5*VP@-PTc3kbog*7=0fN-$7WyhEdI??+D|TN}s=N~AL2ioM z6{y^rD%j~aMA1R2~8yIpl2nIH__VC8(vc8 ziO-hK`7IjO{~V_IRgzoG1hwoVf~6Tvmy!m57ta}(R=@2|{?rIJWKxT00sCp^FH{a& zhhzhyqFF(Xb;7H_5yN4<1;tg*e8sDOIg_R4GVUk`MK{*5fp3?4k7G`WE-gy1JRToo zQrvj)u;i(FqYO#n-#(RYaIyjjPAw8~D5<-iC$SQKorP}w;e4h;{4`(BESlZ%ox?jvO|lcv*)}MNYspDN_?lLXhfY))gLt4x?2Tb1G*bdBnLS`)Gz8 z@<3!CfmxN*2dm0vD1+hN>t)5iWz3hl&C8hT;f8|N5|r@4dDQRHJq_N{CE+*xn$J%v z%U>;;ZVa?up)xtw-1QLaLfHy6V*x@(4YjCz>pkEo)*88mKW3%=Z9Xq4VftAdl}a(r z`seDv*JU?CF~IL;_(l?;HWDqenv1tb!)fMO%mdO=_K{92pK1%s-f&Zvy-m7bA7x=8 z8Tq0XfOxyE5ehN%Cau>Oy>~pwsvh!ly~z7-=}Ox(sIR7?qkvt#;03~gynSijsn28n z+I?%YjbB&JtyPfa%_M+Ovf**?;-^HUd*Jnw;@?fL8m4FuMtEA`9tIl)<~%Dbptv9w zpRdIgtT+5qDYj`%!bJVE3e!?gBe|3UV!z}tvu<_z-_7Lj5yqPY741@qqBAxDmK}l& zX+h+&Q2-t#O9Isa%c#r4E#7&B+12I&8dp3^h2TnxaISz61Rv_KS`XSt-Wv&3I`P%^ z2{}863`n^z=>XJWh{1S|Wn@q3!YCe>edXs*8SCMc0KUUiO;I?qR0p%}d&{V^r4wWt z6E$cewK88$MYdk7e)s4<$)xmvZ+sE;4+~Da3W=5bH#e&345xfY(S~pnRS2vWmwbGi z5ju~{NdA*lV)V~|q~k(Muw(?j=Py%X4xfG05?%UPtlP%>EclA$^jqWMq?bNiP0wCH z;VN$Rfz{tS%?^UFZ&3<_{AOvCzd77xeaf* z6D8IZcOEEN^rQ(eNMrpoDFeK?MGN$U4b6{jJkqQzCa2o;)SOv9vVB)ElW(0OoMkda z*#jpILK2IR&Xx*J#n7`}&ToD7+$7|LP(YI$Va6Qa=hG}WU0Q;g$_O3TG*NGGts!sb z!F`+ulriwPXuzZUhUUkI+uc=nubFt+70wrq-e&tbr2|Nfp27vuuwfMadO(cC~*?)4`q*FBY+WTdnpt-z6)+0tOMpv9|y zvX1eBMaVURWv_hZ!k%OU;CfV3fEzv}!H1frvH)Wlc*lD|=8m-Ne{A?uT;C6JbMH`zzHMqL2x2i|2EC57gu6_3J^=VyU84fW5W8%Y$rNk;m(!NxC9)Z< z#*t_MURc#?0uHEt2aaaw02wzwnQ$M0zCV*gnU;#lnV%I*3?KtqL$vcc5~#XeKdEA8 z0OY!$Y*+Q5Y`D+LROaURNG#=WPQ_ZOE+8Q!OdtWgkETpTY55xFJQ8z>GJp~spjXle z5M%({e*N#I%I}BNKq>o()r~MCA05hj-Yw+8c}qmQeFMyreXkz``(y*56EQa^Gr4gs z5K%?X4nRaP|H?2sctDi^=kN97W^Ugpj4zKr1N~P4uH3m2>7#+ zoJ;)JyvqsLC7rKP=~7y>R?!kJ2UMs4;1K#>V+kR|e~pC}Ua)PzM6!OG7GPe(udn`q z3N$X_hP#>_!h}^J_;t|JkieEi;E)AXBKPzr)b;;<@PBmV(9OsIM$m>NI%+u9*&iIb z!JZ3fDgZt{vjgAG4sJjM9zET=x_urAdb78Rj#C6t^_=W{pP&Jt0rB5HMo10(Z%&SO zKbkinD6(sC62Y*$Wl19e;PkMF`uRtSHF!+gt~0TK6v!=Zwoq^^Gd-C6z>N8^`QPW4 zox7+{Bf$(hi{e|)B)=%@zNYW5qu7p#^8`)RC#XsibmICV6*i)Mf$cx* z`VqcW$v_QYKD-+Jj$#2tWe5V2qEC02152YJuUd=%2h_x!BVc>&RM(dZ;MU39%AUEEJ1>?fng3&JMX*g_$$!ewBF>kR%9=Xp zfxy3Hg4wjG570eb-isHOVrqU3i5TW7B=JX3tG&7Ukcb(x4{ef=7_tHHdh&bO5|OO) zu>Bt^Ws1oxrLLzHAzS2LN*=u4!r zSuaczfbJ_zrKHT_jpU;-0YFEYj3wpcT;6}o zm}uz9|1c9GX^v`1L)6b$M#a8kpQMAA1penPK(kLobDhV`Ml>1iN<0O`NbJ0QL1Kzj z=4F83Nwc_?{IYEKQii|RR#HPri0j7F;D$|>*Z%ih(bT$;tS`TXI$MizU6W5@`WJuX zVHFdii_ZL9Tue|uUE{MMlQ3JadEt^Y8X$-02HRgA6q2n!90xycnlXI--V)jDzg!Z% z{>R}*%3F2FaZh+0&sO!DB4*A2miam&(X%ftM! z_I>FD3s#WX-WWl?@Bh-qs>}|2)X@?>oo?MeI6M-TxO_ZKahm)+47&`o2tr|VI8NzR zG6NmZ%S2F!{u&W!MiaaIH*(5-2m>`}|+dlf%}IP-=(C zUlQjoPZ2|MWfsTv<6@Yyo6d$%&HRv{!CY3X3&`xyyX(Uuc}EXopgKT?v?nz3)YxJo zBR{~T&$L4|_}j~?>epavP}%M!e|Ed`K@nLGurtdt_YCQkCJYJg6&+hZn-tas1sfs= zadU7scpe+8A&?7Wg7yN4m6~X+?6q&snPpKrx|h>3WQhbf8O#_Y%TLd3#c5FqxgcVl zeDSE6d9@qGO4UOOwP(VA1O!WGX%c73K`s_5zs`jYBO7q+$4j_qyVKPl*x=v8v8%VD zT+pKGf`Dg^nTCp{xxURV3RlmLS03fh+(>rl7Fe9I{2jz!)*D%PPjP(0Y3^q>Od^Aa zZQbZ*?yKTrk2;I--w0;*8T%Nu%Y`qckSi9au_KZ|$+)Qx#Az4I=%E1X{ zH!i=(3@~zCueeH%C@u^<{3+2U7QkjUTGTL|>r*jtu03=2B~dNz8UXph`S_P!TIUCj z{@Q6ZlcFRbc^{0w+8O!}Xo^5cP3Vi~I9xZ~4_2*uDZ^#e! zdo8~wcG7P4@079)gi+z5{0A!&3UEablm|EcZ0pnnf9j&gQ{_8aURltAE!6czi2y>+ z0xMC^F2v*V&YNAX!GPEP#8f6OkUS)+Sn0!c@FELKO99ViWN#}wmVM!PdP(WXiSo*_ zth|>Z9u@IZC|2tFf_LQ_l5igVuiv(ukRrag&%Ct33b<(c9LBtP^ zVFPmqLQ6$D7gIex?7l4$p-y4;Dq<^FJKs7#r5;~bH@$Uh?sjhNS-{gcTkEZ`6hUJ$ zYkLrxg{V4|bSo}WB&hGXE=*QLZ}jV0rg?iPh8Q%sC*K3#d<#WJ_O?g0U{ESLYGac^ z@z~MZa=TB9gfW+_3UVqnE(+dUQ!l5VJ=S_YR5iu)V^q)>P%e5`-k1?YZn1e&v->Ob z&ViH}icltyU12rKx4!gNgIcuI{&kukGMvXqJnKm?(62E>^Jgu21SQXZ|9l*zU0}ZX zFPZj(Wpwp=OT~qNQC*Q&b#j;K40bP8`5*{r=xt&%5qvD)J>ukxKbaw_nOFUnnonXp<4g2@Xw{0Jtqm-s6D_RQbz9bY!+A#Z+CIs*o=afy1SU$R(j%z2r z_J8A2V+alhCr1vhpo;pHB3J3mP)XMFUKN!BRGRWtk9zIH9xn?zculA_UnaUz1$1EY zp*-RfLFCoe8<69#&X<;1fmIdMnpOeV>O=tD9JZqQ?bocb@Qy$|$hz7$>Zl(OT}778 zkJ7&H`BncZmKrFF6Y8|MoQD+Tt#j=BYAS3sE<`_XN+gxa@9^K66M6Rmk>l>YFl|rN z|C2-5_EM9n!AebqOS)wh)i7uCjbG!g(7qA0ZdU#wLh#ar!7l#}0+1=*Fp=(Eb6=Ab z!RBI-w7VEKhnlI{Tsm=Usw>Ype`3uMbPlRsA zOgYSGre){M&Ga~aJ*pjaZQL4dkM;~ZL*-}w-B!bP!iVV~3#sj5bD$&bX0|IK*S#Jw z)@%E>-S!(+hBnl}VN=R>A0EjUj>|A=_9yMp?9X4PB@-kTBJYPo?^nk(75PU|e5WFA zfmku>wnP8U{j+B*UZ;bIsy%_=>Yp6(66mzkouK<%G36dx64)^o?#D97>TQyFr3bZJ`J~(=xXkh{BK(%>HTWD(^?j@=l!~tT3b!kh`UVN zip=vkc-}m4@9R?JhN6}^dh;Fm40fA_M^hzbFE)&W!ZR`okG=#;ew-VD?(=P44ege8 zCS3a#k%kBE3ahrqT^$Yg*{p~T+NvPfPTbFll^%-yM&8IkI zc^8Uwae!n~h^lj-luT5S7A`jVAo<2jdQ~<(ShJWSrC>4vH<_t( z*&$h(1oj+#v`;-D2-ym2I2CFCyHBU}o1pQPR(lG6{u#G5icuSozbGS44?a@nPqj4!*td+E5xvVS2ReE$+cU)cUW*aUY%q03yQF5Yh z0jGu~%xK;(s-l7@S>+#RlC|;PBVr$%<-FlcTBXWBP;D*w9I+W5gN?OemtnXH8T{gF z2Ltnc=}kUba=*kpL;XX>^T)Exf(_XN8z+QFs%YMdH{yHgVTUWEOd%`zbgQ3dbtV&6 zs4%@Ffw<$VtZ=|y5w&+(DW*3K%-WS8x)T#+uc+I;DO4nH6&y|0Z0JVTcWLhJn@VTx zt9LTAbHOInOZj~l{;c=!B;0rUk($YP5%iCZA0~#e(yF9;MMc0o>hi9j#s=kShCdw=ME~Oz0jF&xg3@`%sI4 zKb~GWHgh#kUezBD%p}pxF+R}qvkjumwJT=l0!l=ip|aB1A}L&F%C5hccu<44&bFxk zuphO5T{=rKovLVHjs6X^etyv@iMb={@KfSZB=OS4T+}tZa43~!cm_<0P27O`MSKpTUqyW$X$4J*v zf`6?5Ig@4Elw#3+lQEf#=z;{4wav(SuUlGjtGRV_Jel?HMq|W7((&Z`&uZar@xG5T zY5J=AeYvH`d$9#Lv8O(!L~eXiOy0-NVXZX_8=aEuk1IlM z3o1~p@}r7-M>b<~+Mce$Pc22lE)gFsA%~w@mEP?*USfa$22c3Gt-wlp-1-frd!q25qIXHipyjG*qOT0rh>o3RNA}>_B(OZX{k;oxU$4Ir2?w z!|sgx)sd2tr0Zt<$X^VoJy(mQHlZ8-udd{MZCVYgZlI<4^SY6?<-8DP-~+L1F7R&7 zHT@&Ue;JA4Ya`5*&V;v?(5kYi5~i_m2bk&euSedw5GS$0kw=xQ2)fQjQ z!4-HR|MrY{ar~E!(xEE(;utP*h4kYmTx{bc-RSk~ybZiccl73Mm$$9)j(5r3Kl-Tt) z#Y57**ms>`mw_t+X-J}xWVzPK8yloe_v@ig+;>&~5IY-0df@KIPOe>W8Wm7*-m1bk zYd$A@=&nnk3nz|^w?x%4S6o$BvnB3er_V^tck-RppyEw!whgfJSs!ZS)`lQeu-&qG zfR3Fs{WaD8ZsK&(O;l^jdNVY|5VxNWFw~d?qRE=5$m7-?jWfB0qU55IB(xd7Hjl;(7rk;Eo4Eh}+VNSdfo8}y--Tu#C`|l>nxdJ#R$hcW%~tI}3P-`a zSr>?>7DdwPud2J%&hbkZ>tWTLMm-UMVmaO>D=itx5jiEaMfc~;Eh4+NTGdRKaBzJ$ zoAvd-n_%fdto+J6e<1bRpBMDJ28q-s==ayiY3AK0RmMx)CGK~mOnO`GhZk;gck|H; z`2G8_pipc;q6!{GV#>Wz20w&Ly>DvjJwV9(#W4ol-j0mLmL;a|V0r=a2XV zX_XAue`g-9)O@z@THt&nnm-w(C&1L+qq>!KtD1VcBU)j5QW!-}T<_nt`RdAWYYwkw zMAltn3XC2Vh`-}xGwPSdo0?r(k|*yX6ZqvfgreyEy-w3Z2N*I-MIIJ%ev8*r<_W$? z#3`Q~z-5&$eTgQ5%`#TMXNR7dc#jnpZFQ~Z_R>mJ3PUy9R3*zf9MAavC3JeWkXx!I z6H5`8YpS<5y<6HW8L0atQUA{^KWG@!N65zMbuVUzn)ny>*Tx65y(c%MQNl=FIra>Yg%8h0x=hpHCWZHNp`=PRI-6jv$b4fc&dJWxmO zi7z0)1M7g9g_~od=1K(46y!ti`H1FN{(w0;CLi?oo#REzPFDg17Vc>P`pTqnuKApM z3GREY#b^f&G&=(QGV}!{Ma9=>-iX1ix<~?4%m55%}>mWl~4U$;H{X;v#smK|2=#&BYRJn zo~j%!*6Xws@5B}z0q5~(-#wdJ#*y`<^L5X9r5|ev;bNQQ+`SA`b*QRtLL=}7t1oFt2JbEY@{l%AvDeg`jR^m_0h=NgpJtX0d38oV!czZ#7b9P*tq9IlwM zv1X=QIMBq!-uNZKa?%AfwTR5wz)dImyhw?N!G?4jkNRh8uSB$*rw>95FT^T=dI#?0G8Iy6Cznq>k;5!(~?LCU$ZSJ?62Pt9RZSOO)({z3eg&i|ccV4q+ z04FV)O%E)WuMGQMR0?Z+^wzb<@oFa=x+9yHjvd5P+6UkHSiF~3i_Tw>JqBW3E=J=7 z27E8veCzQ$X36Rtj%IV+Sn^&@(MJwy;G_tjRz_Mr>Nq~%?9+$ zr&Mfl-1cQ9ZRJrzFN{3jjpw4=&Q8O$p~%GX^qgVy z_PB75kAg<`z6a0zy+xaA0Cnt$JrwRr|2YP#UY8`PE6Hf+2iWP~pO+-{VJTtS;I}p- z5+f`O>!xgm%hfJDHR`h(HV7-Yjvu`31MLakl?$dp=q5ou5K7(mf-HZ0-zr5rG~TQG zq#$Nv1h)s~i`(EqM>04EKvM}i_ziFBjaTE@2VaJTnHDw3u9`~0EJ)0elW+jC+l>w7 zVc7_e;?Zo`>W~A$wIM<@yx|U|gbM^t%oViR*hS97Y{w* z{w#Z5%rDnh;Tx|z?l3BB=e&TSFPqo%7(zGwYc*B3L{x~{-F8q#voWP#;y5=}!s|V` z&+VPjwGwe8I>M12d_J9Gn$av5A)lk}HwdbZL+Tx_**;>uwNgJP%R7a^TS9&*&mXxJ z2irl%~D=Bs~^}k>rs9|4@F-2PDFtZ-dfpW1Nq0@(<<9gYgToE zttRD6e^UGn#kk(JTSnh?qRo8|QPdZ;yI>FXR1#C@M#RQhR|l@J*nWUkt&NMFG*jTB zi+;ui^EOM-snf+>u)qRiV_od{OJlg|`^B{#`OKfcFY^|`l9*6oIA?u?%Q>SlvmO9i zOBU4DpA>eiMA(XPrR$4xaU(DZu$w87DKR%eSi}KDv`){Nz-VQ3C;q$CTro3G4kZog z=15Vtfv0udcypbYwpM@Gabfk_DQSN-J>pwR6^CoO7tv=9#_RSv-MLkILMNQU7`Da3 zl;{u3QdED$VJl!X7tH%&6bH*s;LHD8exih%T6qZv;rlcDPYBRsAZ0(r+Z#!K9=HZb z3Z){=khrvrI+NKv05TV*CGXT0t`k%LD`{Dazqa2^vJdYD)bS>~>zpVY<#D9zGXtGw z*izK)8s(L-t4E{S1(G=`fl^n3T!I!Rziq>+01#Y8;wIyyN+b0dO3BUXFsuv|vNg%O zR2qrFz%;WQ+am5(@?WV=c53n>^wh+P5=|tA9ddVwfnlZ@Ww;h+`4`z8$*mDpv_jM{ z74MN8&;KMqPk0m~8fHlBVm>^CwG^Kq$S@N&uo!|Pnct0DY&>MP`=-+k9#Eu9cbTvu z`cOLK3TVVr@$4VKIuv#&fUCi7A-?l=k$PI2j%pgh*8mcd32{r5|2M36In7($ zzcL9?pKv2OoCLm*tu!aRk57(6@#Idz3zX+V9F8Mn0O2y^It|pQk$OAjjrV;NcOYE* zKPzOCL4r=`#+;&IHpB{O0+_Qq#rTB8A@OZ^?Ia5L)vQ`$wMB|!jv?e?e|O)ymA&zZ z4gn6OQcQc_iZhGd;Q}Lxb_)K^Fz#OG71g8%Bn~5SGX<2U$kJjlH3_@7G|-&TzwqsX z00@RFjnuJiI!$Q^g3DNbOeOArO61IW-o!=ebO^Vuo&tn4 zh`5tz*x2pemmL`M-NvawvQNV(3#`?vhI@(5YqX&^iPG0u=^yT3Wgu#;510L!5a8pP z6w?u-hKm-lo>96E+?7cCcp*vnQQuoi>&Ik5Ae&Kbn z%#Y%;&#*G7>)J>Jp)O`|fRwWYbzz_q34k74z6Z&dKI-Xmz|P;fM&Q0Aa&U<#4tE@X z4~kxe_X+giX-rU(KTRYszq~+qNLkPc7D-i|g=LN1w2=fg?7W*>t~Af~Sut2Bsn&Rj zAbqq6-YUF);`9>)sx{G{g-YjR6ada}oT9JmF9lN+aVd1MFZGFR#vgm3lDXY1;<*su zbMGw2o#*Rhk)8rNOqBHNiJg`~xVVWI`w+|la&LLMv_Wke5dVhiAf0YmY}}Ncvw=)- zV&q}eUncGr3Kb`>hoe@K6tiw3ZaQ5hdbyBRJiYpeil=EQ)uZrqS7gqZkNJe|3gPbk zy-+fp=8XPpc=TNdu~`tclb++p^8S%f{VB8NX-_-@yQcEjG=%uHxb~e;ZJ*_QJm5rM zOoI&c=ZrP&(N4R5&k3^`pPe!^&979zn-y z=298q$!*??nE0;}hdD;>P&Rv$-+4#q0edi9$K2))&BOq zHQMQ0_SHKw1XZoubYW`^sm;6e!tkvubPcTk3;Z$E{}eKl9FHv>SX+E#-lg!dragk1 z83C#yq4j!MvRAUvcN?GM^!j-M7d85GvUSjN3|eu{3OZT{UM#`%K!$pX{f>S1rt*}C zSM^+LB#__xms1tDwR~1q{%;!%%mGxto{HZZ7ZOs9Rich`nx4`Tl5n-(s`h1*5e7i) z)2x8%RpL1;Ym$F13*+`qNfYA*Ly(TO!yWZ!i`>&)3o|-R>V!r^HvTpH`E8>iMH3j7+1m_krJrHDBoB^O}!MP=Ae@ jWXk_U@c;dT89?ol<6kG*Wt*l;Q(B<(OfOcPcZm95Qv_Qy literal 0 HcmV?d00001 diff --git a/core/common/src/main/res/values/strings.xml b/core/common/src/main/res/values/strings.xml index 12775013..eae4d2ac 100644 --- a/core/common/src/main/res/values/strings.xml +++ b/core/common/src/main/res/values/strings.xml @@ -12,4 +12,19 @@ 픽을 담은 개수 전체 + + + 로그인 + Sign in with Google + Google Logo + + + 더 많은 기능을 이용하기 위해\n로그인이 필요합니다 + 담은 픽을 확인하기 위해\n로그인이 필요합니다 + 픽을 등록하기 위해\n로그인이 필요합니다 + 픽을 담기 위해\n로그인이 필요합니다 + 픽을 담기 위해\n로그인이 필요합니다 + 로그인이 필요합니다 + 취소 + 기기에 로그인된 구글 계정이 없습니다 diff --git a/core/picklist/src/main/java/com/squirtles/picklist/PickListViewModel.kt b/core/picklist/src/main/java/com/squirtles/picklist/PickListViewModel.kt index 1ac1d2cb..dca3b8f3 100644 --- a/core/picklist/src/main/java/com/squirtles/picklist/PickListViewModel.kt +++ b/core/picklist/src/main/java/com/squirtles/picklist/PickListViewModel.kt @@ -3,6 +3,10 @@ package com.squirtles.picklist import android.util.Log import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope +import com.squirtles.domain.picklist.FetchPickListUseCaseInterface +import com.squirtles.domain.picklist.GetPickListOrderUseCaseInterface +import com.squirtles.domain.picklist.RemovePickUseCaseInterface +import com.squirtles.domain.picklist.SavePickListOrderUseCaseInterface import com.squirtles.model.Order import com.squirtles.model.Pick import com.squirtles.user.usecase.GetCurrentUidUseCase diff --git a/data/applemusic/build.gradle.kts b/data/applemusic/build.gradle.kts index 00d7b581..a76d6520 100644 --- a/data/applemusic/build.gradle.kts +++ b/data/applemusic/build.gradle.kts @@ -10,6 +10,9 @@ android { dependencies { implementation(projects.domain.applemusic) + testImplementation(libs.junit) + androidTestImplementation(libs.bundles.test) + implementation(libs.androidx.paging.runtime) // Kotlinx Serialization diff --git a/data/applemusic/src/main/java/com/squirtles/applemusic/di/AppleMusicModule.kt b/data/applemusic/src/main/java/com/squirtles/applemusic/di/AppleMusicDiModule.kt similarity index 98% rename from data/applemusic/src/main/java/com/squirtles/applemusic/di/AppleMusicModule.kt rename to data/applemusic/src/main/java/com/squirtles/applemusic/di/AppleMusicDiModule.kt index 05d119ec..a6dac3fc 100644 --- a/data/applemusic/src/main/java/com/squirtles/applemusic/di/AppleMusicModule.kt +++ b/data/applemusic/src/main/java/com/squirtles/applemusic/di/AppleMusicDiModule.kt @@ -16,7 +16,7 @@ import javax.inject.Singleton @Module @InstallIn(SingletonComponent::class) -object AppleMusicModule{ +object AppleMusicDiModule{ private const val BASE_APPLE_MUSIC_URL = "https://api.music.apple.com/" diff --git a/data/build.gradle.kts b/data/build.gradle.kts deleted file mode 100644 index 3bf54053..00000000 --- a/data/build.gradle.kts +++ /dev/null @@ -1,33 +0,0 @@ -plugins { - alias(libs.plugins.musicroad.android.library) - alias(libs.plugins.musicroad.hilt) - alias(libs.plugins.kotlin.serialization) -} - -android { - namespace = "com.squirtles.data" -} - -dependencies { - implementation(projects.domain) - implementation(projects.core.buildconfig) - - testImplementation(libs.junit) - androidTestImplementation(libs.bundles.test) - - // Firebase - implementation(libs.bundles.firebase) - implementation(libs.geofire.android.common) - - // OkHttp - implementation(libs.bundles.network) - - // Kotlinx Serialization - implementation(libs.kotlinx.serialization.json) - - // Datastore - implementation(libs.androidx.datastore.preferences) - - // Paging - implementation(libs.androidx.paging.runtime) -} diff --git a/data/favorite/build.gradle.kts b/data/favorite/build.gradle.kts index 157879a7..75ba682e 100644 --- a/data/favorite/build.gradle.kts +++ b/data/favorite/build.gradle.kts @@ -9,8 +9,12 @@ android { dependencies { implementation(projects.data.firebase) + implementation(projects.domain.firebase) implementation(projects.domain.favorite) + testImplementation(libs.junit) + androidTestImplementation(libs.bundles.test) + // Kotlinx Serialization implementation(libs.kotlinx.serialization.json) diff --git a/data/favorite/src/main/java/com/squirtles/favorite/CloudFunctionHelper.kt b/data/favorite/src/main/java/com/squirtles/favorite/CloudFunctionHelper.kt index 0db5dae7..15e3431d 100644 --- a/data/favorite/src/main/java/com/squirtles/favorite/CloudFunctionHelper.kt +++ b/data/favorite/src/main/java/com/squirtles/favorite/CloudFunctionHelper.kt @@ -4,7 +4,7 @@ import android.util.Log import com.google.firebase.functions.FirebaseFunctions import com.google.firebase.functions.ktx.functions import com.google.firebase.ktx.Firebase -import com.squirtles.firebase.FirebaseException +import com.squirtles.domain.firebase.FirebaseException import com.squirtles.localproperties.LocalPropertyProvider import kotlinx.coroutines.tasks.await import javax.inject.Singleton diff --git a/data/favorite/src/main/java/com/squirtles/favorite/FirebaseFavoriteRepositoryImpl.kt b/data/favorite/src/main/java/com/squirtles/favorite/FirebaseFavoriteRepositoryImpl.kt index a1e025dc..9e9e523e 100644 --- a/data/favorite/src/main/java/com/squirtles/favorite/FirebaseFavoriteRepositoryImpl.kt +++ b/data/favorite/src/main/java/com/squirtles/favorite/FirebaseFavoriteRepositoryImpl.kt @@ -8,15 +8,15 @@ class FirebaseFavoriteRepositoryImpl @Inject constructor( private val favoriteDataSource: FirebaseFavoriteDataSource ) : FirebaseFavoriteRepository { - override suspend fun fetchIsFavorite(pickId: String, userId: String): Result { - return favoriteDataSource.fetchIsFavorite(pickId, userId) + override suspend fun fetchIsFavorite(pickId: String, uid: String): Result { + return favoriteDataSource.fetchIsFavorite(pickId, uid) } - override suspend fun createFavorite(pickId: String, userId: String): Result { - return favoriteDataSource.createFavorite(pickId, userId) + override suspend fun createFavorite(pickId: String, uid: String): Result { + return favoriteDataSource.createFavorite(pickId, uid) } - override suspend fun deleteFavorite(pickId: String, userId: String): Result { - return favoriteDataSource.deleteFavorite(pickId, userId) + override suspend fun deleteFavorite(pickId: String, uid: String): Result { + return favoriteDataSource.deleteFavorite(pickId, uid) } } diff --git a/data/favorite/src/main/java/com/squirtles/favorite/di/FirebaseFavoriteModule.kt b/data/favorite/src/main/java/com/squirtles/favorite/di/FavoriteDiModule.kt similarity index 97% rename from data/favorite/src/main/java/com/squirtles/favorite/di/FirebaseFavoriteModule.kt rename to data/favorite/src/main/java/com/squirtles/favorite/di/FavoriteDiModule.kt index bbca107a..08841af4 100644 --- a/data/favorite/src/main/java/com/squirtles/favorite/di/FirebaseFavoriteModule.kt +++ b/data/favorite/src/main/java/com/squirtles/favorite/di/FavoriteDiModule.kt @@ -14,7 +14,7 @@ import javax.inject.Singleton @Module @InstallIn(SingletonComponent::class) -object FirebaseFavoriteModule { +object FavoriteDiModule { @Provides @Singleton diff --git a/data/firebase/build.gradle.kts b/data/firebase/build.gradle.kts index d558630b..09697d6f 100644 --- a/data/firebase/build.gradle.kts +++ b/data/firebase/build.gradle.kts @@ -7,6 +7,11 @@ android { } dependencies { + implementation(projects.domain.firebase) + + testImplementation(libs.junit) + androidTestImplementation(libs.bundles.test) + // Firebase implementation(libs.firebase.firestore.ktx) implementation(libs.geofire.android.common) diff --git a/data/firebase/src/main/java/com/squirtles/firebase/BaseFirebaseDataSource.kt b/data/firebase/src/main/java/com/squirtles/firebase/BaseFirebaseDataSource.kt index 3d59deb5..cd03947e 100644 --- a/data/firebase/src/main/java/com/squirtles/firebase/BaseFirebaseDataSource.kt +++ b/data/firebase/src/main/java/com/squirtles/firebase/BaseFirebaseDataSource.kt @@ -7,9 +7,10 @@ import com.google.firebase.firestore.DocumentSnapshot import com.google.firebase.firestore.FirebaseFirestore import com.google.firebase.firestore.Query import com.google.firebase.firestore.QuerySnapshot +import com.squirtles.domain.firebase.FirebaseException import kotlinx.coroutines.tasks.await -open class BaseFirebaseDataSource( +open class BaseFirebaseDataSource( private val db: FirebaseFirestore ) { protected fun fetchCollection(collection: FirebaseCollections): CollectionReference = db.collection(collection.name) diff --git a/data/firebase/src/main/java/com/squirtles/firebase/FirebaseException.kt b/data/firebase/src/main/java/com/squirtles/firebase/FirebaseException.kt deleted file mode 100644 index 8fe72ad7..00000000 --- a/data/firebase/src/main/java/com/squirtles/firebase/FirebaseException.kt +++ /dev/null @@ -1,49 +0,0 @@ -package com.squirtles.firebase - -sealed class FirebaseException(override val message: String) : Exception() { - data class CreatedUserFailedException(override val message: String = "Failed to create user") : FirebaseException(message) - data class FetchUserFailedException(override val message: String = "Failed to fetch user") : FirebaseException(message) - data class UpdateUserFailedException(override val message: String = "Failed to update user info") : FirebaseException(message) - data class NoSuchPickException(override val message: String = "No such pick", val pickId: String) - : FirebaseException("message @$pickId") - data class NoSuchPickInRadiusException(override val message: String = "No such pick in area") : FirebaseException(message) - - data class NoSuchDocumentException( - override val message: String = "No such document", - val docId: String, - val collection: String = "" - ) : FirebaseException("$message @$docId in $collection") - - data class FetchDocumentFailedException( - override val message: String = "Failed to fetch document", - val collection: String = "" - ) : FirebaseException("$message in $collection") - - data class AddDocumentFailedException( - override val message: String = "Failed to add document", - val value: Any, - val collection: String = "" - ) : FirebaseException("$message $value in $collection") - - data class DeleteDocumentFailedException( - override val message: String = "Failed to delete document", - val docId: String, - val collection: String = "" - ): FirebaseException("$message @$docId in $collection") - - data class UpdateDocumentFailedException( - override val message: String = "Failed to update document", - val docId: String, - val collection: String = "" - ) : FirebaseException("$message @$docId in $collection") - - data class ExecuteQueryFailedException( - override val message: String = "Failed to execute query", - val collection: String = "" - ) : FirebaseException("$message in $collection") - - data class CloudFunctionFailedException( - override val message: String = "Failed to run cloud function", - val exceptionMessage: String - ) : FirebaseException("$message : $exceptionMessage") -} diff --git a/data/firebase/src/main/java/com/squirtles/firebase/FirebaseRepositoryUtils.kt b/data/firebase/src/main/java/com/squirtles/firebase/FirebaseRepositoryUtils.kt index 137e5fbe..1b562502 100644 --- a/data/firebase/src/main/java/com/squirtles/firebase/FirebaseRepositoryUtils.kt +++ b/data/firebase/src/main/java/com/squirtles/firebase/FirebaseRepositoryUtils.kt @@ -1,5 +1,7 @@ package com.squirtles.firebase +import com.squirtles.domain.firebase.FirebaseException + suspend fun handleResult( firebaseRepositoryException: FirebaseException, call: suspend () -> T? diff --git a/data/location/build.gradle.kts b/data/location/build.gradle.kts index 3b7287ee..dd94d86d 100644 --- a/data/location/build.gradle.kts +++ b/data/location/build.gradle.kts @@ -9,4 +9,7 @@ android { dependencies { implementation(projects.domain.location) + + testImplementation(libs.junit) + androidTestImplementation(libs.bundles.test) } diff --git a/data/location/src/main/java/com/squirtles/location/di/LocationModule.kt b/data/location/src/main/java/com/squirtles/location/di/LocationDiModule.kt similarity index 94% rename from data/location/src/main/java/com/squirtles/location/di/LocationModule.kt rename to data/location/src/main/java/com/squirtles/location/di/LocationDiModule.kt index 2856cad7..06931790 100644 --- a/data/location/src/main/java/com/squirtles/location/di/LocationModule.kt +++ b/data/location/src/main/java/com/squirtles/location/di/LocationDiModule.kt @@ -10,7 +10,7 @@ import javax.inject.Singleton @Module @InstallIn(SingletonComponent::class) -object LocationModule { +object LocationDiModule { @Provides @Singleton fun provideLocalLocationRepository(): LocalLocationRepository = diff --git a/data/order/build.gradle.kts b/data/order/build.gradle.kts index 0fd62d2a..cd3ac213 100644 --- a/data/order/build.gradle.kts +++ b/data/order/build.gradle.kts @@ -8,4 +8,7 @@ android { dependencies { implementation(projects.domain.order) + + testImplementation(libs.junit) + androidTestImplementation(libs.bundles.test) } diff --git a/data/order/src/main/java/com/squirtles/order/di/OrderModule.kt b/data/order/src/main/java/com/squirtles/order/di/OrderDiModule.kt similarity index 95% rename from data/order/src/main/java/com/squirtles/order/di/OrderModule.kt rename to data/order/src/main/java/com/squirtles/order/di/OrderDiModule.kt index 89261b1e..6d2fb0dd 100644 --- a/data/order/src/main/java/com/squirtles/order/di/OrderModule.kt +++ b/data/order/src/main/java/com/squirtles/order/di/OrderDiModule.kt @@ -10,7 +10,7 @@ import javax.inject.Singleton @Module @InstallIn(SingletonComponent::class) -object OrderModule { +object OrderDiModule { @Provides @Singleton fun provideLocalPickListOrderRepository(): LocalPickListOrderRepository = diff --git a/data/pick/build.gradle.kts b/data/pick/build.gradle.kts index 800d41ca..11f2531f 100644 --- a/data/pick/build.gradle.kts +++ b/data/pick/build.gradle.kts @@ -10,6 +10,9 @@ dependencies { implementation(projects.domain.pick) implementation(projects.data.firebase) + testImplementation(libs.junit) + androidTestImplementation(libs.bundles.test) + // Firebase implementation(libs.firebase.firestore.ktx) implementation(libs.geofire.android.common) diff --git a/data/pick/src/main/java/com/squirtles/pick/FirebasePickRepositoryImpl.kt b/data/pick/src/main/java/com/squirtles/pick/FirebasePickRepositoryImpl.kt index e71c8ab5..885b7f47 100644 --- a/data/pick/src/main/java/com/squirtles/pick/FirebasePickRepositoryImpl.kt +++ b/data/pick/src/main/java/com/squirtles/pick/FirebasePickRepositoryImpl.kt @@ -1,8 +1,6 @@ package com.squirtles.pick -import android.util.Log -import com.squirtles.firebase.FirebaseException -import com.squirtles.firebase.handleResult +import com.squirtles.domain.pick.FirebasePickRepository import com.squirtles.firebase.model.toFirebasePick import com.squirtles.firebase.model.toPick import com.squirtles.model.Pick @@ -23,9 +21,9 @@ class FirebasePickRepositoryImpl @Inject constructor( return pickDataSource.deletePick(pickId, userId) } - override suspend fun fetchPick(pickID: String): Result { + override suspend fun fetchPick(pickId: String): Result { return runCatching { - val firebasePick = pickDataSource.fetchPick(pickID).getOrThrow() + val firebasePick = pickDataSource.fetchPick(pickId).getOrThrow() firebasePick.toPick() } } diff --git a/data/pick/src/main/java/com/squirtles/pick/di/PickModule.kt b/data/pick/src/main/java/com/squirtles/pick/di/PickDiModule.kt similarity index 91% rename from data/pick/src/main/java/com/squirtles/pick/di/PickModule.kt rename to data/pick/src/main/java/com/squirtles/pick/di/PickDiModule.kt index 8b5cc418..7a1a9d69 100644 --- a/data/pick/src/main/java/com/squirtles/pick/di/PickModule.kt +++ b/data/pick/src/main/java/com/squirtles/pick/di/PickDiModule.kt @@ -2,7 +2,7 @@ package com.squirtles.pick.di import com.squirtles.pick.FirebasePickDataSource import com.squirtles.pick.FirebasePickDataSourceImpl -import com.squirtles.pick.FirebasePickRepository +import com.squirtles.domain.pick.FirebasePickRepository import com.squirtles.pick.FirebasePickRepositoryImpl import com.google.firebase.firestore.FirebaseFirestore import dagger.Module @@ -13,7 +13,7 @@ import javax.inject.Singleton @Module @InstallIn(SingletonComponent::class) -object PickModule { +object PickDiModule { @Provides @Singleton diff --git a/data/src/main/java/com/squirtles/data/applemusic/AppleMusicDataSourceImpl.kt b/data/src/main/java/com/squirtles/data/applemusic/AppleMusicDataSourceImpl.kt deleted file mode 100644 index fdf02cec..00000000 --- a/data/src/main/java/com/squirtles/data/applemusic/AppleMusicDataSourceImpl.kt +++ /dev/null @@ -1,62 +0,0 @@ -package com.squirtles.data.applemusic - -import androidx.paging.Pager -import androidx.paging.PagingConfig -import androidx.paging.PagingData -import com.squirtles.data.applemusic.SearchSongsPagingSource.Companion.SEARCH_PAGE_SIZE -import com.squirtles.data.applemusic.api.AppleMusicApi -import com.squirtles.data.applemusic.model.SearchResponse -import com.squirtles.data.applemusic.model.toMusicVideo -import com.squirtles.domain.applemusic.AppleMusicRemoteDataSource -import com.squirtles.domain.model.MusicVideo -import com.squirtles.domain.model.Song -import kotlinx.coroutines.flow.Flow -import retrofit2.Response -import javax.inject.Inject - -class AppleMusicDataSourceImpl @Inject constructor( - private val appleMusicApi: AppleMusicApi -) : AppleMusicRemoteDataSource { - - override fun searchSongs(searchText: String): Flow> { - return Pager( - config = PagingConfig( - pageSize = SEARCH_PAGE_SIZE, - enablePlaceholders = false - ), - pagingSourceFactory = { SearchSongsPagingSource(appleMusicApi, searchText) } - ).flow - } - - override suspend fun searchMusicVideos(searchText: String): List { - val searchResult = requestSearchApi(searchText, "music-videos") - return searchResult.results.musicVideos?.data?.map { - it.toMusicVideo() - } ?: emptyList() - } - - private suspend fun requestSearchApi(searchText: String, types: String): SearchResponse { - return checkResponse( - appleMusicApi.searchSongs( - storefront = DEFAULT_STOREFRONT, - types = types, - term = searchText, - limit = 10, - offset = "0", - ) - ) - } - - private fun checkResponse(response: Response): T { - if (response.isSuccessful) { - return requireNotNull(response.body()) - } else { - val errorBody = requireNotNull(response.errorBody()?.string()) - throw Exception(errorBody) - } - } - - companion object { - const val DEFAULT_STOREFRONT = "kr" - } -} diff --git a/data/src/main/java/com/squirtles/data/applemusic/AppleMusicRepositoryImpl.kt b/data/src/main/java/com/squirtles/data/applemusic/AppleMusicRepositoryImpl.kt deleted file mode 100644 index 9247dbd3..00000000 --- a/data/src/main/java/com/squirtles/data/applemusic/AppleMusicRepositoryImpl.kt +++ /dev/null @@ -1,36 +0,0 @@ -package com.squirtles.data.applemusic - -import androidx.paging.PagingData -import com.squirtles.domain.applemusic.AppleMusicRemoteDataSource -import com.squirtles.domain.model.MusicVideo -import com.squirtles.domain.model.Song -import com.squirtles.domain.applemusic.AppleMusicException -import com.squirtles.domain.applemusic.AppleMusicRepository -import kotlinx.coroutines.flow.Flow -import javax.inject.Inject - -class AppleMusicRepositoryImpl @Inject constructor( - private val appleMusicDataSource: AppleMusicRemoteDataSource -) : AppleMusicRepository { - - override fun searchSongs(searchText: String): Flow> = appleMusicDataSource.searchSongs(searchText) - - override suspend fun searchSongById(songId: String): Result { - TODO("Not yet implemented") - } - - override suspend fun searchMusicVideos(searchText: String): Result> { - return handleResult(AppleMusicException.NotFoundException()) { - appleMusicDataSource.searchMusicVideos(searchText).ifEmpty { null } - } - } - - private suspend fun handleResult( - appleMusicException: AppleMusicException, - call: suspend () -> T? - ): Result { - return runCatching { - call() ?: throw appleMusicException - } - } -} diff --git a/data/src/main/java/com/squirtles/data/applemusic/SearchSongsPagingSource.kt b/data/src/main/java/com/squirtles/data/applemusic/SearchSongsPagingSource.kt deleted file mode 100644 index 72c6aa6b..00000000 --- a/data/src/main/java/com/squirtles/data/applemusic/SearchSongsPagingSource.kt +++ /dev/null @@ -1,64 +0,0 @@ -package com.squirtles.data.applemusic - -import android.util.Log -import androidx.paging.PagingSource -import androidx.paging.PagingState -import com.squirtles.data.applemusic.api.AppleMusicApi -import com.squirtles.data.applemusic.model.toSong -import com.squirtles.domain.model.Song -import retrofit2.HttpException -import java.io.IOException - -class SearchSongsPagingSource( - private val appleMusicApi: AppleMusicApi, - private val searchText: String -) : PagingSource() { - - override suspend fun load(params: LoadParams): LoadResult { - val pageIndex = params.key ?: 0 - return try { - val response = appleMusicApi.searchSongs( - storefront = DEFAULT_STOREFRONT, - types = SEARCH_TYPES, - term = searchText, - limit = SEARCH_PAGE_SIZE, - offset = (pageIndex * SEARCH_PAGE_SIZE).toString() - ) - - if (response.isSuccessful) { - val songs = response.body()?.results?.songs?.data - ?.map { it.toSong() } - ?: emptyList() - - Log.d("SearchSongsPagingSource", "songs: $songs") - - val nextKey = if (response.body()?.results?.songs?.next == null) null else pageIndex + 1 - LoadResult.Page( - data = songs, - prevKey = if (pageIndex == 0) null else pageIndex - 1, - nextKey = nextKey - ) - } else { - val errorBody = response.errorBody()?.string() - throw Exception(errorBody ?: "Unknown error occurred") - } - } catch (exception: IOException) { - LoadResult.Error(exception) - } catch (exception: HttpException) { - LoadResult.Error(exception) - } - } - - override fun getRefreshKey(state: PagingState): Int? { - return state.anchorPosition?.let { anchorPosition -> - state.closestPageToPosition(anchorPosition)?.prevKey?.plus(1) - ?: state.closestPageToPosition(anchorPosition)?.nextKey?.minus(1) - } - } - - companion object { - const val DEFAULT_STOREFRONT = "kr" - const val SEARCH_TYPES = "songs" - const val SEARCH_PAGE_SIZE = 10 - } -} diff --git a/data/src/main/java/com/squirtles/data/applemusic/api/AppleMusicApi.kt b/data/src/main/java/com/squirtles/data/applemusic/api/AppleMusicApi.kt deleted file mode 100644 index 03eb3dff..00000000 --- a/data/src/main/java/com/squirtles/data/applemusic/api/AppleMusicApi.kt +++ /dev/null @@ -1,18 +0,0 @@ -package com.squirtles.data.applemusic.api - -import com.squirtles.data.applemusic.model.SearchResponse -import retrofit2.Response -import retrofit2.http.GET -import retrofit2.http.Path -import retrofit2.http.Query - -interface AppleMusicApi { - @GET("v1/catalog/{storefront}/search") - suspend fun searchSongs( - @Path("storefront") storefront: String, - @Query("types") types: String, - @Query("term") term: String, - @Query("limit") limit: Int, - @Query("offset") offset: String - ): Response -} diff --git a/data/src/main/java/com/squirtles/data/applemusic/di/AppleMusicDi.kt b/data/src/main/java/com/squirtles/data/applemusic/di/AppleMusicDi.kt deleted file mode 100644 index b824fef5..00000000 --- a/data/src/main/java/com/squirtles/data/applemusic/di/AppleMusicDi.kt +++ /dev/null @@ -1,46 +0,0 @@ -package com.squirtles.data.applemusic.di - -import com.squirtles.data.applemusic.AppleMusicDataSourceImpl -import com.squirtles.data.applemusic.AppleMusicRepositoryImpl -import com.squirtles.data.applemusic.api.AppleMusicApi -import com.squirtles.domain.applemusic.AppleMusicRemoteDataSource -import com.squirtles.domain.applemusic.AppleMusicRepository -import dagger.Module -import dagger.Provides -import dagger.hilt.InstallIn -import dagger.hilt.components.SingletonComponent -import okhttp3.OkHttpClient -import retrofit2.Converter -import retrofit2.Retrofit -import javax.inject.Singleton - -@Module -@InstallIn(SingletonComponent::class) -object AppleMusicModule{ - - private const val BASE_APPLE_MUSIC_URL = "https://api.music.apple.com/" - - @Provides - @Singleton - fun provideAppleMusicApi( - appleOkHttpClient: OkHttpClient, - converterFactory: Converter.Factory, - ): AppleMusicApi { - return Retrofit.Builder() - .baseUrl(BASE_APPLE_MUSIC_URL) - .addConverterFactory(converterFactory) - .client(appleOkHttpClient) - .build() - .create(AppleMusicApi::class.java) - } - - @Provides - @Singleton - fun provideAppleMusicRepository(appleMusicDataSource: AppleMusicRemoteDataSource): AppleMusicRepository = - AppleMusicRepositoryImpl(appleMusicDataSource) - - @Provides - @Singleton - fun provideAppleMusicDataSource(api: AppleMusicApi): AppleMusicRemoteDataSource = - AppleMusicDataSourceImpl(api) -} diff --git a/data/src/main/java/com/squirtles/data/applemusic/model/AppleMusicMapper.kt b/data/src/main/java/com/squirtles/data/applemusic/model/AppleMusicMapper.kt deleted file mode 100644 index dd6e07c6..00000000 --- a/data/src/main/java/com/squirtles/data/applemusic/model/AppleMusicMapper.kt +++ /dev/null @@ -1,36 +0,0 @@ -package com.squirtles.data.applemusic.model - -import androidx.core.graphics.toColorInt -import com.squirtles.domain.model.MusicVideo -import com.squirtles.domain.model.Song -import java.time.LocalDate -import java.time.format.DateTimeFormatter - -internal fun Data.toSong(): Song = Song( - id = id, - songName = this.attributes.songName, - artistName = this.attributes.artistName, - albumName = this.attributes.albumName.toString(), - imageUrl = this.attributes.artwork.url, - genreNames = this.attributes.genreNames, - bgColor = "#${this.attributes.artwork.bgColor}".toColorInt(), - externalUrl = this.attributes.externalUrl, - previewUrl = this.attributes.previews[0].url.toString(), -) - -internal fun Data.toMusicVideo(): MusicVideo = MusicVideo( - id = id, - songName = this.attributes.songName, - artistName = this.attributes.artistName, - albumName = this.attributes.albumName.toString(), - releaseDate = this.attributes.releaseDate?.toLocalDate() ?: DEFAULT_DATE, - previewUrl = this.attributes.previews[0].url.toString(), - thumbnailUrl = this.attributes.previews[0].artwork?.url.toString() -) - -private fun String.toLocalDate(): LocalDate { - val formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd") - return LocalDate.parse(this, formatter) -} - -private val DEFAULT_DATE = LocalDate.of(2000, 1, 1) diff --git a/data/src/main/java/com/squirtles/data/applemusic/model/Artwork.kt b/data/src/main/java/com/squirtles/data/applemusic/model/Artwork.kt deleted file mode 100644 index ccdcc5e2..00000000 --- a/data/src/main/java/com/squirtles/data/applemusic/model/Artwork.kt +++ /dev/null @@ -1,11 +0,0 @@ -package com.squirtles.data.applemusic.model - -import kotlinx.serialization.Serializable - -@Serializable -data class Artwork( - val width: Int, - val height: Int, - val url: String, - val bgColor: String? = null -) diff --git a/data/src/main/java/com/squirtles/data/applemusic/model/Attributes.kt b/data/src/main/java/com/squirtles/data/applemusic/model/Attributes.kt deleted file mode 100644 index ecd71865..00000000 --- a/data/src/main/java/com/squirtles/data/applemusic/model/Attributes.kt +++ /dev/null @@ -1,16 +0,0 @@ -package com.squirtles.data.applemusic.model - -import kotlinx.serialization.SerialName -import kotlinx.serialization.Serializable - -@Serializable -data class Attributes( - @SerialName("name") val songName: String, - @SerialName("artistName") val artistName: String, - @SerialName("albumName") val albumName: String? = null, - @SerialName("releaseDate") val releaseDate: String? = null, - @SerialName("genreNames") val genreNames: List, - @SerialName("artwork") val artwork: Artwork, - @SerialName("url") val externalUrl: String, - @SerialName("previews") val previews: List -) diff --git a/data/src/main/java/com/squirtles/data/applemusic/model/Data.kt b/data/src/main/java/com/squirtles/data/applemusic/model/Data.kt deleted file mode 100644 index 5d8dcf82..00000000 --- a/data/src/main/java/com/squirtles/data/applemusic/model/Data.kt +++ /dev/null @@ -1,9 +0,0 @@ -package com.squirtles.data.applemusic.model - -import kotlinx.serialization.Serializable - -@Serializable -data class Data( - val id: String, - val attributes: Attributes, -) diff --git a/data/src/main/java/com/squirtles/data/applemusic/model/MusicVideoResponse.kt b/data/src/main/java/com/squirtles/data/applemusic/model/MusicVideoResponse.kt deleted file mode 100644 index 1dc83d54..00000000 --- a/data/src/main/java/com/squirtles/data/applemusic/model/MusicVideoResponse.kt +++ /dev/null @@ -1,9 +0,0 @@ -package com.squirtles.data.applemusic.model - -import kotlinx.serialization.Serializable - -@Serializable -data class MusicVideoResponse( - val data: List, -) - diff --git a/data/src/main/java/com/squirtles/data/applemusic/model/Preview.kt b/data/src/main/java/com/squirtles/data/applemusic/model/Preview.kt deleted file mode 100644 index 2253cbc9..00000000 --- a/data/src/main/java/com/squirtles/data/applemusic/model/Preview.kt +++ /dev/null @@ -1,10 +0,0 @@ -package com.squirtles.data.applemusic.model - -import kotlinx.serialization.Serializable - -@Serializable -data class Preview( - val url: String? = null, - val hlsUrl: String? = null, - val artwork: Artwork? = null, -) diff --git a/data/src/main/java/com/squirtles/data/applemusic/model/Results.kt b/data/src/main/java/com/squirtles/data/applemusic/model/Results.kt deleted file mode 100644 index 5183ae97..00000000 --- a/data/src/main/java/com/squirtles/data/applemusic/model/Results.kt +++ /dev/null @@ -1,10 +0,0 @@ -package com.squirtles.data.applemusic.model - -import kotlinx.serialization.SerialName -import kotlinx.serialization.Serializable - -@Serializable -data class Results( - @SerialName("songs") val songs: Songs? = null, - @SerialName("music-videos") val musicVideos: MusicVideoResponse? = null -) diff --git a/data/src/main/java/com/squirtles/data/applemusic/model/SearchResponse.kt b/data/src/main/java/com/squirtles/data/applemusic/model/SearchResponse.kt deleted file mode 100644 index e184087e..00000000 --- a/data/src/main/java/com/squirtles/data/applemusic/model/SearchResponse.kt +++ /dev/null @@ -1,8 +0,0 @@ -package com.squirtles.data.applemusic.model - -import kotlinx.serialization.Serializable - -@Serializable -data class SearchResponse( - val results: Results, -) diff --git a/data/src/main/java/com/squirtles/data/applemusic/model/Songs.kt b/data/src/main/java/com/squirtles/data/applemusic/model/Songs.kt deleted file mode 100644 index f4fba366..00000000 --- a/data/src/main/java/com/squirtles/data/applemusic/model/Songs.kt +++ /dev/null @@ -1,9 +0,0 @@ -package com.squirtles.data.applemusic.model - -import kotlinx.serialization.Serializable - -@Serializable -data class Songs( - val next: String? = null, - val data: List, -) diff --git a/data/src/main/java/com/squirtles/data/favorite/CloudFunctionHelper.kt b/data/src/main/java/com/squirtles/data/favorite/CloudFunctionHelper.kt deleted file mode 100644 index e1951bea..00000000 --- a/data/src/main/java/com/squirtles/data/favorite/CloudFunctionHelper.kt +++ /dev/null @@ -1,34 +0,0 @@ -package com.squirtles.data.favorite - -import android.util.Log -import com.google.firebase.functions.FirebaseFunctions -import com.google.firebase.functions.ktx.functions -import com.google.firebase.ktx.Firebase -import com.squirtles.data.firebase.FirebaseException -import com.squirtles.localproperties.LocalPropertyProvider -import kotlinx.coroutines.tasks.await -import javax.inject.Singleton - -@Singleton -class CloudFunctionHelper { - private val functions: FirebaseFunctions = Firebase.functions - - suspend fun updateFavoriteCount(pickId: String): Result { - return runCatching { - val data = hashMapOf("pickId" to pickId) - val result = functions - .getHttpsCallable(LocalPropertyProvider.httpsCallable) - .call(data) - .await() - - // 성공 메시지 반환 - val message = result.getData()?.let { - (it as? Map<*, *>)?.get("message") as? String ?: "Function executed successfully" - } ?: "No message in response" - message - }.onFailure { - Log.d("CloudFunctionHelper", "Error updating favorite count: ${it.message}") - throw FirebaseException.CloudFunctionFailedException(exceptionMessage = it.message.toString()) - } - } -} diff --git a/data/src/main/java/com/squirtles/data/favorite/FirebaseFavoriteDataSourceImpl.kt b/data/src/main/java/com/squirtles/data/favorite/FirebaseFavoriteDataSourceImpl.kt deleted file mode 100644 index 12bd51a2..00000000 --- a/data/src/main/java/com/squirtles/data/favorite/FirebaseFavoriteDataSourceImpl.kt +++ /dev/null @@ -1,71 +0,0 @@ -package com.squirtles.data.favorite - -import android.util.Log -import com.google.firebase.firestore.FirebaseFirestore -import com.google.firebase.firestore.QuerySnapshot -import com.squirtles.data.favorite.model.FirebaseFavorite -import com.squirtles.data.firebase.BaseFirebaseDataSource -import com.squirtles.data.firebase.FirebaseCollections -import com.squirtles.data.firebase.FirebaseDocumentFields -import com.squirtles.domain.favorite.FirebaseFavoriteDataSource -import javax.inject.Inject -import javax.inject.Singleton - -@Singleton -class FirebaseFavoriteDataSourceImpl @Inject constructor( - db: FirebaseFirestore, - private val cloudFunctionHelper: CloudFunctionHelper -) : BaseFirebaseDataSource(db), FirebaseFavoriteDataSource { - - override suspend fun fetchIsFavorite(pickId: String, userId: String): Result { - return runCatching { - val favoriteDocument = queryFavoriteByPickIdAndUserId(pickId, userId).getOrThrow() - favoriteDocument.isEmpty.not() - } - } - - override suspend fun createFavorite(pickId: String, userId: String): Result { - val firebaseFavorite = FirebaseFavorite( - pickId = pickId, - userId = userId - ) - return runCatching { - val addResult = addDocument(FirebaseCollections.Favorites, firebaseFavorite) - val functionResultMessage = updateFavoriteCount(pickId).getOrThrow() - Log.d(TAG_LOG, "Function result message: $functionResultMessage") // XXX - - addResult.getOrThrow().id - } - } - - override suspend fun deleteFavorite(pickId: String, userId: String): Result { - return runCatching { - val favoriteDocRef = queryFavoriteByPickIdAndUserId(pickId, userId).getOrThrow().documents.first().reference - - deleteDocument(favoriteDocRef) - - val functionResultMessage = updateFavoriteCount(pickId).getOrThrow() - - Log.d(TAG_LOG, "Function result message: $functionResultMessage") // XXX - - favoriteDocRef.id - }.onFailure { exception -> - Log.w(TAG_LOG, "Error deleting favorite document", exception) - } - } - - private suspend fun queryFavoriteByPickIdAndUserId(pickId: String, userId: String): Result = - queryDocumentsEquals( - collection = FirebaseCollections.Favorites, - fields = listOf(FirebaseDocumentFields.PickId, FirebaseDocumentFields.UserId), - values = listOf(pickId, userId) - ) - - private suspend fun updateFavoriteCount(pickId: String): Result { - return cloudFunctionHelper.updateFavoriteCount(pickId) - } - - companion object { - private const val TAG_LOG = "FirebaseFavoriteDataSourceImpl" - } -} diff --git a/data/src/main/java/com/squirtles/data/favorite/FirebaseFavoriteRepositoryImpl.kt b/data/src/main/java/com/squirtles/data/favorite/FirebaseFavoriteRepositoryImpl.kt deleted file mode 100644 index f09e3354..00000000 --- a/data/src/main/java/com/squirtles/data/favorite/FirebaseFavoriteRepositoryImpl.kt +++ /dev/null @@ -1,24 +0,0 @@ -package com.squirtles.data.favorite - -import com.squirtles.domain.favorite.FirebaseFavoriteDataSource -import com.squirtles.domain.favorite.FirebaseFavoriteRepository -import javax.inject.Inject -import javax.inject.Singleton - -@Singleton -class FirebaseFavoriteRepositoryImpl @Inject constructor( - private val favoriteDataSource: FirebaseFavoriteDataSource -) : FirebaseFavoriteRepository { - - override suspend fun fetchIsFavorite(pickId: String, userId: String): Result { - return favoriteDataSource.fetchIsFavorite(pickId, userId) - } - - override suspend fun createFavorite(pickId: String, userId: String): Result { - return favoriteDataSource.createFavorite(pickId, userId) - } - - override suspend fun deleteFavorite(pickId: String, userId: String): Result { - return favoriteDataSource.deleteFavorite(pickId, userId) - } -} diff --git a/data/src/main/java/com/squirtles/data/favorite/di/FirebaseFavoriteDi.kt b/data/src/main/java/com/squirtles/data/favorite/di/FirebaseFavoriteDi.kt deleted file mode 100644 index e31420bd..00000000 --- a/data/src/main/java/com/squirtles/data/favorite/di/FirebaseFavoriteDi.kt +++ /dev/null @@ -1,32 +0,0 @@ -package com.squirtles.data.favorite.di - -import com.google.firebase.firestore.FirebaseFirestore -import com.squirtles.data.favorite.FirebaseFavoriteDataSourceImpl -import com.squirtles.data.favorite.FirebaseFavoriteRepositoryImpl -import com.squirtles.data.favorite.CloudFunctionHelper -import com.squirtles.domain.favorite.FirebaseFavoriteDataSource -import com.squirtles.domain.favorite.FirebaseFavoriteRepository -import dagger.Module -import dagger.Provides -import dagger.hilt.InstallIn -import dagger.hilt.components.SingletonComponent -import javax.inject.Singleton - -@Module -@InstallIn(SingletonComponent::class) -object FirebaseFavoriteDi { - - @Provides - @Singleton - fun provideFirebaseFavoriteRepository(firebaseFavoriteDataSource: FirebaseFavoriteDataSource): FirebaseFavoriteRepository = - FirebaseFavoriteRepositoryImpl(firebaseFavoriteDataSource) - - @Provides - @Singleton - fun provideFirebaseFavoriteDataSource(db: FirebaseFirestore, cloudFunctionHelper: CloudFunctionHelper): FirebaseFavoriteDataSource = - FirebaseFavoriteDataSourceImpl(db, cloudFunctionHelper) - - @Provides - @Singleton - fun provideCloudFunctionHelper(): CloudFunctionHelper = CloudFunctionHelper() -} diff --git a/data/src/main/java/com/squirtles/data/favorite/model/FirebaseFavorite.kt b/data/src/main/java/com/squirtles/data/favorite/model/FirebaseFavorite.kt deleted file mode 100644 index 85c11d87..00000000 --- a/data/src/main/java/com/squirtles/data/favorite/model/FirebaseFavorite.kt +++ /dev/null @@ -1,10 +0,0 @@ -package com.squirtles.data.favorite.model - -import com.google.firebase.Timestamp -import com.google.firebase.firestore.ServerTimestamp - -data class FirebaseFavorite( - val pickId: String? = null, - val userId: String? = null, - @ServerTimestamp val addedAt: Timestamp? = null, -) diff --git a/data/src/main/java/com/squirtles/data/firebase/BaseFirebaseDataSource.kt b/data/src/main/java/com/squirtles/data/firebase/BaseFirebaseDataSource.kt deleted file mode 100644 index 91941c7d..00000000 --- a/data/src/main/java/com/squirtles/data/firebase/BaseFirebaseDataSource.kt +++ /dev/null @@ -1,130 +0,0 @@ -package com.squirtles.data.firebase - -import android.util.Log -import com.google.firebase.firestore.CollectionReference -import com.google.firebase.firestore.DocumentReference -import com.google.firebase.firestore.DocumentSnapshot -import com.google.firebase.firestore.FirebaseFirestore -import com.google.firebase.firestore.Query -import com.google.firebase.firestore.QuerySnapshot -import kotlinx.coroutines.tasks.await - -open class BaseFirebaseDataSource( - private val db: FirebaseFirestore -) { - protected fun fetchCollection(collection: FirebaseCollections): CollectionReference = db.collection(collection.name) - - protected fun fetchDocumentReference(collection: FirebaseCollections, documentId: String): DocumentReference = - fetchCollection(collection).document(documentId) - - protected suspend fun fetchDocumentSnapshot(collection: FirebaseCollections, documentId: String): Result { - return runCatching { - fetchCollection(collection).document(documentId).get().await().ifNotExist { - throw FirebaseException.NoSuchDocumentException(docId = documentId, collection = collection.name) - } - }.onFailure { - Log.e("FirebaseDataSource", "Failed to fetch document snapshot", it) - throw FirebaseException.FetchDocumentFailedException(collection = collection.name) - } - } - - protected suspend fun queryDocumentsEquals( - collection: FirebaseCollections, - fields: List, - values: List - ): Result { - return runCatching { - var query: Query = fetchCollection(collection) - fields.forEachIndexed { index, field -> - query = query.whereEqualTo(field.name, values[index]) - } - query.get().await() - }.onFailure { - Log.e("FirebaseDataSource", "Failed to query documents", it) - throw FirebaseException.ExecuteQueryFailedException(collection = collection.name) - } - } - - protected suspend fun queryDocumentsInRange( - collection: FirebaseCollections, - field: FirebaseDocumentFields, - start: String, - end: String - ): Result { - return runCatching { - fetchCollection(collection) - .whereGreaterThanOrEqualTo(field.name, start) - .whereLessThanOrEqualTo(field.name, end) - .get() - .await() - }.onFailure { - Log.e("FirebaseDataSource", "Failed to create query for range", it) - throw FirebaseException.ExecuteQueryFailedException(collection = collection.name) - } - } - - protected suspend fun updateDocument( - collection: FirebaseCollections, - documentId: String, - field: FirebaseDocumentFields, - value: Any - ): Result { - return runCatching { - fetchDocumentReference(collection, documentId).update(field.name, value).await() - }.onFailure { - Log.e("FirebaseDataSource", "Failed to update document", it) - throw FirebaseException.UpdateDocumentFailedException(docId = documentId, collection = collection.name) - } - } - - protected suspend fun updateDocument( - collection: FirebaseCollections, - documentReference: DocumentReference, - field: FirebaseDocumentFields, - value: Any - ): Result { - return runCatching { - documentReference.update(field.name, value).await() - }.onFailure { - Log.e("FirebaseDataSource", "Failed to update document", it) - throw FirebaseException.UpdateDocumentFailedException(docId = documentReference.id, collection = collection.name) - } - } - - protected suspend fun setDocument(collection: FirebaseCollections, docId:String, value: Any): Result { - return runCatching { - fetchDocumentReference(collection, docId).set(value).await() - }.onFailure { - Log.e("FirebaseDataSource", "Failed to set document", it) - throw FirebaseException.AddDocumentFailedException(value = value, collection = collection.name) - } - } - - protected suspend fun addDocument(collection: FirebaseCollections, value: Any): Result { - return runCatching { - fetchCollection(collection).add(value).await() - }.onFailure { - Log.e("FirebaseDataSource", "Failed to add document", it) - throw FirebaseException.AddDocumentFailedException(value = value, collection = collection.name) - } - } - - protected suspend fun deleteDocument(document: DocumentReference, collectionName: String = ""): Result { - return runCatching { - document.delete().await() - }.onFailure { exception -> - Log.e("FirebaseDataSource", "Error deleting favorite document with ID: ${document.id}", exception) - throw FirebaseException.DeleteDocumentFailedException(docId = document.id, collection = collectionName) - } - } - - protected suspend fun deleteDocument(collection: FirebaseCollections, documentId: String): Result { - val doc = fetchDocumentReference(collection, documentId) - return deleteDocument(doc, collection.name) - } - - private fun DocumentSnapshot.ifNotExist(action: () -> Unit): DocumentSnapshot { - if (exists().not()) action() - return this - } -} diff --git a/data/src/main/java/com/squirtles/data/firebase/FirebaseDataSourceConstants.kt b/data/src/main/java/com/squirtles/data/firebase/FirebaseDataSourceConstants.kt deleted file mode 100644 index 3c24c596..00000000 --- a/data/src/main/java/com/squirtles/data/firebase/FirebaseDataSourceConstants.kt +++ /dev/null @@ -1,18 +0,0 @@ -package com.squirtles.data.firebase - -sealed class FirebaseCollections(val name: String) { - data object Favorites: FirebaseCollections("favorites") - data object Picks: FirebaseCollections("picks") - data object Users: FirebaseCollections("users") -} - -sealed class FirebaseDocumentFields(val name: String) { - data object AddedAt: FirebaseDocumentFields("addedAt") - data object PickId: FirebaseDocumentFields("pickId") - data object UserId: FirebaseDocumentFields("userId") - data object MyPicks: FirebaseDocumentFields("myPicks") - data object Name: FirebaseDocumentFields("name") - data object Location: FirebaseDocumentFields("location") - data object GeoHash: FirebaseDocumentFields("geoHash") - data object CreatedUserName: FirebaseDocumentFields("createdBy.userName") -} diff --git a/data/src/main/java/com/squirtles/data/firebase/FirebaseModule.kt b/data/src/main/java/com/squirtles/data/firebase/FirebaseModule.kt deleted file mode 100644 index 8cad67d7..00000000 --- a/data/src/main/java/com/squirtles/data/firebase/FirebaseModule.kt +++ /dev/null @@ -1,20 +0,0 @@ -package com.squirtles.data.firebase - -import com.google.firebase.firestore.FirebaseFirestore -import com.squirtles.localproperties.LocalPropertyProvider -import dagger.Module -import dagger.Provides -import dagger.hilt.InstallIn -import dagger.hilt.components.SingletonComponent -import javax.inject.Singleton - -@Module -@InstallIn(SingletonComponent::class) // SingletonComponent에 등록 -object FirebaseModule { - - @Provides - @Singleton - fun provideFirebaseFirestore(): FirebaseFirestore { - return FirebaseFirestore.getInstance(LocalPropertyProvider.firestoreDbId) - } -} diff --git a/data/src/main/java/com/squirtles/data/firebase/FirebaseRepositoryUtils.kt b/data/src/main/java/com/squirtles/data/firebase/FirebaseRepositoryUtils.kt deleted file mode 100644 index 40e7c555..00000000 --- a/data/src/main/java/com/squirtles/data/firebase/FirebaseRepositoryUtils.kt +++ /dev/null @@ -1,19 +0,0 @@ -package com.squirtles.data.firebase - -suspend fun handleResult( - firebaseRepositoryException: FirebaseException, - call: suspend () -> T? -): Result { - return runCatching { - call() ?: throw firebaseRepositoryException - } -} - -suspend fun handleResult( - call: suspend () -> T -): Result { - return runCatching { - call() - } -} - diff --git a/data/src/main/java/com/squirtles/data/location/LocalLocationRepositoryImpl.kt b/data/src/main/java/com/squirtles/data/location/LocalLocationRepositoryImpl.kt deleted file mode 100644 index 1f15b086..00000000 --- a/data/src/main/java/com/squirtles/data/location/LocalLocationRepositoryImpl.kt +++ /dev/null @@ -1,18 +0,0 @@ -package com.squirtles.data.location - -import android.location.Location -import com.squirtles.domain.location.LocalLocationRepository -import kotlinx.coroutines.flow.MutableStateFlow -import kotlinx.coroutines.flow.StateFlow -import kotlinx.coroutines.flow.asStateFlow -import javax.inject.Singleton - -@Singleton -class LocalLocationRepositoryImpl: LocalLocationRepository { - private var _currentLocation: MutableStateFlow = MutableStateFlow(null) - override val lastLocation: StateFlow = _currentLocation.asStateFlow() - - override suspend fun saveCurrentLocation(geoLocation: Location) { - _currentLocation.emit(geoLocation) - } -} diff --git a/data/src/main/java/com/squirtles/data/location/di/LocationDi.kt b/data/src/main/java/com/squirtles/data/location/di/LocationDi.kt deleted file mode 100644 index cc10a744..00000000 --- a/data/src/main/java/com/squirtles/data/location/di/LocationDi.kt +++ /dev/null @@ -1,18 +0,0 @@ -package com.squirtles.data.location.di - -import com.squirtles.data.location.LocalLocationRepositoryImpl -import com.squirtles.domain.location.LocalLocationRepository -import dagger.Module -import dagger.Provides -import dagger.hilt.InstallIn -import dagger.hilt.components.SingletonComponent -import javax.inject.Singleton - -@Module -@InstallIn(SingletonComponent::class) -object LocationDi{ - @Provides - @Singleton - fun provideLocalLocationRepository(): LocalLocationRepository = - LocalLocationRepositoryImpl() -} diff --git a/data/src/main/java/com/squirtles/data/network/NetworkModule.kt b/data/src/main/java/com/squirtles/data/network/NetworkModule.kt deleted file mode 100644 index 32e823e6..00000000 --- a/data/src/main/java/com/squirtles/data/network/NetworkModule.kt +++ /dev/null @@ -1,50 +0,0 @@ -package com.squirtles.data.network - -import com.squirtles.localproperties.LocalPropertyProvider -import dagger.Module -import dagger.Provides -import dagger.hilt.InstallIn -import dagger.hilt.components.SingletonComponent -import kotlinx.serialization.json.Json -import okhttp3.MediaType.Companion.toMediaType -import okhttp3.OkHttpClient -import okhttp3.logging.HttpLoggingInterceptor -import retrofit2.Converter -import retrofit2.converter.kotlinx.serialization.asConverterFactory -import javax.inject.Singleton - -@Module -@InstallIn(SingletonComponent::class) -object NetworkModule { - - @Provides - @Singleton - fun provideAppleOkhttpClient(): OkHttpClient { - val loggingInterceptor = HttpLoggingInterceptor() - loggingInterceptor.setLevel(HttpLoggingInterceptor.Level.BODY) - return OkHttpClient.Builder() - .addInterceptor { chain -> - val newRequest = chain.request().newBuilder() - .addHeader("Authorization", "Bearer ${LocalPropertyProvider.appleMusicApiToken}") - .build() - chain.proceed(newRequest) - } - .addInterceptor(loggingInterceptor) - .build() - } - - @Provides - @Singleton - fun provideConverterFactory( - json: Json, - ): Converter.Factory { - return json.asConverterFactory("application/json".toMediaType()) - } - - @Provides - @Singleton - fun provideJson(): Json = Json { - ignoreUnknownKeys = true - coerceInputValues = true - } -} diff --git a/data/src/main/java/com/squirtles/data/order/LocalPickListOrderRepositoryImpl.kt b/data/src/main/java/com/squirtles/data/order/LocalPickListOrderRepositoryImpl.kt deleted file mode 100644 index 1bab1717..00000000 --- a/data/src/main/java/com/squirtles/data/order/LocalPickListOrderRepositoryImpl.kt +++ /dev/null @@ -1,22 +0,0 @@ -package com.squirtles.data.order - -import com.squirtles.domain.model.Order -import com.squirtles.domain.order.LocalPickListOrderRepository -import javax.inject.Singleton - -@Singleton -class LocalPickListOrderRepositoryImpl: LocalPickListOrderRepository { - private var _favoriteListOrder = Order.LATEST - override val favoriteListOrder get() = _favoriteListOrder - - private var _myListOrder = Order.LATEST - override val myListOrder get() = _myListOrder - - override suspend fun saveFavoriteListOrder(order: Order) { - _favoriteListOrder = order - } - - override suspend fun saveMyListOrder(order: Order) { - _myListOrder = order - } -} diff --git a/data/src/main/java/com/squirtles/data/order/di/OrderDi.kt b/data/src/main/java/com/squirtles/data/order/di/OrderDi.kt deleted file mode 100644 index 3b3534d3..00000000 --- a/data/src/main/java/com/squirtles/data/order/di/OrderDi.kt +++ /dev/null @@ -1,18 +0,0 @@ -package com.squirtles.data.order.di - -import com.squirtles.data.order.LocalPickListOrderRepositoryImpl -import com.squirtles.domain.order.LocalPickListOrderRepository -import dagger.Module -import dagger.Provides -import dagger.hilt.InstallIn -import dagger.hilt.components.SingletonComponent -import javax.inject.Singleton - -@Module -@InstallIn(SingletonComponent::class) -object OrderDi { - @Provides - @Singleton - fun provideLocalPickListOrderRepository(): LocalPickListOrderRepository = - LocalPickListOrderRepositoryImpl() -} diff --git a/data/src/main/java/com/squirtles/data/pick/FirebasePickDataSourceImpl.kt b/data/src/main/java/com/squirtles/data/pick/FirebasePickDataSourceImpl.kt deleted file mode 100644 index 204ecb34..00000000 --- a/data/src/main/java/com/squirtles/data/pick/FirebasePickDataSourceImpl.kt +++ /dev/null @@ -1,192 +0,0 @@ -package com.squirtles.data.pick - -import android.util.Log -import com.firebase.geofire.GeoFireUtils -import com.firebase.geofire.GeoLocation -import com.google.android.gms.tasks.Task -import com.google.android.gms.tasks.Tasks -import com.google.firebase.firestore.DocumentReference -import com.google.firebase.firestore.DocumentSnapshot -import com.google.firebase.firestore.FieldValue -import com.google.firebase.firestore.FirebaseFirestore -import com.google.firebase.firestore.Query -import com.google.firebase.firestore.QuerySnapshot -import com.google.firebase.firestore.toObject -import com.squirtles.data.favorite.model.FirebaseFavorite -import com.squirtles.data.firebase.BaseFirebaseDataSource -import com.squirtles.data.firebase.FirebaseCollections -import com.squirtles.data.firebase.FirebaseDocumentFields -import com.squirtles.data.pick.model.FirebasePick -import com.squirtles.data.pick.model.toFirebasePick -import com.squirtles.data.pick.model.toPick -import com.squirtles.data.user.model.FirebaseUser -import com.squirtles.domain.model.Pick -import com.squirtles.domain.pick.FirebasePickDataSource -import kotlinx.coroutines.suspendCancellableCoroutine -import kotlinx.coroutines.tasks.await -import javax.inject.Inject -import javax.inject.Singleton -import kotlin.coroutines.resume -import kotlin.coroutines.resumeWithException - -@Singleton -class FirebasePickDataSourceImpl @Inject constructor( - private val db: FirebaseFirestore -) : BaseFirebaseDataSource(db), FirebasePickDataSource { - - /* Fetches a pick by ID from Firestore */ - override suspend fun fetchPick(pickId: String): Result { - return runCatching { - val pickSnap = fetchDocumentSnapshot(FirebaseCollections.Picks, pickId).getOrThrow() - val firestorePick = pickSnap.toObject()?.copy(id = pickId) - firestorePick?.toPick()!! - }.onFailure { exception -> - Log.e(TAG_LOG, "Failed to fetch a pick", exception) - } - } - - /* Fetches picks within a given radius from Firestore */ - override suspend fun fetchPicksInArea( - lat: Double, - lng: Double, - radiusInM: Double - ): Result> { - val center = GeoLocation(lat, lng) - val bounds = GeoFireUtils.getGeoHashQueryBounds(center, radiusInM) - - return runCatching { - val queryResults = bounds.map { bound -> - queryDocumentsInRange( - collection = FirebaseCollections.Picks, - field = FirebaseDocumentFields.GeoHash, - start = bound.startHash, - end = bound.endHash - ) - } - - queryResults.flatMap { querySnapshot -> - querySnapshot.getOrThrow().documents - .filter { doc -> - isAccurate(doc, center, radiusInM) - }.mapNotNull { doc -> - doc.toObject()?.toPick()?.copy(id = doc.id) - } - } - }.onFailure { e -> - Log.e(TAG_LOG, "Failed to fetch picks", e) - } - } - - /* Creates a new pick in Firestore */ - override suspend fun createPick(pick: Pick): Result { - val firebasePick = pick.toFirebasePick() - return runCatching { - val pickRef = addDocument(FirebaseCollections.Picks, firebasePick).getOrThrow() - updateCurrentUserPick(pick.createdBy.uid, pickRef.id) - pickRef.id - }.onFailure { - Log.e(TAG_LOG, "Failed to create a pick", it) - } - } - - override suspend fun deletePick(pickId: String, userId: String): Result { - val pickDocument = fetchDocumentReference(FirebaseCollections.Picks, pickId) - val userDocument = fetchDocumentReference(FirebaseCollections.Users, userId) - - return runCatching { - val favoriteDocuments = fetchFavoriteDocumentRefsByPick(pickId).getOrThrow() - - db.runTransaction { transaction -> - transaction.delete(pickDocument) - favoriteDocuments.forEach { docRef -> - transaction.delete(docRef) - } - transaction.update(userDocument, FirebaseDocumentFields.MyPicks.name, FieldValue.arrayRemove(pickId)) - }.await() - - pickDocument.id - }.onFailure { - Log.e(TAG_LOG, "Failed to delete a pick", it) - } - } - - override suspend fun fetchMyPicks(userId: String): Result> { - return runCatching { - val userDocument = fetchDocumentSnapshot(FirebaseCollections.Users, userId).getOrThrow() - userDocument.toObject()?.myPicks!!.map { - fetchPick(it).getOrThrow() - }.reversed() - } - } - - override suspend fun fetchFavoritePicks(userId: String): Result> { - return runCatching { - val favoriteDocuments = fetchFavoritesByUserId(userId) - favoriteDocuments.map { docSnap -> - fetchPick(docSnap.toObject()?.pickId.toString()).getOrThrow() - } - } - } - - private suspend fun updateCurrentUserPick(userId: String, pickId: String): Result { - return runCatching { - updateDocument( - collection = FirebaseCollections.Users, - documentId = userId, - field = FirebaseDocumentFields.MyPicks, - value = FieldValue.arrayUnion(pickId) - ).getOrThrow() - }.onFailure { e -> - Log.e(TAG_LOG, "Failed to update user picks", e) - } - } - - private suspend fun fetchFavoriteDocumentRefsByPick(pickId: String): Result> { - return runCatching { - queryDocumentsEquals( - collection = FirebaseCollections.Favorites, - fields = listOf(FirebaseDocumentFields.PickId), - values = listOf(pickId), - ).getOrThrow().documents.map { it.reference } - } - } - - /** - * GeoHash의 FP 문제 - Geohash의 쿼리가 정확하지 않으며 클라이언트 측에서 거짓양성 결과를 필터링해야 합니다. - * 이러한 추가 읽기로 인해 앱에 비용과 지연 시간이 추가됩니다. - */ - private fun isAccurate(doc: DocumentSnapshot, center: GeoLocation, radiusInM: Double): Boolean { - val location = doc.getGeoPoint(FirebaseDocumentFields.Location.name) ?: return false - - val docLocation = GeoLocation(location.latitude, location.longitude) - val distanceInM = GeoFireUtils.getDistanceBetween(docLocation, center) - - return distanceInM <= radiusInM - } - - private suspend fun fetchFavoritesByUserId(userId: String): List { - return queryDocumentsEquals( - collection = FirebaseCollections.Favorites, - fields = listOf(FirebaseDocumentFields.UserId), - values = listOf(userId) - ).getOrThrow().documents - } - - private suspend fun executeQuery(query: Query): QuerySnapshot { - return suspendCancellableCoroutine { continuation -> - query.get() - .addOnSuccessListener { result -> - continuation.resume(result) - } - .addOnFailureListener { exception -> - Log.w(TAG_LOG, "Error fetching favorite documents", exception) - continuation.resumeWithException(exception) - } - } - } - - companion object { - private const val TAG_LOG = "FirebasePickDataSourceImpl" - } -} - diff --git a/data/src/main/java/com/squirtles/data/pick/FirebasePickRepositoryImpl.kt b/data/src/main/java/com/squirtles/data/pick/FirebasePickRepositoryImpl.kt deleted file mode 100644 index ad3414a6..00000000 --- a/data/src/main/java/com/squirtles/data/pick/FirebasePickRepositoryImpl.kt +++ /dev/null @@ -1,46 +0,0 @@ -package com.squirtles.data.pick - -import com.squirtles.data.firebase.FirebaseException -import com.squirtles.data.firebase.handleResult -import com.squirtles.domain.model.Pick -import com.squirtles.domain.pick.FirebasePickDataSource -import com.squirtles.domain.pick.FirebasePickRepository -import javax.inject.Inject -import javax.inject.Singleton - -@Singleton -class FirebasePickRepositoryImpl @Inject constructor( - private val pickDataSource: FirebasePickDataSource -) : FirebasePickRepository { - - override suspend fun createPick(pick: Pick): Result { - return pickDataSource.createPick(pick) - } - - override suspend fun deletePick(pickId: String, userId: String): Result { - return pickDataSource.deletePick(pickId, userId) - } - - override suspend fun fetchPick(pickID: String): Result { - return pickDataSource.fetchPick(pickID) - } - - override suspend fun fetchMyPicks(userId: String): Result> { - return pickDataSource.fetchMyPicks(userId) - } - - override suspend fun fetchPicksInArea( - lat: Double, - lng: Double, - radiusInM: Double - ): Result> { - val pickList = pickDataSource.fetchPicksInArea(lat, lng, radiusInM) - return handleResult(FirebaseException.NoSuchPickInRadiusException()) { - pickList.getOrThrow().ifEmpty { null } - } - } - - override suspend fun fetchFavoritePicks(userId: String): Result> { - return pickDataSource.fetchFavoritePicks(userId) - } -} diff --git a/data/src/main/java/com/squirtles/data/pick/di/PickDi.kt b/data/src/main/java/com/squirtles/data/pick/di/PickDi.kt deleted file mode 100644 index b06e8cde..00000000 --- a/data/src/main/java/com/squirtles/data/pick/di/PickDi.kt +++ /dev/null @@ -1,27 +0,0 @@ -package com.squirtles.data.pick.di - -import com.google.firebase.firestore.FirebaseFirestore -import com.squirtles.data.pick.FirebasePickDataSourceImpl -import com.squirtles.data.pick.FirebasePickRepositoryImpl -import com.squirtles.domain.pick.FirebasePickDataSource -import com.squirtles.domain.pick.FirebasePickRepository -import dagger.Module -import dagger.Provides -import dagger.hilt.InstallIn -import dagger.hilt.components.SingletonComponent -import javax.inject.Singleton - -@Module -@InstallIn(SingletonComponent::class) -object PickDi{ - - @Provides - @Singleton - fun provideFirebasePickRepository(firebasePickDataSource: FirebasePickDataSource): FirebasePickRepository = - FirebasePickRepositoryImpl(firebasePickDataSource) - - @Provides - @Singleton - fun provideFirebasePickDataSource(db: FirebaseFirestore): FirebasePickDataSource = - FirebasePickDataSourceImpl(db) -} diff --git a/data/src/main/java/com/squirtles/data/pick/model/FirebasePick.kt b/data/src/main/java/com/squirtles/data/pick/model/FirebasePick.kt deleted file mode 100644 index eeb8729c..00000000 --- a/data/src/main/java/com/squirtles/data/pick/model/FirebasePick.kt +++ /dev/null @@ -1,28 +0,0 @@ -package com.squirtles.data.pick.model - -import com.google.firebase.Timestamp -import com.google.firebase.firestore.GeoPoint -import com.google.firebase.firestore.ServerTimestamp - -/** - * Firestore에 저장된 pick document를 불러와 변환하기위한 데이터 클래스 - */ -data class FirebasePick( - val id: String? = null, - val albumName: String? = null, - val artistName: String? = null, - val artwork: Map? = null, - val comment: String? = null, - @ServerTimestamp val createdAt: Timestamp? = null, // 등록 시 자동으로 서버 시간으로 설정되도록 합니다 - val createdBy: Map? = null, - val externalUrl: String? = null, - val favoriteCount: Int = 0, - val genreNames: List = emptyList(), - val geoHash: String? = null, - val location: GeoPoint? = null, - val previewUrl: String? = null, - val musicVideoUrl: String? = null, - val musicVideoThumbnail: String? = null, - val songId: String? = null, - val songName: String? = null, -) diff --git a/data/src/main/java/com/squirtles/data/pick/model/Mapper.kt b/data/src/main/java/com/squirtles/data/pick/model/Mapper.kt deleted file mode 100644 index bcdc124d..00000000 --- a/data/src/main/java/com/squirtles/data/pick/model/Mapper.kt +++ /dev/null @@ -1,82 +0,0 @@ -package com.squirtles.data.pick.model - -import androidx.core.graphics.toColorInt -import com.firebase.geofire.GeoFireUtils -import com.firebase.geofire.GeoLocation -import com.google.firebase.firestore.GeoPoint -import com.squirtles.domain.model.Creator -import com.squirtles.domain.model.LocationPoint -import com.squirtles.domain.model.Pick -import com.squirtles.domain.model.Song -import java.text.SimpleDateFormat -import java.util.Date -import java.util.Locale - -/** - * using when get pick from firebase and convert to domain data - */ -internal fun FirebasePick.toPick(): Pick = Pick( - id = id.toString(), - song = Song( - id = songId.toString(), - songName = songName.toString(), - artistName = artistName.toString(), - albumName = albumName.toString(), - imageUrl = artwork?.get("url") ?: "", - genreNames = genreNames, - bgColor = artwork?.get("bgColor")?.let { - "#${it}".toColorInt() - } ?: "#000000".toColorInt(), - externalUrl = externalUrl.toString(), - previewUrl = previewUrl.toString(), - ), - comment = comment.toString(), - favoriteCount = favoriteCount, - createdBy = Creator( - uid = createdBy?.get("uid") ?: "", - userName = createdBy?.get("userName") ?: "" - ), - createdAt = createdAt?.toDate()?.formatTimestamp() ?: "", - location = LocationPoint( - latitude = location?.latitude ?: 0.0, - longitude = location?.longitude ?: 0.0 - ), - musicVideoUrl = musicVideoUrl ?: "", - musicVideoThumbnailUrl = musicVideoThumbnail ?: "" -) - -/** - * using when create pick in firebase - */ -internal fun Pick.toFirebasePick(): FirebasePick = FirebasePick( - id = id, - albumName = song.albumName, - artistName = song.artistName, - artwork = mapOf("url" to song.imageUrl, "bgColor" to song.bgColor.toRgbString()), - comment = comment, - createdBy = mapOf("uid" to createdBy.uid, "userName" to createdBy.userName), - externalUrl = song.externalUrl, - favoriteCount = favoriteCount, - genreNames = song.genreNames, - geoHash = location.toGeoHash(), - location = GeoPoint(location.latitude, location.longitude), - previewUrl = song.previewUrl, - musicVideoUrl = musicVideoUrl, - musicVideoThumbnail = musicVideoThumbnailUrl, - songId = song.id, - songName = song.songName, -) - -private fun Int.toRgbString(): String { - return String.format("%06X", 0xFFFFFF and this) -} - -private fun LocationPoint.toGeoHash(): String { - val geoLocation = GeoLocation(this.latitude, this.longitude) - return GeoFireUtils.getGeoHashForLocation(geoLocation) -} - -private fun Date.formatTimestamp(): String { - val dateFormat = SimpleDateFormat("yyyy.MM.dd", Locale.getDefault()) - return dateFormat.format(this) -} diff --git a/data/src/main/java/com/squirtles/data/user/FirebaseUserDataSourceImpl.kt b/data/src/main/java/com/squirtles/data/user/FirebaseUserDataSourceImpl.kt deleted file mode 100644 index c57e1ae8..00000000 --- a/data/src/main/java/com/squirtles/data/user/FirebaseUserDataSourceImpl.kt +++ /dev/null @@ -1,74 +0,0 @@ -package com.squirtles.data.user - -import android.util.Log -import com.google.firebase.firestore.FirebaseFirestore -import com.google.firebase.firestore.toObject -import com.squirtles.data.firebase.BaseFirebaseDataSource -import com.squirtles.data.firebase.FirebaseCollections -import com.squirtles.data.firebase.FirebaseDocumentFields -import com.squirtles.data.user.model.FirebaseUser -import com.squirtles.data.user.model.toUser -import com.squirtles.domain.model.User -import com.squirtles.domain.user.FirebaseUserDataSource -import kotlinx.coroutines.suspendCancellableCoroutine -import javax.inject.Inject -import javax.inject.Singleton -import kotlin.coroutines.resume -import kotlin.coroutines.resumeWithException - -@Singleton -class FirebaseUserDataSourceImpl @Inject constructor( - private val db: FirebaseFirestore -) : BaseFirebaseDataSource(db), FirebaseUserDataSource { - - override suspend fun createGoogleIdUser( - uid: String, - email: String, - userName: String?, - userProfileImage: String? - ): Result { - return runCatching { - val newUser = FirebaseUser(email = email, name = userName, profileImage = userProfileImage) - setDocument(FirebaseCollections.Users, uid, newUser) - newUser.toUser().copy(uid = uid) - }.onFailure { e -> - Log.e(TAG_LOG, e.message.toString()) - } - } - - override suspend fun fetchUser(uid: String): Result { - return runCatching { - val userDocSnap = fetchDocumentSnapshot(FirebaseCollections.Users, uid).getOrThrow() - userDocSnap.toObject()?.toUser()?.copy(uid = uid)!! - }.onFailure { e -> - Log.e(TAG_LOG, "Failed to fetch a user", e) - } - } - - override suspend fun updateUserName(uid: String, newUserName: String): Result { - return runCatching { - val userDocSnap = fetchDocumentSnapshot(FirebaseCollections.Users, uid).getOrThrow() - db.runTransaction { transaction -> - transaction.update(userDocSnap.reference, FirebaseDocumentFields.Name.name, newUserName) - - val myPicks = userDocSnap.toObject()?.myPicks - requireNotNull(myPicks) - myPicks.forEach { pickId -> - val pickRef = fetchDocumentReference(FirebaseCollections.Picks, pickId) - transaction.update(pickRef, FirebaseDocumentFields.CreatedUserName.name, newUserName) - } - } - true - }.onFailure { e -> - Log.e(TAG_LOG, "Failed to update a user name", e) - } - } - - override suspend fun deleteUser(uid: String): Result { - return deleteDocument(FirebaseCollections.Users, uid) - } - - companion object { - const val TAG_LOG = "FirebaseUserDataSourceImpl" - } -} diff --git a/data/src/main/java/com/squirtles/data/user/FirebaseUserRepositoryImpl.kt b/data/src/main/java/com/squirtles/data/user/FirebaseUserRepositoryImpl.kt deleted file mode 100644 index 00e06a3c..00000000 --- a/data/src/main/java/com/squirtles/data/user/FirebaseUserRepositoryImpl.kt +++ /dev/null @@ -1,34 +0,0 @@ -package com.squirtles.data.user - -import com.squirtles.domain.firebase.FirebaseException -import com.squirtles.domain.model.User -import com.squirtles.domain.user.FirebaseUserDataSource -import com.squirtles.domain.user.FirebaseUserRepository -import javax.inject.Singleton - -@Singleton -class FirebaseUserRepositoryImpl( - private val userDataSource: FirebaseUserDataSource -) : FirebaseUserRepository { - - override suspend fun createGoogleIdUser( - uid: String, - email: String, - userName: String?, - userProfileImage: String? - ): Result { - return userDataSource.createGoogleIdUser(uid, email, userName, userProfileImage) - } - - override suspend fun fetchUser(userId: String): Result { - return userDataSource.fetchUser(userId) - } - - override suspend fun updateUserName(userId: String, newUserName: String): Result { - return userDataSource.updateUserName(userId, newUserName) - } - - override suspend fun deleteUser(uid: String): Result { - return userDataSource.deleteUser(uid) - } -} diff --git a/data/src/main/java/com/squirtles/data/user/LocalUserDataSourceImpl.kt b/data/src/main/java/com/squirtles/data/user/LocalUserDataSourceImpl.kt deleted file mode 100644 index c77e67fa..00000000 --- a/data/src/main/java/com/squirtles/data/user/LocalUserDataSourceImpl.kt +++ /dev/null @@ -1,58 +0,0 @@ -package com.squirtles.data.datasource.local - -import android.content.Context -import android.location.Location -import androidx.datastore.preferences.core.edit -import androidx.datastore.preferences.core.stringPreferencesKey -import androidx.datastore.preferences.preferencesDataStore -import com.squirtles.domain.user.LocalUserDataSource -import com.squirtles.domain.model.User -import kotlinx.coroutines.flow.Flow -import kotlinx.coroutines.flow.MutableStateFlow -import kotlinx.coroutines.flow.StateFlow -import kotlinx.coroutines.flow.asStateFlow -import kotlinx.coroutines.flow.map -import javax.inject.Inject -import javax.inject.Singleton - -@Singleton -class LocalUserDataSourceImpl @Inject constructor( - private val context: Context, -) : LocalUserDataSource { - private val Context.dataStore by preferencesDataStore(name = USER_PREFERENCES_NAME) - - private var _currentUser: User? = null - override val currentUser: User? - get() = _currentUser - - override fun readUidDataStore(): Flow { - val dataStoreKey = stringPreferencesKey(USER_ID_KEY) - return context.dataStore.data.map { preferences -> - preferences[dataStoreKey] - } - } - - override suspend fun saveUidDataStore(uid: String) { - val dataStoreKey = stringPreferencesKey(USER_ID_KEY) - context.dataStore.edit { preferences -> - preferences[dataStoreKey] = uid - } - } - - override suspend fun saveCurrentUser(user: User) { - _currentUser = user - } - - override suspend fun clearUser() { - val dataStoreKey = stringPreferencesKey(USER_ID_KEY) - context.dataStore.edit { preferences -> - preferences.remove(dataStoreKey) - } - _currentUser = null - } - - companion object { - private const val USER_PREFERENCES_NAME = "user_preferences" - private const val USER_ID_KEY = "user_id" - } -} diff --git a/data/src/main/java/com/squirtles/data/user/LocalUserRepositoryImpl.kt b/data/src/main/java/com/squirtles/data/user/LocalUserRepositoryImpl.kt deleted file mode 100644 index ced59f96..00000000 --- a/data/src/main/java/com/squirtles/data/user/LocalUserRepositoryImpl.kt +++ /dev/null @@ -1,36 +0,0 @@ -package com.squirtles.data.user - -import com.squirtles.domain.user.LocalUserDataSource -import com.squirtles.domain.model.User -import com.squirtles.domain.user.LocalUserRepository -import kotlinx.coroutines.flow.Flow -import javax.inject.Inject -import javax.inject.Singleton - -@Singleton -class LocalUserRepositoryImpl @Inject constructor( - private val userDataSource: LocalUserDataSource -) : LocalUserRepository { - override val currentUser get() = userDataSource.currentUser - - override fun readUserIdDataStore(): Flow { - return userDataSource.readUidDataStore() - } - - override suspend fun saveUserIdDataStore(userId: String) { - userDataSource.saveUidDataStore(userId) - } - - override suspend fun saveCurrentUser(user: User) { - userDataSource.saveCurrentUser(user) - } - - override suspend fun clearUser(): Result { - return try { - userDataSource.clearUser() - Result.success(Unit) - } catch (e: Exception) { - Result.failure(e) - } - } -} diff --git a/data/src/main/java/com/squirtles/data/user/di/UserDi.kt b/data/src/main/java/com/squirtles/data/user/di/UserDi.kt deleted file mode 100644 index 9c01ffee..00000000 --- a/data/src/main/java/com/squirtles/data/user/di/UserDi.kt +++ /dev/null @@ -1,42 +0,0 @@ -package com.squirtles.data.user.di - -import android.content.Context -import com.google.firebase.firestore.FirebaseFirestore -import com.squirtles.data.datasource.local.LocalUserDataSourceImpl -import com.squirtles.data.user.FirebaseUserDataSourceImpl -import com.squirtles.data.user.FirebaseUserRepositoryImpl -import com.squirtles.data.user.LocalUserRepositoryImpl -import com.squirtles.domain.user.FirebaseUserDataSource -import com.squirtles.domain.user.FirebaseUserRepository -import com.squirtles.domain.user.LocalUserDataSource -import com.squirtles.domain.user.LocalUserRepository -import dagger.Module -import dagger.Provides -import dagger.hilt.InstallIn -import dagger.hilt.android.qualifiers.ApplicationContext -import dagger.hilt.components.SingletonComponent -import javax.inject.Singleton - -@Module -@InstallIn(SingletonComponent::class) -object UserDi { - @Provides - @Singleton - fun provideLocalUserRepository(localUserDataSource: LocalUserDataSource): LocalUserRepository = - LocalUserRepositoryImpl(localUserDataSource) - - @Provides - @Singleton - fun provideLocalUserDataSource(@ApplicationContext context: Context): LocalUserDataSource = - LocalUserDataSourceImpl(context) - - @Provides - @Singleton - fun provideFirebaseUserRepository(firebaseUserDataSource: FirebaseUserDataSource): FirebaseUserRepository = - FirebaseUserRepositoryImpl(firebaseUserDataSource) - - @Provides - @Singleton - fun provideFirebaseUserDataSource(db: FirebaseFirestore): FirebaseUserDataSource = - FirebaseUserDataSourceImpl(db) -} diff --git a/data/src/main/java/com/squirtles/data/user/model/FirebaseUser.kt b/data/src/main/java/com/squirtles/data/user/model/FirebaseUser.kt deleted file mode 100644 index b40c2b92..00000000 --- a/data/src/main/java/com/squirtles/data/user/model/FirebaseUser.kt +++ /dev/null @@ -1,8 +0,0 @@ -package com.squirtles.data.user.model - -data class FirebaseUser( - val email: String? = null, - val name: String? = null, - val profileImage: String? = null, - val myPicks: List = emptyList() -) diff --git a/data/src/main/java/com/squirtles/data/user/model/Mapper.kt b/data/src/main/java/com/squirtles/data/user/model/Mapper.kt deleted file mode 100644 index 0edfc477..00000000 --- a/data/src/main/java/com/squirtles/data/user/model/Mapper.kt +++ /dev/null @@ -1,11 +0,0 @@ -package com.squirtles.data.user.model - -import com.squirtles.domain.model.User - -internal fun FirebaseUser.toUser(): User = User( - uid = "", - email = email ?: "", - userName = name ?: "", - userProfileImage = profileImage, - myPicks = myPicks -) diff --git a/data/user/build.gradle.kts b/data/user/build.gradle.kts index a8c50935..778f8112 100644 --- a/data/user/build.gradle.kts +++ b/data/user/build.gradle.kts @@ -10,6 +10,9 @@ dependencies { implementation(projects.domain.user) implementation(projects.data.firebase) + testImplementation(libs.junit) + androidTestImplementation(libs.bundles.test) + // Datastore implementation(libs.androidx.datastore.preferences) diff --git a/data/user/src/main/java/com/squirtles/user/FirebaseUserRepositoryImpl.kt b/data/user/src/main/java/com/squirtles/user/FirebaseUserRepositoryImpl.kt index de0006c6..0ecf4cc7 100644 --- a/data/user/src/main/java/com/squirtles/user/FirebaseUserRepositoryImpl.kt +++ b/data/user/src/main/java/com/squirtles/user/FirebaseUserRepositoryImpl.kt @@ -4,10 +4,11 @@ import com.google.firebase.auth.FirebaseAuth import com.squirtles.firebase.model.FirebaseUser import com.squirtles.firebase.model.toUser import com.squirtles.model.User +import javax.inject.Inject import javax.inject.Singleton @Singleton -class FirebaseUserRepositoryImpl( +class FirebaseUserRepositoryImpl @Inject constructor( private val userDataSource: FirebaseUserDataSource ) : FirebaseUserRepository { diff --git a/data/user/src/main/java/com/squirtles/user/di/UserDi.kt b/data/user/src/main/java/com/squirtles/user/di/UserDiModule.kt similarity index 98% rename from data/user/src/main/java/com/squirtles/user/di/UserDi.kt rename to data/user/src/main/java/com/squirtles/user/di/UserDiModule.kt index f181ca43..30237ead 100644 --- a/data/user/src/main/java/com/squirtles/user/di/UserDi.kt +++ b/data/user/src/main/java/com/squirtles/user/di/UserDiModule.kt @@ -1,6 +1,7 @@ package com.squirtles.user.di import android.content.Context +import com.google.firebase.firestore.FirebaseFirestore import com.squirtles.user.FirebaseUserDataSource import com.squirtles.user.FirebaseUserDataSourceImpl import com.squirtles.user.FirebaseUserRepository @@ -9,7 +10,6 @@ import com.squirtles.user.LocalUserDataSource import com.squirtles.user.LocalUserDataSourceImpl import com.squirtles.user.LocalUserRepository import com.squirtles.user.LocalUserRepositoryImpl -import com.google.firebase.firestore.FirebaseFirestore import dagger.Module import dagger.Provides import dagger.hilt.InstallIn @@ -19,7 +19,7 @@ import javax.inject.Singleton @Module @InstallIn(SingletonComponent::class) -object UserDi { +object UserDiModule { @Provides @Singleton fun provideLocalUserRepository(localUserDataSource: LocalUserDataSource): LocalUserRepository = diff --git a/domain/applemusic/build.gradle.kts b/domain/applemusic/build.gradle.kts index 0cf6f96b..f150186b 100644 --- a/domain/applemusic/build.gradle.kts +++ b/domain/applemusic/build.gradle.kts @@ -10,4 +10,7 @@ dependencies { implementation(projects.core.model) implementation(libs.androidx.paging.runtime) implementation(libs.inject) + + testImplementation(libs.junit) + androidTestImplementation(libs.bundles.test) } diff --git a/domain/build.gradle.kts b/domain/build.gradle.kts deleted file mode 100644 index ae17c175..00000000 --- a/domain/build.gradle.kts +++ /dev/null @@ -1,27 +0,0 @@ -plugins { - alias(libs.plugins.musicroad.android.library) - alias(libs.plugins.musicroad.hilt) - alias(libs.plugins.kotlin.serialization) -} - -android { - namespace = "com.squirtles.domain" -} - -dependencies { - - implementation(project(":mediaservice")) - - testImplementation(libs.junit) - androidTestImplementation(libs.bundles.test) - - implementation(libs.androidx.paging.runtime) - - implementation(libs.bundles.media3) - - // Firebase - implementation(libs.firebase.auth.ktx) - - // Serialization - implementation(libs.kotlinx.serialization.json) -} diff --git a/domain/favorite/build.gradle.kts b/domain/favorite/build.gradle.kts index d45c0a97..963e844f 100644 --- a/domain/favorite/build.gradle.kts +++ b/domain/favorite/build.gradle.kts @@ -5,4 +5,6 @@ plugins { dependencies { implementation(projects.core.model) implementation(projects.domain.picklist) + + testImplementation(libs.junit) } diff --git a/domain/favorite/src/main/java/com/squirtles/favorite/usecase/DeleteFavoriteUseCase.kt b/domain/favorite/src/main/java/com/squirtles/favorite/usecase/DeleteFavoriteUseCase.kt index eb292313..afa66532 100644 --- a/domain/favorite/src/main/java/com/squirtles/favorite/usecase/DeleteFavoriteUseCase.kt +++ b/domain/favorite/src/main/java/com/squirtles/favorite/usecase/DeleteFavoriteUseCase.kt @@ -1,12 +1,12 @@ package com.squirtles.favorite.usecase import com.squirtles.favorite.FirebaseFavoriteRepository -import com.squirtles.picklist.RemovePickUseCaseInterface +import com.squirtles.domain.picklist.RemovePickUseCaseInterface import javax.inject.Inject class DeleteFavoriteUseCase @Inject constructor( private val favoriteRepository: FirebaseFavoriteRepository ) : RemovePickUseCaseInterface { - override suspend operator fun invoke(pickId: String, userId: String): Result = - favoriteRepository.deleteFavorite(pickId, userId) + override suspend operator fun invoke(pickId: String, uid: String): Result = + favoriteRepository.deleteFavorite(pickId, uid) } diff --git a/domain/.gitignore b/domain/firebase/.gitignore similarity index 100% rename from domain/.gitignore rename to domain/firebase/.gitignore diff --git a/domain/firebase/build.gradle.kts b/domain/firebase/build.gradle.kts new file mode 100644 index 00000000..4d05a18c --- /dev/null +++ b/domain/firebase/build.gradle.kts @@ -0,0 +1,9 @@ +plugins { + id(libs.plugins.musicroad.java.library.get().pluginId) +} + +dependencies { + implementation(projects.core.model) + + testImplementation(libs.junit) +} diff --git a/data/src/main/java/com/squirtles/data/firebase/FirebaseException.kt b/domain/firebase/src/main/java/com/squirtles/domain/firebase/FirebaseException.kt similarity index 98% rename from data/src/main/java/com/squirtles/data/firebase/FirebaseException.kt rename to domain/firebase/src/main/java/com/squirtles/domain/firebase/FirebaseException.kt index dbce9181..28243dcf 100644 --- a/data/src/main/java/com/squirtles/data/firebase/FirebaseException.kt +++ b/domain/firebase/src/main/java/com/squirtles/domain/firebase/FirebaseException.kt @@ -1,4 +1,4 @@ -package com.squirtles.data.firebase +package com.squirtles.domain.firebase sealed class FirebaseException(override val message: String) : Exception() { data class CreatedUserFailedException(override val message: String = "Failed to create user") : FirebaseException(message) diff --git a/domain/location/build.gradle.kts b/domain/location/build.gradle.kts index 7261baad..53c9c9b1 100644 --- a/domain/location/build.gradle.kts +++ b/domain/location/build.gradle.kts @@ -8,4 +8,7 @@ android { dependencies { implementation(libs.inject) + + testImplementation(libs.junit) + androidTestImplementation(libs.bundles.test) } diff --git a/domain/order/src/main/java/com/squirtles/order/usecase/GetFavoriteListOrderUseCase.kt b/domain/order/src/main/java/com/squirtles/order/usecase/GetFavoriteListOrderUseCase.kt index 6a660340..8b747a0d 100644 --- a/domain/order/src/main/java/com/squirtles/order/usecase/GetFavoriteListOrderUseCase.kt +++ b/domain/order/src/main/java/com/squirtles/order/usecase/GetFavoriteListOrderUseCase.kt @@ -1,7 +1,7 @@ package com.squirtles.order.usecase import com.squirtles.order.LocalPickListOrderRepository -import com.squirtles.picklist.GetPickListOrderUseCaseInterface +import com.squirtles.domain.picklist.GetPickListOrderUseCaseInterface import javax.inject.Inject class GetFavoriteListOrderUseCase @Inject constructor( diff --git a/domain/order/src/main/java/com/squirtles/order/usecase/GetMyPickListOrderUseCase.kt b/domain/order/src/main/java/com/squirtles/order/usecase/GetMyPickListOrderUseCase.kt index 06894205..f64b92f2 100644 --- a/domain/order/src/main/java/com/squirtles/order/usecase/GetMyPickListOrderUseCase.kt +++ b/domain/order/src/main/java/com/squirtles/order/usecase/GetMyPickListOrderUseCase.kt @@ -1,7 +1,7 @@ package com.squirtles.order.usecase import com.squirtles.order.LocalPickListOrderRepository -import com.squirtles.picklist.GetPickListOrderUseCaseInterface +import com.squirtles.domain.picklist.GetPickListOrderUseCaseInterface import javax.inject.Inject class GetMyPickListOrderUseCase @Inject constructor( diff --git a/domain/order/src/main/java/com/squirtles/order/usecase/SaveFavoriteListOrderUseCase.kt b/domain/order/src/main/java/com/squirtles/order/usecase/SaveFavoriteListOrderUseCase.kt index 7b315746..c92d84c5 100644 --- a/domain/order/src/main/java/com/squirtles/order/usecase/SaveFavoriteListOrderUseCase.kt +++ b/domain/order/src/main/java/com/squirtles/order/usecase/SaveFavoriteListOrderUseCase.kt @@ -2,7 +2,7 @@ package com.squirtles.order.usecase import com.squirtles.model.Order import com.squirtles.order.LocalPickListOrderRepository -import com.squirtles.picklist.SavePickListOrderUseCaseInterface +import com.squirtles.domain.picklist.SavePickListOrderUseCaseInterface import javax.inject.Inject class SaveFavoriteListOrderUseCase @Inject constructor( diff --git a/domain/order/src/main/java/com/squirtles/order/usecase/SaveMyPickListOrderUseCase.kt b/domain/order/src/main/java/com/squirtles/order/usecase/SaveMyPickListOrderUseCase.kt index 323a134b..8ddc15a6 100644 --- a/domain/order/src/main/java/com/squirtles/order/usecase/SaveMyPickListOrderUseCase.kt +++ b/domain/order/src/main/java/com/squirtles/order/usecase/SaveMyPickListOrderUseCase.kt @@ -2,7 +2,7 @@ package com.squirtles.order.usecase import com.squirtles.model.Order import com.squirtles.order.LocalPickListOrderRepository -import com.squirtles.picklist.SavePickListOrderUseCaseInterface +import com.squirtles.domain.picklist.SavePickListOrderUseCaseInterface import javax.inject.Inject class SaveMyPickListOrderUseCase @Inject constructor( diff --git a/domain/pick/src/main/java/com/squirtles/pick/FirebasePickRepository.kt b/domain/pick/src/main/java/com/squirtles/domain/pick/FirebasePickRepository.kt similarity index 93% rename from domain/pick/src/main/java/com/squirtles/pick/FirebasePickRepository.kt rename to domain/pick/src/main/java/com/squirtles/domain/pick/FirebasePickRepository.kt index 4b6eec17..2e7fef62 100644 --- a/domain/pick/src/main/java/com/squirtles/pick/FirebasePickRepository.kt +++ b/domain/pick/src/main/java/com/squirtles/domain/pick/FirebasePickRepository.kt @@ -1,4 +1,4 @@ -package com.squirtles.pick +package com.squirtles.domain.pick import com.squirtles.model.Pick diff --git a/domain/src/main/java/com/squirtles/domain/pick/usecase/CreatePickUseCase.kt b/domain/pick/src/main/java/com/squirtles/domain/pick/usecase/CreatePickUseCase.kt similarity index 89% rename from domain/src/main/java/com/squirtles/domain/pick/usecase/CreatePickUseCase.kt rename to domain/pick/src/main/java/com/squirtles/domain/pick/usecase/CreatePickUseCase.kt index 33805f7e..9576037b 100644 --- a/domain/src/main/java/com/squirtles/domain/pick/usecase/CreatePickUseCase.kt +++ b/domain/pick/src/main/java/com/squirtles/domain/pick/usecase/CreatePickUseCase.kt @@ -1,6 +1,6 @@ package com.squirtles.domain.pick.usecase -import com.squirtles.domain.model.Pick +import com.squirtles.model.Pick import com.squirtles.domain.pick.FirebasePickRepository import javax.inject.Inject diff --git a/domain/src/main/java/com/squirtles/domain/pick/usecase/DeletePickUseCase.kt b/domain/pick/src/main/java/com/squirtles/domain/pick/usecase/DeletePickUseCase.kt similarity index 100% rename from domain/src/main/java/com/squirtles/domain/pick/usecase/DeletePickUseCase.kt rename to domain/pick/src/main/java/com/squirtles/domain/pick/usecase/DeletePickUseCase.kt diff --git a/domain/src/main/java/com/squirtles/domain/pick/usecase/FetchFavoritePicksUseCase.kt b/domain/pick/src/main/java/com/squirtles/domain/pick/usecase/FetchFavoritePicksUseCase.kt similarity index 92% rename from domain/src/main/java/com/squirtles/domain/pick/usecase/FetchFavoritePicksUseCase.kt rename to domain/pick/src/main/java/com/squirtles/domain/pick/usecase/FetchFavoritePicksUseCase.kt index 99bc60d5..3a766465 100644 --- a/domain/src/main/java/com/squirtles/domain/pick/usecase/FetchFavoritePicksUseCase.kt +++ b/domain/pick/src/main/java/com/squirtles/domain/pick/usecase/FetchFavoritePicksUseCase.kt @@ -1,6 +1,6 @@ package com.squirtles.domain.pick.usecase -import com.squirtles.domain.model.Pick +import com.squirtles.model.Pick import com.squirtles.domain.pick.FirebasePickRepository import com.squirtles.domain.picklist.FetchPickListUseCaseInterface import javax.inject.Inject diff --git a/domain/src/main/java/com/squirtles/domain/pick/usecase/FetchMyPicksUseCase.kt b/domain/pick/src/main/java/com/squirtles/domain/pick/usecase/FetchMyPicksUseCase.kt similarity index 92% rename from domain/src/main/java/com/squirtles/domain/pick/usecase/FetchMyPicksUseCase.kt rename to domain/pick/src/main/java/com/squirtles/domain/pick/usecase/FetchMyPicksUseCase.kt index 863e0a5b..76d30778 100644 --- a/domain/src/main/java/com/squirtles/domain/pick/usecase/FetchMyPicksUseCase.kt +++ b/domain/pick/src/main/java/com/squirtles/domain/pick/usecase/FetchMyPicksUseCase.kt @@ -1,6 +1,6 @@ package com.squirtles.domain.pick.usecase -import com.squirtles.domain.model.Pick +import com.squirtles.model.Pick import com.squirtles.domain.pick.FirebasePickRepository import com.squirtles.domain.picklist.FetchPickListUseCaseInterface import javax.inject.Inject diff --git a/domain/src/main/java/com/squirtles/domain/pick/usecase/FetchPickUseCase.kt b/domain/pick/src/main/java/com/squirtles/domain/pick/usecase/FetchPickUseCase.kt similarity index 92% rename from domain/src/main/java/com/squirtles/domain/pick/usecase/FetchPickUseCase.kt rename to domain/pick/src/main/java/com/squirtles/domain/pick/usecase/FetchPickUseCase.kt index 0adbcf50..415fd085 100644 --- a/domain/src/main/java/com/squirtles/domain/pick/usecase/FetchPickUseCase.kt +++ b/domain/pick/src/main/java/com/squirtles/domain/pick/usecase/FetchPickUseCase.kt @@ -1,6 +1,6 @@ package com.squirtles.domain.pick.usecase -import com.squirtles.domain.model.Pick +import com.squirtles.model.Pick import com.squirtles.domain.pick.FirebasePickRepository import javax.inject.Inject diff --git a/domain/pick/src/main/java/com/squirtles/pick/usecase/CreatePickUseCase.kt b/domain/pick/src/main/java/com/squirtles/pick/usecase/CreatePickUseCase.kt deleted file mode 100644 index 7fef8ae5..00000000 --- a/domain/pick/src/main/java/com/squirtles/pick/usecase/CreatePickUseCase.kt +++ /dev/null @@ -1,11 +0,0 @@ -package com.squirtles.pick.usecase - -import com.squirtles.model.Pick -import com.squirtles.pick.FirebasePickRepository -import javax.inject.Inject - -class CreatePickUseCase @Inject constructor( - private val pickRepository: FirebasePickRepository -) { - suspend operator fun invoke(pick: Pick): Result = pickRepository.createPick(pick) -} diff --git a/domain/pick/src/main/java/com/squirtles/pick/usecase/DeletePickUseCase.kt b/domain/pick/src/main/java/com/squirtles/pick/usecase/DeletePickUseCase.kt deleted file mode 100644 index 1da738ae..00000000 --- a/domain/pick/src/main/java/com/squirtles/pick/usecase/DeletePickUseCase.kt +++ /dev/null @@ -1,12 +0,0 @@ -package com.squirtles.pick.usecase - -import com.squirtles.pick.FirebasePickRepository -import com.squirtles.picklist.RemovePickUseCaseInterface -import javax.inject.Inject - -class DeletePickUseCase @Inject constructor( - private val pickRepository: FirebasePickRepository -) : RemovePickUseCaseInterface { - override suspend operator fun invoke(pickId: String, userId: String): Result = - pickRepository.deletePick(pickId, userId) -} diff --git a/domain/pick/src/main/java/com/squirtles/pick/usecase/FetchFavoritePicksUseCase.kt b/domain/pick/src/main/java/com/squirtles/pick/usecase/FetchFavoritePicksUseCase.kt deleted file mode 100644 index 1e2ca29b..00000000 --- a/domain/pick/src/main/java/com/squirtles/pick/usecase/FetchFavoritePicksUseCase.kt +++ /dev/null @@ -1,13 +0,0 @@ -package com.squirtles.pick.usecase - -import com.squirtles.model.Pick -import com.squirtles.pick.FirebasePickRepository -import com.squirtles.picklist.FetchPickListUseCaseInterface -import javax.inject.Inject - -class FetchFavoritePicksUseCase @Inject constructor( - private val pickRepository: FirebasePickRepository -) : FetchPickListUseCaseInterface { - override suspend operator fun invoke(userId: String): Result> = - pickRepository.fetchFavoritePicks(userId) -} diff --git a/domain/pick/src/main/java/com/squirtles/pick/usecase/FetchMyPicksUseCase.kt b/domain/pick/src/main/java/com/squirtles/pick/usecase/FetchMyPicksUseCase.kt deleted file mode 100644 index e80c2708..00000000 --- a/domain/pick/src/main/java/com/squirtles/pick/usecase/FetchMyPicksUseCase.kt +++ /dev/null @@ -1,13 +0,0 @@ -package com.squirtles.pick.usecase - -import com.squirtles.model.Pick -import com.squirtles.pick.FirebasePickRepository -import com.squirtles.picklist.FetchPickListUseCaseInterface -import javax.inject.Inject - -class FetchMyPicksUseCase @Inject constructor( - private val pickRepository: FirebasePickRepository -) : FetchPickListUseCaseInterface { - override suspend operator fun invoke(userId: String) : Result> = - pickRepository.fetchMyPicks(userId) -} diff --git a/domain/pick/src/main/java/com/squirtles/pick/usecase/FetchPickUseCase.kt b/domain/pick/src/main/java/com/squirtles/pick/usecase/FetchPickUseCase.kt deleted file mode 100644 index 6428cbb9..00000000 --- a/domain/pick/src/main/java/com/squirtles/pick/usecase/FetchPickUseCase.kt +++ /dev/null @@ -1,15 +0,0 @@ -package com.squirtles.pick.usecase - -import com.squirtles.model.Pick -import com.squirtles.pick.FirebasePickRepository -import javax.inject.Inject - -class FetchPickUseCase @Inject constructor( - private val pickRepository: FirebasePickRepository -) { - suspend operator fun invoke(pickId: String): Result = - pickRepository.fetchPick(pickId) - - suspend operator fun invoke(lat: Double, lng: Double, radiusInM: Double): Result> = - pickRepository.fetchPicksInArea(lat, lng, radiusInM) -} diff --git a/domain/picklist/src/main/java/com/squirtles/picklist/FetchPickListUseCaseInterface.kt b/domain/picklist/src/main/java/com/squirtles/domain/picklist/FetchPickListUseCaseInterface.kt similarity index 79% rename from domain/picklist/src/main/java/com/squirtles/picklist/FetchPickListUseCaseInterface.kt rename to domain/picklist/src/main/java/com/squirtles/domain/picklist/FetchPickListUseCaseInterface.kt index c6d19e14..3ad5ccbf 100644 --- a/domain/picklist/src/main/java/com/squirtles/picklist/FetchPickListUseCaseInterface.kt +++ b/domain/picklist/src/main/java/com/squirtles/domain/picklist/FetchPickListUseCaseInterface.kt @@ -1,4 +1,4 @@ -package com.squirtles.picklist +package com.squirtles.domain.picklist import com.squirtles.model.Pick diff --git a/domain/picklist/src/main/java/com/squirtles/picklist/GetPickListOrderUseCaseInterface.kt b/domain/picklist/src/main/java/com/squirtles/domain/picklist/GetPickListOrderUseCaseInterface.kt similarity index 76% rename from domain/picklist/src/main/java/com/squirtles/picklist/GetPickListOrderUseCaseInterface.kt rename to domain/picklist/src/main/java/com/squirtles/domain/picklist/GetPickListOrderUseCaseInterface.kt index 3494f825..e7b97951 100644 --- a/domain/picklist/src/main/java/com/squirtles/picklist/GetPickListOrderUseCaseInterface.kt +++ b/domain/picklist/src/main/java/com/squirtles/domain/picklist/GetPickListOrderUseCaseInterface.kt @@ -1,4 +1,4 @@ -package com.squirtles.picklist +package com.squirtles.domain.picklist import com.squirtles.model.Order diff --git a/domain/src/main/java/com/squirtles/domain/picklist/RemovePickUseCaseInterface.kt b/domain/picklist/src/main/java/com/squirtles/domain/picklist/RemovePickUseCaseInterface.kt similarity index 100% rename from domain/src/main/java/com/squirtles/domain/picklist/RemovePickUseCaseInterface.kt rename to domain/picklist/src/main/java/com/squirtles/domain/picklist/RemovePickUseCaseInterface.kt diff --git a/domain/picklist/src/main/java/com/squirtles/picklist/SavePickListOrderUseCaseInterface.kt b/domain/picklist/src/main/java/com/squirtles/domain/picklist/SavePickListOrderUseCaseInterface.kt similarity index 77% rename from domain/picklist/src/main/java/com/squirtles/picklist/SavePickListOrderUseCaseInterface.kt rename to domain/picklist/src/main/java/com/squirtles/domain/picklist/SavePickListOrderUseCaseInterface.kt index 8a4ab6d7..f83993d3 100644 --- a/domain/picklist/src/main/java/com/squirtles/picklist/SavePickListOrderUseCaseInterface.kt +++ b/domain/picklist/src/main/java/com/squirtles/domain/picklist/SavePickListOrderUseCaseInterface.kt @@ -1,4 +1,4 @@ -package com.squirtles.picklist +package com.squirtles.domain.picklist import com.squirtles.model.Order diff --git a/domain/picklist/src/main/java/com/squirtles/picklist/RemovePickUseCaseInterface.kt b/domain/picklist/src/main/java/com/squirtles/picklist/RemovePickUseCaseInterface.kt deleted file mode 100644 index 8373d410..00000000 --- a/domain/picklist/src/main/java/com/squirtles/picklist/RemovePickUseCaseInterface.kt +++ /dev/null @@ -1,5 +0,0 @@ -package com.squirtles.picklist - -interface RemovePickUseCaseInterface { - suspend operator fun invoke(pickId: String, userId: String): Result -} diff --git a/domain/player/build.gradle.kts b/domain/player/build.gradle.kts index 9980d58d..ed215d51 100644 --- a/domain/player/build.gradle.kts +++ b/domain/player/build.gradle.kts @@ -10,6 +10,9 @@ dependencies { implementation(projects.core.model) implementation(projects.core.mediaservice) + testImplementation(libs.junit) + androidTestImplementation(libs.bundles.test) + implementation(libs.inject) implementation(libs.bundles.media3) } diff --git a/domain/src/main/AndroidManifest.xml b/domain/src/main/AndroidManifest.xml deleted file mode 100644 index a5918e68..00000000 --- a/domain/src/main/AndroidManifest.xml +++ /dev/null @@ -1,4 +0,0 @@ - - - - \ No newline at end of file diff --git a/domain/src/main/java/com/squirtles/domain/applemusic/AppleMusicException.kt b/domain/src/main/java/com/squirtles/domain/applemusic/AppleMusicException.kt deleted file mode 100644 index 779ea832..00000000 --- a/domain/src/main/java/com/squirtles/domain/applemusic/AppleMusicException.kt +++ /dev/null @@ -1,12 +0,0 @@ -package com.squirtles.domain.applemusic - -/** - * 400 에러가 여러 종류가 있는데 이를 구분할 용도로 만든 예외 클래스 - */ -sealed class AppleMusicException(override val message: String) : Exception() { - data class InvalidParameterException(override val message: String) : - AppleMusicException(message) - - data class NotFoundException(override val message: String = "No such resource") : - AppleMusicException(message) -} diff --git a/domain/src/main/java/com/squirtles/domain/applemusic/AppleMusicRemoteDataSource.kt b/domain/src/main/java/com/squirtles/domain/applemusic/AppleMusicRemoteDataSource.kt deleted file mode 100644 index 4e09600a..00000000 --- a/domain/src/main/java/com/squirtles/domain/applemusic/AppleMusicRemoteDataSource.kt +++ /dev/null @@ -1,11 +0,0 @@ -package com.squirtles.domain.applemusic - -import androidx.paging.PagingData -import com.squirtles.domain.model.MusicVideo -import com.squirtles.domain.model.Song -import kotlinx.coroutines.flow.Flow - -interface AppleMusicRemoteDataSource { - fun searchSongs(searchText: String): Flow> - suspend fun searchMusicVideos(searchText: String): List -} diff --git a/domain/src/main/java/com/squirtles/domain/applemusic/AppleMusicRepository.kt b/domain/src/main/java/com/squirtles/domain/applemusic/AppleMusicRepository.kt deleted file mode 100644 index 4df96e51..00000000 --- a/domain/src/main/java/com/squirtles/domain/applemusic/AppleMusicRepository.kt +++ /dev/null @@ -1,12 +0,0 @@ -package com.squirtles.domain.applemusic - -import androidx.paging.PagingData -import com.squirtles.domain.model.MusicVideo -import com.squirtles.domain.model.Song -import kotlinx.coroutines.flow.Flow - -interface AppleMusicRepository { - fun searchSongs(searchText: String): Flow> - suspend fun searchSongById(songId: String): Result - suspend fun searchMusicVideos(searchText: String): Result> -} diff --git a/domain/src/main/java/com/squirtles/domain/applemusic/usecase/FetchMusicVideoUseCase.kt b/domain/src/main/java/com/squirtles/domain/applemusic/usecase/FetchMusicVideoUseCase.kt deleted file mode 100644 index 7dbff84f..00000000 --- a/domain/src/main/java/com/squirtles/domain/applemusic/usecase/FetchMusicVideoUseCase.kt +++ /dev/null @@ -1,20 +0,0 @@ -package com.squirtles.domain.applemusic.usecase - -import com.squirtles.domain.model.MusicVideo -import com.squirtles.domain.model.Song -import com.squirtles.domain.applemusic.AppleMusicRepository -import javax.inject.Inject - -class FetchMusicVideoUseCase @Inject constructor( - private val appleMusicRepository: AppleMusicRepository -) { - suspend operator fun invoke(song: Song): MusicVideo? { - val keyword = "${song.songName}-${song.artistName}" - appleMusicRepository.searchMusicVideos(keyword).onSuccess { musicVideos -> - return musicVideos.find { - it.artistName == song.artistName && song.songName in it.songName - } - } - return null - } -} diff --git a/domain/src/main/java/com/squirtles/domain/applemusic/usecase/FetchSongsUseCase.kt b/domain/src/main/java/com/squirtles/domain/applemusic/usecase/FetchSongsUseCase.kt deleted file mode 100644 index e24548ef..00000000 --- a/domain/src/main/java/com/squirtles/domain/applemusic/usecase/FetchSongsUseCase.kt +++ /dev/null @@ -1,11 +0,0 @@ -package com.squirtles.domain.applemusic.usecase - -import com.squirtles.domain.applemusic.AppleMusicRepository -import javax.inject.Inject - -class FetchSongsUseCase @Inject constructor( - private val appleMusicRepository: AppleMusicRepository -) { - operator fun invoke(searchText: String) - = appleMusicRepository.searchSongs(searchText) -} diff --git a/domain/src/main/java/com/squirtles/domain/favorite/FirebaseFavoriteDataSource.kt b/domain/src/main/java/com/squirtles/domain/favorite/FirebaseFavoriteDataSource.kt deleted file mode 100644 index 17f04a26..00000000 --- a/domain/src/main/java/com/squirtles/domain/favorite/FirebaseFavoriteDataSource.kt +++ /dev/null @@ -1,7 +0,0 @@ -package com.squirtles.domain.favorite - -interface FirebaseFavoriteDataSource { - suspend fun fetchIsFavorite(pickId: String, userId: String): Result - suspend fun createFavorite(pickId: String, userId: String): Result - suspend fun deleteFavorite(pickId: String, userId: String): Result -} diff --git a/domain/src/main/java/com/squirtles/domain/favorite/FirebaseFavoriteRepository.kt b/domain/src/main/java/com/squirtles/domain/favorite/FirebaseFavoriteRepository.kt deleted file mode 100644 index f5f70e64..00000000 --- a/domain/src/main/java/com/squirtles/domain/favorite/FirebaseFavoriteRepository.kt +++ /dev/null @@ -1,9 +0,0 @@ -package com.squirtles.domain.favorite - -import com.squirtles.domain.model.Pick - -interface FirebaseFavoriteRepository { - suspend fun fetchIsFavorite(pickId: String, userId: String): Result - suspend fun createFavorite(pickId: String, userId: String): Result - suspend fun deleteFavorite(pickId: String, userId: String): Result -} diff --git a/domain/src/main/java/com/squirtles/domain/favorite/usecase/CreateFavoriteUseCase.kt b/domain/src/main/java/com/squirtles/domain/favorite/usecase/CreateFavoriteUseCase.kt deleted file mode 100644 index 6846475c..00000000 --- a/domain/src/main/java/com/squirtles/domain/favorite/usecase/CreateFavoriteUseCase.kt +++ /dev/null @@ -1,11 +0,0 @@ -package com.squirtles.domain.favorite.usecase - -import com.squirtles.domain.favorite.FirebaseFavoriteRepository -import javax.inject.Inject - -class CreateFavoriteUseCase @Inject constructor( - private val favoriteRepository: FirebaseFavoriteRepository -) { - suspend operator fun invoke(pickId: String, userId: String) = - favoriteRepository.createFavorite(pickId, userId) -} diff --git a/domain/src/main/java/com/squirtles/domain/favorite/usecase/DeleteFavoriteUseCase.kt b/domain/src/main/java/com/squirtles/domain/favorite/usecase/DeleteFavoriteUseCase.kt deleted file mode 100644 index fb0e19eb..00000000 --- a/domain/src/main/java/com/squirtles/domain/favorite/usecase/DeleteFavoriteUseCase.kt +++ /dev/null @@ -1,12 +0,0 @@ -package com.squirtles.domain.favorite.usecase - -import com.squirtles.domain.favorite.FirebaseFavoriteRepository -import com.squirtles.domain.picklist.RemovePickUseCaseInterface -import javax.inject.Inject - -class DeleteFavoriteUseCase @Inject constructor( - private val favoriteRepository: FirebaseFavoriteRepository -) : RemovePickUseCaseInterface { - override suspend operator fun invoke(pickId: String, userId: String): Result = - favoriteRepository.deleteFavorite(pickId, userId) -} diff --git a/domain/src/main/java/com/squirtles/domain/favorite/usecase/FetchIsFavoriteUseCase.kt b/domain/src/main/java/com/squirtles/domain/favorite/usecase/FetchIsFavoriteUseCase.kt deleted file mode 100644 index 9a30d0cc..00000000 --- a/domain/src/main/java/com/squirtles/domain/favorite/usecase/FetchIsFavoriteUseCase.kt +++ /dev/null @@ -1,11 +0,0 @@ -package com.squirtles.domain.favorite.usecase - -import com.squirtles.domain.favorite.FirebaseFavoriteRepository -import javax.inject.Inject - -class FetchIsFavoriteUseCase @Inject constructor( - private val favoriteRepository: FirebaseFavoriteRepository -) { - suspend operator fun invoke(pickId: String, userId: String) = - favoriteRepository.fetchIsFavorite(pickId, userId) -} diff --git a/domain/src/main/java/com/squirtles/domain/firebase/FirebaseException.kt b/domain/src/main/java/com/squirtles/domain/firebase/FirebaseException.kt deleted file mode 100644 index 4fccebe1..00000000 --- a/domain/src/main/java/com/squirtles/domain/firebase/FirebaseException.kt +++ /dev/null @@ -1,13 +0,0 @@ -package com.squirtles.domain.firebase - -sealed class FirebaseException(override val message: String) : Exception() { - data class CreatedUserFailedException(override val message: String = "Failed to create a user") : - FirebaseException(message) - - data class UserNotFoundException(override val message: String = "Failed to fetch a user") : - FirebaseException(message) - - data class NoSuchPickException(override val message: String = "No such pick") : FirebaseException(message) - data class NoSuchPickInRadiusException(override val message: String = "No such pick in area") : - FirebaseException(message) -} diff --git a/domain/src/main/java/com/squirtles/domain/location/LocalLocationRepository.kt b/domain/src/main/java/com/squirtles/domain/location/LocalLocationRepository.kt deleted file mode 100644 index 943b5b00..00000000 --- a/domain/src/main/java/com/squirtles/domain/location/LocalLocationRepository.kt +++ /dev/null @@ -1,10 +0,0 @@ -package com.squirtles.domain.location - -import android.location.Location -import kotlinx.coroutines.flow.StateFlow - -interface LocalLocationRepository { - val lastLocation: StateFlow - - suspend fun saveCurrentLocation(geoLocation: Location) -} diff --git a/domain/src/main/java/com/squirtles/domain/location/usecase/GetLastLocationUseCase.kt b/domain/src/main/java/com/squirtles/domain/location/usecase/GetLastLocationUseCase.kt deleted file mode 100644 index d4a29cfc..00000000 --- a/domain/src/main/java/com/squirtles/domain/location/usecase/GetLastLocationUseCase.kt +++ /dev/null @@ -1,10 +0,0 @@ -package com.squirtles.domain.location.usecase - -import com.squirtles.domain.location.LocalLocationRepository -import javax.inject.Inject - -class GetLastLocationUseCase @Inject constructor( - private val localLocationRepository: LocalLocationRepository -) { - operator fun invoke() = localLocationRepository.lastLocation -} diff --git a/domain/src/main/java/com/squirtles/domain/location/usecase/SaveLastLocationUseCase.kt b/domain/src/main/java/com/squirtles/domain/location/usecase/SaveLastLocationUseCase.kt deleted file mode 100644 index 62e0226b..00000000 --- a/domain/src/main/java/com/squirtles/domain/location/usecase/SaveLastLocationUseCase.kt +++ /dev/null @@ -1,11 +0,0 @@ -package com.squirtles.domain.location.usecase - -import android.location.Location -import com.squirtles.domain.location.LocalLocationRepository -import javax.inject.Inject - -class SaveLastLocationUseCase @Inject constructor( - private val localLocationRepository: LocalLocationRepository -) { - suspend operator fun invoke(location: Location) = localLocationRepository.saveCurrentLocation(location) -} diff --git a/domain/src/main/java/com/squirtles/domain/model/GeoLocation.kt b/domain/src/main/java/com/squirtles/domain/model/GeoLocation.kt deleted file mode 100644 index e2a1a16f..00000000 --- a/domain/src/main/java/com/squirtles/domain/model/GeoLocation.kt +++ /dev/null @@ -1,11 +0,0 @@ -package com.squirtles.domain.model - -data class GeoLocation( - val latitude: Double, - val longitude: Double, -) { - init { - require(latitude in -90.0..90.0) { "latitude must be in -90.0 ~ 90.0 range" } - require(longitude in -180.0..180.0) { "longitude must be in -180.0 ~ 180.0 range" } - } -} diff --git a/domain/src/main/java/com/squirtles/domain/model/MusicVideo.kt b/domain/src/main/java/com/squirtles/domain/model/MusicVideo.kt deleted file mode 100644 index e500fa1b..00000000 --- a/domain/src/main/java/com/squirtles/domain/model/MusicVideo.kt +++ /dev/null @@ -1,13 +0,0 @@ -package com.squirtles.domain.model - -import java.time.LocalDate - -data class MusicVideo( - val id: String, - val songName: String, - val artistName: String, - val albumName: String, - val releaseDate: LocalDate, - val previewUrl: String, - val thumbnailUrl: String -) diff --git a/domain/src/main/java/com/squirtles/domain/model/Order.kt b/domain/src/main/java/com/squirtles/domain/model/Order.kt deleted file mode 100644 index b5120989..00000000 --- a/domain/src/main/java/com/squirtles/domain/model/Order.kt +++ /dev/null @@ -1,7 +0,0 @@ -package com.squirtles.domain.model - -enum class Order { - LATEST, - OLDEST, - FAVORITE_DESC, -} diff --git a/domain/src/main/java/com/squirtles/domain/model/Pick.kt b/domain/src/main/java/com/squirtles/domain/model/Pick.kt deleted file mode 100644 index c6d3c225..00000000 --- a/domain/src/main/java/com/squirtles/domain/model/Pick.kt +++ /dev/null @@ -1,28 +0,0 @@ -package com.squirtles.domain.model - -/** - * 앱에서 사용하기 위한 Pick 정보 데이터클래스 - */ -data class Pick( - val id: String, - val song: Song, - val comment: String, - val favoriteCount: Int = 0, - val createdBy: Creator, - val createdAt: String, - val location: LocationPoint, - val musicVideoUrl: String = "", - val musicVideoThumbnailUrl: String = "" -) - -data class LocationPoint( - val latitude: Double, - val longitude: Double -) { - /* TODO: Location 변환 함수 */ -} - -data class Creator( - val uid: String, - val userName: String -) diff --git a/domain/src/main/java/com/squirtles/domain/model/PlayerState.kt b/domain/src/main/java/com/squirtles/domain/model/PlayerState.kt deleted file mode 100644 index ad1394d1..00000000 --- a/domain/src/main/java/com/squirtles/domain/model/PlayerState.kt +++ /dev/null @@ -1,11 +0,0 @@ -package com.squirtles.domain.model - -data class PlayerState( - val id: String = "", - val isLoading: Boolean = false, - val isPlaying: Boolean = false, - val hasNext: Boolean = false, - val currentPosition: Long = 0L, - val duration: Long = 30_000L, - val bufferPercentage: Int = 0, -) diff --git a/domain/src/main/java/com/squirtles/domain/model/Song.kt b/domain/src/main/java/com/squirtles/domain/model/Song.kt deleted file mode 100644 index b80007df..00000000 --- a/domain/src/main/java/com/squirtles/domain/model/Song.kt +++ /dev/null @@ -1,25 +0,0 @@ -package com.squirtles.domain.model - -import kotlinx.serialization.Serializable - -/** - * 애플뮤직에서 불러온 노래 정보를 비즈니스 로직에서 사용하기 위해 변환한 클래스 - */ -@Serializable -data class Song( - val id: String, - val songName: String, - val artistName: String, - val albumName: String, - val imageUrl: String, - val genreNames: List, - val bgColor: Int, - val externalUrl: String, - val previewUrl: String, -) { - fun getImageUrlWithSize(width: Int, height: Int): String? { - return if (imageUrl.isEmpty()) null - else imageUrl.replace("{w}", width.toString()) - .replace("{h}", height.toString()) - } -} diff --git a/domain/src/main/java/com/squirtles/domain/model/User.kt b/domain/src/main/java/com/squirtles/domain/model/User.kt deleted file mode 100644 index 3f69249f..00000000 --- a/domain/src/main/java/com/squirtles/domain/model/User.kt +++ /dev/null @@ -1,9 +0,0 @@ -package com.squirtles.domain.model - -data class User( - val uid: String, - val email: String, - val userName: String, - val userProfileImage: String?, - val myPicks: List, -) diff --git a/domain/src/main/java/com/squirtles/domain/order/LocalPickListOrderRepository.kt b/domain/src/main/java/com/squirtles/domain/order/LocalPickListOrderRepository.kt deleted file mode 100644 index fe9a8a16..00000000 --- a/domain/src/main/java/com/squirtles/domain/order/LocalPickListOrderRepository.kt +++ /dev/null @@ -1,11 +0,0 @@ -package com.squirtles.domain.order - -import com.squirtles.domain.model.Order - -interface LocalPickListOrderRepository { - val favoriteListOrder: Order // 픽 보관함 정렬 순서 - val myListOrder: Order // 등록한 픽 정렬 순서 - - suspend fun saveFavoriteListOrder(order: Order) - suspend fun saveMyListOrder(order: Order) -} diff --git a/domain/src/main/java/com/squirtles/domain/order/usecase/GetFavoriteListOrderUseCase.kt b/domain/src/main/java/com/squirtles/domain/order/usecase/GetFavoriteListOrderUseCase.kt deleted file mode 100644 index b7d9b0db..00000000 --- a/domain/src/main/java/com/squirtles/domain/order/usecase/GetFavoriteListOrderUseCase.kt +++ /dev/null @@ -1,11 +0,0 @@ -package com.squirtles.domain.order.usecase - -import com.squirtles.domain.order.LocalPickListOrderRepository -import com.squirtles.domain.picklist.GetPickListOrderUseCaseInterface -import javax.inject.Inject - -class GetFavoriteListOrderUseCase @Inject constructor( - private val localPickListOrderRepository: LocalPickListOrderRepository -) : GetPickListOrderUseCaseInterface { - override suspend operator fun invoke() = localPickListOrderRepository.favoriteListOrder -} diff --git a/domain/src/main/java/com/squirtles/domain/order/usecase/GetMyPickListOrderUseCase.kt b/domain/src/main/java/com/squirtles/domain/order/usecase/GetMyPickListOrderUseCase.kt deleted file mode 100644 index ab8b72e6..00000000 --- a/domain/src/main/java/com/squirtles/domain/order/usecase/GetMyPickListOrderUseCase.kt +++ /dev/null @@ -1,11 +0,0 @@ -package com.squirtles.domain.order.usecase - -import com.squirtles.domain.order.LocalPickListOrderRepository -import com.squirtles.domain.picklist.GetPickListOrderUseCaseInterface -import javax.inject.Inject - -class GetMyPickListOrderUseCase @Inject constructor( - private val localPickListOrderRepository: LocalPickListOrderRepository -) : GetPickListOrderUseCaseInterface { - override suspend operator fun invoke() = localPickListOrderRepository.myListOrder -} diff --git a/domain/src/main/java/com/squirtles/domain/order/usecase/SaveFavoriteListOrderUseCase.kt b/domain/src/main/java/com/squirtles/domain/order/usecase/SaveFavoriteListOrderUseCase.kt deleted file mode 100644 index 1ea94ee2..00000000 --- a/domain/src/main/java/com/squirtles/domain/order/usecase/SaveFavoriteListOrderUseCase.kt +++ /dev/null @@ -1,12 +0,0 @@ -package com.squirtles.domain.order.usecase - -import com.squirtles.domain.model.Order -import com.squirtles.domain.order.LocalPickListOrderRepository -import com.squirtles.domain.picklist.SavePickListOrderUseCaseInterface -import javax.inject.Inject - -class SaveFavoriteListOrderUseCase @Inject constructor( - private val localPickListOrderRepository: LocalPickListOrderRepository -) : SavePickListOrderUseCaseInterface { - override suspend operator fun invoke(order: Order) = localPickListOrderRepository.saveFavoriteListOrder(order) -} diff --git a/domain/src/main/java/com/squirtles/domain/order/usecase/SaveMyPickListOrderUseCase.kt b/domain/src/main/java/com/squirtles/domain/order/usecase/SaveMyPickListOrderUseCase.kt deleted file mode 100644 index 6d06fa5d..00000000 --- a/domain/src/main/java/com/squirtles/domain/order/usecase/SaveMyPickListOrderUseCase.kt +++ /dev/null @@ -1,12 +0,0 @@ -package com.squirtles.domain.order.usecase - -import com.squirtles.domain.model.Order -import com.squirtles.domain.order.LocalPickListOrderRepository -import com.squirtles.domain.picklist.SavePickListOrderUseCaseInterface -import javax.inject.Inject - -class SaveMyPickListOrderUseCase @Inject constructor( - private val localPickListOrderRepository: LocalPickListOrderRepository -) : SavePickListOrderUseCaseInterface { - override suspend operator fun invoke(order: Order) = localPickListOrderRepository.saveMyListOrder(order) -} diff --git a/domain/src/main/java/com/squirtles/domain/pick/FirebasePickDataSource.kt b/domain/src/main/java/com/squirtles/domain/pick/FirebasePickDataSource.kt deleted file mode 100644 index a3f33764..00000000 --- a/domain/src/main/java/com/squirtles/domain/pick/FirebasePickDataSource.kt +++ /dev/null @@ -1,12 +0,0 @@ -package com.squirtles.domain.pick - -import com.squirtles.domain.model.Pick - -interface FirebasePickDataSource { - suspend fun fetchPick(pickId: String): Result - suspend fun fetchPicksInArea(lat: Double, lng: Double, radiusInM: Double): Result> - suspend fun createPick(pick: Pick): Result - suspend fun deletePick(pickId: String, userId: String): Result - suspend fun fetchMyPicks(userId: String): Result> - suspend fun fetchFavoritePicks(userId: String): Result> -} diff --git a/domain/src/main/java/com/squirtles/domain/pick/FirebasePickRepository.kt b/domain/src/main/java/com/squirtles/domain/pick/FirebasePickRepository.kt deleted file mode 100644 index e572597d..00000000 --- a/domain/src/main/java/com/squirtles/domain/pick/FirebasePickRepository.kt +++ /dev/null @@ -1,12 +0,0 @@ -package com.squirtles.domain.pick - -import com.squirtles.domain.model.Pick - -interface FirebasePickRepository { - suspend fun createPick(pick: Pick): Result - suspend fun deletePick(pickId: String, userId: String): Result - suspend fun fetchPick(pickID: String): Result - suspend fun fetchPicksInArea(lat: Double, lng: Double, radiusInM: Double): Result> - suspend fun fetchMyPicks(userId: String): Result> - suspend fun fetchFavoritePicks(userId: String): Result> -} diff --git a/domain/src/main/java/com/squirtles/domain/picklist/FetchPickListUseCaseInterface.kt b/domain/src/main/java/com/squirtles/domain/picklist/FetchPickListUseCaseInterface.kt deleted file mode 100644 index f072c2f1..00000000 --- a/domain/src/main/java/com/squirtles/domain/picklist/FetchPickListUseCaseInterface.kt +++ /dev/null @@ -1,7 +0,0 @@ -package com.squirtles.domain.picklist - -import com.squirtles.domain.model.Pick - -interface FetchPickListUseCaseInterface { - suspend operator fun invoke(uid: String): Result> -} diff --git a/domain/src/main/java/com/squirtles/domain/picklist/GetPickListOrderUseCaseInterface.kt b/domain/src/main/java/com/squirtles/domain/picklist/GetPickListOrderUseCaseInterface.kt deleted file mode 100644 index d2e3350c..00000000 --- a/domain/src/main/java/com/squirtles/domain/picklist/GetPickListOrderUseCaseInterface.kt +++ /dev/null @@ -1,7 +0,0 @@ -package com.squirtles.domain.picklist - -import com.squirtles.domain.model.Order - -interface GetPickListOrderUseCaseInterface { - suspend operator fun invoke(): Order -} diff --git a/domain/src/main/java/com/squirtles/domain/picklist/SavePickListOrderUseCaseInterface.kt b/domain/src/main/java/com/squirtles/domain/picklist/SavePickListOrderUseCaseInterface.kt deleted file mode 100644 index d7664764..00000000 --- a/domain/src/main/java/com/squirtles/domain/picklist/SavePickListOrderUseCaseInterface.kt +++ /dev/null @@ -1,7 +0,0 @@ -package com.squirtles.domain.picklist - -import com.squirtles.domain.model.Order - -interface SavePickListOrderUseCaseInterface { - suspend operator fun invoke(order: Order) -} diff --git a/domain/src/main/java/com/squirtles/domain/player/MediaPlayerListenerUseCase.kt b/domain/src/main/java/com/squirtles/domain/player/MediaPlayerListenerUseCase.kt deleted file mode 100644 index 1a395da4..00000000 --- a/domain/src/main/java/com/squirtles/domain/player/MediaPlayerListenerUseCase.kt +++ /dev/null @@ -1,141 +0,0 @@ -package com.squirtles.domain.player - -import androidx.media3.common.Player -import androidx.media3.common.Tracks -import com.squirtles.domain.model.PlayerState -import com.squirtles.mediaservice.MediaControllerProvider -import kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.Job -import kotlinx.coroutines.cancel -import kotlinx.coroutines.channels.awaitClose -import kotlinx.coroutines.delay -import kotlinx.coroutines.flow.MutableStateFlow -import kotlinx.coroutines.flow.callbackFlow -import kotlinx.coroutines.flow.collect -import kotlinx.coroutines.flow.distinctUntilChanged -import kotlinx.coroutines.flow.first -import kotlinx.coroutines.flow.map -import kotlinx.coroutines.flow.onEach -import kotlinx.coroutines.flow.update -import kotlinx.coroutines.isActive -import kotlinx.coroutines.launch -import javax.inject.Inject - -/* 현재 플레이어에 이벤트 리스너 등록 -> 로딩,재생,seekbar 탐색, 재생시간 등 데이터를 UI데이터로 변환 */ -class MediaPlayerListenerUseCase @Inject constructor( - private val mediaControllerProvider: MediaControllerProvider -) { - private val coroutineScope = CoroutineScope(Dispatchers.Main) - private var timerJob: Job? = null - - fun playerStateFlow() = callbackFlow { - val mediaController = mediaControllerProvider.mediaControllerFlow.first() - - val currentPlayerState = MutableStateFlow( - if (mediaController.currentMediaItem != null) { - PlayerState( - id = mediaController.currentMediaItem!!.mediaId, - isLoading = mediaController.isLoading, - isPlaying = mediaController.isPlaying, - hasNext = mediaController.hasNextMediaItem(), - currentPosition = mediaController.currentPosition, - duration = mediaController.duration, - bufferPercentage = mediaController.bufferedPercentage - ) - } else { - PlayerState() - } - ) - - val playerListener = object : Player.Listener { - override fun onIsPlayingChanged(isPlaying: Boolean) { - super.onIsPlayingChanged(isPlaying) - currentPlayerState.update { - it.copy(isPlaying = isPlaying) - } - } - - override fun onIsLoadingChanged(isLoading: Boolean) { - super.onIsLoadingChanged(isLoading) -// currentPlayerState.update { -// it.copy(id = mediaController.currentMediaItem?.mediaId ?: "", isLoading = isLoading) -// } - } - - override fun onPositionDiscontinuity( - oldPosition: Player.PositionInfo, - newPosition: Player.PositionInfo, - reason: Int - ) { - super.onPositionDiscontinuity(oldPosition, newPosition, reason) - if (reason == Player.DISCONTINUITY_REASON_SEEK) { - currentPlayerState.update { - it.copy(currentPosition = mediaController.currentPosition) - } - } - } - - override fun onPlaybackStateChanged(playbackState: Int) { - super.onPlaybackStateChanged(playbackState) - when (playbackState) { - Player.STATE_ENDED -> { - mediaController.seekTo(0) - mediaController.pause() - } - - Player.STATE_READY -> { - currentPlayerState.update { - it.copy(id = mediaController.currentMediaItem?.mediaId ?: "") - } - } - } - } - - override fun onTracksChanged(tracks: Tracks) { - super.onTracksChanged(tracks) - currentPlayerState.value = PlayerState() - } - } - - coroutineScope.launch { - currentPlayerState - .map { it.isLoading.not() && it.isPlaying } - .distinctUntilChanged() - .collect { isPlaying -> - if (isPlaying) { - timerJob = coroutineScope.launch { - val startDuration = mediaController.currentPosition - val maxDuration = mediaController.contentDuration - - while (isActive && startDuration <= maxDuration) { - currentPlayerState.update { - it.copy( - currentPosition = mediaController.currentPosition, - bufferPercentage = mediaController.bufferedPercentage - ) - } - delay(1000L) - } - } - } else { - timerJob?.cancel() - timerJob = null - } - } - } - - coroutineScope.launch { - currentPlayerState - .onEach { send(it) } - .collect() - } - - mediaController.addListener(playerListener) - - awaitClose { - mediaController.removeListener(playerListener) - coroutineScope.cancel() - } - } -} diff --git a/domain/src/main/java/com/squirtles/domain/player/MediaPlayerUseCase.kt b/domain/src/main/java/com/squirtles/domain/player/MediaPlayerUseCase.kt deleted file mode 100644 index 41878c52..00000000 --- a/domain/src/main/java/com/squirtles/domain/player/MediaPlayerUseCase.kt +++ /dev/null @@ -1,132 +0,0 @@ -package com.squirtles.domain.player - -import androidx.media3.common.MediaItem -import androidx.media3.common.MediaMetadata -import androidx.media3.common.Player -import androidx.media3.session.MediaController -import com.squirtles.domain.model.Pick -import com.squirtles.mediaservice.MediaControllerProvider -import kotlinx.coroutines.flow.first -import kotlinx.coroutines.flow.flow -import javax.inject.Inject - -/* */ -class MediaPlayerUseCase @Inject constructor( - private val mediaControllerProvider: MediaControllerProvider, -) { - private var mediaController: MediaController? = null - - val audioSessionId = flow { - emit(mediaControllerProvider.audioSessionFlow.first()) - } - - suspend fun readyPlayer() { - mediaController = mediaControllerProvider.mediaControllerFlow.first() - } - - fun addMediaItem(pick: Pick) { - val mediaItem = pick.toMediaItem() - mediaController?.addMediaItem(mediaItem) - } - - fun addMediaItems(picks: List) { - mediaController?.run { - addMediaItems(picks.map { it.toMediaItem() }) - } - } - - fun setMediaItem(pick: Pick) { - mediaController?.run { - clearMediaItems() - pause() - setMediaItem(pick.toMediaItem()) - prepare() - repeatMode = Player.REPEAT_MODE_OFF - volume = 0.5f - } - } - - fun setMediaItems(picks: List) { - mediaController?.run { - setMediaItems(picks.map { - it.toMediaItem() - }) - prepare() - playWhenReady = false - repeatMode = Player.REPEAT_MODE_ALL - volume = 0.5f - } - } - - fun changeRepeatMode(repeatable: Boolean) { - mediaController?.repeatMode = if (repeatable) { - Player.REPEAT_MODE_ALL - } else { - Player.REPEAT_MODE_OFF - } - } - - fun play() { - mediaController?.play() - } - - fun pause() { - mediaController?.pause() - } - - fun stop() { - mediaController?.stop() - } - - fun release() { - mediaController?.release() - } - - fun next() { - if (mediaController?.hasNextMediaItem() == true) { - mediaController?.seekToNextMediaItem() - } - } - - fun previous() { - if (mediaController?.hasPreviousMediaItem() == true) { - mediaController?.seekToPreviousMediaItem() - } else { - mediaController?.seekToDefaultPosition() - } - } - - fun advanceBy() { - mediaController?.apply { - seekTo(currentPosition + com.squirtles.mediaservice.SEEK_TO_DURATION) - } - } - - fun rewindBy() { - mediaController?.apply { - seekTo(currentPosition - com.squirtles.mediaservice.SEEK_TO_DURATION) - } - } - - fun onSeekingStarted() { - mediaController?.seekToDefaultPosition() - } - - fun onSeekingFinished(time: Long) { - mediaController?.seekTo(time) - } - - private fun Pick.toMediaItem(): MediaItem { - return MediaItem.Builder() - .setMediaId(this.id) - .setMediaMetadata( - MediaMetadata.Builder() - .setTitle(song.songName) - .setArtist(song.artistName) - .setAlbumTitle(song.albumName) - .build() - ) - .setUri(song.previewUrl) - .build() - } -} diff --git a/domain/src/main/java/com/squirtles/domain/user/FirebaseUserDataSource.kt b/domain/src/main/java/com/squirtles/domain/user/FirebaseUserDataSource.kt deleted file mode 100644 index 3ca3b1d7..00000000 --- a/domain/src/main/java/com/squirtles/domain/user/FirebaseUserDataSource.kt +++ /dev/null @@ -1,15 +0,0 @@ -package com.squirtles.domain.user - -import com.squirtles.domain.model.User - -interface FirebaseUserDataSource { - suspend fun fetchUser(uid: String): Result - suspend fun createGoogleIdUser( - uid: String, - email: String, - userName: String?, - userProfileImage: String? - ): Result - suspend fun updateUserName(uid: String, newUserName: String): Result - suspend fun deleteUser(uid: String): Result -} diff --git a/domain/src/main/java/com/squirtles/domain/user/FirebaseUserRepository.kt b/domain/src/main/java/com/squirtles/domain/user/FirebaseUserRepository.kt deleted file mode 100644 index 569f2633..00000000 --- a/domain/src/main/java/com/squirtles/domain/user/FirebaseUserRepository.kt +++ /dev/null @@ -1,10 +0,0 @@ -package com.squirtles.domain.user - -import com.squirtles.domain.model.User - -interface FirebaseUserRepository { - suspend fun createGoogleIdUser(uid: String, email: String, userName: String?, userProfileImage: String?): Result - suspend fun fetchUser(userId: String): Result - suspend fun updateUserName(userId: String, newUserName: String): Result - suspend fun deleteUser(uid: String): Result -} diff --git a/domain/src/main/java/com/squirtles/domain/user/LocalUserDataSource.kt b/domain/src/main/java/com/squirtles/domain/user/LocalUserDataSource.kt deleted file mode 100644 index 514a3615..00000000 --- a/domain/src/main/java/com/squirtles/domain/user/LocalUserDataSource.kt +++ /dev/null @@ -1,13 +0,0 @@ -package com.squirtles.domain.user - -import com.squirtles.domain.model.User -import kotlinx.coroutines.flow.Flow - -interface LocalUserDataSource { - val currentUser: User? - - fun readUidDataStore(): Flow - suspend fun saveUidDataStore(uid: String) - suspend fun saveCurrentUser(user: User) - suspend fun clearUser() -} diff --git a/domain/src/main/java/com/squirtles/domain/user/LocalUserRepository.kt b/domain/src/main/java/com/squirtles/domain/user/LocalUserRepository.kt deleted file mode 100644 index 243f9226..00000000 --- a/domain/src/main/java/com/squirtles/domain/user/LocalUserRepository.kt +++ /dev/null @@ -1,13 +0,0 @@ -package com.squirtles.domain.user - -import com.squirtles.domain.model.User -import kotlinx.coroutines.flow.Flow - -interface LocalUserRepository { - val currentUser: User? - - fun readUserIdDataStore(): Flow - suspend fun saveUserIdDataStore(userId: String) - suspend fun saveCurrentUser(user: User) - suspend fun clearUser(): Result -} diff --git a/domain/src/main/java/com/squirtles/domain/user/usecase/CreateGoogleIdUserUseCase.kt b/domain/src/main/java/com/squirtles/domain/user/usecase/CreateGoogleIdUserUseCase.kt deleted file mode 100644 index e04f8010..00000000 --- a/domain/src/main/java/com/squirtles/domain/user/usecase/CreateGoogleIdUserUseCase.kt +++ /dev/null @@ -1,23 +0,0 @@ -package com.squirtles.domain.user.usecase - -import com.squirtles.domain.model.User -import com.squirtles.domain.user.LocalUserRepository -import com.squirtles.domain.user.FirebaseUserRepository -import javax.inject.Inject - -class CreateGoogleIdUserUseCase @Inject constructor( - private val localUserRepository: LocalUserRepository, - private val firebaseUserRepository: FirebaseUserRepository -) { - suspend operator fun invoke( - uid: String, - email: String, - userName: String? = null, - userProfileImage: String? = null - ): Result = firebaseUserRepository.createGoogleIdUser( - uid, - email, - userName, - userProfileImage - ) -} diff --git a/domain/src/main/java/com/squirtles/domain/user/usecase/DeleteAccountUseCase.kt b/domain/src/main/java/com/squirtles/domain/user/usecase/DeleteAccountUseCase.kt deleted file mode 100644 index 1c999df1..00000000 --- a/domain/src/main/java/com/squirtles/domain/user/usecase/DeleteAccountUseCase.kt +++ /dev/null @@ -1,50 +0,0 @@ -package com.squirtles.domain.user.usecase - -import android.util.Log -import com.google.firebase.auth.FirebaseAuth -import com.squirtles.domain.favorite.usecase.DeleteFavoriteUseCase -import com.squirtles.domain.pick.usecase.DeletePickUseCase -import com.squirtles.domain.pick.usecase.FetchFavoritePicksUseCase -import com.squirtles.domain.pick.usecase.FetchMyPicksUseCase -import com.squirtles.domain.user.FirebaseUserRepository -import kotlinx.coroutines.async -import kotlinx.coroutines.awaitAll -import kotlinx.coroutines.coroutineScope -import javax.inject.Inject - -class DeleteAccountUseCase @Inject constructor( - private val fetchFavoritePicksUseCase: FetchFavoritePicksUseCase, - private val deleteFavoriteUseCase: DeleteFavoriteUseCase, - private val fetchMyPicksUseCase: FetchMyPicksUseCase, - private val deletePickUseCase: DeletePickUseCase, - private val firebaseUserRepository: FirebaseUserRepository -) { - suspend operator fun invoke() = coroutineScope { - FirebaseAuth.getInstance().currentUser?.let { currentUser -> - try { - // 1. 좋아한 픽 삭제 - val favoritePicks = fetchFavoritePicksUseCase(currentUser.uid).getOrNull() ?: emptyList() - val favoritePicksDeleteJobs = favoritePicks.map { - async { deleteFavoriteUseCase(it.id, currentUser.uid) } - } - - // 2. 등록한 픽 삭제 - val myPicks = fetchMyPicksUseCase(currentUser.uid).getOrNull() ?: emptyList() - val myPicksDeleteJobs = myPicks.map { - async { deletePickUseCase(it.id, currentUser.uid) } - } - - // 모든 삭제 작업이 끝날 때까지 기다림 - (favoritePicksDeleteJobs + myPicksDeleteJobs).awaitAll() - - // 3. Firebase Firestore 유저 정보 삭제 - firebaseUserRepository.deleteUser(currentUser.uid) - - // 4. Firebase Auth 유저 삭제 - currentUser.delete() - } catch (e: Exception) { - Log.e("DeleteAccount", "Error deleting user account: ${e.message}") - } - } - } -} diff --git a/domain/src/main/java/com/squirtles/domain/user/usecase/FetchUserByIdUseCase.kt b/domain/src/main/java/com/squirtles/domain/user/usecase/FetchUserByIdUseCase.kt deleted file mode 100644 index f5295d0b..00000000 --- a/domain/src/main/java/com/squirtles/domain/user/usecase/FetchUserByIdUseCase.kt +++ /dev/null @@ -1,11 +0,0 @@ -package com.squirtles.domain.user.usecase - -import com.squirtles.domain.user.FirebaseUserRepository -import javax.inject.Inject - -class FetchUserByIdUseCase @Inject constructor( - private val firebaseUserRepository: FirebaseUserRepository -) { - suspend operator fun invoke(userId: String) = - firebaseUserRepository.fetchUser(userId) -} diff --git a/domain/src/main/java/com/squirtles/domain/user/usecase/GetCurrentUidUseCase.kt b/domain/src/main/java/com/squirtles/domain/user/usecase/GetCurrentUidUseCase.kt deleted file mode 100644 index 912c969f..00000000 --- a/domain/src/main/java/com/squirtles/domain/user/usecase/GetCurrentUidUseCase.kt +++ /dev/null @@ -1,8 +0,0 @@ -package com.squirtles.domain.user.usecase - -import com.google.firebase.auth.FirebaseAuth -import javax.inject.Inject - -class GetCurrentUidUseCase @Inject constructor() { - operator fun invoke() = FirebaseAuth.getInstance().currentUser?.uid -} diff --git a/domain/src/main/java/com/squirtles/domain/user/usecase/SignOutUseCase.kt b/domain/src/main/java/com/squirtles/domain/user/usecase/SignOutUseCase.kt deleted file mode 100644 index f071accd..00000000 --- a/domain/src/main/java/com/squirtles/domain/user/usecase/SignOutUseCase.kt +++ /dev/null @@ -1,8 +0,0 @@ -package com.squirtles.domain.user.usecase - -import com.google.firebase.auth.FirebaseAuth -import javax.inject.Inject - -class SignOutUseCase @Inject constructor() { - operator fun invoke() = FirebaseAuth.getInstance().signOut() -} diff --git a/domain/src/main/java/com/squirtles/domain/user/usecase/UpdateUserNameUseCase.kt b/domain/src/main/java/com/squirtles/domain/user/usecase/UpdateUserNameUseCase.kt deleted file mode 100644 index b79f4b99..00000000 --- a/domain/src/main/java/com/squirtles/domain/user/usecase/UpdateUserNameUseCase.kt +++ /dev/null @@ -1,11 +0,0 @@ -package com.squirtles.domain.user.usecase - -import com.squirtles.domain.user.FirebaseUserRepository -import javax.inject.Inject - -class UpdateUserNameUseCase @Inject constructor( - private val firebaseUserRepository: FirebaseUserRepository -) { - suspend operator fun invoke(userId: String, newUserName: String) = - firebaseUserRepository.updateUserName(userId, newUserName) -} diff --git a/domain/user/src/main/java/com/squirtles/user/usecase/DeleteAccountUseCase.kt b/domain/user/src/main/java/com/squirtles/user/usecase/DeleteAccountUseCase.kt index 2c8b9d64..f36bb95c 100644 --- a/domain/user/src/main/java/com/squirtles/user/usecase/DeleteAccountUseCase.kt +++ b/domain/user/src/main/java/com/squirtles/user/usecase/DeleteAccountUseCase.kt @@ -1,9 +1,9 @@ package com.squirtles.user.usecase import com.squirtles.favorite.usecase.DeleteFavoriteUseCase -import com.squirtles.pick.usecase.DeletePickUseCase -import com.squirtles.pick.usecase.FetchFavoritePicksUseCase -import com.squirtles.pick.usecase.FetchMyPicksUseCase +import com.squirtles.domain.pick.usecase.DeletePickUseCase +import com.squirtles.domain.pick.usecase.FetchFavoritePicksUseCase +import com.squirtles.domain.pick.usecase.FetchMyPicksUseCase import com.squirtles.user.FirebaseUserRepository import kotlinx.coroutines.async import kotlinx.coroutines.awaitAll diff --git a/domain/user/src/main/java/com/squirtles/user/usecase/SignOutUseCase.kt b/domain/user/src/main/java/com/squirtles/user/usecase/SignOutUseCase.kt index f5ac64e6..073a0426 100644 --- a/domain/user/src/main/java/com/squirtles/user/usecase/SignOutUseCase.kt +++ b/domain/user/src/main/java/com/squirtles/user/usecase/SignOutUseCase.kt @@ -1,4 +1,4 @@ -package com.squirtles.domain.user.usecase +package com.squirtles.user.usecase import com.squirtles.user.FirebaseUserRepository import javax.inject.Inject diff --git a/feature/create/src/main/java/com/squirtles/create/CreatePickViewModel.kt b/feature/create/src/main/java/com/squirtles/create/CreatePickViewModel.kt index 6128b313..5d326848 100644 --- a/feature/create/src/main/java/com/squirtles/create/CreatePickViewModel.kt +++ b/feature/create/src/main/java/com/squirtles/create/CreatePickViewModel.kt @@ -13,7 +13,7 @@ import com.squirtles.model.LocationPoint import com.squirtles.model.Pick import com.squirtles.model.Song import com.squirtles.navigation.SearchRoute -import com.squirtles.pick.usecase.CreatePickUseCase +import com.squirtles.domain.pick.usecase.CreatePickUseCase import com.squirtles.user.usecase.FetchUserByIdUseCase import com.squirtles.user.usecase.GetCurrentUidUseCase import com.squirtles.util.serializableType diff --git a/feature/detail/.gitignore b/feature/detail/.gitignore new file mode 100644 index 00000000..42afabfd --- /dev/null +++ b/feature/detail/.gitignore @@ -0,0 +1 @@ +/build \ No newline at end of file diff --git a/feature/detail/build.gradle.kts b/feature/detail/build.gradle.kts new file mode 100644 index 00000000..a5428afc --- /dev/null +++ b/feature/detail/build.gradle.kts @@ -0,0 +1,28 @@ +plugins { + alias(libs.plugins.musicroad.feature) + alias(libs.plugins.kotlin.serialization) +} + +android { + namespace = "com.squirtles.detail" +} + +dependencies { + implementation(projects.audioVisualizer) + implementation(projects.core.account) + implementation(projects.core.musicplayer) + implementation(projects.domain.pick) + implementation(projects.domain.picklist) + implementation(projects.domain.user) + implementation(projects.domain.favorite) + + implementation(libs.coil.compose) + implementation(libs.androidx.media3.exoplayer) + implementation(libs.googleid) + + testImplementation(libs.junit) + androidTestImplementation(libs.bundles.test) + + // Serialization + implementation(libs.kotlinx.serialization.json) +} diff --git a/domain/consumer-rules.pro b/feature/detail/consumer-rules.pro similarity index 100% rename from domain/consumer-rules.pro rename to feature/detail/consumer-rules.pro diff --git a/domain/proguard-rules.pro b/feature/detail/proguard-rules.pro similarity index 100% rename from domain/proguard-rules.pro rename to feature/detail/proguard-rules.pro diff --git a/domain/src/androidTest/java/com/squirtles/domain/ExampleInstrumentedTest.kt b/feature/detail/src/androidTest/java/com/squirtles/detail/ExampleInstrumentedTest.kt similarity index 85% rename from domain/src/androidTest/java/com/squirtles/domain/ExampleInstrumentedTest.kt rename to feature/detail/src/androidTest/java/com/squirtles/detail/ExampleInstrumentedTest.kt index 7871980c..b2b4c3a0 100644 --- a/domain/src/androidTest/java/com/squirtles/domain/ExampleInstrumentedTest.kt +++ b/feature/detail/src/androidTest/java/com/squirtles/detail/ExampleInstrumentedTest.kt @@ -1,4 +1,4 @@ -package com.squirtles.domain +package com.squirtles.detail import androidx.test.platform.app.InstrumentationRegistry import androidx.test.ext.junit.runners.AndroidJUnit4 @@ -19,6 +19,6 @@ class ExampleInstrumentedTest { fun useAppContext() { // Context of the app under test. val appContext = InstrumentationRegistry.getInstrumentation().targetContext - assertEquals("com.squirtles.domain.test", appContext.packageName) + assertEquals("com.squirtles.detail.test", appContext.packageName) } -} \ No newline at end of file +} diff --git a/feature/detail/src/main/java/com/squirtles/detail/DetailViewModel.kt b/feature/detail/src/main/java/com/squirtles/detail/DetailViewModel.kt new file mode 100644 index 00000000..c7df6bc4 --- /dev/null +++ b/feature/detail/src/main/java/com/squirtles/detail/DetailViewModel.kt @@ -0,0 +1,174 @@ +package com.squirtles.detail + +import androidx.core.graphics.toColorInt +import androidx.lifecycle.SavedStateHandle +import androidx.lifecycle.ViewModel +import androidx.lifecycle.viewModelScope +import com.squirtles.favorite.usecase.CreateFavoriteUseCase +import com.squirtles.favorite.usecase.DeleteFavoriteUseCase +import com.squirtles.favorite.usecase.FetchIsFavoriteUseCase +import com.squirtles.model.Creator +import com.squirtles.model.LocationPoint +import com.squirtles.model.Pick +import com.squirtles.model.Song +import com.squirtles.domain.pick.usecase.DeletePickUseCase +import com.squirtles.domain.pick.usecase.FetchPickUseCase +import com.squirtles.user.usecase.GetCurrentUidUseCase +import com.squirtles.util.throttleFirst +import dagger.hilt.android.lifecycle.HiltViewModel +import kotlinx.coroutines.async +import kotlinx.coroutines.flow.MutableSharedFlow +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.asSharedFlow +import kotlinx.coroutines.flow.asStateFlow +import kotlinx.coroutines.launch +import javax.inject.Inject + +@HiltViewModel +class DetailViewModel @Inject constructor( + private val fetchPickUseCase: FetchPickUseCase, + private val fetchIsFavoriteUseCase: FetchIsFavoriteUseCase, + private val getCurrentUidUseCase: GetCurrentUidUseCase, + private val deletePickUseCase: DeletePickUseCase, + private val createFavoriteUseCase: CreateFavoriteUseCase, + private val deleteFavoriteUseCase: DeleteFavoriteUseCase, +) : ViewModel() { + + private val _pickDetailUiState = MutableStateFlow(PickDetailUiState.Loading) + val pickDetailUiState = _pickDetailUiState.asStateFlow() + + private var _currentTab = DETAIL_PICK_TAB + val currentTab get() = _currentTab + + private val _favoriteAction = MutableSharedFlow() + val favoriteAction = _favoriteAction.asSharedFlow() + + private val actionClick = MutableSharedFlow>() + + init { + viewModelScope.launch { + actionClick + .throttleFirst(1000) + .collect { (pickId, isAdding) -> + getUid()?.let { uid -> + if (isAdding) { + addFavorite(pickId, uid) + } else { + deleteFavorite(pickId, uid) + } + } + } + } + } + + fun getUid() = getCurrentUidUseCase() + + fun fetchPick(pickId: String) { + viewModelScope.launch { + _pickDetailUiState.emit(PickDetailUiState.Loading) + + val fetchPick = async { fetchPickUseCase(pickId) } + + val fetchIsFavoriteResult: Result = getUid()?.let { + async { fetchIsFavoriteUseCase(pickId, it) }.await() + } ?: Result.success(false) // 비회원일시 항상 false + val fetchPickResult = fetchPick.await() + + when { + fetchPickResult.isSuccess && fetchIsFavoriteResult.isSuccess -> { + _pickDetailUiState.emit( + PickDetailUiState.Success( + pick = fetchPickResult.getOrDefault(DEFAULT_PICK), + isFavorite = fetchIsFavoriteResult.getOrDefault(false) + ) + ) + } + + else -> { + _pickDetailUiState.emit(PickDetailUiState.Error) + } + } + } + } + + fun toggleFavoritePick(pickId: String, isAdding: Boolean) { + viewModelScope.launch { + actionClick.emit(pickId to isAdding) + } + } + + fun deletePick(pickId: String) { + viewModelScope.launch { + getUid()?.let { uid -> + _pickDetailUiState.emit(PickDetailUiState.Loading) + deletePickUseCase(pickId, uid) + .onSuccess { + _pickDetailUiState.emit(PickDetailUiState.Deleted) + } + .onFailure { + _pickDetailUiState.emit(PickDetailUiState.Error) + } + } + } + } + + private fun addFavorite(pickId: String, uid: String) { + viewModelScope.launch { + createFavoriteUseCase(pickId, uid) + .onSuccess { + _favoriteAction.emit(FavoriteAction.ADDED) + val currentUiState = _pickDetailUiState.value as? PickDetailUiState.Success + currentUiState?.let { successState -> + _pickDetailUiState.emit(successState.copy(isFavorite = true)) + } + } + .onFailure { + _pickDetailUiState.emit(PickDetailUiState.Error) + } + } + } + + private fun deleteFavorite(pickId: String, uid: String) { + viewModelScope.launch { + deleteFavoriteUseCase(pickId, uid) + .onSuccess { + _favoriteAction.emit(FavoriteAction.DELETED) + val currentUiState = _pickDetailUiState.value as? PickDetailUiState.Success + currentUiState?.let { successState -> + _pickDetailUiState.emit(successState.copy(isFavorite = false)) + } + } + .onFailure { + _pickDetailUiState.emit(PickDetailUiState.Error) + } + } + } + + fun setCurrentTab(index: Int) { + _currentTab = index + } + + companion object { + val DEFAULT_PICK = + Pick( + id = "", + song = Song( + id = "", + songName = "", + artistName = "", + albumName = "", + imageUrl = "", + genreNames = listOf(), + bgColor = "#000000".toColorInt(), + externalUrl = "", + previewUrl = "" + ), + comment = "", + createdAt = "", + createdBy = Creator(uid = "", userName = "짱구"), + favoriteCount = 0, + location = LocationPoint(1.0, 1.0), + musicVideoUrl = "", + ) + } +} diff --git a/feature/detail/src/main/java/com/squirtles/detail/FavoriteAction.kt b/feature/detail/src/main/java/com/squirtles/detail/FavoriteAction.kt new file mode 100644 index 00000000..b4066409 --- /dev/null +++ b/feature/detail/src/main/java/com/squirtles/detail/FavoriteAction.kt @@ -0,0 +1,5 @@ +package com.squirtles.detail + +enum class FavoriteAction { + ADDED, DELETED +} diff --git a/feature/detail/src/main/java/com/squirtles/detail/PickDetailScreen.kt b/feature/detail/src/main/java/com/squirtles/detail/PickDetailScreen.kt new file mode 100644 index 00000000..80c7499b --- /dev/null +++ b/feature/detail/src/main/java/com/squirtles/detail/PickDetailScreen.kt @@ -0,0 +1,541 @@ +package com.squirtles.detail + +import android.app.Activity +import android.content.Context +import android.widget.Toast +import androidx.activity.compose.BackHandler +import androidx.compose.foundation.background +import androidx.compose.foundation.clickable +import androidx.compose.foundation.interaction.MutableInteractionSource +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size +import androidx.compose.foundation.layout.statusBarsPadding +import androidx.compose.foundation.pager.HorizontalPager +import androidx.compose.foundation.pager.rememberPagerState +import androidx.compose.foundation.rememberScrollState +import androidx.compose.foundation.verticalScroll +import androidx.compose.material3.CircularProgressIndicator +import androidx.compose.material3.Scaffold +import androidx.compose.runtime.Composable +import androidx.compose.runtime.DisposableEffect +import androidx.compose.runtime.LaunchedEffect +import androidx.compose.runtime.SideEffect +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableIntStateOf +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.rememberCoroutineScope +import androidx.compose.runtime.saveable.rememberSaveable +import androidx.compose.runtime.setValue +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Brush +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.graphics.graphicsLayer +import androidx.compose.ui.graphics.luminance +import androidx.compose.ui.platform.LocalContext +import androidx.compose.ui.platform.LocalView +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.dp +import androidx.compose.ui.util.lerp +import androidx.compose.ui.zIndex +import androidx.core.content.ContextCompat.getString +import androidx.core.view.WindowInsetsControllerCompat +import androidx.hilt.navigation.compose.hiltViewModel +import androidx.lifecycle.Lifecycle +import androidx.lifecycle.compose.LocalLifecycleOwner +import androidx.lifecycle.compose.collectAsStateWithLifecycle +import androidx.lifecycle.flowWithLifecycle +import com.squirtles.account.AccountViewModel +import com.squirtles.account.GoogleId +import com.squirtles.common.ui.DialogTextButton +import com.squirtles.common.ui.HorizontalSpacer +import com.squirtles.common.ui.MessageAlertDialog +import com.squirtles.common.ui.SignInAlertDialog +import com.squirtles.common.ui.VerticalSpacer +import com.squirtles.common.ui.theme.Black +import com.squirtles.common.ui.theme.Primary +import com.squirtles.common.ui.theme.White +import com.squirtles.detail.DetailViewModel.Companion.DEFAULT_PICK +import com.squirtles.detail.components.CircleAlbumCover +import com.squirtles.detail.components.PickCommentText +import com.squirtles.detail.components.DetailPickTopAppBar +import com.squirtles.detail.components.MusicVideoKnob +import com.squirtles.detail.components.PickInformation +import com.squirtles.detail.components.SongInfo +import com.squirtles.detail.components.music.MusicPlayer +import com.squirtles.detail.videoplayer.MusicVideoScreen +import com.squirtles.model.Pick +import com.squirtles.musicplayer.PlayerServiceViewModel +import kotlinx.coroutines.launch +import kotlin.math.absoluteValue + +@Composable +fun PickDetailScreen( + pickId: String, + onUserInfoClick: (String) -> Unit, + onBackClick: () -> Unit, + onDeleted: (Context) -> Unit, + playerServiceViewModel: PlayerServiceViewModel, + detailViewModel: DetailViewModel = hiltViewModel(), + accountViewModel: AccountViewModel = hiltViewModel() +) { + val context = LocalContext.current + val lifecycleOwner = LocalLifecycleOwner.current + val uiState by detailViewModel.pickDetailUiState.collectAsStateWithLifecycle() + var showDeletePickDialog by rememberSaveable { mutableStateOf(false) } + var showProcessIndicator by rememberSaveable { mutableStateOf(false) } + var isMusicVideoAvailable by remember { mutableStateOf(false) } + + // Sign In Dialog + var showSignInDialog by remember { mutableStateOf(false) } + var signInDialogDescription by remember { mutableStateOf("") } + + BackHandler { + if (showProcessIndicator.not()) { + onBackClick() + } + } + + LaunchedEffect(Unit) { + detailViewModel.fetchPick(pickId) + + launch { + accountViewModel.signInSuccess + .flowWithLifecycle(lifecycleOwner.lifecycle, Lifecycle.State.STARTED) + .collect { isSuccess -> + if (isSuccess) { + showProcessIndicator = false + detailViewModel.fetchPick(pickId) + } + } + } + } + + when (uiState) { + PickDetailUiState.Loading -> { + Box( + modifier = Modifier + .fillMaxSize() + .background(Black), + contentAlignment = Alignment.Center + ) { + CircularProgressIndicator() + } + } + + is PickDetailUiState.Success -> { + val lifecycleOwner = LocalLifecycleOwner.current + val pick = (uiState as PickDetailUiState.Success).pick + val isFavorite = (uiState as PickDetailUiState.Success).isFavorite + val isNonMember = detailViewModel.getUid() == null + val isCreatedBySelf = detailViewModel.getUid() == pick.createdBy.uid + var favoriteCount by rememberSaveable { mutableIntStateOf(pick.favoriteCount) } + val onActionClick: () -> Unit = { + when { + isNonMember -> { + signInDialogDescription = getString(context, R.string.sign_in_dialog_title_favorite) + showSignInDialog = true + } + + isCreatedBySelf -> { + playerServiceViewModel.onPause() + showDeletePickDialog = true + } + + isFavorite -> { + showProcessIndicator = true + detailViewModel.toggleFavoritePick( + pickId = pickId, + isAdding = false + ) + } + + else -> { + showProcessIndicator = true + detailViewModel.toggleFavoritePick( + pickId = pickId, + isAdding = true + ) + } + } + } + + val scrollScope = rememberCoroutineScope() + val pagerState = rememberPagerState( + pageCount = { if (isMusicVideoAvailable) 2 else 1 } + ) + + LaunchedEffect(Unit) { + detailViewModel.favoriteAction + .flowWithLifecycle(lifecycleOwner.lifecycle, Lifecycle.State.STARTED) + .collect { action -> + when (action) { + FavoriteAction.ADDED -> { + showProcessIndicator = false + favoriteCount += 1 + context.showShortToast(context.getString(R.string.success_add_to_favorite)) + } + + FavoriteAction.DELETED -> { + showProcessIndicator = false + favoriteCount -= 1 + context.showShortToast(context.getString(R.string.success_delete_at_favorite)) + } + } + } + } + + // 비디오 플레이어 설정 + LaunchedEffect(pick) { + playerServiceViewModel.readyPlayer() + playerServiceViewModel.setMediaItem(pick) + isMusicVideoAvailable = pick.musicVideoUrl.isNotEmpty() + } + + LaunchedEffect(pagerState) { + pagerState.scrollToPage(page = detailViewModel.currentTab) + } + + DisposableEffect(Unit) { + onDispose { + detailViewModel.setCurrentTab(pagerState.currentPage) + } + } + + HorizontalPager( + state = pagerState + ) { page -> + when (page) { + DETAIL_PICK_TAB -> { + PickDetailContents( + pick = pick, + currentUid = detailViewModel.getUid(), + isFavorite = isFavorite, + pickUid = pick.createdBy.uid, + pickUserName = pick.createdBy.userName, + favoriteCount = favoriteCount, + isMusicVideoAvailable = isMusicVideoAvailable, + onUserInfoClick = onUserInfoClick, + playerServiceViewModel = playerServiceViewModel, + onBackClick = { + onBackClick() + }, + onActionClick = onActionClick, + ) + } + + MUSIC_VIDEO_TAB -> { + MusicVideoScreen( + pick = pick, + modifier = Modifier + .background(Black) + .graphicsLayer { + val pageOffset = ( + (pagerState.currentPage - page) + pagerState + .currentPageOffsetFraction + ).absoluteValue + alpha = lerp( + start = 0.5f, + stop = 1f, + fraction = 1f - pageOffset.coerceIn(0f, 1f) + ) + }, + onBackClick = { + scrollScope.launch { + pagerState.animateScrollToPage(page = DETAIL_PICK_TAB) + } + }, + ) + } + } + + // 페이지 전환에 따른 음원과 뮤비 재생 상태 + if (page != DETAIL_PICK_TAB) playerServiceViewModel.onPause() + } + } + + PickDetailUiState.Deleted -> { + LaunchedEffect(Unit) { + onBackClick() + onDeleted(context) + Toast.makeText( + context, + context.getString(R.string.success_delete_pick), + Toast.LENGTH_SHORT + ).show() + } + } + + PickDetailUiState.Error -> { + LaunchedEffect(Unit) { + Toast.makeText( + context, + context.getString(R.string.error_loading_pick_list), + Toast.LENGTH_SHORT + ).show() + } + + // Show default pick + PickDetailContents( + pick = DEFAULT_PICK, + currentUid = null, + isFavorite = false, + pickUid = "", + pickUserName = "", + favoriteCount = 0, + isMusicVideoAvailable = false, + playerServiceViewModel = playerServiceViewModel, + onUserInfoClick = onUserInfoClick, + onBackClick = onBackClick, + onActionClick = { } + ) + } + } + + if (showDeletePickDialog) { + MessageAlertDialog( + onDismissRequest = { + showDeletePickDialog = false + }, + title = stringResource(R.string.delete_pick_dialog_title), + body = stringResource(R.string.delete_pick_dialog_body), + buttons = { + DialogTextButton( + onClick = { + showDeletePickDialog = false + }, + text = stringResource(R.string.delete_pick_dialog_cancel) + ) + + HorizontalSpacer(8) + + DialogTextButton( + onClick = { + showDeletePickDialog = false + detailViewModel.deletePick(pickId) + }, + text = stringResource(R.string.delete_pick_dialog_delete), + textColor = Primary, + fontWeight = FontWeight.Bold + ) + }, + ) + } + + if (showProcessIndicator) { + Box( + modifier = Modifier + .fillMaxSize() + .background(Black.copy(alpha = 0.5F)) + .clickable( // 클릭 효과 제거 및 클릭 이벤트 무시 + interactionSource = remember { MutableInteractionSource() }, + indication = null, + onClick = {} + ), + contentAlignment = Alignment.Center + ) { + CircularProgressIndicator() + } + } + + if (showSignInDialog) { + SignInAlertDialog( + onDismissRequest = { showSignInDialog = false }, + onGoogleSignInClick = { + showSignInDialog = false + showProcessIndicator = true + GoogleId(context).signIn( + onSuccess = { uid, credential -> + accountViewModel.signIn(uid, credential) + } + ) + }, + description = signInDialogDescription + ) + } +} + +@Composable +private fun PickDetailContents( + pick: Pick, + isFavorite: Boolean, + currentUid: String?, + pickUid: String, + pickUserName: String, + favoriteCount: Int, + isMusicVideoAvailable: Boolean, + playerServiceViewModel: PlayerServiceViewModel, + onUserInfoClick: (String) -> Unit, + onBackClick: () -> Unit, + onActionClick: () -> Unit +) { + val isCreatedBySelf = remember { currentUid == pickUid } + val scrollState = rememberScrollState() + val dynamicBackgroundColor = Color(pick.song.bgColor) + val onDynamicBackgroundColor = if (dynamicBackgroundColor.luminance() >= 0.5f) Black else White + val view = LocalView.current + + val audioEffectColor = dynamicBackgroundColor.copy( + red = (dynamicBackgroundColor.red + 0.2f).coerceAtMost(1.0f), + green = (dynamicBackgroundColor.green + 0.2f).coerceAtMost(1.0f), + blue = (dynamicBackgroundColor.blue + 0.2f).coerceAtMost(1.0f), + ) + + val playerUiState by playerServiceViewModel.playerState.collectAsStateWithLifecycle() + val audioSessionId by playerServiceViewModel.audioSessionId.collectAsStateWithLifecycle(0) + + if (!view.isInEditMode) { + SideEffect { + val window = (view.context as Activity).window + val windowInsetsController = WindowInsetsControllerCompat(window, view) + val isLightStatusBar = dynamicBackgroundColor.luminance() >= 0.5f + + windowInsetsController.isAppearanceLightStatusBars = isLightStatusBar + } + } + + Scaffold( + topBar = { + DetailPickTopAppBar( + modifier = Modifier.statusBarsPadding(), + isCreatedBySelf = isCreatedBySelf, + isFavorite = isFavorite, + uid = pickUid, + userName = pickUserName, + onDynamicBackgroundColor = onDynamicBackgroundColor, + onUserInfoClick = onUserInfoClick, + onBackClick = { + onBackClick() + }, + onActionClick = { + onActionClick() + } + ) + } + ) { innerPadding -> + + Column( + modifier = Modifier + .fillMaxSize() + .background( + brush = Brush.verticalGradient( + colorStops = arrayOf( + 0.0f to dynamicBackgroundColor, + 0.47f to Black + ) + ) + ) + .padding(innerPadding) + ) { + Column( + modifier = Modifier + .fillMaxWidth() + .weight(1f) + .verticalScroll(scrollState) + .padding(top = 10.dp), + horizontalAlignment = Alignment.CenterHorizontally, + verticalArrangement = Arrangement.spacedBy(10.dp), + ) { + SongInfo( + song = pick.song, + dynamicOnBackgroundColor = onDynamicBackgroundColor, + modifier = Modifier.zIndex(1f) + ) + + Box( + modifier = Modifier + .fillMaxWidth() + .height(320.dp) + .align(Alignment.CenterHorizontally) + .zIndex(0f) + ) { + if (audioSessionId != 0) { + CircleAlbumCover( + modifier = Modifier + .size(320.dp) + .align(Alignment.Center), + song = pick.song, + currentPosition = { playerUiState.currentPosition }, + duration = { playerUiState.duration }, + audioEffectColor = audioEffectColor, + audioSessionId = audioSessionId, + onSeekChanged = { timeMs -> + playerServiceViewModel.onSeekingFinished(timeMs) + }, + ) + } + + if (isMusicVideoAvailable) { + MusicVideoKnob( + thumbnail = pick.musicVideoThumbnailUrl, + modifier = Modifier.align(Alignment.CenterEnd) + ) + } + } + + PickInformation( + formattedDate = pick.createdAt, + favoriteCount = favoriteCount + ) + + PickCommentText(comment = pick.comment) + + VerticalSpacer(height = 8) + } + + if (pick.song.previewUrl.isBlank().not()) { + MusicPlayer( + song = pick.song, + playerState = playerUiState, + onSeekChanged = { timeMs -> + playerServiceViewModel.onSeekingFinished(timeMs) + }, + onReplayForwardClick = { isForward -> + if (isForward) { + playerServiceViewModel.onAdvanceBy() + } else { + playerServiceViewModel.onRewindBy() + } + }, + onPauseToggle = { song -> + playerServiceViewModel.togglePlayPause(song) + }, + ) + } + } + } +} + +fun Context.showShortToast(message: String) { + Toast.makeText(this, message, Toast.LENGTH_SHORT).show() +} + +@Preview +@Composable +private fun PickDetailPreview() { + PickDetailContents( + pick = DEFAULT_PICK, + currentUid = null, + isFavorite = false, + pickUid = "", + pickUserName = "짱구", + favoriteCount = 0, + isMusicVideoAvailable = true, + onUserInfoClick = {}, + playerServiceViewModel = hiltViewModel(), + onBackClick = {}, + onActionClick = {}, + ) +} + +internal const val DETAIL_PICK_TAB = 0 +internal const val MUSIC_VIDEO_TAB = 1 diff --git a/feature/detail/src/main/java/com/squirtles/detail/PickDetailUiState.kt b/feature/detail/src/main/java/com/squirtles/detail/PickDetailUiState.kt new file mode 100644 index 00000000..39134689 --- /dev/null +++ b/feature/detail/src/main/java/com/squirtles/detail/PickDetailUiState.kt @@ -0,0 +1,10 @@ +package com.squirtles.detail + +import com.squirtles.model.Pick + +sealed class PickDetailUiState { + data object Loading : PickDetailUiState() + data class Success(val pick: Pick, val isFavorite: Boolean) : PickDetailUiState() + data object Deleted : PickDetailUiState() + data object Error : PickDetailUiState() +} diff --git a/feature/detail/src/main/java/com/squirtles/detail/components/CircleAlbumCover.kt b/feature/detail/src/main/java/com/squirtles/detail/components/CircleAlbumCover.kt new file mode 100644 index 00000000..e781c45d --- /dev/null +++ b/feature/detail/src/main/java/com/squirtles/detail/components/CircleAlbumCover.kt @@ -0,0 +1,82 @@ +package com.squirtles.detail.components + +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.aspectRatio +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.shape.CircleShape +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.clip +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.layout.ContentScale +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.unit.dp +import coil3.compose.AsyncImage +import com.miller198.audiovisualizer.configs.GradientConfig +import com.miller198.audiovisualizer.configs.VisualizerConfig +import com.miller198.audiovisualizer.soundeffect.SoundEffects +import com.miller198.audiovisualizer.ui.CircleVisualizer +import com.squirtles.detail.R +import com.squirtles.model.Song + +@Composable +internal fun CircleAlbumCover( + audioSessionId: Int, + song: Song, + currentPosition: () -> Long, + duration: () -> Long, + audioEffectColor: Color, + onSeekChanged: (Long) -> Unit, + modifier: Modifier = Modifier, +) { + Box( + modifier = modifier + ) { + CircleVisualizer( + audioSessionId = audioSessionId, + soundEffects = SoundEffects.BAR, + visualizerConfig = VisualizerConfig.FftCaptureConfig.Default, + gradientConfig = GradientConfig.Enabled( + color = audioEffectColor.mixedWhite(), + duration = 2500 + ), + color = audioEffectColor, + modifier = modifier.align(Alignment.Center) + ) + + PlayCircularProgressIndicator( + modifier = modifier + .fillMaxSize() + .padding(10.dp) + .align(Alignment.Center), + currentTime = currentPosition().toFloat(), + duration = duration().toFloat(), + strokeWidth = 5.dp, + onSeekChanged = { timeMs -> + onSeekChanged(timeMs.toLong()) + }, + ) + + AsyncImage( + model = song.getImageUrlWithSize(400, 400), + contentDescription = song.albumName + stringResource(id = R.string.pick_album_description), + modifier = Modifier + .fillMaxWidth() + .padding(horizontal = 30.dp) + .aspectRatio(1f) + .clip(CircleShape) + .align(Alignment.Center), + contentScale = ContentScale.Crop + ) + } +} + +private fun Color.mixedWhite(): Color = Color( + red = (Color.White.red + this.red) / 2, + green = (Color.White.green + this.green) / 2, + blue = (Color.White.blue + this.blue) / 2, + alpha = 0.9f +) diff --git a/feature/detail/src/main/java/com/squirtles/detail/components/DetailPickTopAppBar.kt b/feature/detail/src/main/java/com/squirtles/detail/components/DetailPickTopAppBar.kt new file mode 100644 index 00000000..2feb5130 --- /dev/null +++ b/feature/detail/src/main/java/com/squirtles/detail/components/DetailPickTopAppBar.kt @@ -0,0 +1,104 @@ +package com.squirtles.detail.components + +import androidx.compose.foundation.clickable +import androidx.compose.foundation.interaction.MutableInteractionSource +import androidx.compose.foundation.layout.Row +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.automirrored.filled.ArrowBack +import androidx.compose.material3.CenterAlignedTopAppBar +import androidx.compose.material3.ExperimentalMaterial3Api +import androidx.compose.material3.Icon +import androidx.compose.material3.IconButton +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.TopAppBarDefaults +import androidx.compose.runtime.Composable +import androidx.compose.runtime.remember +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.res.painterResource +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.text.font.FontWeight +import com.squirtles.common.ui.CreatedByOtherUserText +import com.squirtles.common.ui.CreatedBySelfText +import com.squirtles.detail.R + +@OptIn(ExperimentalMaterial3Api::class) +@Composable +fun DetailPickTopAppBar( + modifier: Modifier = Modifier, + isCreatedBySelf: Boolean, + isFavorite: Boolean, + uid: String, + userName: String, + onDynamicBackgroundColor: Color, + onUserInfoClick: (String) -> Unit, + onBackClick: () -> Unit, + onActionClick: () -> Unit, +) { + CenterAlignedTopAppBar( + title = { + Row( + modifier = Modifier.clickable( + interactionSource = remember { MutableInteractionSource() }, + indication = null, + onClick = { onUserInfoClick(uid) } + ), + verticalAlignment = Alignment.CenterVertically + ) { + if (isCreatedBySelf) { + CreatedBySelfText( + showUnderline = true, + color = onDynamicBackgroundColor, + style = MaterialTheme.typography.titleLarge.copy(fontWeight = FontWeight.Bold) + ) + } else { + CreatedByOtherUserText( + userName = userName, + modifier = Modifier.weight(weight = 1f, fill = false), + showUnderline = true, + color = onDynamicBackgroundColor, + style = MaterialTheme.typography.titleLarge.copy(fontWeight = FontWeight.Bold) + ) + } + } + }, + modifier = modifier, + navigationIcon = { + IconButton( + onClick = { onBackClick() } + ) { + Icon( + imageVector = Icons.AutoMirrored.Filled.ArrowBack, + contentDescription = stringResource(id = R.string.top_app_bar_back_description), + tint = onDynamicBackgroundColor + ) + } + }, + actions = { + IconButton( + onClick = { onActionClick() } + ) { + val painterResourceId = when { + isCreatedBySelf -> R.drawable.ic_delete + isFavorite -> R.drawable.ic_favorite_true + else -> R.drawable.ic_favorite_false + } + val stringResourceId = when { + isCreatedBySelf -> R.string.pick_delete_icon_description + isFavorite -> R.string.pick_favorite_true_icon_description + else -> R.string.pick_favorite_false_icon_description + } + + Icon( + painter = painterResource(painterResourceId), + contentDescription = stringResource(stringResourceId), + tint = onDynamicBackgroundColor + ) + } + }, + colors = TopAppBarDefaults.centerAlignedTopAppBarColors( + containerColor = Color.Transparent + ) + ) +} diff --git a/feature/detail/src/main/java/com/squirtles/detail/components/MusicVideoKnob.kt b/feature/detail/src/main/java/com/squirtles/detail/components/MusicVideoKnob.kt new file mode 100644 index 00000000..b865acc2 --- /dev/null +++ b/feature/detail/src/main/java/com/squirtles/detail/components/MusicVideoKnob.kt @@ -0,0 +1,80 @@ +package com.squirtles.detail.components + +import android.util.Size +import androidx.compose.animation.core.FastOutSlowInEasing +import androidx.compose.animation.core.RepeatMode +import androidx.compose.animation.core.animateFloat +import androidx.compose.animation.core.infiniteRepeatable +import androidx.compose.animation.core.rememberInfiniteTransition +import androidx.compose.animation.core.tween +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.offset +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.material3.Surface +import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.clip +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.graphics.painter.ColorPainter +import androidx.compose.ui.layout.ContentScale +import androidx.compose.ui.platform.LocalContext +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.dp +import coil3.compose.AsyncImage +import coil3.request.ImageRequest +import com.squirtles.common.ui.theme.White +import com.squirtles.common.ui.toImageUrlWithSize +import com.squirtles.detail.R + +@Composable +internal fun MusicVideoKnob( + thumbnail: String, + modifier: Modifier +) { + val infiniteTransition = rememberInfiniteTransition(label = "infinite repeatable") + val offsetX by infiniteTransition.animateFloat( + initialValue = 8f, + targetValue = 0f, + animationSpec = infiniteRepeatable( + animation = tween(durationMillis = 600, easing = FastOutSlowInEasing), + repeatMode = RepeatMode.Reverse + ), + label = "infinite repeatable" + ) + + Surface( + modifier = modifier + .size(width = 16.dp, height = 320.dp) + .offset(x = offsetX.dp), + color = White, + shape = RoundedCornerShape(topStart = 30.dp, bottomStart = 30.dp) + ) { + AsyncImage( + model = ImageRequest.Builder(LocalContext.current) + .data(thumbnail.toImageUrlWithSize(Size(560, 320))) + .build(), + contentDescription = stringResource(R.string.pick_swipe_icon_description), + modifier = Modifier + .fillMaxSize() + .padding(vertical = 2.dp) + .padding(start = 2.dp) + .clip(RoundedCornerShape(topStart = 30.dp, bottomStart = 30.dp)), + placeholder = ColorPainter(Color.Transparent), + error = ColorPainter(Color.Transparent), + contentScale = ContentScale.Crop, + ) + } +} + +@Preview +@Composable +private fun MusicVideoKnobPreview() { + MusicVideoKnob( + thumbnail = "", + modifier = Modifier + ) +} diff --git a/feature/detail/src/main/java/com/squirtles/detail/components/PickCommentText.kt b/feature/detail/src/main/java/com/squirtles/detail/components/PickCommentText.kt new file mode 100644 index 00000000..1b3b9575 --- /dev/null +++ b/feature/detail/src/main/java/com/squirtles/detail/components/PickCommentText.kt @@ -0,0 +1,56 @@ +package com.squirtles.detail.components + +import androidx.compose.foundation.background +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.heightIn +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.wrapContentHeight +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.clip +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.dp +import com.squirtles.common.ui.theme.Dark +import com.squirtles.common.ui.theme.Gray +import com.squirtles.common.ui.theme.White +import com.squirtles.detail.R + +@Composable +internal fun PickCommentText( + comment: String, + modifier: Modifier = Modifier +) { + Text( + text = comment.ifEmpty { stringResource(id = R.string.pick_comment_empty) }, + modifier = modifier + .fillMaxWidth() + .wrapContentHeight() + .heightIn(min = 100.dp) + .padding(horizontal = 30.dp) + .clip(shape = RoundedCornerShape(10.dp)) + .background(Dark) + .padding(horizontal = 12.dp, vertical = 8.dp), + style = MaterialTheme.typography.bodyLarge.copy(if (comment.isNotEmpty()) White else Gray) + ) +} + +@Preview +@Composable +private fun CommentTextPreview() { + Column(verticalArrangement = Arrangement.spacedBy(10.dp)) { + PickCommentText(comment = "") + + PickCommentText(comment = "노래가 좋아서 추천합니다.") + + PickCommentText( + comment = "노래가 너무너무너무너무너무너무너무너무너무너무너무너무너무너무너무너무너무너무너무너무너무너무너무너무너무너무" + + "너무너무너무너무너무너무너무너무너무너무너무너무너무너무너무너무너무너무너무너무너무너무 좋아요" + ) + } +} diff --git a/feature/detail/src/main/java/com/squirtles/detail/components/PickInformation.kt b/feature/detail/src/main/java/com/squirtles/detail/components/PickInformation.kt new file mode 100644 index 00000000..3e960705 --- /dev/null +++ b/feature/detail/src/main/java/com/squirtles/detail/components/PickInformation.kt @@ -0,0 +1,39 @@ +package com.squirtles.detail.components + +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.padding +import androidx.compose.material3.Icon +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.res.painterResource +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.unit.dp +import com.squirtles.common.ui.theme.Gray +import com.squirtles.detail.R + +@Composable +internal fun PickInformation(formattedDate: String, favoriteCount: Int) { + Row( + modifier = Modifier + .fillMaxWidth() + .padding(horizontal = 30.dp, vertical = 4.dp), + horizontalArrangement = Arrangement.spacedBy(4.dp), + verticalAlignment = Alignment.CenterVertically + ) { + if (formattedDate.isNotBlank()) { + Text(text = formattedDate, style = MaterialTheme.typography.titleMedium.copy(Gray)) + Icon( + painter = painterResource(id = R.drawable.ic_favorite), + contentDescription = stringResource(R.string.pick_favorite_count_icon_description), + modifier = Modifier.padding(start = 4.dp), + tint = Gray + ) + Text(text = "$favoriteCount", style = MaterialTheme.typography.titleMedium.copy(Gray)) + } + } +} diff --git a/feature/detail/src/main/java/com/squirtles/detail/components/PlayCircularProgressIndicator.kt b/feature/detail/src/main/java/com/squirtles/detail/components/PlayCircularProgressIndicator.kt new file mode 100644 index 00000000..ccf20264 --- /dev/null +++ b/feature/detail/src/main/java/com/squirtles/detail/components/PlayCircularProgressIndicator.kt @@ -0,0 +1,65 @@ +package com.squirtles.detail.components + +import androidx.compose.foundation.gestures.detectTapGestures +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.aspectRatio +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.material3.CircularProgressIndicator +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.graphics.StrokeCap +import androidx.compose.ui.input.pointer.pointerInput +import androidx.compose.ui.unit.Dp +import androidx.compose.ui.unit.dp +import com.squirtles.common.ui.theme.White +import kotlin.math.atan2 +import kotlin.math.hypot + +@Composable +internal fun PlayCircularProgressIndicator( + modifier: Modifier = Modifier, + currentTime: Float, + strokeWidth: Dp, + duration: Float, + innerRadiusRatio: Float = 0.5f, // 터치 비활성 비율 + onSeekChanged: (Float) -> Unit +) { + Box( + modifier = modifier + .aspectRatio(1f) + .pointerInput(Unit) { + detectTapGestures { offset -> + val centerX = size.width / 2 + val centerY = size.height / 2 + + val distanceFromCenter = hypot(offset.x - centerX, offset.y - centerY) // 터치 좌표랑 중심 사이 + val innerRadius = size.width * innerRadiusRatio / 2 + if (distanceFromCenter < innerRadius) return@detectTapGestures // 중앙의 반경 내 터치 무시 + + // 중심 기준 터치좌표의 각도 + val angle = Math + .toDegrees(atan2(offset.y - centerY, offset.x - centerX).toDouble()) + .toFloat() + + // 원의 위쪽 점(12시 방향)을 기준으로 시계 방향 + val normalizedAngle = ((angle + 360) % 360 + 90) % 360 + + // 각도를 재생 시간으로 변환 + val seekTime = (normalizedAngle / 360f) * duration + onSeekChanged(seekTime) + } + } + ) { + CircularProgressIndicator( + modifier = Modifier.fillMaxSize(), + progress = { currentTime / duration }, + color = White, + trackColor = Color.Transparent, + strokeWidth = strokeWidth, + strokeCap = StrokeCap.Round, + gapSize = 0.dp, + ) + } +} + diff --git a/feature/detail/src/main/java/com/squirtles/detail/components/SongInfo.kt b/feature/detail/src/main/java/com/squirtles/detail/components/SongInfo.kt new file mode 100644 index 00000000..df68de19 --- /dev/null +++ b/feature/detail/src/main/java/com/squirtles/detail/components/SongInfo.kt @@ -0,0 +1,39 @@ +package com.squirtles.detail.components + +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.text.style.TextAlign +import com.squirtles.model.Song + +@Composable +internal fun SongInfo( + song: Song, + dynamicOnBackgroundColor: Color, + modifier: Modifier, +) { + Column( + modifier = modifier.fillMaxWidth(), + horizontalAlignment = Alignment.CenterHorizontally + ) { + Text( + text = song.songName, + color = dynamicOnBackgroundColor, + textAlign = TextAlign.Center, + style = MaterialTheme.typography.titleLarge.copy(fontWeight = FontWeight.Bold) + ) + + Text( + text = song.artistName, + color = dynamicOnBackgroundColor, + textAlign = TextAlign.Center, + style = MaterialTheme.typography.bodyLarge + ) + } +} diff --git a/feature/detail/src/main/java/com/squirtles/detail/components/SwipeUpIcon.kt b/feature/detail/src/main/java/com/squirtles/detail/components/SwipeUpIcon.kt new file mode 100644 index 00000000..bb74fba0 --- /dev/null +++ b/feature/detail/src/main/java/com/squirtles/detail/components/SwipeUpIcon.kt @@ -0,0 +1,32 @@ +package com.squirtles.detail.components + +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.heightIn +import androidx.compose.material3.Icon +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.res.painterResource +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.unit.dp +import com.squirtles.common.ui.theme.White +import com.squirtles.detail.R + +@Composable +internal fun SwipeUpIcon( + swipeableModifier: Modifier +) { + Box( + modifier = swipeableModifier + .fillMaxWidth() + .heightIn(min = 100.dp) + ) { + Icon( + painter = painterResource(id = R.drawable.ic_swipe), + contentDescription = stringResource(id = R.string.pick_swipe_icon_description), + modifier = Modifier.align(Alignment.Center), + tint = White + ) + } +} diff --git a/feature/detail/src/main/java/com/squirtles/detail/components/music/MusicPlayer.kt b/feature/detail/src/main/java/com/squirtles/detail/components/music/MusicPlayer.kt new file mode 100644 index 00000000..689a6745 --- /dev/null +++ b/feature/detail/src/main/java/com/squirtles/detail/components/music/MusicPlayer.kt @@ -0,0 +1,55 @@ +package com.squirtles.detail.components.music + +import androidx.compose.foundation.background +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.unit.dp +import com.squirtles.common.ui.Constants.DEFAULT_PADDING +import com.squirtles.common.ui.theme.PlayerBackground +import com.squirtles.model.PlayerState +import com.squirtles.model.Song + +@Composable +fun MusicPlayer( + song: Song, + playerState: PlayerState, + onSeekChanged: (Long) -> Unit, + onReplayForwardClick: (Boolean) -> Unit, + onPauseToggle: (Song) -> Unit, +) { + Column( + modifier = Modifier + .fillMaxWidth() + .padding(horizontal = 8.dp, vertical = DEFAULT_PADDING) + .background( + color = PlayerBackground, + shape = RoundedCornerShape(16.dp) + ) + .padding(horizontal = 30.dp, vertical = DEFAULT_PADDING), + horizontalAlignment = Alignment.CenterHorizontally, + ) { + PlayBar( + duration = playerState.duration, + currentTime = playerState.currentPosition, + bufferPercentage = playerState.bufferPercentage, + isPlaying = playerState.isPlaying, + onSeekChanged = { timeMs -> + onSeekChanged(timeMs.toLong()) + }, + onReplayClick = { + onReplayForwardClick(false) + }, + onPauseToggle = { + onPauseToggle(song) + }, + onForwardClick = { + onReplayForwardClick(true) + }, + ) + } +} diff --git a/feature/detail/src/main/java/com/squirtles/detail/components/music/PlayBar.kt b/feature/detail/src/main/java/com/squirtles/detail/components/music/PlayBar.kt new file mode 100644 index 00000000..525c7cd5 --- /dev/null +++ b/feature/detail/src/main/java/com/squirtles/detail/components/music/PlayBar.kt @@ -0,0 +1,99 @@ +package com.squirtles.detail.components.music + +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.padding +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.dp +import com.squirtles.common.ui.theme.Gray +import com.squirtles.common.ui.theme.MusicRoadTheme +import java.util.concurrent.TimeUnit + +@Composable +internal fun PlayBar( + duration: Long, + currentTime: Long, + bufferPercentage: Int, + isPlaying: Boolean, + onSeekChanged: (timeMs: Float) -> Unit, + onReplayClick: () -> Unit, + onPauseToggle: () -> Unit, + onForwardClick: () -> Unit, + modifier: Modifier = Modifier, +) { + Column(modifier = modifier) { + Box( + modifier = Modifier + .fillMaxWidth() + .padding(top = 12.dp) + ) { + PlayProgressIndicator( + currentTime = currentTime.toFloat(), + duration = duration.toFloat(), + bufferPercentage = bufferPercentage.toFloat(), + onSeekChanged = onSeekChanged + ) + } + + Row( + modifier = Modifier + .fillMaxWidth() + .padding(top = 10.dp), + horizontalArrangement = Arrangement.SpaceBetween + ) { + Text( + text = currentTime.formatMinSec(), + color = Gray + ) + Box(modifier = Modifier.weight(2f)) { + PlayerControls( + modifier = Modifier.fillMaxWidth(), + isPlaying = isPlaying, + onReplayClick = onReplayClick, + onPauseToggle = onPauseToggle, + onForwardClick = onForwardClick + ) + } + Text( + text = duration.formatMinSec(), + color = Gray + ) + } + } +} + +private fun Long.formatMinSec(): String { + return if (this == 0L) { + "00:00" + } else { + val totalSeconds = + TimeUnit.MILLISECONDS.toSeconds(this) + (this % 1000 / 500) // 500ms 이상일 경우 반올림 + val minutes = totalSeconds / 60 + val seconds = totalSeconds % 60 + "${minutes.toString().padStart(2, '0')}:${seconds.toString().padStart(2, '0')}" + } +} + +@Preview +@Composable +private fun PlayBarPreview() { + MusicRoadTheme { + PlayBar( + modifier = Modifier.fillMaxWidth(), + duration = 10000L, + currentTime = 5000L, + bufferPercentage = 50, + onSeekChanged = {}, + isPlaying = true, + onReplayClick = {}, + onPauseToggle = {}, + onForwardClick = {} + ) + } +} diff --git a/feature/detail/src/main/java/com/squirtles/detail/components/music/PlayProgressIndicator.kt b/feature/detail/src/main/java/com/squirtles/detail/components/music/PlayProgressIndicator.kt new file mode 100644 index 00000000..11a587cf --- /dev/null +++ b/feature/detail/src/main/java/com/squirtles/detail/components/music/PlayProgressIndicator.kt @@ -0,0 +1,55 @@ +package com.squirtles.detail.components.music + +import androidx.compose.foundation.gestures.detectTapGestures +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.height +import androidx.compose.material3.LinearProgressIndicator +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.input.pointer.pointerInput +import androidx.compose.ui.unit.dp +import com.squirtles.common.ui.theme.DarkGray +import com.squirtles.common.ui.theme.White + +@Composable +internal fun PlayProgressIndicator( + modifier: Modifier = Modifier, + currentTime: Float, + bufferPercentage: Float, + duration: Float, + onSeekChanged: (Float) -> Unit +) { + Box(modifier = modifier) { + LinearProgressIndicator( + progress = { bufferPercentage / 100f }, + modifier = Modifier.fillMaxWidth(), + color = DarkGray, + trackColor = Color.DarkGray, + gapSize = 0.dp, + drawStopIndicator = { } + ) + + LinearProgressIndicator( + progress = { currentTime / duration }, + modifier = Modifier.fillMaxWidth(), + color = White, + trackColor = Color.Transparent, + gapSize = 0.dp, + drawStopIndicator = { } + ) + + Box( + modifier = modifier + .fillMaxWidth() + .height(3.dp) + .pointerInput(Unit) { + detectTapGestures { offset -> + val newTime = offset.x / size.width * duration + onSeekChanged(newTime) + } + } + ) + } +} diff --git a/feature/detail/src/main/java/com/squirtles/detail/components/music/PlayerControls.kt b/feature/detail/src/main/java/com/squirtles/detail/components/music/PlayerControls.kt new file mode 100644 index 00000000..487de399 --- /dev/null +++ b/feature/detail/src/main/java/com/squirtles/detail/components/music/PlayerControls.kt @@ -0,0 +1,101 @@ +package com.squirtles.detail.components.music + +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.size +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.filled.Forward5 +import androidx.compose.material.icons.filled.Pause +import androidx.compose.material.icons.filled.PlayArrow +import androidx.compose.material.icons.filled.Replay5 +import androidx.compose.material3.Icon +import androidx.compose.material3.IconButton +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.dp +import com.squirtles.common.ui.theme.MusicRoadTheme +import com.squirtles.common.ui.theme.White +import com.squirtles.detail.R + +@Composable +internal fun PlayerControls( + isPlaying: Boolean, + onReplayClick: () -> Unit, + onPauseToggle: () -> Unit, + onForwardClick: () -> Unit, + modifier: Modifier = Modifier +) { + + Row( + modifier = modifier, + ) { + Box( + modifier = modifier + .weight(1f), + contentAlignment = Alignment.Center + ) { + IconButton( + onClick = onReplayClick + ) { + Icon( + imageVector = Icons.Default.Replay5, + contentDescription = stringResource(id = R.string.player_replay_description), + modifier = Modifier.size(64.dp), + tint = White + ) + } + } + Box( + modifier = modifier + .weight(1f), + contentAlignment = Alignment.Center + ) { + IconButton( + onClick = onPauseToggle + ) { + Icon( + imageVector = if (isPlaying) { + Icons.Default.Pause + } else { + Icons.Default.PlayArrow + }, + contentDescription = stringResource(id = R.string.player_play_pause_description), + modifier = Modifier.size(64.dp), + tint = White + ) + } + } + + Box( + modifier = modifier + .weight(1f), + contentAlignment = Alignment.Center + ) { + IconButton( + onClick = onForwardClick + ) { + Icon( + imageVector = Icons.Default.Forward5, + contentDescription = stringResource(id = R.string.player_forward_description), + modifier = Modifier.size(64.dp), + tint = White + ) + } + } + } +} + +@Preview +@Composable +private fun PlayerControlsPreview() { + MusicRoadTheme { + PlayerControls( + isPlaying = true, + onReplayClick = {}, + onPauseToggle = {}, + onForwardClick = {}) + } +} diff --git a/feature/detail/src/main/java/com/squirtles/detail/navigation/PickDetailNavigation.kt b/feature/detail/src/main/java/com/squirtles/detail/navigation/PickDetailNavigation.kt new file mode 100644 index 00000000..c9f33788 --- /dev/null +++ b/feature/detail/src/main/java/com/squirtles/detail/navigation/PickDetailNavigation.kt @@ -0,0 +1,34 @@ +package com.squirtles.detail.navigation + +import android.content.Context +import androidx.navigation.NavController +import androidx.navigation.NavGraphBuilder +import androidx.navigation.NavOptions +import androidx.navigation.compose.composable +import androidx.navigation.toRoute +import com.squirtles.detail.PickDetailScreen +import com.squirtles.musicplayer.PlayerServiceViewModel +import com.squirtles.navigation.MapRoute + +fun NavController.navigatePickDetail(pickId: String, navOptions: NavOptions? = null) { + navigate(MapRoute.PickDetail(pickId), navOptions) +} + +fun NavGraphBuilder.detailNavGraph( + playerServiceViewModel: PlayerServiceViewModel, + onUserInfoClick: (String) -> Unit, + onBackClick: () -> Unit, + onDeleted: (Context) -> Unit, +) { + composable { backStackEntry -> + val pickId = backStackEntry.toRoute().pickId + + PickDetailScreen( + pickId = pickId, + playerServiceViewModel = playerServiceViewModel, + onUserInfoClick = onUserInfoClick, + onBackClick = onBackClick, + onDeleted = onDeleted, + ) + } +} diff --git a/feature/detail/src/main/java/com/squirtles/detail/videoplayer/MusicVideoPlayer.kt b/feature/detail/src/main/java/com/squirtles/detail/videoplayer/MusicVideoPlayer.kt new file mode 100644 index 00000000..b3873e1e --- /dev/null +++ b/feature/detail/src/main/java/com/squirtles/detail/videoplayer/MusicVideoPlayer.kt @@ -0,0 +1,113 @@ +package com.squirtles.detail.videoplayer + +import android.graphics.Matrix +import android.graphics.SurfaceTexture +import android.view.Surface +import android.view.TextureView +import androidx.annotation.OptIn +import androidx.compose.runtime.Composable +import androidx.compose.runtime.DisposableEffect +import androidx.compose.runtime.LaunchedEffect +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.rememberCoroutineScope +import androidx.compose.runtime.setValue +import androidx.compose.ui.platform.LocalContext +import androidx.compose.ui.viewinterop.AndroidView +import androidx.hilt.navigation.compose.hiltViewModel +import androidx.lifecycle.Lifecycle +import androidx.lifecycle.compose.LocalLifecycleOwner +import androidx.lifecycle.compose.collectAsStateWithLifecycle +import androidx.lifecycle.flowWithLifecycle +import com.squirtles.model.Pick +import dagger.hilt.android.UnstableApi +import kotlinx.coroutines.launch + +@OptIn(UnstableApi::class) +@Composable +fun MusicVideoPlayer( + musicVideoUrl: String, + videoPlayerViewModel: VideoPlayerViewModel = hiltViewModel() +) { + val context = LocalContext.current + val lifecycleOwner = LocalLifecycleOwner.current + val coroutineScope = rememberCoroutineScope() + + val textureView = remember { TextureView(context) } + var currentSurfaceTexture by remember { mutableStateOf(null) } + val videoSize by videoPlayerViewModel.videoSize.collectAsStateWithLifecycle() + + DisposableEffect(Unit) { + onDispose { + videoPlayerViewModel.pause() + videoPlayerViewModel.setLastPosition() + textureView.surfaceTexture?.release() + textureView.surfaceTextureListener = null + } + } + + LaunchedEffect(currentSurfaceTexture) { + if (currentSurfaceTexture != null) { + val surface = Surface(currentSurfaceTexture) + videoPlayerViewModel.setSurface(surface) + } + } + + AndroidView( + factory = { + videoPlayerViewModel.initializePlayer(context, musicVideoUrl) + textureView.apply { + surfaceTextureListener = object : TextureView.SurfaceTextureListener { + override fun onSurfaceTextureAvailable(surfaceTexture: SurfaceTexture, width: Int, height: Int) { + currentSurfaceTexture = surfaceTexture + coroutineScope.launch { + videoPlayerViewModel.videoSize + .flowWithLifecycle(lifecycleOwner.lifecycle, Lifecycle.State.STARTED) + .collect { + setVideoSize(textureView, it.width, it.height) + } + } + } + + override fun onSurfaceTextureSizeChanged(surfaceTexture: SurfaceTexture, width: Int, height: Int) { + + } + + override fun onSurfaceTextureDestroyed(surfaceTexture: SurfaceTexture): Boolean { + videoPlayerViewModel.setSurface(null) + return true + } + + override fun onSurfaceTextureUpdated(surfaceTexture: SurfaceTexture) { + // TODO + } + } + } + } + ) +} + +private fun setVideoSize(textureView: TextureView, videoWidth: Int, videoHeight: Int) { + // 비율 조정 + val viewWidth = textureView.width.toFloat() + val viewHeight = textureView.height.toFloat() + val videoAspect = videoWidth.toFloat() / videoHeight + val viewAspect = viewWidth / viewHeight + val scaleX: Float + val scaleY: Float + + if (videoAspect > viewAspect) { + scaleX = videoAspect / viewAspect + scaleY = 1f + } else { + scaleX = 1f + scaleY = viewAspect / videoAspect + } + + // 중앙 정렬 + val matrix = Matrix() + matrix.setScale(scaleX, scaleY, viewWidth / 2, viewHeight / 2) + + textureView.setTransform(matrix) +} diff --git a/feature/detail/src/main/java/com/squirtles/detail/videoplayer/MusicVideoScreen.kt b/feature/detail/src/main/java/com/squirtles/detail/videoplayer/MusicVideoScreen.kt new file mode 100644 index 00000000..dee1c942 --- /dev/null +++ b/feature/detail/src/main/java/com/squirtles/detail/videoplayer/MusicVideoScreen.kt @@ -0,0 +1,37 @@ +package com.squirtles.detail.videoplayer + +import androidx.activity.compose.BackHandler +import androidx.annotation.OptIn +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.material3.CircularProgressIndicator +import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.hilt.navigation.compose.hiltViewModel +import androidx.lifecycle.compose.collectAsStateWithLifecycle +import com.squirtles.model.Pick +import dagger.hilt.android.UnstableApi + +@OptIn(UnstableApi::class) +@Composable +fun MusicVideoScreen( + pick: Pick, + modifier: Modifier, + onBackClick: () -> Unit, + videoPlayerViewModel: VideoPlayerViewModel = hiltViewModel() +) { + val isLoading by videoPlayerViewModel.isLoading.collectAsStateWithLifecycle() + + BackHandler { onBackClick() } + + Box(modifier = modifier.fillMaxSize()) { + MusicVideoPlayer(pick.musicVideoUrl) + VideoPlayerOverlay(pick, onBackClick) + + if (isLoading) { + CircularProgressIndicator(Modifier.align(Alignment.Center)) + } + } +} diff --git a/feature/detail/src/main/java/com/squirtles/detail/videoplayer/VideoPlayerOverlay.kt b/feature/detail/src/main/java/com/squirtles/detail/videoplayer/VideoPlayerOverlay.kt new file mode 100644 index 00000000..e43bf43b --- /dev/null +++ b/feature/detail/src/main/java/com/squirtles/detail/videoplayer/VideoPlayerOverlay.kt @@ -0,0 +1,234 @@ +package com.squirtles.detail.videoplayer + +import androidx.compose.animation.core.Animatable +import androidx.compose.animation.core.tween +import androidx.compose.foundation.background +import androidx.compose.foundation.gestures.detectTapGestures +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.displayCutoutPadding +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.navigationBarsPadding +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size +import androidx.compose.foundation.layout.statusBarsPadding +import androidx.compose.foundation.shape.CircleShape +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.automirrored.filled.ArrowBack +import androidx.compose.material.icons.filled.Pause +import androidx.compose.material.icons.filled.PlayArrow +import androidx.compose.material.icons.filled.Replay +import androidx.compose.material3.CenterAlignedTopAppBar +import androidx.compose.material3.ExperimentalMaterial3Api +import androidx.compose.material3.Icon +import androidx.compose.material3.IconButton +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.Text +import androidx.compose.material3.TopAppBarDefaults +import androidx.compose.runtime.Composable +import androidx.compose.runtime.LaunchedEffect +import androidx.compose.runtime.remember +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.graphics.graphicsLayer +import androidx.compose.ui.input.pointer.pointerInput +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.text.style.TextAlign +import androidx.compose.ui.text.style.TextOverflow +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.dp +import androidx.compose.ui.unit.sp +import androidx.core.graphics.toColorInt +import androidx.hilt.navigation.compose.hiltViewModel +import androidx.lifecycle.compose.collectAsStateWithLifecycle +import com.squirtles.common.ui.VerticalSpacer +import com.squirtles.common.ui.theme.Black +import com.squirtles.common.ui.theme.Gray +import com.squirtles.common.ui.theme.White +import com.squirtles.detail.R +import com.squirtles.model.Creator +import com.squirtles.model.LocationPoint +import com.squirtles.model.Pick +import com.squirtles.model.Song + +@OptIn(ExperimentalMaterial3Api::class) +@Composable +fun VideoPlayerOverlay( + pick: Pick, + onBackClick: () -> Unit, + videoPlayerViewModel: VideoPlayerViewModel = hiltViewModel() +) { + val playerState = videoPlayerViewModel.playerState.collectAsStateWithLifecycle(VideoPlayerState.Playing) + val alpha = remember { Animatable(0f) } + + LaunchedEffect(Unit) { + videoPlayerViewModel.playerState.collect { + when (it) { + VideoPlayerState.Playing -> alpha.animateTo(0f, animationSpec = tween(durationMillis = 300)) + else -> alpha.animateTo(1.0f, animationSpec = tween(durationMillis = 300)) + } + } + } + + Box( + modifier = Modifier + .fillMaxSize() + .graphicsLayer { this.alpha = alpha.value } + .background(Black.copy(alpha = alpha.value.coerceAtMost(0.5f))) + .displayCutoutPadding() + .pointerInput(Unit) { + detectTapGestures( + onTap = { + when (playerState.value) { + VideoPlayerState.Pause -> videoPlayerViewModel.setPlayState(VideoPlayerState.Playing) + VideoPlayerState.Playing -> videoPlayerViewModel.setPlayState(VideoPlayerState.Pause) + VideoPlayerState.Replay -> videoPlayerViewModel.setPlayState(VideoPlayerState.Playing) + } + } + ) + } + ) { + CenterAlignedTopAppBar( + title = { + Text( + text = pick.createdBy.userName + stringResource(id = R.string.pick_app_bar_title_user), + color = White, + style = MaterialTheme.typography.titleLarge.copy(fontWeight = FontWeight.Bold), + ) + }, + modifier = Modifier + .statusBarsPadding() + .align(Alignment.TopCenter), + navigationIcon = { + IconButton( + onClick = onBackClick, + enabled = alpha.value > 0.5f + ) { + Icon( + imageVector = Icons.AutoMirrored.Filled.ArrowBack, + contentDescription = stringResource(id = R.string.pick_app_bar_back_description), + tint = White + ) + } + }, + colors = TopAppBarDefaults.centerAlignedTopAppBarColors( + containerColor = Color.Transparent + ) + ) + + Box( + modifier = Modifier + .size(70.dp) + .align(Alignment.Center) + .background(Black.copy(0.5f), shape = CircleShape) + ) { + Icon( + imageVector = when (playerState.value) { + VideoPlayerState.Pause -> Icons.Default.PlayArrow + VideoPlayerState.Playing -> Icons.Default.Pause + VideoPlayerState.Replay -> Icons.Default.Replay + }, + contentDescription = stringResource(id = R.string.player_play_pause_description), + modifier = Modifier + .align(Alignment.Center) + .size(40.dp), + tint = White + ) + } + + Column( + modifier = Modifier + .fillMaxWidth() + .padding(horizontal = 16.dp) + .padding(bottom = 40.dp) + .navigationBarsPadding() + .align(Alignment.BottomCenter) + ) { + Text( + text = pick.song.songName, + color = White, + fontSize = 20.sp, + fontWeight = FontWeight.Bold, + overflow = TextOverflow.Ellipsis, + maxLines = 1, + style = MaterialTheme.typography.titleLarge + ) + + VerticalSpacer(8) + + Text( + text = pick.song.artistName, + color = Gray, + fontSize = 20.sp, + overflow = TextOverflow.Ellipsis, + maxLines = 1, + style = MaterialTheme.typography.titleLarge + ) + + VerticalSpacer(24) + + if (pick.comment.isNotEmpty()) { + Text( + text = pick.comment, + color = White, + fontSize = 16.sp, + overflow = TextOverflow.Ellipsis, + maxLines = 2, + style = MaterialTheme.typography.titleLarge + ) + + VerticalSpacer(24) + } + + Row( + modifier = Modifier.fillMaxWidth(), + horizontalArrangement = Arrangement.End + ) { + Text( + text = stringResource(id = R.string.video_quality_description), + modifier = Modifier + .background(Gray.copy(0.3f), RoundedCornerShape(8.dp)) + .padding(vertical = 4.dp, horizontal = 8.dp), + color = White, + fontSize = 16.sp, + textAlign = TextAlign.Right, + style = MaterialTheme.typography.titleSmall + ) + } + } + } +} + +@Preview +@Composable +private fun VideoPlayerOverlayPreview() { + VideoPlayerOverlay( + pick = Pick( + id = "", + song = Song( + id = "", + songName = "Super Shy", + artistName = "뉴진스", + albumName = "NewJeans 'Super Shy' - Single", + imageUrl = "https://i.scdn.co/image/ab67616d0000b2733d98a0ae7c78a3a9babaf8af", + genreNames = listOf("KPop", "R&B", "Rap"), + bgColor = "#8fc1e2".toColorInt(), + externalUrl = "", + previewUrl = "" + ), + comment = "강남역 거리는 Super Shy 듣기 좋네요 ^-^!", + createdAt = "2024.11.02", + createdBy = Creator(uid = "", userName = "짱구"), + favoriteCount = 100, + location = LocationPoint(1.0, 1.0), + musicVideoUrl = "", + ), + onBackClick = {} + ) +} diff --git a/feature/detail/src/main/java/com/squirtles/detail/videoplayer/VideoPlayerState.kt b/feature/detail/src/main/java/com/squirtles/detail/videoplayer/VideoPlayerState.kt new file mode 100644 index 00000000..0a6277fc --- /dev/null +++ b/feature/detail/src/main/java/com/squirtles/detail/videoplayer/VideoPlayerState.kt @@ -0,0 +1,5 @@ +package com.squirtles.detail.videoplayer + +enum class VideoPlayerState { + Playing, Pause, Replay +} diff --git a/feature/detail/src/main/java/com/squirtles/detail/videoplayer/VideoPlayerViewModel.kt b/feature/detail/src/main/java/com/squirtles/detail/videoplayer/VideoPlayerViewModel.kt new file mode 100644 index 00000000..0683a0d9 --- /dev/null +++ b/feature/detail/src/main/java/com/squirtles/detail/videoplayer/VideoPlayerViewModel.kt @@ -0,0 +1,135 @@ +package com.squirtles.detail.videoplayer + +import android.content.Context +import android.util.Size +import android.view.Surface +import android.widget.Toast +import androidx.core.content.ContextCompat.getString +import androidx.lifecycle.ViewModel +import androidx.lifecycle.viewModelScope +import androidx.media3.common.MediaItem +import androidx.media3.common.Player +import androidx.media3.common.VideoSize +import androidx.media3.exoplayer.ExoPlayer +import com.squirtles.detail.R +import dagger.hilt.android.lifecycle.HiltViewModel +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.asStateFlow +import kotlinx.coroutines.flow.combine +import kotlinx.coroutines.launch +import javax.inject.Inject + +@HiltViewModel +class VideoPlayerViewModel @Inject constructor() : ViewModel() { + + private var player: ExoPlayer? = null + + private var _playerState = MutableStateFlow(VideoPlayerState.Playing) // 현재 플레이어의 상태 + val playerState = _playerState.asStateFlow() + + private var _videoSize = MutableStateFlow(Size(0, 0)) // 영상 크기 + val videoSize = _videoSize.asStateFlow() + + private val _isLoading = MutableStateFlow(true) + val isLoading = _isLoading.asStateFlow() + + private var lastPosition = 0L + + fun initializePlayer(context: Context, url: String) { + if (player != null) { + setPlayer() + return + } + + val exoPlayer = ExoPlayer.Builder(context).build().apply { + playWhenReady = false + addListener(object : Player.Listener { + override fun onVideoSizeChanged(videoSize: VideoSize) { + viewModelScope.launch { + _videoSize.emit(Size(videoSize.width, videoSize.height)) + } + } + + override fun onPlaybackStateChanged(state: Int) { + viewModelScope.launch { + when (state) { + Player.STATE_IDLE, + Player.STATE_BUFFERING -> { + _isLoading.emit(true) + } + + Player.STATE_READY -> { + _isLoading.emit(false) + } + + Player.STATE_ENDED -> { + Toast.makeText(context, getString(context, R.string.video_player_ended_message), Toast.LENGTH_SHORT).show() + _playerState.emit(VideoPlayerState.Replay) + seekTo(0) + } + } + } + } + }) + } + player = exoPlayer + + setPlayerSource(url) + } + + fun play() { + player?.play() + } + + fun pause() { + player?.pause() + } + + private fun setPlayerSource(url: String) { + player?.run { + val mediaItem = MediaItem.fromUri(url) + setMediaItem(mediaItem) + prepare() + seekTo(lastPosition) + } + setPlayer() + } + + private fun releasePlayer() { + player?.release() + player = null + } + + fun setPlayState(playerState: VideoPlayerState) { + viewModelScope.launch { + _playerState.emit(playerState) + } + } + + fun setLastPosition() { + lastPosition = player?.currentPosition ?: 0 + } + + fun setSurface(surface: Surface?) { + player?.setVideoSurface(surface) + } + + private fun setPlayer() { + viewModelScope.launch { + combine(isLoading, playerState) { isLoading, playState -> + !isLoading && (playState == VideoPlayerState.Playing) + }.collect { shouldPlay -> + if (shouldPlay) { + player?.play() + } else { + player?.pause() + } + } + } + } + + override fun onCleared() { + releasePlayer() + super.onCleared() + } +} diff --git a/feature/detail/src/main/res/drawable/ic_delete.xml b/feature/detail/src/main/res/drawable/ic_delete.xml new file mode 100644 index 00000000..47dd0af5 --- /dev/null +++ b/feature/detail/src/main/res/drawable/ic_delete.xml @@ -0,0 +1,10 @@ + + + + diff --git a/feature/detail/src/main/res/drawable/ic_favorite.xml b/feature/detail/src/main/res/drawable/ic_favorite.xml new file mode 100644 index 00000000..67b2268e --- /dev/null +++ b/feature/detail/src/main/res/drawable/ic_favorite.xml @@ -0,0 +1,10 @@ + + + + \ No newline at end of file diff --git a/feature/detail/src/main/res/drawable/ic_favorite_false.xml b/feature/detail/src/main/res/drawable/ic_favorite_false.xml new file mode 100644 index 00000000..1073fca7 --- /dev/null +++ b/feature/detail/src/main/res/drawable/ic_favorite_false.xml @@ -0,0 +1,10 @@ + + + + diff --git a/feature/detail/src/main/res/drawable/ic_favorite_true.xml b/feature/detail/src/main/res/drawable/ic_favorite_true.xml new file mode 100644 index 00000000..2acf3267 --- /dev/null +++ b/feature/detail/src/main/res/drawable/ic_favorite_true.xml @@ -0,0 +1,10 @@ + + + + diff --git a/feature/detail/src/main/res/drawable/ic_swipe.xml b/feature/detail/src/main/res/drawable/ic_swipe.xml new file mode 100644 index 00000000..20d5a921 --- /dev/null +++ b/feature/detail/src/main/res/drawable/ic_swipe.xml @@ -0,0 +1,13 @@ + + + + diff --git a/feature/detail/src/main/res/values/strings.xml b/feature/detail/src/main/res/values/strings.xml new file mode 100644 index 00000000..e5fad94b --- /dev/null +++ b/feature/detail/src/main/res/values/strings.xml @@ -0,0 +1,39 @@ + + + + 픽을 담기 위해\n로그인이 필요합니다 + 삭제되었습니다 + 픽 보관함에 추가되었습니다. + 픽 보관함에서 제거되었습니다. + 일시적인 오류가 발생했습니다. + + 삭제하시겠습니까? + 등록하신 픽이 삭제됩니다. + 취소 + 삭제하기 + + 픽을 담은 개수 + 등록된 한마디가 없습니다. + 앨범 이미지 + + + 님의 픽 + 뒤로 가기 + 재생/일시정지 + 480p + + + 밀어서 뮤직비디오 보기 + Apple Music에서 제공하는 30초 미리보기 영상입니다. + + + 상단 바 뒤로 가기 버튼 + 삭제하기 + 담은 픽 + 픽 담기 + + + 5초 뒤로 + 5초 앞으로 + + diff --git a/feature/detail/src/test/java/com/squirtles/detail/ExampleUnitTest.kt b/feature/detail/src/test/java/com/squirtles/detail/ExampleUnitTest.kt new file mode 100644 index 00000000..c84953f2 --- /dev/null +++ b/feature/detail/src/test/java/com/squirtles/detail/ExampleUnitTest.kt @@ -0,0 +1,17 @@ +package com.squirtles.detail + +import org.junit.Test + +import org.junit.Assert.* + +/** + * Example local unit test, which will execute on the development machine (host). + * + * See [testing documentation](http://d.android.com/tools/testing). + */ +class ExampleUnitTest { + @Test + fun addition_isCorrect() { + assertEquals(4, 2 + 2) + } +} diff --git a/feature/favorite/build.gradle.kts b/feature/favorite/build.gradle.kts index dda46676..90c66643 100644 --- a/feature/favorite/build.gradle.kts +++ b/feature/favorite/build.gradle.kts @@ -7,7 +7,6 @@ android { } dependencies { - implementation(projects.core.common) implementation(projects.core.picklist) implementation(projects.domain.picklist) implementation(projects.domain.favorite) diff --git a/feature/favorite/src/main/java/com/squirtles/favorite/FavoriteListViewModel.kt b/feature/favorite/src/main/java/com/squirtles/favorite/FavoriteListViewModel.kt index c31d6c04..9dbcfb59 100644 --- a/feature/favorite/src/main/java/com/squirtles/favorite/FavoriteListViewModel.kt +++ b/feature/favorite/src/main/java/com/squirtles/favorite/FavoriteListViewModel.kt @@ -3,7 +3,7 @@ package com.squirtles.favorite import com.squirtles.favorite.usecase.DeleteFavoriteUseCase import com.squirtles.order.usecase.GetFavoriteListOrderUseCase import com.squirtles.order.usecase.SaveFavoriteListOrderUseCase -import com.squirtles.pick.usecase.FetchFavoritePicksUseCase +import com.squirtles.domain.pick.usecase.FetchFavoritePicksUseCase import com.squirtles.picklist.PickListViewModel import com.squirtles.user.usecase.GetCurrentUidUseCase import dagger.hilt.android.lifecycle.HiltViewModel diff --git a/feature/favorite/src/main/java/com/squirtles/favorite/navigation/FavoriteNavigation.kt b/feature/favorite/src/main/java/com/squirtles/favorite/navigation/FavoriteNavigation.kt index 07e1f9bd..6b2e7df3 100644 --- a/feature/favorite/src/main/java/com/squirtles/favorite/navigation/FavoriteNavigation.kt +++ b/feature/favorite/src/main/java/com/squirtles/favorite/navigation/FavoriteNavigation.kt @@ -8,8 +8,8 @@ import androidx.navigation.toRoute import com.squirtles.favorite.FavoriteScreen import com.squirtles.navigation.MainRoute -fun NavController.navigateFavorite(userId: String, navOptions: NavOptions? = null) { - navigate(MainRoute.Favorite(userId), navOptions) +fun NavController.navigateFavorite(uid: String, navOptions: NavOptions? = null) { + navigate(MainRoute.Favorite(uid), navOptions) } fun NavGraphBuilder.favoriteNavGraph( diff --git a/feature/main/.gitignore b/feature/main/.gitignore new file mode 100644 index 00000000..42afabfd --- /dev/null +++ b/feature/main/.gitignore @@ -0,0 +1 @@ +/build \ No newline at end of file diff --git a/feature/main/build.gradle.kts b/feature/main/build.gradle.kts new file mode 100644 index 00000000..216bee11 --- /dev/null +++ b/feature/main/build.gradle.kts @@ -0,0 +1,30 @@ +plugins { + alias(libs.plugins.musicroad.feature) + +} + +android { + namespace = "com.squirtles.main" +} + +dependencies { + implementation(projects.feature.map) + implementation(projects.feature.create) + implementation(projects.feature.detail) + implementation(projects.feature.mypick) + implementation(projects.feature.favorite) + implementation(projects.feature.search) + implementation(projects.feature.userinfo) + implementation(projects.core.musicplayer) + implementation(projects.domain.user) + implementation(projects.domain.firebase) + + implementation(libs.androidx.core.ktx) + implementation(libs.androidx.appcompat) + implementation(libs.material) + implementation(libs.androidx.core.splashscreen) + implementation(libs.firebase.auth.ktx) + + testImplementation(libs.junit) + androidTestImplementation(libs.bundles.test) +} diff --git a/feature/main/consumer-rules.pro b/feature/main/consumer-rules.pro new file mode 100644 index 00000000..e69de29b diff --git a/feature/main/proguard-rules.pro b/feature/main/proguard-rules.pro new file mode 100644 index 00000000..481bb434 --- /dev/null +++ b/feature/main/proguard-rules.pro @@ -0,0 +1,21 @@ +# Add project specific ProGuard rules here. +# You can control the set of applied configuration files using the +# proguardFiles setting in build.gradle. +# +# For more details, see +# http://developer.android.com/guide/developing/tools/proguard.html + +# If your project uses WebView with JS, uncomment the following +# and specify the fully qualified class name to the JavaScript interface +# class: +#-keepclassmembers class fqcn.of.javascript.interface.for.webview { +# public *; +#} + +# Uncomment this to preserve the line number information for +# debugging stack traces. +#-keepattributes SourceFile,LineNumberTable + +# If you keep the line number information, uncomment this to +# hide the original source file name. +#-renamesourcefileattribute SourceFile \ No newline at end of file diff --git a/data/src/androidTest/java/com/squirtles/data/ExampleInstrumentedTest.kt b/feature/main/src/androidTest/java/com/squirtles/main/ExampleInstrumentedTest.kt similarity index 86% rename from data/src/androidTest/java/com/squirtles/data/ExampleInstrumentedTest.kt rename to feature/main/src/androidTest/java/com/squirtles/main/ExampleInstrumentedTest.kt index fe6ed515..92eae24b 100644 --- a/data/src/androidTest/java/com/squirtles/data/ExampleInstrumentedTest.kt +++ b/feature/main/src/androidTest/java/com/squirtles/main/ExampleInstrumentedTest.kt @@ -1,4 +1,4 @@ -package com.squirtles.data +package com.squirtles.main import androidx.test.platform.app.InstrumentationRegistry import androidx.test.ext.junit.runners.AndroidJUnit4 @@ -19,6 +19,6 @@ class ExampleInstrumentedTest { fun useAppContext() { // Context of the app under test. val appContext = InstrumentationRegistry.getInstrumentation().targetContext - assertEquals("com.squirtles.data.test", appContext.packageName) + assertEquals("com.squirtles.main.test", appContext.packageName) } -} \ No newline at end of file +} diff --git a/feature/main/src/main/AndroidManifest.xml b/feature/main/src/main/AndroidManifest.xml new file mode 100644 index 00000000..22e07b82 --- /dev/null +++ b/feature/main/src/main/AndroidManifest.xml @@ -0,0 +1,23 @@ + + + + + + + + + + + + + + + + + + + + diff --git a/feature/main/src/main/java/com/squirtles/main/LoadingState.kt b/feature/main/src/main/java/com/squirtles/main/LoadingState.kt new file mode 100644 index 00000000..f5ebf39a --- /dev/null +++ b/feature/main/src/main/java/com/squirtles/main/LoadingState.kt @@ -0,0 +1,9 @@ +package com.squirtles.main + +sealed class LoadingState { + data object Loading : LoadingState() + data class Success(val uid: String?) : LoadingState() + data class NetworkError(val error: String) : LoadingState() + data class CreatedUserError(val error: String) : LoadingState() + data class UserNotFoundError(val error: String) : LoadingState() +} diff --git a/feature/main/src/main/java/com/squirtles/main/MainActivity.kt b/feature/main/src/main/java/com/squirtles/main/MainActivity.kt new file mode 100644 index 00000000..dcfb7b69 --- /dev/null +++ b/feature/main/src/main/java/com/squirtles/main/MainActivity.kt @@ -0,0 +1,196 @@ +package com.squirtles.main + +import android.Manifest +import android.content.Context +import android.content.Intent +import android.net.Uri +import android.os.Bundle +import android.provider.Settings +import android.util.Log +import android.widget.Toast +import androidx.activity.compose.setContent +import androidx.activity.enableEdgeToEdge +import androidx.activity.viewModels +import androidx.appcompat.app.AppCompatActivity +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.core.content.PermissionChecker +import androidx.core.splashscreen.SplashScreen +import androidx.core.splashscreen.SplashScreen.Companion.installSplashScreen +import androidx.lifecycle.Lifecycle +import androidx.lifecycle.lifecycleScope +import androidx.lifecycle.repeatOnLifecycle +import androidx.navigation.compose.rememberNavController +import com.google.firebase.auth.FirebaseAuth +import com.squirtles.common.ui.theme.MusicRoadTheme +import com.squirtles.main.navigation.MainNavHost +import com.squirtles.main.navigation.MainNavigator +import com.squirtles.main.navigation.rememberMainNavigator +import dagger.hilt.android.AndroidEntryPoint +import kotlinx.coroutines.cancel +import kotlinx.coroutines.launch + +@AndroidEntryPoint +class MainActivity : AppCompatActivity() { + private val mainViewModel by viewModels() + + override fun onCreate(savedInstanceState: Bundle?) { + val splashScreen = installSplashScreen() + super.onCreate(savedInstanceState) + + setKeepOnScreenCondition(splashScreen) + enableEdgeToEdge() + + if (!checkSelfPermission()) { + requestPermissions(PERMISSIONS, REQUEST_PERMISSION_CODE) + } else { + setMusicRoadContent() + } + } + + override fun onRequestPermissionsResult( + requestCode: Int, + permissions: Array, + grantResults: IntArray + ) { + super.onRequestPermissionsResult(requestCode, permissions, grantResults) + + val deniedPermission = permissions.filterIndexed { index, _ -> + grantResults[index] == -1 + } + + if (requestCode == REQUEST_PERMISSION_CODE) { + if (deniedPermission.isEmpty()) { // 모든 권한이 허용된 경우 + setMusicRoadContent() + } else { // 권한이 하나라도 거부된 경우 + if (shouldShowRequestPermissionRationale(deniedPermission[0])) { // 권한 요청 가능 시 재요청 + showNeedPermissionDialog(true) { + showNeedPermissionDialog(false) + requestPermissions(deniedPermission.toTypedArray(), REQUEST_PERMISSION_CODE) + } + } else { // 권한 2번 거절 시 + mainViewModel.setCanRequestPermission(false) + showPermissionBar() + } + } + } + } + + override fun onResume() { + super.onResume() + + if (checkSelfPermission()) { + setMusicRoadContent() + } else if (mainViewModel.canRequestPermission.not()) { + showPermissionBar() + } + } + + private fun setKeepOnScreenCondition(splashScreen: SplashScreen) { + lifecycleScope.launch { + repeatOnLifecycle(Lifecycle.State.STARTED) { + mainViewModel.loadingState.collect { state -> + when (state) { + is LoadingState.Loading -> { + splashScreen.setKeepOnScreenCondition { true } + } + + is LoadingState.Success -> { + Log.d("MainActivity", "Success: ${state.uid}") + splashScreen.setKeepOnScreenCondition { false } + cancel() + } + + is LoadingState.UserNotFoundError -> { + FirebaseAuth.getInstance().signOut() + splashScreen.setKeepOnScreenCondition { false } + cancel() + } + + is LoadingState.NetworkError -> { + showToast(getString(R.string.main_network_error_message)) + finish() + } + + is LoadingState.CreatedUserError -> { + showToast(getString(R.string.main_create_user_fail_message)) + finish() + } + } + } + } + } + } + + private fun checkSelfPermission(): Boolean { + return PERMISSIONS.all { permission -> + PermissionChecker.checkSelfPermission(this, permission) == + PermissionChecker.PERMISSION_GRANTED + } + } + + private fun Context.showToast(message: String) { + Toast.makeText(this, message, Toast.LENGTH_LONG).show() + } + + private fun setMusicRoadContent() { + setContent { + val navigator: MainNavigator = rememberMainNavigator() + + MusicRoadTheme { + val navController = rememberNavController() + + MainNavHost( + navigator = navigator, + finishActivity = { this.finish() }, + ) + } + } + } + + private fun showNeedPermissionDialog( + showDialog: Boolean, + onConfirmClick: () -> Unit = {}, + ) { + setContent { + MusicRoadTheme { + Box( + modifier = Modifier.fillMaxSize(), + contentAlignment = Alignment.Center + ) { + NeedPermissionDialog( + showDialog = showDialog, + onConfirmClick = onConfirmClick + ) + } + } + } + } + + private fun showPermissionBar() { + setContent { + MusicRoadTheme { + PermissionBar( + onClick = { + val intent = + Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS) + val uri = Uri.fromParts("package", packageName, null) + intent.data = uri + startActivity(intent) + }, + ) + } + } + } + + companion object { + private val PERMISSIONS = arrayOf( + Manifest.permission.ACCESS_FINE_LOCATION, + Manifest.permission.ACCESS_COARSE_LOCATION, + Manifest.permission.RECORD_AUDIO + ) + private const val REQUEST_PERMISSION_CODE = 1000 + } +} diff --git a/feature/main/src/main/java/com/squirtles/main/MainViewModel.kt b/feature/main/src/main/java/com/squirtles/main/MainViewModel.kt new file mode 100644 index 00000000..60d4889c --- /dev/null +++ b/feature/main/src/main/java/com/squirtles/main/MainViewModel.kt @@ -0,0 +1,61 @@ +package com.squirtles.main + +import android.util.Log +import androidx.lifecycle.ViewModel +import androidx.lifecycle.viewModelScope +import com.squirtles.domain.firebase.FirebaseException +import com.squirtles.user.usecase.FetchUserByIdUseCase +import com.squirtles.user.usecase.GetCurrentUidUseCase +import dagger.hilt.android.lifecycle.HiltViewModel +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.asStateFlow +import kotlinx.coroutines.launch +import javax.inject.Inject + +@HiltViewModel +class MainViewModel @Inject constructor( + private val fetchUserByIdUseCase: FetchUserByIdUseCase, + private val getCurrentUidUseCase: GetCurrentUidUseCase +) : ViewModel() { + + private val _loadingState = MutableStateFlow(LoadingState.Loading) + val loadingState = _loadingState.asStateFlow() + + private var _canRequestPermission = true + val canRequestPermission get() = _canRequestPermission + + init { + viewModelScope.launch { + getCurrentUidUseCase().let { uid -> + Log.d("AutoLogin", "현재 uid : $uid") + if (uid == null) { // 비로그인 상태 + _loadingState.emit(LoadingState.Success(null)) + } else { + fetchUser(uid) + } + } + } + } + + fun setCanRequestPermission(canRequest: Boolean) { + _canRequestPermission = canRequest + } + + private suspend fun fetchUser(uid: String) { + fetchUserByIdUseCase(uid) + .onSuccess { + _loadingState.emit(LoadingState.Success(it.uid)) + } + .onFailure { exception -> + when (exception) { + is FirebaseException.FetchDocumentFailedException -> { + _loadingState.emit(LoadingState.UserNotFoundError(exception.message)) + } + + else -> { + _loadingState.emit(LoadingState.NetworkError(exception.message.toString())) + } + } + } + } +} diff --git a/feature/main/src/main/java/com/squirtles/main/NeedPermissionDialog.kt b/feature/main/src/main/java/com/squirtles/main/NeedPermissionDialog.kt new file mode 100644 index 00000000..f2db36ee --- /dev/null +++ b/feature/main/src/main/java/com/squirtles/main/NeedPermissionDialog.kt @@ -0,0 +1,95 @@ +package com.squirtles.main + +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.material3.BasicAlertDialog +import androidx.compose.material3.ButtonDefaults +import androidx.compose.material3.ExperimentalMaterial3Api +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.Surface +import androidx.compose.material3.Text +import androidx.compose.material3.TextButton +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.text.style.TextAlign +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.dp +import com.squirtles.common.ui.HorizontalSpacer +import com.squirtles.common.ui.VerticalSpacer +import com.squirtles.common.ui.theme.Black +import com.squirtles.common.ui.theme.DarkGray +import com.squirtles.common.ui.theme.MusicRoadTheme +import com.squirtles.common.ui.theme.White + +@OptIn(ExperimentalMaterial3Api::class) +@Composable +fun NeedPermissionDialog( + showDialog: Boolean, + onConfirmClick: () -> Unit, +) { + if (showDialog) { + BasicAlertDialog( + onDismissRequest = {}, + ) { + Surface( + modifier = Modifier.fillMaxWidth(), + shape = RoundedCornerShape(16.dp), + color = White + ) { + Column( + modifier = Modifier.padding(24.dp), + horizontalAlignment = Alignment.CenterHorizontally, + verticalArrangement = Arrangement.Center + ) { + Text( + text = stringResource(R.string.request_permissions_title), + color = Black, + fontWeight = FontWeight.Bold, + style = MaterialTheme.typography.bodyLarge + ) + + VerticalSpacer(8) + + Text( + text = stringResource(R.string.request_permissions_body), + color = Black, + textAlign = TextAlign.Center, + style = MaterialTheme.typography.bodyLarge + ) + + VerticalSpacer(24) + + TextButton( + onClick = onConfirmClick, + colors = ButtonDefaults.buttonColors().copy( + containerColor = Color.Transparent, + contentColor = DarkGray + ) + ) { + Text(stringResource(R.string.confirm_text)) + } + + HorizontalSpacer(8) + } + } + } + } +} + +@Preview(showBackground = true) +@Composable +fun NeedPermissionDialogPreview() { + MusicRoadTheme { + NeedPermissionDialog( + showDialog = true, + onConfirmClick = {}, + ) + } +} diff --git a/feature/main/src/main/java/com/squirtles/main/PermissionBar.kt b/feature/main/src/main/java/com/squirtles/main/PermissionBar.kt new file mode 100644 index 00000000..ea2e50bc --- /dev/null +++ b/feature/main/src/main/java/com/squirtles/main/PermissionBar.kt @@ -0,0 +1,80 @@ +package com.squirtles.main + +import androidx.compose.foundation.background +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.WindowInsets +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.navigationBars +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.wrapContentHeight +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.material3.CardDefaults +import androidx.compose.material3.ElevatedCard +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.Scaffold +import androidx.compose.material3.Text +import androidx.compose.material3.TextButton +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.platform.LocalContext +import androidx.compose.ui.unit.dp +import com.squirtles.common.ui.Constants.DEFAULT_PADDING + +@Composable +fun PermissionBar( + onClick: () -> Unit, +) { + val context = LocalContext.current + + Scaffold( + contentWindowInsets = WindowInsets.navigationBars + ) { innerPadding -> + Box( + modifier = Modifier + .fillMaxSize() + .padding(innerPadding) + .background(MaterialTheme.colorScheme.surface), + contentAlignment = Alignment.BottomCenter + ) { + ElevatedCard( + modifier = Modifier + .fillMaxWidth() + .wrapContentHeight() + .padding(DEFAULT_PADDING), + shape = RoundedCornerShape(4.dp), + colors = CardDefaults.elevatedCardColors( + containerColor = MaterialTheme.colorScheme.primaryContainer + ), + elevation = CardDefaults.elevatedCardElevation( + defaultElevation = 4.dp + ) + ) { + Row( + verticalAlignment = Alignment.CenterVertically + ) { + Text( + text = context.getString(R.string.snackbar_text), + modifier = Modifier + .weight(1f) + .padding(start = DEFAULT_PADDING), + color = MaterialTheme.colorScheme.onPrimaryContainer, + style = MaterialTheme.typography.bodyMedium + ) + + TextButton( + onClick = onClick, + modifier = Modifier.padding(end = DEFAULT_PADDING / 2) + ) { + Text( + text = context.getString(R.string.action_to_setting), + style = MaterialTheme.typography.bodyMedium + ) + } + } + } + } + } +} diff --git a/feature/main/src/main/java/com/squirtles/main/navigation/MainNavHost.kt b/feature/main/src/main/java/com/squirtles/main/navigation/MainNavHost.kt new file mode 100644 index 00000000..876dc8fb --- /dev/null +++ b/feature/main/src/main/java/com/squirtles/main/navigation/MainNavHost.kt @@ -0,0 +1,78 @@ +package com.squirtles.main.navigation + +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import androidx.hilt.navigation.compose.hiltViewModel +import androidx.navigation.compose.NavHost +import com.squirtles.create.navigation.createNavGraph +import com.squirtles.detail.navigation.detailNavGraph +import com.squirtles.favorite.navigation.favoriteNavGraph +import com.squirtles.main.MainActivity +import com.squirtles.map.MapViewModel +import com.squirtles.map.navigation.mapNavGraph +import com.squirtles.musicplayer.PlayerServiceViewModel +import com.squirtles.mypick.navigation.myPickNavGraph +import com.squirtles.search.navigation.searchNavGraph +import com.squirtles.userinfo.navigation.userInfoNavGraph + +@Composable +internal fun MainNavHost( + modifier: Modifier = Modifier, + navigator: MainNavigator, + finishActivity: () -> Unit, + mapViewModel: MapViewModel = hiltViewModel(), + playerServiceViewModel: PlayerServiceViewModel = hiltViewModel(), +) { + NavHost( + navController = navigator.navController, + startDestination = navigator.startDestination, + ) { + mapNavGraph( + mapViewModel = mapViewModel, + playerServiceViewModel = playerServiceViewModel, + onFavoriteClick = navigator::navigateFavorite, + onCenterClick = navigator::navigateSearch, + onUserInfoClick = navigator::navigateUserInfo, + onPickSummaryClick = navigator::navigatePickDetail, + onLoadingDialogCloseClick = finishActivity + ) + + searchNavGraph( + onBackClick = navigator::popBackStackIfNotMap, + onItemClick = navigator::navigateCreate, + ) + + detailNavGraph( + playerServiceViewModel = playerServiceViewModel, + onUserInfoClick = navigator::navigateUserInfo, + onBackClick = navigator::popBackStackIfNotMap, + onDeleted = mapViewModel::resetClickedMarkerState + ) + + createNavGraph( + onBackClick = navigator::popBackStackIfNotMap, + onCreateClick = { pickId -> + navigator.navigatePickDetail(pickId, true) + }, + ) + + favoriteNavGraph( + onBackClick = navigator::popBackStackIfNotMap, + onItemClick = navigator::navigatePickDetail + ) + + userInfoNavGraph( + onBackClick = navigator::popBackStackIfNotMap, + onBackToMapClick = navigator::navigateMap, + onFavoritePicksClick = navigator::navigateFavorite, + onMyPicksClick = navigator::navigateMyPicks, + onEditProfileClick = navigator::navigateEditProfile, + onEditNotificationClick = navigator::navigateEditNotificationSetting, + ) + + myPickNavGraph( + onBackClick = navigator::popBackStackIfNotMap, + onItemClick = navigator::navigatePickDetail, + ) + } +} diff --git a/feature/main/src/main/java/com/squirtles/main/navigation/MainNavigator.kt b/feature/main/src/main/java/com/squirtles/main/navigation/MainNavigator.kt new file mode 100644 index 00000000..37cf17d0 --- /dev/null +++ b/feature/main/src/main/java/com/squirtles/main/navigation/MainNavigator.kt @@ -0,0 +1,127 @@ +package com.squirtles.main.navigation + +import androidx.compose.runtime.Composable +import androidx.compose.runtime.remember +import androidx.navigation.NavDestination +import androidx.navigation.NavDestination.Companion.hasRoute +import androidx.navigation.NavHostController +import androidx.navigation.compose.currentBackStackEntryAsState +import androidx.navigation.compose.rememberNavController +import androidx.navigation.navOptions +import com.squirtles.create.navigation.navigateCreate +import com.squirtles.detail.navigation.navigatePickDetail +import com.squirtles.favorite.navigation.navigateFavorite +import com.squirtles.map.navigation.navigateMap +import com.squirtles.model.Song +import com.squirtles.mypick.navigation.navigateMyPicks +import com.squirtles.navigation.Route +import com.squirtles.search.navigation.navigateSearch +import com.squirtles.userinfo.navigation.navigateEditNotificationSetting +import com.squirtles.userinfo.navigation.navigateEditProfile +import com.squirtles.userinfo.navigation.navigateUserInfo + +internal class MainNavigator( + val navController: NavHostController +) { + private val currentDestination: NavDestination? + @Composable get() = navController + .currentBackStackEntryAsState().value?.destination + + val startDestination = Route.Map + + fun navigateMap() { + navController.navigateMap( + navOptions { + popUpTo(startDestination) { + inclusive = true + } + launchSingleTop = true + } + ) + } + + fun navigateFavorite(uid: String) { + navController.navigateFavorite( + uid = uid, + navOptions { + launchSingleTop = true + } + ) + } + + fun navigatePickDetail(pickId: String, navigateToMap: Boolean = false) { + navController.navigatePickDetail( + pickId = pickId, + navOptions = navOptions { + if (navigateToMap) { + popUpTo(startDestination) { + inclusive = false + } + } + launchSingleTop = true + } + ) + } + + fun navigateMyPicks(uid: String) { + navController.navigateMyPicks( + uid = uid, + navOptions = navOptions { launchSingleTop = true } + ) + } + + fun navigateUserInfo(uid: String) { + navController.navigateUserInfo( + uid = uid, + navOptions = navOptions { launchSingleTop = true } + ) + } + + fun navigateEditProfile(userName: String) { + navController.navigateEditProfile( + userName = userName, + navOptions = navOptions { launchSingleTop = true } + ) + } + + fun navigateEditNotificationSetting() { + navController.navigateEditNotificationSetting( + navOptions = navOptions { launchSingleTop = true } + ) + } + + fun navigateSearch() { + navController.navigateSearch( + navOptions = navOptions { launchSingleTop = true } + ) + } + + fun navigateCreate(song: Song) { + navController.navigateCreate( + song = song, + navOptions = navOptions { launchSingleTop = true } + ) + } + + private fun popBackStack() { + navController.popBackStack() + } + + fun popBackStackIfNotMap() { + if (!isSameCurrentDestination()) { + popBackStack() + } + } + + private inline fun isSameCurrentDestination(): Boolean { + return navController.currentDestination?.hasRoute() == true + } + +} + +@Composable +internal fun rememberMainNavigator( + navController: NavHostController = rememberNavController(), +): MainNavigator = remember(navController) { + MainNavigator(navController) +} diff --git a/feature/main/src/main/res/values/colors.xml b/feature/main/src/main/res/values/colors.xml new file mode 100644 index 00000000..3c07dfa6 --- /dev/null +++ b/feature/main/src/main/res/values/colors.xml @@ -0,0 +1,11 @@ + + + #FFFF5F61 + #FFBB86FC + #FF6200EE + #FF3700B3 + #FF03DAC5 + #FF018786 + #FF000000 + #FFFFFFFF + \ No newline at end of file diff --git a/feature/main/src/main/res/values/strings.xml b/feature/main/src/main/res/values/strings.xml new file mode 100644 index 00000000..2c403a91 --- /dev/null +++ b/feature/main/src/main/res/values/strings.xml @@ -0,0 +1,15 @@ + + + 확인 + 권한 요청 + 앱 실행을 위해\n위치와 마이크 권한이 필요합니다. + + + 설정(앱 정보)에서 권한을 허용해주세요. + 설정으로 이동 + + + 네트워크 연결이 원활하지 않습니다. + 유저 정보가 존재하지 않습니다. 앱을 재설치 해주세요. + 유저 등록 실패. 문의해주세요 + diff --git a/feature/main/src/main/res/values/themes.xml b/feature/main/src/main/res/values/themes.xml new file mode 100644 index 00000000..052adf48 --- /dev/null +++ b/feature/main/src/main/res/values/themes.xml @@ -0,0 +1,11 @@ + + + + + \ No newline at end of file diff --git a/domain/src/test/java/com/squirtles/domain/ExampleUnitTest.kt b/feature/main/src/test/java/com/squirtles/main/ExampleUnitTest.kt similarity index 91% rename from domain/src/test/java/com/squirtles/domain/ExampleUnitTest.kt rename to feature/main/src/test/java/com/squirtles/main/ExampleUnitTest.kt index 16fc0af5..10ddfe44 100644 --- a/domain/src/test/java/com/squirtles/domain/ExampleUnitTest.kt +++ b/feature/main/src/test/java/com/squirtles/main/ExampleUnitTest.kt @@ -1,4 +1,4 @@ -package com.squirtles.domain +package com.squirtles.main import org.junit.Test @@ -14,4 +14,4 @@ class ExampleUnitTest { fun addition_isCorrect() { assertEquals(4, 2 + 2) } -} \ No newline at end of file +} diff --git a/feature/map/.gitignore b/feature/map/.gitignore new file mode 100644 index 00000000..42afabfd --- /dev/null +++ b/feature/map/.gitignore @@ -0,0 +1 @@ +/build \ No newline at end of file diff --git a/feature/map/build.gradle.kts b/feature/map/build.gradle.kts new file mode 100644 index 00000000..1e104871 --- /dev/null +++ b/feature/map/build.gradle.kts @@ -0,0 +1,24 @@ +plugins { + alias(libs.plugins.musicroad.feature) +} + +android { + namespace = "com.squirtles.map" +} + +dependencies { + implementation(projects.core.account) + implementation(projects.core.musicplayer) + implementation(projects.domain.pick) + implementation(projects.domain.location) + implementation(projects.domain.user) + + testImplementation(libs.junit) + androidTestImplementation(libs.bundles.test) + + // Map + implementation(libs.map.sdk) + implementation(libs.play.services.location) + implementation(libs.bundles.coil) + implementation(libs.bundles.auth) +} diff --git a/feature/map/consumer-rules.pro b/feature/map/consumer-rules.pro new file mode 100644 index 00000000..e69de29b diff --git a/feature/map/proguard-rules.pro b/feature/map/proguard-rules.pro new file mode 100644 index 00000000..481bb434 --- /dev/null +++ b/feature/map/proguard-rules.pro @@ -0,0 +1,21 @@ +# Add project specific ProGuard rules here. +# You can control the set of applied configuration files using the +# proguardFiles setting in build.gradle. +# +# For more details, see +# http://developer.android.com/guide/developing/tools/proguard.html + +# If your project uses WebView with JS, uncomment the following +# and specify the fully qualified class name to the JavaScript interface +# class: +#-keepclassmembers class fqcn.of.javascript.interface.for.webview { +# public *; +#} + +# Uncomment this to preserve the line number information for +# debugging stack traces. +#-keepattributes SourceFile,LineNumberTable + +# If you keep the line number information, uncomment this to +# hide the original source file name. +#-renamesourcefileattribute SourceFile \ No newline at end of file diff --git a/feature/map/src/androidTest/java/com/squirtles/map/ExampleInstrumentedTest.kt b/feature/map/src/androidTest/java/com/squirtles/map/ExampleInstrumentedTest.kt new file mode 100644 index 00000000..0b3933fe --- /dev/null +++ b/feature/map/src/androidTest/java/com/squirtles/map/ExampleInstrumentedTest.kt @@ -0,0 +1,24 @@ +package com.squirtles.map + +import androidx.test.platform.app.InstrumentationRegistry +import androidx.test.ext.junit.runners.AndroidJUnit4 + +import org.junit.Test +import org.junit.runner.RunWith + +import org.junit.Assert.* + +/** + * Instrumented test, which will execute on an Android device. + * + * See [testing documentation](http://d.android.com/tools/testing). + */ +@RunWith(AndroidJUnit4::class) +class ExampleInstrumentedTest { + @Test + fun useAppContext() { + // Context of the app under test. + val appContext = InstrumentationRegistry.getInstrumentation().targetContext + assertEquals("com.squirtles.map.test", appContext.packageName) + } +} diff --git a/feature/map/src/main/AndroidManifest.xml b/feature/map/src/main/AndroidManifest.xml new file mode 100644 index 00000000..706c2553 --- /dev/null +++ b/feature/map/src/main/AndroidManifest.xml @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/feature/map/src/main/java/com/squirtles/map/Constants.kt b/feature/map/src/main/java/com/squirtles/map/Constants.kt new file mode 100644 index 00000000..3fbe632b --- /dev/null +++ b/feature/map/src/main/java/com/squirtles/map/Constants.kt @@ -0,0 +1,16 @@ +package com.squirtles.map + +internal enum class BottomNavigationSize( + val size: Int +) { + WIDTH(245), + HEIGHT(50), + HORIZONTAL_PADDING(32) +} + +internal enum class BottomNavigationIconSize( + val size: Int, +) { + CENTER(82), + CENTER_ICON(34) +} diff --git a/feature/map/src/main/java/com/squirtles/map/MapScreen.kt b/feature/map/src/main/java/com/squirtles/map/MapScreen.kt new file mode 100644 index 00000000..23c21c5b --- /dev/null +++ b/feature/map/src/main/java/com/squirtles/map/MapScreen.kt @@ -0,0 +1,274 @@ +package com.squirtles.map + +import androidx.activity.compose.BackHandler +import androidx.compose.foundation.background +import androidx.compose.foundation.clickable +import androidx.compose.foundation.interaction.MutableInteractionSource +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.WindowInsets +import androidx.compose.foundation.layout.asPaddingValues +import androidx.compose.foundation.layout.fillMaxHeight +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.navigationBars +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.statusBars +import androidx.compose.material3.CircularProgressIndicator +import androidx.compose.material3.Scaffold +import androidx.compose.runtime.Composable +import androidx.compose.runtime.LaunchedEffect +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.saveable.rememberSaveable +import androidx.compose.runtime.setValue +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.platform.LocalContext +import androidx.compose.ui.unit.dp +import androidx.core.content.ContextCompat.getString +import androidx.hilt.navigation.compose.hiltViewModel +import androidx.lifecycle.Lifecycle +import androidx.lifecycle.compose.LocalLifecycleOwner +import androidx.lifecycle.compose.collectAsStateWithLifecycle +import androidx.lifecycle.flowWithLifecycle +import com.squirtles.account.AccountViewModel +import com.squirtles.account.GoogleId +import com.squirtles.common.ui.SignInAlertDialog +import com.squirtles.common.ui.VerticalSpacer +import com.squirtles.common.ui.theme.Black +import com.squirtles.map.components.ClusterBottomSheet +import com.squirtles.map.components.InfoWindow +import com.squirtles.map.components.LoadingDialog +import com.squirtles.map.components.MapBottomNavBar +import com.squirtles.map.components.PickNotificationBanner +import com.squirtles.musicplayer.PlayerServiceViewModel +import kotlinx.coroutines.launch + +@Composable +fun MapScreen( + mapViewModel: MapViewModel, + playerServiceViewModel: PlayerServiceViewModel, + onFavoriteClick: (String) -> Unit, + onCenterClick: () -> Unit, + onUserInfoClick: (String) -> Unit, + onPickSummaryClick: (String) -> Unit, + onLoadingDialogCloseClick: () -> Unit, + accountViewModel: AccountViewModel = hiltViewModel() +) { + val nearPicks by mapViewModel.nearPicks.collectAsStateWithLifecycle() + val lastLocation by mapViewModel.lastLocation.collectAsStateWithLifecycle() + + val clickedMarkerState by mapViewModel.clickedMarkerState.collectAsStateWithLifecycle() + val playerState by playerServiceViewModel.playerState.collectAsStateWithLifecycle() + + val context = LocalContext.current + val lifecycleOwner = LocalLifecycleOwner.current + var showBottomSheet by remember { mutableStateOf(false) } + var showLocationLoading by rememberSaveable { mutableStateOf(true) } + var isPlaying: Boolean by remember { mutableStateOf(false) } + + // Sign In Dialog + var showSignInDialog by remember { mutableStateOf(false) } + var signInDialogDescription by remember { mutableStateOf("") } + var onSignInSuccess by remember { mutableStateOf<(String) -> Unit>({}) } + var showLoadingIndicator by rememberSaveable { mutableStateOf(false) } + + BackHandler(enabled = showLoadingIndicator) { } + + LaunchedEffect(Unit) { + playerServiceViewModel.readyPlayer() + + launch { + accountViewModel.signInSuccess + .flowWithLifecycle(lifecycleOwner.lifecycle, Lifecycle.State.STARTED) + .collect { isSuccess -> + if (isSuccess) { + showLoadingIndicator = false + mapViewModel.getUid()?.let { uid -> + onSignInSuccess(uid) + } + } + } + } + } + + LaunchedEffect(playerState) { + isPlaying = playerState.isPlaying + } + + LaunchedEffect(lastLocation) { + showLocationLoading = lastLocation == null + } + + Scaffold( + contentWindowInsets = WindowInsets.navigationBars + ) { innerPadding -> + Box( + modifier = Modifier + .fillMaxSize() + .padding(innerPadding) + ) { + NaverMap( + mapViewModel = mapViewModel, + lastLocation = lastLocation + ) + + if (nearPicks.isNotEmpty()) { + PickNotificationBanner( + nearPicks = nearPicks, + isPlaying = isPlaying, + onClick = { + playerServiceViewModel.shuffleNext( + if (nearPicks.size == 1) nearPicks.first() + else nearPicks.filter { it.id != playerState.id }.random() + ) + } + ) + } + + Column( + modifier = Modifier.fillMaxSize(), + verticalArrangement = Arrangement.Bottom, + horizontalAlignment = Alignment.CenterHorizontally + ) { + clickedMarkerState.prevClickedMarker?.let { + if (clickedMarkerState.curPickId != null) { // 단말 마커 클릭 시 + showBottomSheet = false + mapViewModel.picks[clickedMarkerState.curPickId]?.let { pick -> + InfoWindow( + pick = pick, + uid = mapViewModel.getUid(), + navigateToPick = { pickId -> + onPickSummaryClick(pickId) + }, + calculateDistance = { lat, lng -> + mapViewModel.calculateDistance(lat, lng).let { distance -> + when { + distance >= 1000.0 -> "%.1fkm".format(distance / 1000.0) + distance >= 0 -> "%.0fm".format(distance) + else -> "" + } + } + } + ) + } + } else { // 클러스터 마커 클릭 시 + showBottomSheet = true + } + } + + VerticalSpacer(16) + + MapBottomNavBar( + modifier = Modifier.padding(bottom = 16.dp), + lastLocation = lastLocation, + onFavoriteClick = { + mapViewModel.getUid()?.let { uid -> + onFavoriteClick(uid) + } ?: run { + signInDialogDescription = getString(context, R.string.sign_in_dialog_title_favorite_picks) + showSignInDialog = true + onSignInSuccess = onFavoriteClick + } + }, + onCenterClick = { + mapViewModel.getUid()?.let { + onCenterClick() + mapViewModel.saveCurLocationForced() + } ?: run { + signInDialogDescription = getString(context, R.string.sign_in_dialog_title_add_pick) + showSignInDialog = true + onSignInSuccess = { + onCenterClick() + mapViewModel.saveCurLocationForced() + } + } + }, + onUserInfoClick = { + mapViewModel.getUid()?.let { uid -> + onUserInfoClick(uid) + } ?: run { + signInDialogDescription = getString(context, R.string.sign_in_dialog) + showSignInDialog = true + onSignInSuccess = onUserInfoClick + } + } + ) + } + + if (showBottomSheet) { + ClusterBottomSheet( + onDismissRequest = { + showBottomSheet = false + mapViewModel.resetClickedMarkerState(context) + }, + modifier = Modifier + .fillMaxHeight() + .padding(WindowInsets.statusBars.asPaddingValues()), + clusterPickList = clickedMarkerState.clusterPickList, + uid = mapViewModel.getUid(), + calculateDistance = { lat, lng -> + mapViewModel.calculateDistance(lat, lng).let { distance -> + when { + distance >= 1000.0 -> "%.1fkm".format(distance / 1000.0) + distance >= 0 -> "%.0fm".format(distance) + else -> "" + } + } + + }, + onClickItem = { pickId -> + onPickSummaryClick(pickId) + } + ) + } + + if (showLocationLoading) { + Box( + modifier = Modifier.fillMaxSize(), + contentAlignment = Alignment.Center + ) { + LoadingDialog( + onCloseClick = { + onLoadingDialogCloseClick() + } + ) + } + } + } + } + + if (showSignInDialog) { + SignInAlertDialog( + onDismissRequest = { showSignInDialog = false }, + onGoogleSignInClick = { + showSignInDialog = false + showLoadingIndicator = true + GoogleId(context).signIn( + onSuccess = { uid, credential -> + accountViewModel.signIn(uid, credential) + } + ) + }, + description = signInDialogDescription + ) + } + + if (showLoadingIndicator) { + Box( + modifier = Modifier + .fillMaxSize() + .background(Black.copy(alpha = 0.5F)) + .clickable( + interactionSource = remember { MutableInteractionSource() }, + indication = null, + onClick = {} + ), + contentAlignment = Alignment.Center + ) { + CircularProgressIndicator() + } + } +} diff --git a/feature/map/src/main/java/com/squirtles/map/MapViewModel.kt b/feature/map/src/main/java/com/squirtles/map/MapViewModel.kt new file mode 100644 index 00000000..13d948b7 --- /dev/null +++ b/feature/map/src/main/java/com/squirtles/map/MapViewModel.kt @@ -0,0 +1,195 @@ +package com.squirtles.map + +import android.content.Context +import android.location.Location +import android.util.Log +import androidx.lifecycle.ViewModel +import androidx.lifecycle.viewModelScope +import com.naver.maps.geometry.LatLng +import com.naver.maps.map.CameraPosition +import com.naver.maps.map.clustering.Clusterer +import com.naver.maps.map.overlay.Marker +import com.squirtles.location.usecase.GetLastLocationUseCase +import com.squirtles.location.usecase.SaveLastLocationUseCase +import com.squirtles.map.marker.MarkerKey +import com.squirtles.model.Pick +import com.squirtles.domain.pick.usecase.FetchPickUseCase +import com.squirtles.user.usecase.GetCurrentUidUseCase +import dagger.hilt.android.lifecycle.HiltViewModel +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.StateFlow +import kotlinx.coroutines.flow.asStateFlow +import kotlinx.coroutines.launch +import javax.inject.Inject + +data class MarkerState( + val prevClickedMarker: Marker? = null, // 이전에 클릭한 마커(클러스터 마커 & 단말 마커) + val clusterPickList: List? = null, // 클러스터 마커의 픽 정보 + val curPickId: String? = null // 현재 선택한 마커의 pick id +) + +@HiltViewModel +class MapViewModel @Inject constructor( + getLastLocationUseCase: GetLastLocationUseCase, + private val saveLastLocationUseCase: SaveLastLocationUseCase, + private val fetchPickUseCase: FetchPickUseCase, + private val getCurrentUidUseCase: GetCurrentUidUseCase +) : ViewModel() { + + private val _centerLatLng: MutableStateFlow = MutableStateFlow(null) + val centerLatLng = _centerLatLng.asStateFlow() + + private var _lastCameraPosition: CameraPosition? = null + val lastCameraPosition get() = _lastCameraPosition + + private val _picks: MutableMap = mutableMapOf() // key: pickId, value: Pick + val picks: Map get() = _picks + + private val _nearPicks = MutableStateFlow>(emptyList()) + val nearPicks = _nearPicks.asStateFlow() + + private val _clickedMarkerState = MutableStateFlow(MarkerState()) + val clickedMarkerState = _clickedMarkerState.asStateFlow() + + // FIXME : 네이버맵의 LocationChangeListener에서 실시간으로 변하는 위치 정보 -> 더 나은 방법이 있으면 고쳐주세요 + private var _currentLocation: Location? = null + + // LocalDataSource에 저장되는 위치 정보 + // Firestore 데이터 쿼리 작업 최소화 및 위치데이터 공유 용도 + val lastLocation: StateFlow = getLastLocationUseCase() + + fun getUid() = getCurrentUidUseCase() + + fun setLastCameraPosition(cameraPosition: CameraPosition) { + _lastCameraPosition = cameraPosition + } + + fun updateCurLocation(location: Location) { + _currentLocation = location + + if (lastLocation.value == null + || calculateDistance(location.latitude, location.longitude) > 5.0 + ) { + saveCurLocation(location) + } + } + + private fun saveCurLocation(location: Location) { + viewModelScope.launch { + saveLastLocationUseCase(location) + } + } + + fun saveCurLocationForced() { + _currentLocation?.let { location -> + saveCurLocation(location) + } + } + + fun calculateDistance( + lat: Double, + lng: Double, + from: Location? = lastLocation.value, + ): Double { + return from?.let { + val location = Location("pickLocation").apply { + latitude = lat + longitude = lng + } + from.distanceTo(location).toDouble() + } ?: -1.0 + } + + fun updateCenterLatLng(latLng: LatLng) { + _centerLatLng.value = latLng + } + + // FIXME: 인자로 Context 받는 것 수정하기 + fun setClickedMarker(context: Context, marker: Marker) { + viewModelScope.launch { + marker.toggleSizeByClick(context, true) + _clickedMarkerState.emit(_clickedMarkerState.value.copy(prevClickedMarker = marker)) + } + } + + fun setClickedMarkerState( + context: Context, + marker: Marker, + clusterTag: String? = null, + pickId: String? = null + ) { + viewModelScope.launch { + val prevClickedMarker = _clickedMarkerState.value.prevClickedMarker + if (prevClickedMarker == marker) return@launch + + prevClickedMarker?.toggleSizeByClick(context, false) + marker.toggleSizeByClick(context, true) + val pickList = clusterTag?.split(",")?.mapNotNull { id -> picks[id] } + _clickedMarkerState.emit(MarkerState(marker, pickList, pickId)) + } + } + + fun resetClickedMarkerState(context: Context) { + viewModelScope.launch { + val prevClickedMarker = _clickedMarkerState.value.prevClickedMarker + prevClickedMarker?.toggleSizeByClick(context, false) + _clickedMarkerState.emit(MarkerState(null, null, null)) + } + } + + fun fetchPicksInBounds(leftTop: LatLng, clusterer: Clusterer?) { + viewModelScope.launch { + _centerLatLng.value?.run { + val radiusInM = leftTop.distanceTo(this) + fetchPickUseCase(this.latitude, this.longitude, radiusInM) + .onSuccess { pickList -> + val newKeyTagMap: MutableMap = mutableMapOf() + pickList.forEach { pick -> + newKeyTagMap[MarkerKey(pick)] = pick.id + _picks[pick.id] = pick + } + _clickedMarkerState.value.clusterPickList?.let { clusterPickList -> // 클러스터 마커가 선택되어 있는 경우 + val updatedPickList = mutableListOf() + clusterPickList.forEach { pick -> + _picks[pick.id]?.let { updatedPick -> + updatedPickList.add(updatedPick) + } + } + _clickedMarkerState.emit(_clickedMarkerState.value.copy(clusterPickList = updatedPickList.toList())) // 최신 픽 정보로 clusterPickList 업데이트 + } + clusterer?.addAll(newKeyTagMap) + } + .onFailure { + // TODO: NoSuchPickInRadiusException일 때 + Log.e("MapViewModel", "${it.message}") + } + } + } + } + + fun requestPickNotificationArea(location: Location, notiRadius: Double) { + viewModelScope.launch { + fetchPickUseCase(location.latitude, location.longitude, notiRadius) + .onSuccess { + _nearPicks.emit(it) + }.onFailure { + _nearPicks.emit(emptyList()) + } + } + } + + private fun Marker.toggleSizeByClick(context: Context, isClicked: Boolean) { + val defaultIconWidth = this.icon.getIntrinsicWidth(context) + val defaultIconHeight = this.icon.getIntrinsicHeight(context) + + zIndex = if (isClicked) CLICKED_MARKER_Z_INDEX else DEFAULT_MARKER_Z_INDEX + this.width = + if (isClicked) (defaultIconWidth * MARKER_SCALE).toInt() else defaultIconWidth + this.height = + if (isClicked) (defaultIconHeight * MARKER_SCALE).toInt() else defaultIconHeight + } + + companion object { + private const val MARKER_SCALE = 1.5 + } +} diff --git a/feature/map/src/main/java/com/squirtles/map/NaverMap.kt b/feature/map/src/main/java/com/squirtles/map/NaverMap.kt new file mode 100644 index 00000000..ff8b36b6 --- /dev/null +++ b/feature/map/src/main/java/com/squirtles/map/NaverMap.kt @@ -0,0 +1,286 @@ +package com.squirtles.map + +import android.Manifest +import android.annotation.SuppressLint +import android.app.Activity +import android.content.Context +import android.graphics.PointF +import android.location.Location +import androidx.compose.foundation.isSystemInDarkTheme +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.runtime.Composable +import androidx.compose.runtime.DisposableEffect +import androidx.compose.runtime.LaunchedEffect +import androidx.compose.runtime.MutableState +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.rememberCoroutineScope +import androidx.compose.runtime.setValue +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.toArgb +import androidx.compose.ui.platform.LocalContext +import androidx.compose.ui.viewinterop.AndroidView +import androidx.core.content.PermissionChecker +import androidx.lifecycle.Lifecycle +import androidx.lifecycle.LifecycleEventObserver +import androidx.lifecycle.compose.LocalLifecycleOwner +import androidx.lifecycle.compose.collectAsStateWithLifecycle +import com.google.android.gms.location.FusedLocationProviderClient +import com.google.android.gms.location.LocationServices +import com.naver.maps.geometry.LatLng +import com.naver.maps.geometry.LatLngBounds +import com.naver.maps.map.CameraAnimation +import com.naver.maps.map.CameraPosition +import com.naver.maps.map.CameraUpdate +import com.naver.maps.map.LocationTrackingMode +import com.naver.maps.map.MapView +import com.naver.maps.map.NaverMap +import com.naver.maps.map.UiSettings +import com.naver.maps.map.clustering.Clusterer +import com.naver.maps.map.overlay.CircleOverlay +import com.naver.maps.map.overlay.LocationOverlay +import com.naver.maps.map.overlay.OverlayImage +import com.naver.maps.map.util.FusedLocationSource +import com.squirtles.common.ui.theme.Primary +import com.squirtles.common.ui.theme.Purple15 +import com.squirtles.map.marker.MarkerKey +import com.squirtles.map.marker.buildClusterer +import kotlinx.coroutines.launch +import kotlin.coroutines.resume +import kotlin.coroutines.suspendCoroutine + +@Composable +fun NaverMap( + mapViewModel: MapViewModel, + lastLocation: Location? +) { + val context = LocalContext.current + val mapView = remember { MapView(context) } + val naverMap = remember { mutableStateOf(null) } + + val fusedLocationClient = remember { LocationServices.getFusedLocationProviderClient(context) } + val locationSource = + remember { FusedLocationSource(context as Activity, LOCATION_PERMISSION_REQUEST_CODE) } + val locationOverlay = remember { mutableStateOf(null) } + val circleOverlay = remember { CircleOverlay() } + var clusterer by remember { mutableStateOf?>(null) } + + val lifecycleOwner = LocalLifecycleOwner.current + val coroutineScope = rememberCoroutineScope() + + val centerLatLng by mapViewModel.centerLatLng.collectAsStateWithLifecycle() + + LaunchedEffect(lastLocation) { + // 현재 위치와 마지막 위치가 5미터 이상 차이가 날때만 현위치 기준 반경 100m 픽 정보 개수 불러오기 + lastLocation?.let { + mapViewModel.requestPickNotificationArea(lastLocation, CIRCLE_RADIUS_METER) + } + } + + LaunchedEffect(centerLatLng) { + naverMap.value?.projection?.fromScreenLocation(PointF(0F, 0F))?.run { + mapViewModel.fetchPicksInBounds( + leftTop = this, + clusterer = clusterer + ) + } + } + + DisposableEffect(Unit) { + clusterer = buildClusterer(context, mapViewModel) + + onDispose { + clusterer?.clear() + } + } + + DisposableEffect(lifecycleOwner) { + val mapLifecycleObserver = LifecycleEventObserver { _, event -> + when (event) { + Lifecycle.Event.ON_CREATE -> mapView.onCreate(null) + Lifecycle.Event.ON_START -> mapView.onStart() + Lifecycle.Event.ON_RESUME -> mapView.onResume() + Lifecycle.Event.ON_PAUSE -> mapView.onPause() + Lifecycle.Event.ON_STOP -> mapView.onStop() + Lifecycle.Event.ON_DESTROY -> mapView.onDestroy() + else -> throw IllegalStateException() + } + } + + lifecycleOwner.lifecycle.addObserver(mapLifecycleObserver) + + onDispose { + naverMap.value?.let { + mapViewModel.setLastCameraPosition(it.cameraPosition) + } + lifecycleOwner.lifecycle.removeObserver(mapLifecycleObserver) + mapView.onDestroy() + } + } + + AndroidView( + factory = { + mapView.apply { + coroutineScope.launch { + naverMap.value = suspendCoroutine { continuation -> + getMapAsync { + continuation.resume(it) + } + } + + naverMap.value?.run { + mapType = NaverMap.MapType.Navi + initMapSettings() + initDeviceLocation( + context = context, + circleOverlay = circleOverlay, + fusedLocationClient = fusedLocationClient, + lastCameraPosition = mapViewModel.lastCameraPosition + ) { + mapViewModel.fetchPicksInBounds( + leftTop = this.projection.fromScreenLocation(PointF(0F, 0F)), + clusterer = clusterer + ) + } + initLocationOverlay(locationSource, locationOverlay) + setLocationChangeListener(circleOverlay, mapViewModel) + setMapClickListener { mapViewModel.resetClickedMarkerState(context) } + setCameraIdleListener { centerLatLng -> + mapViewModel.updateCenterLatLng(centerLatLng) + } + clusterer?.map = this + } + } + } + }, + modifier = Modifier.fillMaxSize() + ) + + if (isSystemInDarkTheme()) { + naverMap.value?.isNightModeEnabled = true + } +} + +internal fun setCameraToMarker( + map: NaverMap, + clickedMarkerPosition: LatLng +) { + val cameraUpdate = CameraUpdate + .scrollTo(clickedMarkerPosition) + .animate(CameraAnimation.Easing) + map.moveCamera(cameraUpdate) +} + +private fun NaverMap.initLocationOverlay( + currentLocationSource: FusedLocationSource, + currentLocationOverlay: MutableState +) { + locationSource = currentLocationSource + locationTrackingMode = LocationTrackingMode.Follow + currentLocationOverlay.value = locationOverlay + currentLocationOverlay.value?.run { + isVisible = true + icon = OverlayImage.fromResource(R.drawable.ic_location) + } +} + +@SuppressLint("MissingPermission") +private fun NaverMap.initDeviceLocation( + context: Context, + circleOverlay: CircleOverlay, + fusedLocationClient: FusedLocationProviderClient, + lastCameraPosition: CameraPosition?, + fetchPicksInBounds: () -> Unit, +) { + if (checkSelfPermission(context)) { + fusedLocationClient.lastLocation.addOnSuccessListener { location -> + if (location != null) { + locationOverlay.position = LatLng(location) + setCircleOverlay(circleOverlay, location) + lastCameraPosition?.let { + moveCamera(CameraUpdate.toCameraPosition(it)) + fetchPicksInBounds() + } ?: run { + moveCamera(CameraUpdate.scrollTo(LatLng(location))) + moveCamera(CameraUpdate.zoomTo(INITIAL_CAMERA_ZOOM)) + } + } + } + } +} + +private fun NaverMap.setLocationChangeListener( + circleOverlay: CircleOverlay, + mapViewModel: MapViewModel +) { + addOnLocationChangeListener { location -> + this.setCircleOverlay(circleOverlay, location) + mapViewModel.updateCurLocation(location) + } +} + +private fun NaverMap.setCircleOverlay(circleOverlay: CircleOverlay, location: Location) { + circleOverlay.center = LatLng(location.latitude, location.longitude) + circleOverlay.color = Purple15.toArgb() + circleOverlay.outlineColor = Primary.toArgb() + circleOverlay.outlineWidth = 3 + circleOverlay.radius = CIRCLE_RADIUS_METER + circleOverlay.map = this +} + +private fun NaverMap.initMapSettings() { + extent = LatLngBounds(SOUTHWEST, NORTHEAST) + setCameraZoomLimit() + uiSettings.setNaverMapMapUi() +} + +private fun UiSettings.setNaverMapMapUi() { + isLocationButtonEnabled = true + isZoomControlEnabled = false + isTiltGesturesEnabled = false +} + +private fun NaverMap.setCameraZoomLimit() { + minZoom = MIN_ZOOM_LEVEL + maxZoom = MAX_ZOOM_LEVEL +} + +// 지도 클릭 이벤트 설정 +private fun NaverMap.setMapClickListener( + resetSelectedMarkerAndPick: () -> Unit +) { + this.setOnMapClickListener { _, _ -> + resetSelectedMarkerAndPick() + } +} + +// 카메라 대기 이벤트 설정 +private fun NaverMap.setCameraIdleListener( + updateCenterLatLng: (LatLng) -> Unit +) { + addOnCameraIdleListener { + updateCenterLatLng(cameraPosition.target) + } +} + +private fun checkSelfPermission(context: Context): Boolean { + return PermissionChecker.checkSelfPermission(context, PERMISSIONS[0]) == + PermissionChecker.PERMISSION_GRANTED && + PermissionChecker.checkSelfPermission(context, PERMISSIONS[1]) == + PermissionChecker.PERMISSION_GRANTED +} + +private val SOUTHWEST = LatLng(33.011268, 124.344361) +private val NORTHEAST = LatLng(39.346507, 130.826372) +private const val LOCATION_PERMISSION_REQUEST_CODE = 1000 +private const val CIRCLE_RADIUS_METER = 100.0 +private const val INITIAL_CAMERA_ZOOM = 16.5 +private const val MIN_ZOOM_LEVEL = 6.0 +private const val MAX_ZOOM_LEVEL = 18.0 +private val PERMISSIONS = arrayOf( + Manifest.permission.ACCESS_FINE_LOCATION, + Manifest.permission.ACCESS_COARSE_LOCATION +) +internal const val DEFAULT_MARKER_Z_INDEX = 0 +internal const val CLICKED_MARKER_Z_INDEX = 100 diff --git a/feature/map/src/main/java/com/squirtles/map/components/ClusterBottomSheet.kt b/feature/map/src/main/java/com/squirtles/map/components/ClusterBottomSheet.kt new file mode 100644 index 00000000..5b86075b --- /dev/null +++ b/feature/map/src/main/java/com/squirtles/map/components/ClusterBottomSheet.kt @@ -0,0 +1,158 @@ +package com.squirtles.map.components + +import androidx.compose.foundation.clickable +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size +import androidx.compose.foundation.lazy.LazyColumn +import androidx.compose.foundation.lazy.items +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.material3.ExperimentalMaterial3Api +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.ModalBottomSheet +import androidx.compose.material3.Text +import androidx.compose.material3.rememberModalBottomSheetState +import androidx.compose.runtime.Composable +import androidx.compose.runtime.rememberCoroutineScope +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.clip +import androidx.compose.ui.unit.dp +import com.squirtles.common.ui.AlbumImage +import com.squirtles.common.ui.CommentText +import com.squirtles.common.ui.Constants.DEFAULT_PADDING +import com.squirtles.common.ui.Constants.REQUEST_IMAGE_SIZE_DEFAULT +import com.squirtles.common.ui.CountText +import com.squirtles.common.ui.CreatedByOtherUserText +import com.squirtles.common.ui.CreatedBySelfText +import com.squirtles.common.ui.FavoriteCountText +import com.squirtles.common.ui.HorizontalSpacer +import com.squirtles.common.ui.SongInfoText +import com.squirtles.common.ui.VerticalSpacer +import com.squirtles.model.LocationPoint +import com.squirtles.model.Pick +import com.squirtles.model.Song +import kotlinx.coroutines.launch + +@OptIn(ExperimentalMaterial3Api::class) +@Composable +fun ClusterBottomSheet( + onDismissRequest: () -> Unit, + modifier: Modifier = Modifier, + clusterPickList: List?, + uid: String?, + calculateDistance: (Double, Double) -> String, + onClickItem: (String) -> Unit +) { + val sheetState = rememberModalBottomSheetState() + val scope = rememberCoroutineScope() + + ModalBottomSheet( + onDismissRequest = { onDismissRequest() }, + modifier = modifier, + sheetState = sheetState, + containerColor = MaterialTheme.colorScheme.surface + ) { + clusterPickList?.let { pickList -> + CountText( + totalCount = pickList.size, + modifier = Modifier + .fillMaxWidth() + .padding(start = DEFAULT_PADDING) + ) + + VerticalSpacer(height = 8) + + LazyColumn { + items( + items = pickList, + key = { it.id } + ) { pick -> + BottomSheetItem( + song = pick.song, + pickLocation = pick.location, + createdUserName = pick.createdBy.userName.takeIf { pick.createdBy.uid != uid }, + comment = pick.comment, + favoriteCount = pick.favoriteCount, + calculateDistance = calculateDistance, + onClickItem = { + scope + .launch { sheetState.hide() } + .invokeOnCompletion { onClickItem(pick.id) } + } + ) + } + } + } + } +} + +@Composable +fun BottomSheetItem( + song: Song, + pickLocation: LocationPoint, + createdUserName: String?, + comment: String, + favoriteCount: Int, + calculateDistance: (Double, Double) -> String, + onClickItem: () -> Unit +) { + Row( + modifier = Modifier + .fillMaxWidth() + .clickable { onClickItem() } + .padding(horizontal = DEFAULT_PADDING, vertical = DEFAULT_PADDING / 2), + horizontalArrangement = Arrangement.spacedBy(DEFAULT_PADDING), + verticalAlignment = Alignment.CenterVertically + ) { + AlbumImage( + imageUrl = song.getImageUrlWithSize(REQUEST_IMAGE_SIZE_DEFAULT.width, REQUEST_IMAGE_SIZE_DEFAULT.height), + modifier = Modifier + .size(45.dp) + .clip(RoundedCornerShape(4.dp)) + ) + + Column( + modifier = Modifier.weight(1f) + ) { + SongInfoText( + songInfo = "${song.songName} - ${song.artistName}" + ) + + Row( + verticalAlignment = Alignment.CenterVertically + ) { + createdUserName?.let { userName -> + CreatedByOtherUserText( + userName = userName, + modifier = Modifier.weight(weight = 1f, fill = false) + ) + } ?: run { + CreatedBySelfText( + modifier = Modifier.weight(weight = 1f, fill = false) + ) + } + + HorizontalSpacer(8) + + FavoriteCountText( + favoriteCount = favoriteCount + ) + } + + CommentText( + comment = comment + ) + } + + Text( + text = calculateDistance(pickLocation.latitude, pickLocation.longitude), + modifier = Modifier.align(Alignment.Top), + color = MaterialTheme.colorScheme.onSurfaceVariant, + style = MaterialTheme.typography.bodyMedium + ) + } +} diff --git a/feature/map/src/main/java/com/squirtles/map/components/InfoWindowCard.kt b/feature/map/src/main/java/com/squirtles/map/components/InfoWindowCard.kt new file mode 100644 index 00000000..5e89330c --- /dev/null +++ b/feature/map/src/main/java/com/squirtles/map/components/InfoWindowCard.kt @@ -0,0 +1,157 @@ +package com.squirtles.map.components + +import android.content.res.Configuration.UI_MODE_NIGHT_YES +import android.util.Size +import androidx.compose.foundation.background +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.fillMaxHeight +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.material3.ElevatedCard +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.clip +import androidx.compose.ui.text.style.TextOverflow +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.dp +import androidx.core.graphics.toColorInt +import com.squirtles.common.ui.AlbumImage +import com.squirtles.common.ui.CreatedByOtherUserText +import com.squirtles.common.ui.CreatedBySelfText +import com.squirtles.common.ui.FavoriteCountText +import com.squirtles.common.ui.HorizontalSpacer +import com.squirtles.common.ui.SongInfoText +import com.squirtles.common.ui.theme.Gray +import com.squirtles.common.ui.theme.MusicRoadTheme +import com.squirtles.model.Creator +import com.squirtles.model.LocationPoint +import com.squirtles.model.Pick +import com.squirtles.model.Song + +@Composable +fun InfoWindow( + pick: Pick, + uid: String?, + navigateToPick: (String) -> Unit, + calculateDistance: (Double, Double) -> String +) { + ElevatedCard( + onClick = { navigateToPick(pick.id) }, + modifier = Modifier + .fillMaxWidth() + .height(122.dp) + .padding(horizontal = 16.dp) + ) { + Row( + modifier = Modifier + .background(MaterialTheme.colorScheme.surface) + .padding(16.dp), + horizontalArrangement = Arrangement.spacedBy(16.dp) + ) { + AlbumImage( + imageUrl = pick.song.getImageUrlWithSize(RequestImageSize.width, RequestImageSize.height), + modifier = Modifier + .size(90.dp) + .clip(RoundedCornerShape(4.dp)) + ) + + Column( + modifier = Modifier.weight(1f) + ) { + SongInfoText( + songInfo = "${pick.song.songName} - ${pick.song.artistName}" + ) + + Row( + verticalAlignment = Alignment.CenterVertically + ) { + if (pick.createdBy.uid != uid) { + CreatedByOtherUserText( + userName = pick.createdBy.userName, + modifier = Modifier.weight(weight = 1f, fill = false), + style = MaterialTheme.typography.bodyLarge + ) + } else { + CreatedBySelfText( + modifier = Modifier.weight(weight = 1f, fill = false), + style = MaterialTheme.typography.bodyLarge + ) + } + + HorizontalSpacer(8) + + FavoriteCountText( + favoriteCount = pick.favoriteCount, + style = MaterialTheme.typography.bodyLarge + ) + } + + Box( + modifier = Modifier + .weight(1f) + .fillMaxHeight(), + contentAlignment = Alignment.Center + ) { + Text( + text = pick.comment, + color = MaterialTheme.colorScheme.onSurface, + overflow = TextOverflow.Ellipsis, + maxLines = 1, + style = MaterialTheme.typography.bodyMedium, + ) + } + } + + Text( + text = calculateDistance(pick.location.latitude, pick.location.longitude), + style = MaterialTheme.typography.bodyMedium.copy(Gray) + ) + } + } +} + +@Preview("info window card") +@Preview("info window card (dark)", uiMode = UI_MODE_NIGHT_YES) +@Composable +private fun InfoWindowPreview() { + MusicRoadTheme { + InfoWindow( + pick = Pick( + id = "", + song = Song( + id = "", + songName = "Ditto", + artistName = "NewJeans", + albumName = "Ditto", + imageUrl = "https://i.scdn.co/image/ab67616d0000b2733d98a0ae7c78a3a9babaf8af", + genreNames = listOf("KPop", "R&B", "Rap"), + bgColor = "#000000".toColorInt(), + externalUrl = "", + previewUrl = "" + ), + comment = "강남역 거리는 ditto 듣기 좋네요 ^-^!", + createdAt = "1970.01.21", + createdBy = Creator(uid = "", userName = "짱구"), + favoriteCount = 100, + location = LocationPoint(1.0, 1.0), + musicVideoUrl = "", + ), + uid = "", + navigateToPick = { }, + calculateDistance = { _, _ -> + TODO() + } + ) + } +} + +private val RequestImageSize = Size(400, 400) diff --git a/feature/map/src/main/java/com/squirtles/map/components/LoadingDialog.kt b/feature/map/src/main/java/com/squirtles/map/components/LoadingDialog.kt new file mode 100644 index 00000000..027a83cf --- /dev/null +++ b/feature/map/src/main/java/com/squirtles/map/components/LoadingDialog.kt @@ -0,0 +1,87 @@ +package com.squirtles.map.components + +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.material3.BasicAlertDialog +import androidx.compose.material3.ButtonDefaults +import androidx.compose.material3.CircularProgressIndicator +import androidx.compose.material3.ExperimentalMaterial3Api +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.Surface +import androidx.compose.material3.Text +import androidx.compose.material3.TextButton +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.dp +import com.squirtles.common.ui.VerticalSpacer +import com.squirtles.common.ui.theme.Black +import com.squirtles.common.ui.theme.DarkGray +import com.squirtles.common.ui.theme.MusicRoadTheme +import com.squirtles.common.ui.theme.Primary +import com.squirtles.common.ui.theme.White +import com.squirtles.map.R + +@OptIn(ExperimentalMaterial3Api::class) +@Composable +fun LoadingDialog( + onCloseClick: () -> Unit, +) { + BasicAlertDialog( + onDismissRequest = {}, + ) { + Surface( + modifier = Modifier.fillMaxWidth(), + shape = RoundedCornerShape(16.dp), + color = White + ) { + Column( + modifier = Modifier.padding(top = 48.dp, bottom = 16.dp), + horizontalAlignment = Alignment.CenterHorizontally, + verticalArrangement = Arrangement.Center + ) { + CircularProgressIndicator( + color = Primary + ) + + VerticalSpacer(16) + + Text( + text = stringResource(R.string.loading_current_location_dialog_text), + color = Black, + style = MaterialTheme.typography.bodyLarge + ) + + VerticalSpacer(24) + + TextButton( + onClick = { onCloseClick() }, + colors = ButtonDefaults.buttonColors().copy( + containerColor = Color.Transparent, + contentColor = DarkGray + ) + ) { + Text( + text = stringResource(R.string.close_loading_current_location_dialog) + ) + } + } + } + } +} + +@Preview +@Composable +fun LoadingDialogPreview() { + MusicRoadTheme { + LoadingDialog( + onCloseClick = {} + ) + } +} diff --git a/feature/map/src/main/java/com/squirtles/map/components/MapBottomNavBar.kt b/feature/map/src/main/java/com/squirtles/map/components/MapBottomNavBar.kt new file mode 100644 index 00000000..f29104c6 --- /dev/null +++ b/feature/map/src/main/java/com/squirtles/map/components/MapBottomNavBar.kt @@ -0,0 +1,140 @@ +package com.squirtles.map.components + +import android.content.res.Configuration +import android.location.Location +import androidx.compose.foundation.background +import androidx.compose.foundation.clickable +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.fillMaxHeight +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size +import androidx.compose.foundation.shape.CircleShape +import androidx.compose.material3.Icon +import androidx.compose.material3.MaterialTheme +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.clip +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.graphics.painter.Painter +import androidx.compose.ui.graphics.vector.rememberVectorPainter +import androidx.compose.ui.res.painterResource +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.dp +import com.squirtles.common.ui.theme.MusicRoadTheme +import com.squirtles.common.ui.theme.Primary +import com.squirtles.map.R +import com.squirtles.map.BottomNavigationSize +import com.squirtles.map.BottomNavigationIconSize +import com.squirtles.map.navigation.NavTab + +@Composable +internal fun MapBottomNavBar( + modifier: Modifier = Modifier, + lastLocation: Location?, + onFavoriteClick: () -> Unit, + onCenterClick: () -> Unit, + onUserInfoClick: () -> Unit, +) { + Box( + modifier = modifier, + contentAlignment = Alignment.Center + ) { + Row( + modifier = Modifier + .size(BottomNavigationSize.WIDTH.size.dp, BottomNavigationSize.HEIGHT.size.dp) + .clip(CircleShape) + .background(color = MaterialTheme.colorScheme.surface) + ) { + // 왼쪽 버튼 + MapBottomNavigationItem( + modifier = Modifier + .weight(1f) + .fillMaxHeight() + .padding(end = BottomNavigationSize.HORIZONTAL_PADDING.size.dp), + tab = NavTab.FAVORITE, + painter = null, + tint = Primary, + onClick = onFavoriteClick + ) + + // 오른쪽 버튼 + MapBottomNavigationItem( + modifier = Modifier + .weight(1f) + .fillMaxHeight() + .padding(start = BottomNavigationSize.HORIZONTAL_PADDING.size.dp), + tab = NavTab.MYPAGE, + painter = null, + tint = Primary, + onClick = onUserInfoClick + ) + } + + // 중앙 버튼 + MapBottomNavigationItem( + modifier = Modifier + .size(BottomNavigationIconSize.CENTER.size.dp) + .clip(CircleShape) + .background( + color = lastLocation?.let { + MaterialTheme.colorScheme.primary + } ?: Color.Gray + ), + tab = NavTab.SEARCH, + painter = painterResource(R.drawable.ic_musical_note_64), + tint = MaterialTheme.colorScheme.onPrimary, + onClick = onCenterClick + ) + } +} + +@Composable +private fun MapBottomNavigationItem( + modifier: Modifier = Modifier, + tab: NavTab, + painter: Painter?, + tint: Color, + onClick: () -> Unit +) { + Box( + modifier = modifier + .clickable { onClick() }, + contentAlignment = Alignment.Center, + ) { + Icon( + modifier = if (tab.iconSize != null) Modifier.size(tab.iconSize.dp) else Modifier, + painter = painter ?: rememberVectorPainter(tab.icon), + contentDescription = stringResource(tab.contentDescription), + tint = tint + ) + } +} + +@Preview(showBackground = true) +@Composable +fun BottomNavigationLightPreview() { + MusicRoadTheme { + MapBottomNavBar( + onFavoriteClick = {}, + lastLocation = null, + onCenterClick = {}, + onUserInfoClick = {} + ) + } +} + +@Preview(showBackground = true, uiMode = Configuration.UI_MODE_NIGHT_YES) +@Composable +fun BottomNavigationDarkPreview() { + MusicRoadTheme { + MapBottomNavBar( + onFavoriteClick = {}, + lastLocation = null, + onCenterClick = {}, + onUserInfoClick = {} + ) + } +} diff --git a/feature/map/src/main/java/com/squirtles/map/components/PickNotificationBanner.kt b/feature/map/src/main/java/com/squirtles/map/components/PickNotificationBanner.kt new file mode 100644 index 00000000..136b8974 --- /dev/null +++ b/feature/map/src/main/java/com/squirtles/map/components/PickNotificationBanner.kt @@ -0,0 +1,106 @@ +package com.squirtles.map.components + +import androidx.compose.animation.core.FastOutSlowInEasing +import androidx.compose.animation.core.RepeatMode +import androidx.compose.animation.core.animateFloat +import androidx.compose.animation.core.infiniteRepeatable +import androidx.compose.animation.core.rememberInfiniteTransition +import androidx.compose.animation.core.tween +import androidx.compose.foundation.background +import androidx.compose.foundation.clickable +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.offset +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.clip +import androidx.compose.ui.platform.LocalConfiguration +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.dp +import androidx.core.graphics.toColorInt +import com.squirtles.common.ui.theme.Black +import com.squirtles.common.ui.theme.MusicRoadTheme +import com.squirtles.common.ui.theme.White +import com.squirtles.map.R +import com.squirtles.model.Creator +import com.squirtles.model.LocationPoint +import com.squirtles.model.Pick +import com.squirtles.model.Song + +@Composable +fun PickNotificationBanner( + modifier: Modifier = Modifier, + isPlaying: Boolean = false, + nearPicks: List, + onClick: () -> Unit, +) { + val infiniteTransition = rememberInfiniteTransition(label = "infinite repeatable") + val offsetY by infiniteTransition.animateFloat( + initialValue = 8f, + targetValue = 0f, + animationSpec = infiniteRepeatable( + animation = tween(durationMillis = 600, easing = FastOutSlowInEasing), + repeatMode = RepeatMode.Reverse + ), + label = "infinite repeatable" + ) + + Box( + modifier = modifier + .fillMaxSize() + .offset(y = offsetY.dp), + ) { + Text( + text = stringResource( + id = if (isPlaying) R.string.map_pick_notification_stop else R.string.map_pick_notification_play, + nearPicks.count() + ), + modifier = Modifier + .align(Alignment.TopCenter) + .offset(y = (LocalConfiguration.current.screenHeightDp * 0.08).dp) + .clip(RoundedCornerShape(30.dp)) + .clickable { + onClick() + } + .background(White.copy(alpha = 0.8f)) + .padding(vertical = 10.dp, horizontal = 23.dp), + color = Black, + ) + } +} + +@Preview +@Composable +private fun PickNotificationBannerPreview() { + MusicRoadTheme { + PickNotificationBanner( + nearPicks = listOf( Pick( + id = "", + song = Song( + id = "", + songName = "", + artistName = "", + albumName = "", + imageUrl = "", + genreNames = listOf(), + bgColor = "#000000".toColorInt(), + externalUrl = "", + previewUrl = "" + ), + comment = "", + createdAt = "", + createdBy = Creator(uid = "", userName = "짱구"), + favoriteCount = 0, + location = LocationPoint(1.0, 1.0), + musicVideoUrl = "", + )), + onClick = { } + ) + } +} diff --git a/feature/map/src/main/java/com/squirtles/map/marker/ClusterMarkerIconView.kt b/feature/map/src/main/java/com/squirtles/map/marker/ClusterMarkerIconView.kt new file mode 100644 index 00000000..9dc4dfb6 --- /dev/null +++ b/feature/map/src/main/java/com/squirtles/map/marker/ClusterMarkerIconView.kt @@ -0,0 +1,55 @@ +package com.squirtles.map.marker + +import android.annotation.SuppressLint +import android.content.Context +import android.graphics.Canvas +import android.graphics.Paint +import android.view.View + +@SuppressLint("ViewConstructor") +class ClusterMarkerIconView( + context: Context, + private val densityType: DensityType +) : View(context) { + + private val fillPaint = Paint().apply { + style = Paint.Style.FILL + } + + override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) { + val width = resolveSize(MARKER_WIDTH.dpToPx(), widthMeasureSpec) + val height = resolveSize(MARKER_HEIGHT.dpToPx(), heightMeasureSpec) + setMeasuredDimension(width, height) + } + + override fun onDraw(canvas: Canvas) { + super.onDraw(canvas) + fillPaint.color = densityType.color + + // back circle + fillPaint.alpha = 64 + canvas.drawCircle( + width / 2f, + height / 2f, + (width / 2f) - densityType.offset, + fillPaint + ) + + // circle + fillPaint.alpha = 255 + canvas.drawCircle( + width / 2f, + height / 2f, + (width / 2f) - OFFSET - densityType.offset, + fillPaint + ) + } + + private fun Int.dpToPx() = (this * resources.displayMetrics.density).toInt() + + companion object { + private const val OFFSET = 8 + private const val MARKER_WIDTH = 40 + private const val MARKER_HEIGHT = 50 + } +} diff --git a/feature/map/src/main/java/com/squirtles/map/marker/Clusterer.kt b/feature/map/src/main/java/com/squirtles/map/marker/Clusterer.kt new file mode 100644 index 00000000..4456a438 --- /dev/null +++ b/feature/map/src/main/java/com/squirtles/map/marker/Clusterer.kt @@ -0,0 +1,143 @@ +package com.squirtles.map.marker + +import android.content.Context +import android.graphics.PointF +import android.view.View +import androidx.compose.ui.graphics.toArgb +import com.naver.maps.map.clustering.ClusterMarkerInfo +import com.naver.maps.map.clustering.Clusterer +import com.naver.maps.map.clustering.ClusteringKey +import com.naver.maps.map.clustering.DefaultClusterMarkerUpdater +import com.naver.maps.map.clustering.DefaultLeafMarkerUpdater +import com.naver.maps.map.clustering.DefaultMarkerManager +import com.naver.maps.map.clustering.LeafMarkerInfo +import com.naver.maps.map.overlay.Align +import com.naver.maps.map.overlay.Marker +import com.naver.maps.map.overlay.Overlay +import com.naver.maps.map.overlay.OverlayImage +import com.squirtles.common.ui.theme.Black +import com.squirtles.common.ui.theme.Blue +import com.squirtles.common.ui.theme.Primary +import com.squirtles.common.ui.theme.White +import com.squirtles.map.DEFAULT_MARKER_Z_INDEX +import com.squirtles.map.MapViewModel +import com.squirtles.map.setCameraToMarker + + +internal fun buildClusterer( + context: Context, + mapViewModel: MapViewModel, +): Clusterer { + return Clusterer.ComplexBuilder() + .thresholdStrategy { zoom -> + when { + zoom >= 17.0 -> 15.0 + zoom >= 16.0 -> 16.0 + zoom >= 15.0 -> 17.0 + zoom >= 14.0 -> 20.0 + zoom >= 13.5 -> 25.0 + zoom >= 13.0 -> 30.0 + zoom >= 12.5 -> 35.0 + zoom >= 12.0 -> 40.0 + zoom >= 11.0 -> 45.0 + zoom >= 9.0 -> 40.0 + zoom >= 7.5 -> 21.0 + zoom >= 7.0 -> 18.0 + else -> 15.0 + } + } + .tagMergeStrategy { cluster -> + cluster.children.map { it.tag }.joinToString(",") + } + .markerManager(object : DefaultMarkerManager() { + override fun createMarker(): Marker { + val marker = Marker() + with(marker) { + icon = OverlayImage.fromView(View(context)) + setCaptionAligns(Align.Center) + captionHaloColor = android.graphics.Color.TRANSPARENT + } + return marker + } + }) + .clusterMarkerUpdater(object : DefaultClusterMarkerUpdater() { + override fun updateClusterMarker(info: ClusterMarkerInfo, marker: Marker) { + // 클릭된 마커가 클러스터 마커 안에 포함되면 클릭된 마커 해제 + mapViewModel.clickedMarkerState.value.curPickId?.let { curPickId -> + if (info.tag.toString().contains(curPickId)) { + mapViewModel.resetClickedMarkerState(context) + } + } + val densityType = when { + info.size < 10 -> DensityType.LOW + info.size < 100 -> DensityType.MEDIUM + else -> DensityType.HIGH + } + val captionColor = when { + densityType == DensityType.LOW -> Black + else -> White + } + val clusterMarkerIconView = ClusterMarkerIconView(context, densityType) + marker.icon = OverlayImage.fromView(clusterMarkerIconView) + marker.zIndex = DEFAULT_MARKER_Z_INDEX + marker.anchor = PointF(0.5F, 0.5F) + marker.captionText = info.size.toString() + marker.captionColor = captionColor.toArgb() + marker.onClickListener = Overlay.OnClickListener { + marker.map?.let { map -> + setCameraToMarker( + map = map, + clickedMarkerPosition = marker.position + ) + mapViewModel.setClickedMarkerState( + context = context, + marker = marker, + clusterTag = info.tag.toString() + ) + } + true + } + // 클러스터 마커를 클릭한 채로 configuration change 시 크기 유지 + if (info.tag.toString() + == mapViewModel.clickedMarkerState.value.clusterPickList?.joinToString(",") { it.id } + ) { + mapViewModel.setClickedMarker(context, marker) + } + } + }) + .leafMarkerUpdater(object : DefaultLeafMarkerUpdater() { + override fun updateLeafMarker(info: LeafMarkerInfo, marker: Marker) { + marker.anchor = Marker.DEFAULT_ANCHOR + marker.zIndex = DEFAULT_MARKER_Z_INDEX + marker.captionText = "" + + val pick = (info.key as MarkerKey).pick + val leafMarkerIconView = LeafMarkerIconView(context).apply { + val color = if (pick.createdBy.uid == mapViewModel.getUid()) Blue else Primary + setPaintColor(color.toArgb()) + } + leafMarkerIconView.setLeafMarkerIcon(pick) { + marker.icon = OverlayImage.fromView(leafMarkerIconView) + marker.setOnClickListener { + marker.map?.let { map -> + setCameraToMarker( + map = map, + clickedMarkerPosition = marker.position + ) + mapViewModel.setClickedMarkerState( + context = context, + marker = marker, + pickId = pick.id + ) + } + true + } + // 단말 마커를 클릭한 채로 configuration change 시 크기 유지 + if (pick.id == mapViewModel.clickedMarkerState.value.curPickId) { + mapViewModel.setClickedMarker(context, marker) + } + } + } + }) + .build() +} diff --git a/feature/map/src/main/java/com/squirtles/map/marker/DensityType.kt b/feature/map/src/main/java/com/squirtles/map/marker/DensityType.kt new file mode 100644 index 00000000..f9441c05 --- /dev/null +++ b/feature/map/src/main/java/com/squirtles/map/marker/DensityType.kt @@ -0,0 +1,12 @@ +package com.squirtles.map.marker + +import androidx.compose.ui.graphics.toArgb +import com.squirtles.common.ui.theme.Primary20 +import com.squirtles.common.ui.theme.Primary50 +import com.squirtles.common.ui.theme.Primary80 + +enum class DensityType(val offset: Int, val color: Int) { + LOW(4, Primary80.toArgb()), + MEDIUM(2, Primary50.toArgb()), + HIGH(0, Primary20.toArgb()) +} diff --git a/feature/map/src/main/java/com/squirtles/map/marker/LeafMarkerIconView.kt b/feature/map/src/main/java/com/squirtles/map/marker/LeafMarkerIconView.kt new file mode 100644 index 00000000..318144ca --- /dev/null +++ b/feature/map/src/main/java/com/squirtles/map/marker/LeafMarkerIconView.kt @@ -0,0 +1,106 @@ +package com.squirtles.map.marker + +import android.content.Context +import android.graphics.Bitmap +import android.graphics.Canvas +import android.graphics.Paint +import android.graphics.Rect +import android.util.AttributeSet +import android.view.View +import androidx.annotation.ColorInt +import coil3.SingletonImageLoader +import coil3.request.ImageRequest +import coil3.request.allowHardware +import coil3.request.transformations +import coil3.toBitmap +import coil3.transform.CircleCropTransformation +import com.squirtles.common.ui.Constants.REQUEST_IMAGE_SIZE_DEFAULT +import com.squirtles.model.Pick + +class LeafMarkerIconView( + context: Context, + attrs: AttributeSet? = null +) : View(context, attrs) { + private val fillPaint = Paint().apply { + style = Paint.Style.FILL + } + private val strokePaint = Paint().apply { + style = Paint.Style.STROKE + strokeWidth = STROKE_WIDTH + } + private val imageLoader = SingletonImageLoader.get(context) + private var bitmap: Bitmap? = null + private val bitmapRect = Rect() + + override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) { + val width = resolveSize(MARKER_WIDTH.dpToPx(), widthMeasureSpec) + val height = resolveSize(MARKER_HEIGHT.dpToPx(), heightMeasureSpec) + setMeasuredDimension(width, height) + } + + override fun onDraw(canvas: Canvas) { + super.onDraw(canvas) + + canvas.drawRect( + (width - STROKE_WIDTH) / 2f, + width / 2f, + (width + STROKE_WIDTH) / 2f, + height.toFloat(), + fillPaint + ) + bitmap?.let { + bitmapRect.set(0, 0, width, width) + canvas.drawBitmap(it, null, bitmapRect, null) + canvas.drawCircle( + width / 2f, + width / 2f, + (width - STROKE_WIDTH) / 2f, + strokePaint + ) // bitmap 테두리 그리기 + } ?: canvas.drawCircle( + width / 2f, + width / 2f, + width / 2f, + fillPaint + ) // bitmap이 null이면 이미지 없이 원만 그려지도록 + } + + fun setPaintColor(@ColorInt color: Int) { + fillPaint.color = color + strokePaint.color = color + } + + fun setLeafMarkerIcon(pick: Pick, onImageLoaded: () -> Unit) { + val song = pick.song + loadImage(song.getImageUrlWithSize(REQUEST_IMAGE_SIZE_DEFAULT.width, REQUEST_IMAGE_SIZE_DEFAULT.height)) { + onImageLoaded() + } + } + + private fun loadImage(url: String?, onImageLoaded: () -> Unit) { + val request = ImageRequest.Builder(context) + .data(url) + .allowHardware(false) + .transformations(CircleCropTransformation()) + .listener( + onSuccess = { _, result -> + bitmap = result.image.toBitmap() + onImageLoaded() + }, + onError = { _, error -> + onImageLoaded() + } + ) + .build() + + imageLoader.enqueue(request) + } + + private fun Int.dpToPx() = (this * resources.displayMetrics.density).toInt() + + companion object { + private const val STROKE_WIDTH = 8f + private const val MARKER_WIDTH = 40 + private const val MARKER_HEIGHT = 50 + } +} diff --git a/feature/map/src/main/java/com/squirtles/map/marker/MarkerKey.kt b/feature/map/src/main/java/com/squirtles/map/marker/MarkerKey.kt new file mode 100644 index 00000000..6a6c5ced --- /dev/null +++ b/feature/map/src/main/java/com/squirtles/map/marker/MarkerKey.kt @@ -0,0 +1,9 @@ +package com.squirtles.map.marker + +import com.naver.maps.geometry.LatLng +import com.naver.maps.map.clustering.ClusteringKey +import com.squirtles.model.Pick + +data class MarkerKey(val pick: Pick) : ClusteringKey { + override fun getPosition() = LatLng(pick.location.latitude, pick.location.longitude) +} diff --git a/feature/map/src/main/java/com/squirtles/map/navigation/MapNavigation.kt b/feature/map/src/main/java/com/squirtles/map/navigation/MapNavigation.kt new file mode 100644 index 00000000..476dd4d7 --- /dev/null +++ b/feature/map/src/main/java/com/squirtles/map/navigation/MapNavigation.kt @@ -0,0 +1,36 @@ +package com.squirtles.map.navigation + +import androidx.navigation.NavController +import androidx.navigation.NavGraphBuilder +import androidx.navigation.NavOptions +import androidx.navigation.compose.composable +import com.squirtles.map.MapScreen +import com.squirtles.map.MapViewModel +import com.squirtles.musicplayer.PlayerServiceViewModel +import com.squirtles.navigation.Route + +fun NavController.navigateMap(navOptions: NavOptions? = null) { + navigate(Route.Map, navOptions) +} + +fun NavGraphBuilder.mapNavGraph( + mapViewModel: MapViewModel, + playerServiceViewModel: PlayerServiceViewModel, + onFavoriteClick: (String) -> Unit, + onCenterClick: () -> Unit, + onUserInfoClick: (String) -> Unit, + onPickSummaryClick: (String) -> Unit, + onLoadingDialogCloseClick: () -> Unit +) { + composable { + MapScreen( + mapViewModel = mapViewModel, + playerServiceViewModel = playerServiceViewModel, + onFavoriteClick = onFavoriteClick, + onCenterClick = onCenterClick, + onUserInfoClick = onUserInfoClick, + onPickSummaryClick = onPickSummaryClick, + onLoadingDialogCloseClick = onLoadingDialogCloseClick + ) + } +} diff --git a/feature/map/src/main/java/com/squirtles/map/navigation/NavTab.kt b/feature/map/src/main/java/com/squirtles/map/navigation/NavTab.kt new file mode 100644 index 00000000..022106af --- /dev/null +++ b/feature/map/src/main/java/com/squirtles/map/navigation/NavTab.kt @@ -0,0 +1,34 @@ +package com.squirtles.map.navigation + +import androidx.annotation.StringRes +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.filled.FavoriteBorder +import androidx.compose.material.icons.outlined.AccountCircle +import androidx.compose.material.icons.outlined.MusicNote +import androidx.compose.ui.graphics.vector.ImageVector +import com.squirtles.map.R +import com.squirtles.map.BottomNavigationIconSize + +internal enum class NavTab( + @StringRes val contentDescription: Int, + val icon: ImageVector, + val iconSize: Int?, +) { + FAVORITE( + contentDescription = R.string.map_navigation_favorite_icon_description, + icon = Icons.Default.FavoriteBorder, + iconSize = null, + ), + + MYPAGE( + contentDescription = R.string.map_navigation_setting_icon_description, + icon = Icons.Outlined.AccountCircle, + iconSize = null, + ), + + SEARCH( + contentDescription = R.string.map_navigation_center_icon_description, + icon = Icons.Outlined.MusicNote, + iconSize = BottomNavigationIconSize.CENTER_ICON.size, + ), +} diff --git a/feature/map/src/main/res/drawable/ic_location.xml b/feature/map/src/main/res/drawable/ic_location.xml new file mode 100644 index 00000000..df94a6aa --- /dev/null +++ b/feature/map/src/main/res/drawable/ic_location.xml @@ -0,0 +1,12 @@ + + + + \ No newline at end of file diff --git a/feature/map/src/main/res/drawable/ic_musical_note_64.png b/feature/map/src/main/res/drawable/ic_musical_note_64.png new file mode 100644 index 0000000000000000000000000000000000000000..6d2225db467258d3cd64e50b6bc320bb275c3139 GIT binary patch literal 1067 zcmV+`1l0S9P)=dWm6c(@z3SVHAWa?{J(iIUQojeg9(jElr*VJ zNmAZWW8xJlQS*irCFO+@(ZWX#V&k{jV+F&6{$_;pH(@bqzodPvDqQ1?(CCI1O8H*eHT^ z5df^m(>Q$Cfw~F+uEchnILshj20%P8eSqm<1?Uohm+(izew@e&k6>BTLAwNCJFdXq z0qr;8U94>~XqN!M49>@A1KLl=8@L-wnh4l60PwMRF?w`Bdk>rOvIY`l7XiR8;wOJK z_6+#WI=qeZ789te01oI`oP`$#v`>lLac{ zi3Is+qWxGrkL6Vzmjd8`J`~sd>xs5g@laKVr2!xkt=p{f8@;1xAX(Op#4yTHT7X!2A8a0#v* z)nWd~YH7T~EG=28>Vf6^-u_&D_l2wxcYOUn7sde~S zGn3yu;;}cT)uR4ZcjCLmPVl1D6I0ebV_z@oumP!Bs002ovPDHLkV1j+s;OhVY literal 0 HcmV?d00001 diff --git a/feature/map/src/main/res/values/strings.xml b/feature/map/src/main/res/values/strings.xml new file mode 100644 index 00000000..76a73ef0 --- /dev/null +++ b/feature/map/src/main/res/values/strings.xml @@ -0,0 +1,22 @@ + + + 현재 위치 로딩 중... + 종료 + + + 픽 보관함 이동 버튼 아이콘 + 설정 이동 버튼 아이콘 + 내비게이션 중앙 버튼 아이콘 + 🎧 주변에 %d개의 픽이 있습니다! + 🔇 주변에 %d개의 픽이 있습니다! + 앨범 이미지 + 님의 픽 + 픽을 담은 개수 + 내가 + 등록한 픽 + + + 담은 픽을 확인하기 위해\n로그인이 필요합니다 + 픽을 등록하기 위해\n로그인이 필요합니다 + 로그인이 필요합니다 + diff --git a/data/src/test/java/com/squirtles/data/ExampleUnitTest.kt b/feature/map/src/test/java/com/squirtles/map/ExampleUnitTest.kt similarity index 91% rename from data/src/test/java/com/squirtles/data/ExampleUnitTest.kt rename to feature/map/src/test/java/com/squirtles/map/ExampleUnitTest.kt index 8e88260a..99fcf0f5 100644 --- a/data/src/test/java/com/squirtles/data/ExampleUnitTest.kt +++ b/feature/map/src/test/java/com/squirtles/map/ExampleUnitTest.kt @@ -1,4 +1,4 @@ -package com.squirtles.data +package com.squirtles.map import org.junit.Test @@ -14,4 +14,4 @@ class ExampleUnitTest { fun addition_isCorrect() { assertEquals(4, 2 + 2) } -} \ No newline at end of file +} diff --git a/feature/mypick/src/main/java/com/squirtles/mypick/MyPickListViewModel.kt b/feature/mypick/src/main/java/com/squirtles/mypick/MyPickListViewModel.kt index d10378a6..fef28f3e 100644 --- a/feature/mypick/src/main/java/com/squirtles/mypick/MyPickListViewModel.kt +++ b/feature/mypick/src/main/java/com/squirtles/mypick/MyPickListViewModel.kt @@ -2,8 +2,8 @@ package com.squirtles.mypick import com.squirtles.order.usecase.GetMyPickListOrderUseCase import com.squirtles.order.usecase.SaveMyPickListOrderUseCase -import com.squirtles.pick.usecase.DeletePickUseCase -import com.squirtles.pick.usecase.FetchMyPicksUseCase +import com.squirtles.domain.pick.usecase.DeletePickUseCase +import com.squirtles.domain.pick.usecase.FetchMyPicksUseCase import com.squirtles.picklist.PickListViewModel import com.squirtles.user.usecase.GetCurrentUidUseCase import dagger.hilt.android.lifecycle.HiltViewModel diff --git a/gradle.properties b/gradle.properties index 20e2a015..2d0095c0 100644 --- a/gradle.properties +++ b/gradle.properties @@ -6,7 +6,7 @@ # http://www.gradle.org/docs/current/userguide/build_environment.html # Specifies the JVM arguments used for the daemon process. # The setting is particularly useful for tweaking memory settings. -org.gradle.jvmargs=-Xmx2048m -Dfile.encoding=UTF-8 +org.gradle.jvmargs=-Xmx4048m -Dfile.encoding=UTF-8 # When configured, Gradle will run in incubating parallel mode. # This option should only be used with decoupled projects. For more details, visit # https://developer.android.com/r/tools/gradle-multi-project-decoupled-projects @@ -20,4 +20,4 @@ kotlin.code.style=official # Enables namespacing of each library's R class so that its R class includes only the # resources declared in the library itself and none from the library's dependencies, # thereby reducing the size of the R class for that library -android.nonTransitiveRClass=true \ No newline at end of file +android.nonTransitiveRClass=true diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index be596a3f..49867684 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -66,9 +66,9 @@ hiltNavigationCompose = "1.2.0" inject = "1" # Test -espressoCore = "3.6.1" +espressoCore = "3.5.0" junit = "4.13.2" -junitVersion = "1.2.1" +junitVersion = "1.1.5" # Google googleServices = "4.4.2" @@ -78,8 +78,7 @@ androidx-credentials = "1.3.0" googleid = "1.1.1" # Paging -pagingRuntime = "3.3.4" -pagingComposeAndroid = "3.3.4" +paging = "3.3.4" # Serialization kotlinxSerializationJson = "1.6.0" @@ -95,6 +94,9 @@ retrofit = "2.11.0" material = "1.12.0" composeMaterial3 = "1.3.1" composeMaterialIconsExtended = "1.7.8" +animationCoreAndroid = "1.7.8" +foundationLayoutAndroid = "1.7.8" +foundationAndroid = "1.7.8" [libraries] # AndroidX @@ -135,10 +137,12 @@ compose-ui-test-manifest = { group = "androidx.compose.ui", name = "ui-test-mani compose-ui-test-junit4 = { group = "androidx.compose.ui", name = "ui-test-junit4" } compose-runtime-android = { group = "androidx.compose.runtime", name = "runtime-android", version.ref = "compose-runtime-android" } kotlinx-immutable = { group = "org.jetbrains.kotlinx", name = "kotlinx-collections-immutable", version.ref = "kotlinxImmutable" } +androidx-animation-core-android = { group = "androidx.compose.animation", name = "animation-core-android", version.ref = "animationCoreAndroid" } +androidx-foundation-layout-android = { group = "androidx.compose.foundation", name = "foundation-layout-android", version.ref = "foundationLayoutAndroid" } # Paging -androidx-paging-runtime = { module = "androidx.paging:paging-runtime", version.ref = "pagingRuntime" } -androidx-paging-compose = { group = "androidx.paging", name = "paging-compose-android", version.ref = "pagingComposeAndroid" } +androidx-paging-runtime = { module = "androidx.paging:paging-runtime", version.ref = "paging" } +androidx-paging-compose = { group = "androidx.paging", name = "paging-compose-android", version.ref = "paging" } # Auth androidx-credentials = { module = "androidx.credentials:credentials", version.ref = "androidx-credentials" } @@ -197,6 +201,7 @@ android-gradlePlugin = { group = "com.android.tools.build", name = "gradle", ver android-tools-common = { group = "com.android.tools", name = "common", version.ref = "androidTools" } kotlin-gradlePlugin = { group = "org.jetbrains.kotlin", name = "kotlin-gradle-plugin", version.ref = "kotlin" } ksp-gradlePlugin = { group = "com.google.devtools.ksp", name = "com.google.devtools.ksp.gradle.plugin", version.ref = "ksp" } +androidx-foundation-android = { group = "androidx.compose.foundation", name = "foundation-android", version.ref = "foundationAndroid" } [bundles] androidx-core = [ diff --git a/mediaservice/src/main/java/com/squirtles/mediaservice/CustomMediaSessionCallback.kt b/mediaservice/src/main/java/com/squirtles/mediaservice/CustomMediaSessionCallback.kt index ac9ae731..a6601231 100644 --- a/mediaservice/src/main/java/com/squirtles/mediaservice/CustomMediaSessionCallback.kt +++ b/mediaservice/src/main/java/com/squirtles/mediaservice/CustomMediaSessionCallback.kt @@ -1,96 +1,96 @@ -package com.squirtles.mediaservice - -import android.os.Build -import android.os.Bundle -import androidx.annotation.OptIn -import androidx.media3.common.util.UnstableApi -import androidx.media3.session.CommandButton -import androidx.media3.session.MediaSession -import androidx.media3.session.SessionCommand -import androidx.media3.session.SessionResult -import com.google.common.util.concurrent.Futures -import com.google.common.util.concurrent.ListenableFuture - -const val SEEK_TO_DURATION = 5_000L - -@OptIn(UnstableApi::class) -internal class CustomMediaSessionCallback : MediaSession.Callback { - - /* MediaSession이 MediaController의 연결 요청을 수락할 때 사용할 수 있는 명령어 집합을 구성하고 반환 */ - override fun onConnect( - session: MediaSession, - controller: MediaSession.ControllerInfo - ): MediaSession.ConnectionResult = - - // 컨트롤러가 미디어 알림과 연관된 컨트롤러인지 확인 - if (session.isMediaNotificationController(controller)) { - val connectionResult = super.onConnect(session, controller) - val availableSessionCommands = connectionResult.availableSessionCommands.buildUpon() - var customCommands = PlayerCommands.entries.toList() - - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE) { - // 안드로이드 14 (API 34) 이상 - customCommands = customCommands.reversed() - } - - customCommands.forEach { commandButton -> - commandButton.sessionCommand.let { - it - availableSessionCommands.add(it) - } - } - - MediaSession.ConnectionResult.AcceptedResultBuilder(session) - .setAvailableSessionCommands(availableSessionCommands.build()) - .setCustomLayout( - customCommands - .filter { - it.customAction == PlayerCommands.SEEK_REWIND.customAction - || it.customAction == PlayerCommands.SEEK_FORWARD.customAction - }.map { - CommandButton.Builder() - .setDisplayName(it.displayName) - .setIconResId(it.iconResId(session.player.isPlaying)) - .setSessionCommand(it.sessionCommand) - .build() - } - ) - .build() - } else { - MediaSession.ConnectionResult.AcceptedResultBuilder(session).build() - } - - // 사용자 정의 명령이 수신되었을 때 호출되는 콜백 - override fun onCustomCommand( - session: MediaSession, - controller: MediaSession.ControllerInfo, - customCommand: SessionCommand, - args: Bundle - ): ListenableFuture { - - when (customCommand.customAction) { - PlayerCommands.SEEK_REWIND.customAction -> { - session.player.run { - seekTo(currentPosition - SEEK_TO_DURATION) - } - } - - PlayerCommands.PLAY_AND_PAUSE.customAction -> { - if (!session.player.isPlaying) session.player.play() - else session.player.pause() - } - - PlayerCommands.SEEK_FORWARD.customAction -> { - session.player.run { - seekTo(currentPosition + SEEK_TO_DURATION) - } - } - - else -> { - // Do nothing - } - } - - return Futures.immediateFuture(SessionResult(SessionResult.RESULT_SUCCESS)) - } -} +//package com.squirtles.mediaservice +// +//import android.os.Build +//import android.os.Bundle +//import androidx.annotation.OptIn +//import androidx.media3.common.util.UnstableApi +//import androidx.media3.session.CommandButton +//import androidx.media3.session.MediaSession +//import androidx.media3.session.SessionCommand +//import androidx.media3.session.SessionResult +//import com.google.common.util.concurrent.Futures +//import com.google.common.util.concurrent.ListenableFuture +// +//const val SEEK_TO_DURATION = 5_000L +// +//@OptIn(UnstableApi::class) +//internal class CustomMediaSessionCallback : MediaSession.Callback { +// +// /* MediaSession이 MediaController의 연결 요청을 수락할 때 사용할 수 있는 명령어 집합을 구성하고 반환 */ +// override fun onConnect( +// session: MediaSession, +// controller: MediaSession.ControllerInfo +// ): MediaSession.ConnectionResult = +// +// // 컨트롤러가 미디어 알림과 연관된 컨트롤러인지 확인 +// if (session.isMediaNotificationController(controller)) { +// val connectionResult = super.onConnect(session, controller) +// val availableSessionCommands = connectionResult.availableSessionCommands.buildUpon() +// var customCommands = PlayerCommands.entries.toList() +// +// if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE) { +// // 안드로이드 14 (API 34) 이상 +// customCommands = customCommands.reversed() +// } +// +// customCommands.forEach { commandButton -> +// commandButton.sessionCommand.let { +// it +// availableSessionCommands.add(it) +// } +// } +// +// MediaSession.ConnectionResult.AcceptedResultBuilder(session) +// .setAvailableSessionCommands(availableSessionCommands.build()) +// .setCustomLayout( +// customCommands +// .filter { +// it.customAction == PlayerCommands.SEEK_REWIND.customAction +// || it.customAction == PlayerCommands.SEEK_FORWARD.customAction +// }.map { +// CommandButton.Builder() +// .setDisplayName(it.displayName) +// .setIconResId(it.iconResId(session.player.isPlaying)) +// .setSessionCommand(it.sessionCommand) +// .build() +// } +// ) +// .build() +// } else { +// MediaSession.ConnectionResult.AcceptedResultBuilder(session).build() +// } +// +// // 사용자 정의 명령이 수신되었을 때 호출되는 콜백 +// override fun onCustomCommand( +// session: MediaSession, +// controller: MediaSession.ControllerInfo, +// customCommand: SessionCommand, +// args: Bundle +// ): ListenableFuture { +// +// when (customCommand.customAction) { +// PlayerCommands.SEEK_REWIND.customAction -> { +// session.player.run { +// seekTo(currentPosition - SEEK_TO_DURATION) +// } +// } +// +// PlayerCommands.PLAY_AND_PAUSE.customAction -> { +// if (!session.player.isPlaying) session.player.play() +// else session.player.pause() +// } +// +// PlayerCommands.SEEK_FORWARD.customAction -> { +// session.player.run { +// seekTo(currentPosition + SEEK_TO_DURATION) +// } +// } +// +// else -> { +// // Do nothing +// } +// } +// +// return Futures.immediateFuture(SessionResult(SessionResult.RESULT_SUCCESS)) +// } +//} diff --git a/mediaservice/src/main/java/com/squirtles/mediaservice/MediaControllerProvider.kt b/mediaservice/src/main/java/com/squirtles/mediaservice/MediaControllerProvider.kt index b17d9b54..8f8e89d4 100644 --- a/mediaservice/src/main/java/com/squirtles/mediaservice/MediaControllerProvider.kt +++ b/mediaservice/src/main/java/com/squirtles/mediaservice/MediaControllerProvider.kt @@ -1,58 +1,58 @@ -package com.squirtles.mediaservice - -import androidx.media3.common.util.UnstableApi -import androidx.media3.session.MediaController -import com.google.common.util.concurrent.FutureCallback -import com.google.common.util.concurrent.Futures -import com.google.common.util.concurrent.ListenableFuture -import com.google.common.util.concurrent.MoreExecutors -import kotlinx.coroutines.cancel -import kotlinx.coroutines.channels.awaitClose -import kotlinx.coroutines.flow.Flow -import kotlinx.coroutines.flow.callbackFlow -import javax.inject.Inject -import javax.inject.Singleton -import kotlin.coroutines.cancellation.CancellationException - -interface MediaControllerProvider { - val mediaControllerFlow: Flow - val audioSessionFlow: Flow -} - -/* mediaControllerFuture: MediaController를 비동기적으로 제공하는 ListenableFuture */ -@UnstableApi -@Singleton -class MediaControllerProviderImpl @Inject constructor( - private val audioSessionId: Int, - mediaControllerFuture: ListenableFuture -) : MediaControllerProvider { - - /* mediaControllerFlow가 처음 구독될 때 callbackFlow가 실행 */ - override val mediaControllerFlow: Flow = callbackFlow { - /* Futures.addCallback을 사용하여 mediaControllerFuture의 결과를 기다림 */ - Futures.addCallback( - mediaControllerFuture, - object : FutureCallback { - - /* MediaController 객체가 준비되면 trySend(result)를 통해 Flow로 결과를 전송 - 즉, mediaControllerFlow를 구독하고 있는 곳에 MediaController 객체가 전달 */ - override fun onSuccess(result: MediaController) { - result.setPlaybackSpeed(1.0f) - trySend(result) - } - - override fun onFailure(t: Throwable) { - cancel(CancellationException(t.message)) - } - }, - MoreExecutors.directExecutor() - ) - - awaitClose { } - } - - override val audioSessionFlow = callbackFlow { - trySend(audioSessionId) - awaitClose { } - } -} +//package com.squirtles.mediaservice +// +//import androidx.media3.common.util.UnstableApi +//import androidx.media3.session.MediaController +//import com.google.common.util.concurrent.FutureCallback +//import com.google.common.util.concurrent.Futures +//import com.google.common.util.concurrent.ListenableFuture +//import com.google.common.util.concurrent.MoreExecutors +//import kotlinx.coroutines.cancel +//import kotlinx.coroutines.channels.awaitClose +//import kotlinx.coroutines.flow.Flow +//import kotlinx.coroutines.flow.callbackFlow +//import javax.inject.Inject +//import javax.inject.Singleton +//import kotlin.coroutines.cancellation.CancellationException +// +//interface MediaControllerProvider { +// val mediaControllerFlow: Flow +// val audioSessionFlow: Flow +//} +// +///* mediaControllerFuture: MediaController를 비동기적으로 제공하는 ListenableFuture */ +//@UnstableApi +//@Singleton +//class MediaControllerProviderImpl @Inject constructor( +// private val audioSessionId: Int, +// mediaControllerFuture: ListenableFuture +//) : MediaControllerProvider { +// +// /* mediaControllerFlow가 처음 구독될 때 callbackFlow가 실행 */ +// override val mediaControllerFlow: Flow = callbackFlow { +// /* Futures.addCallback을 사용하여 mediaControllerFuture의 결과를 기다림 */ +// Futures.addCallback( +// mediaControllerFuture, +// object : FutureCallback { +// +// /* MediaController 객체가 준비되면 trySend(result)를 통해 Flow로 결과를 전송 +// 즉, mediaControllerFlow를 구독하고 있는 곳에 MediaController 객체가 전달 */ +// override fun onSuccess(result: MediaController) { +// result.setPlaybackSpeed(1.0f) +// trySend(result) +// } +// +// override fun onFailure(t: Throwable) { +// cancel(CancellationException(t.message)) +// } +// }, +// MoreExecutors.directExecutor() +// ) +// +// awaitClose { } +// } +// +// override val audioSessionFlow = callbackFlow { +// trySend(audioSessionId) +// awaitClose { } +// } +//} diff --git a/mediaservice/src/main/java/com/squirtles/mediaservice/MediaNotificationProvider.kt b/mediaservice/src/main/java/com/squirtles/mediaservice/MediaNotificationProvider.kt index ada84fe7..93ca80d4 100644 --- a/mediaservice/src/main/java/com/squirtles/mediaservice/MediaNotificationProvider.kt +++ b/mediaservice/src/main/java/com/squirtles/mediaservice/MediaNotificationProvider.kt @@ -1,120 +1,120 @@ -package com.squirtles.mediaservice - -import android.annotation.SuppressLint -import android.app.NotificationChannel -import android.app.NotificationManager -import android.app.PendingIntent -import android.content.ComponentName -import android.content.Context -import android.content.Intent -import android.os.Build -import androidx.annotation.OptIn -import androidx.core.app.NotificationCompat -import androidx.core.graphics.drawable.IconCompat -import androidx.media3.common.Player -import androidx.media3.common.util.UnstableApi -import androidx.media3.session.MediaNotification -import androidx.media3.session.MediaSession -import androidx.media3.session.MediaStyleNotificationHelper -import javax.inject.Inject - -interface MediaNotificationProvider { - - @OptIn(UnstableApi::class) - fun createMediaNotification(actionFactory: MediaNotification.ActionFactory): MediaNotification -} - -class MediaNotificationProviderImpl @Inject constructor( - private val context: Context, - private val mediaSession: MediaSession -) : MediaNotificationProvider { - - @OptIn(UnstableApi::class) - override fun createMediaNotification(actionFactory: MediaNotification.ActionFactory): MediaNotification { - val notificationManager: NotificationManager = - context.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager - - makeNotificationChannel(notificationManager) - - val mediaItem = mediaSession.player.currentMediaItem - - val notificationBuilder = NotificationCompat.Builder( - context, - NOTIFICATION_CHANNEL_ID.toString() - ).apply { - priority = NotificationCompat.PRIORITY_DEFAULT - setSilent(true) - setSmallIcon(R.drawable.ic_musicroad_foreground) - setContentTitle(mediaItem?.mediaMetadata?.title) - setContentIntent(createNotifyPendingIntent()) - setDeleteIntent( - actionFactory.createMediaActionPendingIntent( - mediaSession, - Player.COMMAND_STOP.toLong() - ) - ) - setStyle( - MediaStyleNotificationHelper - .MediaStyle(mediaSession) - ) - setOngoing(true) - - PlayerCommands.entries.forEach { commandButton -> - addAction( - actionFactory.createCustomAction( - mediaSession, - IconCompat.createWithResource( - context, - commandButton.iconResId(mediaSession.player.isPlaying) - ), - commandButton.displayName, - commandButton.customAction, - commandButton.sessionCommand.customExtras - ) - ) - } - } - - return MediaNotification( - NOTIFICATION_CHANNEL_ID, - notificationBuilder.build() - ) - } - - /* 알림 누르면 이동할 intent 설정 */ - private fun createNotifyPendingIntent(): PendingIntent = - PendingIntent.getActivity( - context, - 0, - Intent().apply { - action = Intent.ACTION_VIEW - component = ComponentName(context, TARGET_ACTIVITY) - flags = Intent.FLAG_ACTIVITY_CLEAR_TOP or Intent.FLAG_ACTIVITY_SINGLE_TOP - }, - PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE - ) - - /* Notification Channel 생성 */ - @SuppressLint("ObsoleteSdkInt") - private fun makeNotificationChannel(notificationManager: NotificationManager) { - // Android 8.0 미만 버전에서는 Notification Channel을 생성할 필요가 없음 -> 앱 단일 Channel 가짐 - if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O || - notificationManager.getNotificationChannel(NOTIFICATION_CHANNEL_ID.toString()) != null - ) { - return - } - - val channel = NotificationChannel( - NOTIFICATION_CHANNEL_ID.toString(), - "MediaPlayer", - NotificationManager.IMPORTANCE_DEFAULT - ) - - notificationManager.createNotificationChannel(channel) - } - - companion object { - const val NOTIFICATION_CHANNEL_ID = 100 - const val TARGET_ACTIVITY = "com.squirtles.musicroad.main.MainActivity" - } -} +//package com.squirtles.mediaservice +// +//import android.annotation.SuppressLint +//import android.app.NotificationChannel +//import android.app.NotificationManager +//import android.app.PendingIntent +//import android.content.ComponentName +//import android.content.Context +//import android.content.Intent +//import android.os.Build +//import androidx.annotation.OptIn +//import androidx.core.app.NotificationCompat +//import androidx.core.graphics.drawable.IconCompat +//import androidx.media3.common.Player +//import androidx.media3.common.util.UnstableApi +//import androidx.media3.session.MediaNotification +//import androidx.media3.session.MediaSession +//import androidx.media3.session.MediaStyleNotificationHelper +//import javax.inject.Inject +// +//interface MediaNotificationProvider { +// +// @OptIn(UnstableApi::class) +// fun createMediaNotification(actionFactory: MediaNotification.ActionFactory): MediaNotification +//} +// +//class MediaNotificationProviderImpl @Inject constructor( +// private val context: Context, +// private val mediaSession: MediaSession +//) : MediaNotificationProvider { +// +// @OptIn(UnstableApi::class) +// override fun createMediaNotification(actionFactory: MediaNotification.ActionFactory): MediaNotification { +// val notificationManager: NotificationManager = +// context.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager +// +// makeNotificationChannel(notificationManager) +// +// val mediaItem = mediaSession.player.currentMediaItem +// +// val notificationBuilder = NotificationCompat.Builder( +// context, +// NOTIFICATION_CHANNEL_ID.toString() +// ).apply { +// priority = NotificationCompat.PRIORITY_DEFAULT +// setSilent(true) +// setSmallIcon(R.drawable.ic_musicroad_foreground) +// setContentTitle(mediaItem?.mediaMetadata?.title) +// setContentIntent(createNotifyPendingIntent()) +// setDeleteIntent( +// actionFactory.createMediaActionPendingIntent( +// mediaSession, +// Player.COMMAND_STOP.toLong() +// ) +// ) +// setStyle( +// MediaStyleNotificationHelper +// .MediaStyle(mediaSession) +// ) +// setOngoing(true) +// +// PlayerCommands.entries.forEach { commandButton -> +// addAction( +// actionFactory.createCustomAction( +// mediaSession, +// IconCompat.createWithResource( +// context, +// commandButton.iconResId(mediaSession.player.isPlaying) +// ), +// commandButton.displayName, +// commandButton.customAction, +// commandButton.sessionCommand.customExtras +// ) +// ) +// } +// } +// +// return MediaNotification( +// NOTIFICATION_CHANNEL_ID, +// notificationBuilder.build() +// ) +// } +// +// /* 알림 누르면 이동할 intent 설정 */ +// private fun createNotifyPendingIntent(): PendingIntent = +// PendingIntent.getActivity( +// context, +// 0, +// Intent().apply { +// action = Intent.ACTION_VIEW +// component = ComponentName(context, TARGET_ACTIVITY) +// flags = Intent.FLAG_ACTIVITY_CLEAR_TOP or Intent.FLAG_ACTIVITY_SINGLE_TOP +// }, +// PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE +// ) +// +// /* Notification Channel 생성 */ +// @SuppressLint("ObsoleteSdkInt") +// private fun makeNotificationChannel(notificationManager: NotificationManager) { +// // Android 8.0 미만 버전에서는 Notification Channel을 생성할 필요가 없음 -> 앱 단일 Channel 가짐 +// if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O || +// notificationManager.getNotificationChannel(NOTIFICATION_CHANNEL_ID.toString()) != null +// ) { +// return +// } +// +// val channel = NotificationChannel( +// NOTIFICATION_CHANNEL_ID.toString(), +// "MediaPlayer", +// NotificationManager.IMPORTANCE_DEFAULT +// ) +// +// notificationManager.createNotificationChannel(channel) +// } +// +// companion object { +// const val NOTIFICATION_CHANNEL_ID = 100 +// const val TARGET_ACTIVITY = "com.squirtles.musicroad.main.MainActivity" +// } +//} diff --git a/mediaservice/src/main/java/com/squirtles/mediaservice/MediaPlayerService.kt b/mediaservice/src/main/java/com/squirtles/mediaservice/MediaPlayerService.kt index b5a028a4..f3735b88 100644 --- a/mediaservice/src/main/java/com/squirtles/mediaservice/MediaPlayerService.kt +++ b/mediaservice/src/main/java/com/squirtles/mediaservice/MediaPlayerService.kt @@ -1,62 +1,62 @@ -package com.squirtles.mediaservice - -import android.content.Intent -import android.os.Bundle -import androidx.annotation.OptIn -import androidx.media3.common.Player -import androidx.media3.common.util.UnstableApi -import androidx.media3.session.CommandButton -import androidx.media3.session.MediaNotification -import androidx.media3.session.MediaSession -import androidx.media3.session.MediaSessionService -import com.google.common.collect.ImmutableList -import dagger.hilt.android.AndroidEntryPoint -import javax.inject.Inject - -@OptIn(UnstableApi::class) -@AndroidEntryPoint -class MediaPlayerService : MediaSessionService() { - @Inject - lateinit var mediaNotificationProvider: MediaNotificationProvider - - @Inject - lateinit var mediaSession: MediaSession - - override fun onCreate() { - super.onCreate() - - setMediaNotificationProvider(object : MediaNotification.Provider { - override fun createNotification( - mediaSession: MediaSession, - customLayout: ImmutableList, - actionFactory: MediaNotification.ActionFactory, - onNotificationChangedCallback: MediaNotification.Provider.Callback - ): MediaNotification = - mediaNotificationProvider.createMediaNotification(actionFactory) - - override fun handleCustomCommand(session: MediaSession, action: String, extras: Bundle): Boolean = false - }) - } - - // The user dismissed the app from the recent tasks - override fun onTaskRemoved(rootIntent: Intent?) { - val player = mediaSession.player - if (!player.playWhenReady - || player.mediaItemCount == 0 - || player.playbackState == Player.STATE_ENDED - ) { - // stopSelf() - } - } - - override fun onGetSession(controllerInfo: MediaSession.ControllerInfo): MediaSession = mediaSession - - // Remember to release the player and media session in onDestroy - override fun onDestroy() { - mediaSession.run { - player.release() - release() - } - super.onDestroy() - } -} +//package com.squirtles.mediaservice +// +//import android.content.Intent +//import android.os.Bundle +//import androidx.annotation.OptIn +//import androidx.media3.common.Player +//import androidx.media3.common.util.UnstableApi +//import androidx.media3.session.CommandButton +//import androidx.media3.session.MediaNotification +//import androidx.media3.session.MediaSession +//import androidx.media3.session.MediaSessionService +//import com.google.common.collect.ImmutableList +//import dagger.hilt.android.AndroidEntryPoint +//import javax.inject.Inject +// +//@OptIn(UnstableApi::class) +//@AndroidEntryPoint +//class MediaPlayerService : MediaSessionService() { +// @Inject +// lateinit var mediaNotificationProvider: MediaNotificationProvider +// +// @Inject +// lateinit var mediaSession: MediaSession +// +// override fun onCreate() { +// super.onCreate() +// +// setMediaNotificationProvider(object : MediaNotification.Provider { +// override fun createNotification( +// mediaSession: MediaSession, +// customLayout: ImmutableList, +// actionFactory: MediaNotification.ActionFactory, +// onNotificationChangedCallback: MediaNotification.Provider.Callback +// ): MediaNotification = +// mediaNotificationProvider.createMediaNotification(actionFactory) +// +// override fun handleCustomCommand(session: MediaSession, action: String, extras: Bundle): Boolean = false +// }) +// } +// +// // The user dismissed the app from the recent tasks +// override fun onTaskRemoved(rootIntent: Intent?) { +// val player = mediaSession.player +// if (!player.playWhenReady +// || player.mediaItemCount == 0 +// || player.playbackState == Player.STATE_ENDED +// ) { +// // stopSelf() +// } +// } +// +// override fun onGetSession(controllerInfo: MediaSession.ControllerInfo): MediaSession = mediaSession +// +// // Remember to release the player and media session in onDestroy +// override fun onDestroy() { +// mediaSession.run { +// player.release() +// release() +// } +// super.onDestroy() +// } +//} diff --git a/mediaservice/src/main/java/com/squirtles/mediaservice/PlayerCommands.kt b/mediaservice/src/main/java/com/squirtles/mediaservice/PlayerCommands.kt index a26936d0..2c4fcb9b 100644 --- a/mediaservice/src/main/java/com/squirtles/mediaservice/PlayerCommands.kt +++ b/mediaservice/src/main/java/com/squirtles/mediaservice/PlayerCommands.kt @@ -1,40 +1,40 @@ -package com.squirtles.mediaservice - -import android.os.Bundle -import androidx.media3.session.SessionCommand - -private const val ACTION_SEEK_FORWARD = "action_seek_forward" -private const val ACTION_SEEK_REWIND = "action_seek_rewind" -private const val ACTION_PLAY_AND_PAUSE = "action_play_and_pause" - -enum class PlayerCommands( - val customAction: String, - val displayName: String, - val iconResId: (Boolean) -> Int, - val sessionCommand: SessionCommand, -) { - SEEK_REWIND( - customAction = ACTION_SEEK_REWIND, - displayName = "SeekRewind", - iconResId = { androidx.media3.session.R.drawable.media3_icon_skip_back_5 }, - sessionCommand = SessionCommand(ACTION_SEEK_REWIND, Bundle.EMPTY) - ), - PLAY_AND_PAUSE( - customAction = ACTION_PLAY_AND_PAUSE, - displayName = "PlayPause", - iconResId = { isPlaying -> - if (isPlaying) { - androidx.media3.session.R.drawable.media3_icon_pause - } else { - androidx.media3.session.R.drawable.media3_icon_play - } - }, - sessionCommand = SessionCommand(ACTION_PLAY_AND_PAUSE, Bundle.EMPTY) - ), - SEEK_FORWARD( - customAction = ACTION_SEEK_FORWARD, - displayName = "SeekForward", - iconResId = { androidx.media3.session.R.drawable.media3_icon_skip_forward_5 }, - sessionCommand = SessionCommand(ACTION_SEEK_FORWARD, Bundle.EMPTY) - ), -} +//package com.squirtles.mediaservice +// +//import android.os.Bundle +//import androidx.media3.session.SessionCommand +// +//private const val ACTION_SEEK_FORWARD = "action_seek_forward" +//private const val ACTION_SEEK_REWIND = "action_seek_rewind" +//private const val ACTION_PLAY_AND_PAUSE = "action_play_and_pause" +// +//enum class PlayerCommands( +// val customAction: String, +// val displayName: String, +// val iconResId: (Boolean) -> Int, +// val sessionCommand: SessionCommand, +//) { +// SEEK_REWIND( +// customAction = ACTION_SEEK_REWIND, +// displayName = "SeekRewind", +// iconResId = { androidx.media3.session.R.drawable.media3_icon_skip_back_5 }, +// sessionCommand = SessionCommand(ACTION_SEEK_REWIND, Bundle.EMPTY) +// ), +// PLAY_AND_PAUSE( +// customAction = ACTION_PLAY_AND_PAUSE, +// displayName = "PlayPause", +// iconResId = { isPlaying -> +// if (isPlaying) { +// androidx.media3.session.R.drawable.media3_icon_pause +// } else { +// androidx.media3.session.R.drawable.media3_icon_play +// } +// }, +// sessionCommand = SessionCommand(ACTION_PLAY_AND_PAUSE, Bundle.EMPTY) +// ), +// SEEK_FORWARD( +// customAction = ACTION_SEEK_FORWARD, +// displayName = "SeekForward", +// iconResId = { androidx.media3.session.R.drawable.media3_icon_skip_forward_5 }, +// sessionCommand = SessionCommand(ACTION_SEEK_FORWARD, Bundle.EMPTY) +// ), +//} diff --git a/mediaservice/src/main/java/com/squirtles/mediaservice/di/MediaDiModule.kt b/mediaservice/src/main/java/com/squirtles/mediaservice/di/MediaDiModule.kt index 5d9d13a5..161bce3b 100644 --- a/mediaservice/src/main/java/com/squirtles/mediaservice/di/MediaDiModule.kt +++ b/mediaservice/src/main/java/com/squirtles/mediaservice/di/MediaDiModule.kt @@ -1,101 +1,101 @@ -package com.squirtles.mediaservice.di - -import android.content.ComponentName -import android.content.Context -import androidx.annotation.OptIn -import androidx.media3.common.AudioAttributes -import androidx.media3.common.util.UnstableApi -import androidx.media3.exoplayer.ExoPlayer -import androidx.media3.session.MediaController -import androidx.media3.session.MediaSession -import androidx.media3.session.SessionToken -import com.google.common.util.concurrent.ListenableFuture -import com.squirtles.mediaservice.CustomMediaSessionCallback -import com.squirtles.mediaservice.MediaControllerProvider -import com.squirtles.mediaservice.MediaControllerProviderImpl -import com.squirtles.mediaservice.MediaNotificationProvider -import com.squirtles.mediaservice.MediaNotificationProviderImpl -import com.squirtles.mediaservice.MediaPlayerService -import dagger.Binds -import dagger.Module -import dagger.Provides -import dagger.hilt.InstallIn -import dagger.hilt.android.qualifiers.ApplicationContext -import dagger.hilt.components.SingletonComponent -import javax.inject.Singleton - - -@Module -@InstallIn(SingletonComponent::class) -abstract class MediaServiceBinds { - - @Binds - abstract fun bindsMediaNotificationProvider( - mediaNotification: MediaNotificationProviderImpl - ): MediaNotificationProvider - - @OptIn(UnstableApi::class) - @Binds - abstract fun bindsMediaControllerProvider( - mediaControllerProvider: MediaControllerProviderImpl - ): MediaControllerProvider -} - -@Module -@InstallIn(SingletonComponent::class) -object MediaServiceModule { - - @Singleton - @Provides - fun providesExoPlayer( - @ApplicationContext context: Context, - ): ExoPlayer = - ExoPlayer.Builder(context) - .setAudioAttributes(AudioAttributes.DEFAULT, true) - .build() - - @OptIn(UnstableApi::class) - @Singleton - @Provides - fun provideAudioSessionId( - exoPlayer: ExoPlayer - ): Int = exoPlayer.audioSessionId - - @Singleton - @Provides - fun providesMediaSession( - @ApplicationContext context: Context, - player: ExoPlayer, - ): MediaSession = - MediaSession.Builder(context, player) - .setCallback(CustomMediaSessionCallback()) - .build() - - @Singleton - @Provides - fun providesMediaNotificationManager( - @ApplicationContext context: Context, - mediaSession: MediaSession, - ): MediaNotificationProviderImpl = - MediaNotificationProviderImpl(context, mediaSession) - - @Singleton - @Provides - fun providesSessionToken( - @ApplicationContext context: Context - ): SessionToken { - val sessionToken = SessionToken(context, ComponentName(context, MediaPlayerService::class.java)) - return sessionToken - } - - - @Singleton - @Provides - fun providesListenableFutureMediaController( - @ApplicationContext context: Context, - sessionToken: SessionToken - ): ListenableFuture = - MediaController - .Builder(context, sessionToken) - .buildAsync() -} +//package com.squirtles.mediaservice.di +// +//import android.content.ComponentName +//import android.content.Context +//import androidx.annotation.OptIn +//import androidx.media3.common.AudioAttributes +//import androidx.media3.common.util.UnstableApi +//import androidx.media3.exoplayer.ExoPlayer +//import androidx.media3.session.MediaController +//import androidx.media3.session.MediaSession +//import androidx.media3.session.SessionToken +//import com.google.common.util.concurrent.ListenableFuture +//import com.squirtles.mediaservice.CustomMediaSessionCallback +//import com.squirtles.mediaservice.MediaControllerProvider +//import com.squirtles.mediaservice.MediaControllerProviderImpl +//import com.squirtles.mediaservice.MediaNotificationProvider +//import com.squirtles.mediaservice.MediaNotificationProviderImpl +//import com.squirtles.mediaservice.MediaPlayerService +//import dagger.Binds +//import dagger.Module +//import dagger.Provides +//import dagger.hilt.InstallIn +//import dagger.hilt.android.qualifiers.ApplicationContext +//import dagger.hilt.components.SingletonComponent +//import javax.inject.Singleton +// +// +//@Module +//@InstallIn(SingletonComponent::class) +//abstract class MediaServiceBinds { +// +// @Binds +// abstract fun bindsMediaNotificationProvider( +// mediaNotification: MediaNotificationProviderImpl +// ): MediaNotificationProvider +// +// @OptIn(UnstableApi::class) +// @Binds +// abstract fun bindsMediaControllerProvider( +// mediaControllerProvider: MediaControllerProviderImpl +// ): MediaControllerProvider +//} +// +//@Module +//@InstallIn(SingletonComponent::class) +//object MediaServiceModule { +// +// @Singleton +// @Provides +// fun providesExoPlayer( +// @ApplicationContext context: Context, +// ): ExoPlayer = +// ExoPlayer.Builder(context) +// .setAudioAttributes(AudioAttributes.DEFAULT, true) +// .build() +// +// @OptIn(UnstableApi::class) +// @Singleton +// @Provides +// fun provideAudioSessionId( +// exoPlayer: ExoPlayer +// ): Int = exoPlayer.audioSessionId +// +// @Singleton +// @Provides +// fun providesMediaSession( +// @ApplicationContext context: Context, +// player: ExoPlayer, +// ): MediaSession = +// MediaSession.Builder(context, player) +// .setCallback(CustomMediaSessionCallback()) +// .build() +// +// @Singleton +// @Provides +// fun providesMediaNotificationManager( +// @ApplicationContext context: Context, +// mediaSession: MediaSession, +// ): MediaNotificationProviderImpl = +// MediaNotificationProviderImpl(context, mediaSession) +// +// @Singleton +// @Provides +// fun providesSessionToken( +// @ApplicationContext context: Context +// ): SessionToken { +// val sessionToken = SessionToken(context, ComponentName(context, MediaPlayerService::class.java)) +// return sessionToken +// } +// +// +// @Singleton +// @Provides +// fun providesListenableFutureMediaController( +// @ApplicationContext context: Context, +// sessionToken: SessionToken +// ): ListenableFuture = +// MediaController +// .Builder(context, sessionToken) +// .buildAsync() +//} diff --git a/settings.gradle.kts b/settings.gradle.kts index 9430897a..6763983e 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -62,3 +62,8 @@ include(":core:buildconfig") include(":feature:search") include(":feature:userinfo") include(":feature:mypick") +include(":audio_visualizer") +include(":feature:detail") +include(":feature:map") +include(":feature:main") +include(":domain:firebase") From af6b1e0c3bac2dbcea3b7744a9eca77b710548c3 Mon Sep 17 00:00:00 2001 From: miller198 Date: Tue, 22 Apr 2025 19:42:44 +0900 Subject: [PATCH 45/62] =?UTF-8?q?[refactor]=20core:navigation=20=EC=A0=81?= =?UTF-8?q?=EC=9A=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../musicroad/create/CreatePickViewModel.kt | 8 ++++--- .../create/navigation/CreateNavigation.kt | 2 +- .../detail/navigation/PickDetailNavigation.kt | 2 +- .../favorite/navigation/FavoriteNavigation.kt | 2 +- .../main/navigation/MainNavigator.kt | 2 +- .../musicroad/map/navigation/MapNavigation.kt | 2 +- .../mypick/navigation/MyPickNavigation.kt | 2 +- .../musicroad/navigation/MainRoute.kt | 15 ------------- .../musicroad/navigation/MapRoute.kt | 9 -------- .../squirtles/musicroad/navigation/Route.kt | 9 -------- .../musicroad/navigation/SearchRoute.kt | 21 ------------------- .../musicroad/navigation/UserInfoRoute.kt | 16 -------------- .../search/navigation/SearchNavigation.kt | 2 +- .../userinfo/navigation/UserInfoNavigation.kt | 4 ++-- 14 files changed, 14 insertions(+), 82 deletions(-) delete mode 100644 app/src/main/java/com/squirtles/musicroad/navigation/MainRoute.kt delete mode 100644 app/src/main/java/com/squirtles/musicroad/navigation/MapRoute.kt delete mode 100644 app/src/main/java/com/squirtles/musicroad/navigation/Route.kt delete mode 100644 app/src/main/java/com/squirtles/musicroad/navigation/SearchRoute.kt delete mode 100644 app/src/main/java/com/squirtles/musicroad/navigation/UserInfoRoute.kt diff --git a/app/src/main/java/com/squirtles/musicroad/create/CreatePickViewModel.kt b/app/src/main/java/com/squirtles/musicroad/create/CreatePickViewModel.kt index 80655dae..18cf510d 100644 --- a/app/src/main/java/com/squirtles/musicroad/create/CreatePickViewModel.kt +++ b/app/src/main/java/com/squirtles/musicroad/create/CreatePickViewModel.kt @@ -13,9 +13,10 @@ import com.squirtles.model.Creator import com.squirtles.model.LocationPoint import com.squirtles.model.Pick import com.squirtles.model.Song -import com.squirtles.musicroad.navigation.SearchRoute +import com.squirtles.navigation.SearchRoute import com.squirtles.user.usecase.FetchUserByIdUseCase import com.squirtles.user.usecase.GetCurrentUidUseCase +import com.squirtles.util.serializableType import com.squirtles.util.throttleFirst import dagger.hilt.android.lifecycle.HiltViewModel import kotlinx.coroutines.flow.MutableSharedFlow @@ -23,6 +24,7 @@ import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.asStateFlow import kotlinx.coroutines.launch import javax.inject.Inject +import kotlin.reflect.typeOf @HiltViewModel class CreatePickViewModel @Inject constructor( @@ -33,8 +35,8 @@ class CreatePickViewModel @Inject constructor( private val getCurrentUidUseCase: GetCurrentUidUseCase, private val fetchUserByIdUseCase: FetchUserByIdUseCase ) : ViewModel() { - - private val song = savedStateHandle.toRoute(SearchRoute.Create.typeMap).song + private val songTypeMap = mapOf(typeOf() to serializableType()) + private val song = savedStateHandle.toRoute(songTypeMap).song private val _createPickUiState = MutableStateFlow>(CreateUiState.Default) val createPickUiState = _createPickUiState.asStateFlow() diff --git a/app/src/main/java/com/squirtles/musicroad/create/navigation/CreateNavigation.kt b/app/src/main/java/com/squirtles/musicroad/create/navigation/CreateNavigation.kt index f26c761e..437c5a99 100644 --- a/app/src/main/java/com/squirtles/musicroad/create/navigation/CreateNavigation.kt +++ b/app/src/main/java/com/squirtles/musicroad/create/navigation/CreateNavigation.kt @@ -7,7 +7,7 @@ import androidx.navigation.compose.composable import androidx.navigation.toRoute import com.squirtles.model.Song import com.squirtles.musicroad.create.CreatePickScreen -import com.squirtles.musicroad.navigation.SearchRoute +import com.squirtles.navigation.SearchRoute import com.squirtles.util.serializableType import java.net.URLEncoder import java.nio.charset.StandardCharsets diff --git a/app/src/main/java/com/squirtles/musicroad/detail/navigation/PickDetailNavigation.kt b/app/src/main/java/com/squirtles/musicroad/detail/navigation/PickDetailNavigation.kt index 6cc347ed..007c4233 100644 --- a/app/src/main/java/com/squirtles/musicroad/detail/navigation/PickDetailNavigation.kt +++ b/app/src/main/java/com/squirtles/musicroad/detail/navigation/PickDetailNavigation.kt @@ -8,7 +8,7 @@ import androidx.navigation.compose.composable import androidx.navigation.toRoute import com.squirtles.musicplayer.PlayerServiceViewModel import com.squirtles.musicroad.detail.PickDetailScreen -import com.squirtles.musicroad.navigation.MapRoute +import com.squirtles.navigation.MapRoute fun NavController.navigatePickDetail(pickId: String, navOptions: NavOptions? = null) { navigate(MapRoute.PickDetail(pickId), navOptions) diff --git a/app/src/main/java/com/squirtles/musicroad/favorite/navigation/FavoriteNavigation.kt b/app/src/main/java/com/squirtles/musicroad/favorite/navigation/FavoriteNavigation.kt index bc81d820..9af63728 100644 --- a/app/src/main/java/com/squirtles/musicroad/favorite/navigation/FavoriteNavigation.kt +++ b/app/src/main/java/com/squirtles/musicroad/favorite/navigation/FavoriteNavigation.kt @@ -6,7 +6,7 @@ import androidx.navigation.NavOptions import androidx.navigation.compose.composable import androidx.navigation.toRoute import com.squirtles.musicroad.favorite.FavoriteScreen -import com.squirtles.musicroad.navigation.MainRoute +import com.squirtles.navigation.MainRoute fun NavController.navigateFavorite(uid: String, navOptions: NavOptions? = null) { navigate(MainRoute.Favorite(uid), navOptions) diff --git a/app/src/main/java/com/squirtles/musicroad/main/navigation/MainNavigator.kt b/app/src/main/java/com/squirtles/musicroad/main/navigation/MainNavigator.kt index 46ff641e..48ad2a2b 100644 --- a/app/src/main/java/com/squirtles/musicroad/main/navigation/MainNavigator.kt +++ b/app/src/main/java/com/squirtles/musicroad/main/navigation/MainNavigator.kt @@ -14,11 +14,11 @@ import com.squirtles.model.Song import com.squirtles.musicroad.favorite.navigation.navigateFavorite import com.squirtles.musicroad.map.navigation.navigateMap import com.squirtles.musicroad.mypick.navigation.navigateMyPicks -import com.squirtles.musicroad.navigation.Route import com.squirtles.musicroad.search.navigation.navigateSearch import com.squirtles.musicroad.userinfo.navigation.navigateEditNotificationSetting import com.squirtles.musicroad.userinfo.navigation.navigateEditProfile import com.squirtles.musicroad.userinfo.navigation.navigateUserInfo +import com.squirtles.navigation.Route internal class MainNavigator( val navController: NavHostController diff --git a/app/src/main/java/com/squirtles/musicroad/map/navigation/MapNavigation.kt b/app/src/main/java/com/squirtles/musicroad/map/navigation/MapNavigation.kt index 2a387990..721bd213 100644 --- a/app/src/main/java/com/squirtles/musicroad/map/navigation/MapNavigation.kt +++ b/app/src/main/java/com/squirtles/musicroad/map/navigation/MapNavigation.kt @@ -7,7 +7,7 @@ import androidx.navigation.compose.composable import com.squirtles.musicplayer.PlayerServiceViewModel import com.squirtles.musicroad.map.MapScreen import com.squirtles.musicroad.map.MapViewModel -import com.squirtles.musicroad.navigation.Route +import com.squirtles.navigation.Route fun NavController.navigateMap(navOptions: NavOptions? = null) { navigate(Route.Map, navOptions) diff --git a/app/src/main/java/com/squirtles/musicroad/mypick/navigation/MyPickNavigation.kt b/app/src/main/java/com/squirtles/musicroad/mypick/navigation/MyPickNavigation.kt index 9371ba69..60f621f0 100644 --- a/app/src/main/java/com/squirtles/musicroad/mypick/navigation/MyPickNavigation.kt +++ b/app/src/main/java/com/squirtles/musicroad/mypick/navigation/MyPickNavigation.kt @@ -6,7 +6,7 @@ import androidx.navigation.NavOptions import androidx.navigation.compose.composable import androidx.navigation.toRoute import com.squirtles.musicroad.mypick.MyPickScreen -import com.squirtles.musicroad.navigation.UserInfoRoute +import com.squirtles.navigation.UserInfoRoute fun NavController.navigateMyPicks(uid: String, navOptions: NavOptions) { navigate(UserInfoRoute.MyPicks(uid), navOptions) diff --git a/app/src/main/java/com/squirtles/musicroad/navigation/MainRoute.kt b/app/src/main/java/com/squirtles/musicroad/navigation/MainRoute.kt deleted file mode 100644 index 2d4c35dd..00000000 --- a/app/src/main/java/com/squirtles/musicroad/navigation/MainRoute.kt +++ /dev/null @@ -1,15 +0,0 @@ -package com.squirtles.musicroad.navigation - -import kotlinx.serialization.Serializable - -@Serializable -sealed interface MainRoute : Route { - @Serializable - data object Search : MainRoute - - @Serializable - data class Favorite(val uid: String) : MainRoute - - @Serializable - data class UserInfo(val uid: String) : MainRoute -} diff --git a/app/src/main/java/com/squirtles/musicroad/navigation/MapRoute.kt b/app/src/main/java/com/squirtles/musicroad/navigation/MapRoute.kt deleted file mode 100644 index 4a38424e..00000000 --- a/app/src/main/java/com/squirtles/musicroad/navigation/MapRoute.kt +++ /dev/null @@ -1,9 +0,0 @@ -package com.squirtles.musicroad.navigation - -import kotlinx.serialization.Serializable - -@Serializable -sealed interface MapRoute : Route { - @Serializable - data class PickDetail(val pickId: String) : MapRoute -} diff --git a/app/src/main/java/com/squirtles/musicroad/navigation/Route.kt b/app/src/main/java/com/squirtles/musicroad/navigation/Route.kt deleted file mode 100644 index 7e7db78e..00000000 --- a/app/src/main/java/com/squirtles/musicroad/navigation/Route.kt +++ /dev/null @@ -1,9 +0,0 @@ -package com.squirtles.musicroad.navigation - -import kotlinx.serialization.Serializable - -@Serializable -sealed interface Route { - @Serializable - data object Map : Route -} diff --git a/app/src/main/java/com/squirtles/musicroad/navigation/SearchRoute.kt b/app/src/main/java/com/squirtles/musicroad/navigation/SearchRoute.kt deleted file mode 100644 index a689b2c7..00000000 --- a/app/src/main/java/com/squirtles/musicroad/navigation/SearchRoute.kt +++ /dev/null @@ -1,21 +0,0 @@ -package com.squirtles.musicroad.navigation - -import androidx.lifecycle.SavedStateHandle -import androidx.navigation.toRoute -import com.squirtles.model.Song -import com.squirtles.util.serializableType -import kotlinx.serialization.Serializable -import kotlin.reflect.typeOf - -@Serializable -sealed interface SearchRoute : Route { - @Serializable - data class Create(val song: Song) : SearchRoute { - companion object { - val typeMap = mapOf(typeOf() to serializableType()) - - fun from(savedStateHandle: SavedStateHandle) = - savedStateHandle.toRoute(typeMap) - } - } -} diff --git a/app/src/main/java/com/squirtles/musicroad/navigation/UserInfoRoute.kt b/app/src/main/java/com/squirtles/musicroad/navigation/UserInfoRoute.kt deleted file mode 100644 index b5a7d09f..00000000 --- a/app/src/main/java/com/squirtles/musicroad/navigation/UserInfoRoute.kt +++ /dev/null @@ -1,16 +0,0 @@ -package com.squirtles.musicroad.navigation - -import kotlinx.serialization.Serializable - -@Serializable -sealed interface UserInfoRoute : Route { - @Serializable - data class MyPicks(val uid: String) : UserInfoRoute - - @Serializable - data class EditProfile(val userName: String) : UserInfoRoute - - @Serializable - data object EditNotification : UserInfoRoute -} - diff --git a/app/src/main/java/com/squirtles/musicroad/search/navigation/SearchNavigation.kt b/app/src/main/java/com/squirtles/musicroad/search/navigation/SearchNavigation.kt index 23da197a..7f5eacfe 100644 --- a/app/src/main/java/com/squirtles/musicroad/search/navigation/SearchNavigation.kt +++ b/app/src/main/java/com/squirtles/musicroad/search/navigation/SearchNavigation.kt @@ -5,8 +5,8 @@ import androidx.navigation.NavGraphBuilder import androidx.navigation.NavOptions import androidx.navigation.compose.composable import com.squirtles.model.Song -import com.squirtles.musicroad.navigation.MainRoute import com.squirtles.musicroad.search.SearchMusicScreen +import com.squirtles.navigation.MainRoute fun NavController.navigateSearch(navOptions: NavOptions? = null) { navigate(MainRoute.Search, navOptions) diff --git a/app/src/main/java/com/squirtles/musicroad/userinfo/navigation/UserInfoNavigation.kt b/app/src/main/java/com/squirtles/musicroad/userinfo/navigation/UserInfoNavigation.kt index ea1be161..02b9d871 100644 --- a/app/src/main/java/com/squirtles/musicroad/userinfo/navigation/UserInfoNavigation.kt +++ b/app/src/main/java/com/squirtles/musicroad/userinfo/navigation/UserInfoNavigation.kt @@ -5,11 +5,11 @@ import androidx.navigation.NavGraphBuilder import androidx.navigation.NavOptions import androidx.navigation.compose.composable import androidx.navigation.toRoute -import com.squirtles.musicroad.navigation.MainRoute -import com.squirtles.musicroad.navigation.UserInfoRoute import com.squirtles.musicroad.userinfo.screen.EditNotificationSettingScreen import com.squirtles.musicroad.userinfo.screen.EditProfileScreen import com.squirtles.musicroad.userinfo.screen.UserInfoScreen +import com.squirtles.navigation.MainRoute +import com.squirtles.navigation.UserInfoRoute fun NavController.navigateUserInfo(uid: String, navOptions: NavOptions? = null) { navigate(MainRoute.UserInfo(uid), navOptions) From 543028562b1691f050218cf9b872d955d5b636ba Mon Sep 17 00:00:00 2001 From: miller198 Date: Tue, 22 Apr 2025 20:05:26 +0900 Subject: [PATCH 46/62] =?UTF-8?q?[refactor]=20core:common=20=EC=A0=81?= =?UTF-8?q?=EC=9A=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../squirtles/musicroad/common/AlbumImage.kt | 39 ----- .../squirtles/musicroad/common/Constants.kt | 17 --- .../musicroad/common/CreatedByPickText.kt | 64 --------- .../musicroad/common/DefaultTopAppBar.kt | 56 -------- .../musicroad/common/MessageAlertDialog.kt | 129 ----------------- .../musicroad/common/PickInfoText.kt | 102 ------------- .../musicroad/common/SignInAlertDialog.kt | 135 ------------------ .../com/squirtles/musicroad/common/Spacer.kt | 14 -- .../musicroad/create/CreatePickScreen.kt | 12 +- .../musicroad/detail/PickDetailScreen.kt | 20 +-- .../detail/components/DetailPickTopAppBar.kt | 4 +- .../detail/components/MusicVideoKnob.kt | 4 +- ...ommentText.kt => PickDatailCommentText.kt} | 14 +- .../detail/components/PickInformation.kt | 2 +- .../PlayCircularProgressIndicator.kt | 2 +- .../detail/components/SwipeUpIcon.kt | 2 +- .../detail/components/music/MusicPlayer.kt | 4 +- .../detail/components/music/PlayBar.kt | 4 +- .../components/music/PlayProgressIndicator.kt | 4 +- .../detail/components/music/PlayerControls.kt | 4 +- .../detail/videoplayer/VideoPlayerOverlay.kt | 8 +- .../squirtles/musicroad/main/MainActivity.kt | 2 +- .../musicroad/main/NeedPermissionDialog.kt | 12 +- .../squirtles/musicroad/main/PermissionBar.kt | 2 +- .../com/squirtles/musicroad/map/MapScreen.kt | 6 +- .../com/squirtles/musicroad/map/NaverMap.kt | 4 +- .../map/components/ClusterBottomSheet.kt | 22 +-- .../map/components/InfoWindowCard.kt | 16 +-- .../musicroad/map/components/LoadingDialog.kt | 12 +- .../map/components/MapBottomNavBar.kt | 4 +- .../map/components/PickNotificationBanner.kt | 6 +- .../musicroad/map/marker/Clusterer.kt | 10 +- .../musicroad/map/marker/DensityType.kt | 6 +- .../musicroad/search/SearchMusicScreen.kt | 14 +- .../com/squirtles/musicroad/ui/theme/Color.kt | 50 +++---- .../com/squirtles/musicroad/ui/theme/Theme.kt | 90 ++++++------ .../com/squirtles/musicroad/ui/theme/Type.kt | 68 ++++----- .../musicroad/userinfo/components/MenuItem.kt | 2 +- .../userinfo/components/UserInfoMenus.kt | 8 +- .../screen/EditNotificationSettingScreen.kt | 8 +- .../userinfo/screen/EditProfileScreen.kt | 20 +-- .../userinfo/screen/UserInfoScreen.kt | 18 +-- audio_visualizer/src/main/AndroidManifest.xml | 4 - 43 files changed, 232 insertions(+), 792 deletions(-) delete mode 100644 app/src/main/java/com/squirtles/musicroad/common/AlbumImage.kt delete mode 100644 app/src/main/java/com/squirtles/musicroad/common/Constants.kt delete mode 100644 app/src/main/java/com/squirtles/musicroad/common/CreatedByPickText.kt delete mode 100644 app/src/main/java/com/squirtles/musicroad/common/DefaultTopAppBar.kt delete mode 100644 app/src/main/java/com/squirtles/musicroad/common/MessageAlertDialog.kt delete mode 100644 app/src/main/java/com/squirtles/musicroad/common/PickInfoText.kt delete mode 100644 app/src/main/java/com/squirtles/musicroad/common/SignInAlertDialog.kt delete mode 100644 app/src/main/java/com/squirtles/musicroad/common/Spacer.kt rename app/src/main/java/com/squirtles/musicroad/detail/components/{CommentText.kt => PickDatailCommentText.kt} (85%) delete mode 100644 audio_visualizer/src/main/AndroidManifest.xml diff --git a/app/src/main/java/com/squirtles/musicroad/common/AlbumImage.kt b/app/src/main/java/com/squirtles/musicroad/common/AlbumImage.kt deleted file mode 100644 index 9dc6df16..00000000 --- a/app/src/main/java/com/squirtles/musicroad/common/AlbumImage.kt +++ /dev/null @@ -1,39 +0,0 @@ -package com.squirtles.musicroad.common - -import android.util.Size -import androidx.compose.runtime.Composable -import androidx.compose.ui.Modifier -import androidx.compose.ui.graphics.painter.ColorPainter -import androidx.compose.ui.layout.ContentScale -import androidx.compose.ui.platform.LocalContext -import androidx.compose.ui.res.stringResource -import coil3.compose.AsyncImage -import coil3.request.ImageRequest -import coil3.request.crossfade -import com.squirtles.musicroad.R -import com.squirtles.musicroad.ui.theme.Gray - -@Composable -fun AlbumImage( - imageUrl: String?, - modifier: Modifier = Modifier, - contentDescription: String = stringResource(R.string.map_album_image_description), -) { - AsyncImage( - model = ImageRequest.Builder(LocalContext.current) - .data(imageUrl) - .crossfade(true) - .build(), - contentDescription = contentDescription, - modifier = modifier, - placeholder = ColorPainter(Gray), - error = ColorPainter(Gray), - contentScale = ContentScale.Crop, - ) -} - -fun String.toImageUrlWithSize(size: Size): String? { - return if (isEmpty()) null - else replace("{w}", size.width.toString()) - .replace("{h}", size.height.toString()) -} diff --git a/app/src/main/java/com/squirtles/musicroad/common/Constants.kt b/app/src/main/java/com/squirtles/musicroad/common/Constants.kt deleted file mode 100644 index 0ec41c4a..00000000 --- a/app/src/main/java/com/squirtles/musicroad/common/Constants.kt +++ /dev/null @@ -1,17 +0,0 @@ -package com.squirtles.musicroad.common - -import android.util.Size -import androidx.compose.ui.unit.dp -import com.squirtles.musicroad.ui.theme.Black -import com.squirtles.musicroad.ui.theme.Primary - -internal object Constants { - val DEFAULT_PADDING = 16.dp - - val REQUEST_IMAGE_SIZE_DEFAULT = Size(300, 300) - - val COLOR_STOPS = arrayOf( - 0.0f to Primary, - 0.25f to Black - ) -} \ No newline at end of file diff --git a/app/src/main/java/com/squirtles/musicroad/common/CreatedByPickText.kt b/app/src/main/java/com/squirtles/musicroad/common/CreatedByPickText.kt deleted file mode 100644 index d9c6d39d..00000000 --- a/app/src/main/java/com/squirtles/musicroad/common/CreatedByPickText.kt +++ /dev/null @@ -1,64 +0,0 @@ -package com.squirtles.musicroad.common - -import androidx.compose.material3.MaterialTheme -import androidx.compose.material3.Text -import androidx.compose.runtime.Composable -import androidx.compose.ui.Modifier -import androidx.compose.ui.graphics.Color -import androidx.compose.ui.res.stringResource -import androidx.compose.ui.text.SpanStyle -import androidx.compose.ui.text.TextStyle -import androidx.compose.ui.text.buildAnnotatedString -import androidx.compose.ui.text.style.TextDecoration -import androidx.compose.ui.text.style.TextOverflow -import androidx.compose.ui.text.withStyle -import com.squirtles.musicroad.R - -@Composable -fun CreatedBySelfText( - modifier: Modifier = Modifier, - showUnderline: Boolean = false, - color: Color = MaterialTheme.colorScheme.onSurface, - style: TextStyle = MaterialTheme.typography.bodyMedium -) { - val myPickDescription = buildAnnotatedString { - withStyle(style = SpanStyle(textDecoration = if (showUnderline) TextDecoration.Underline else null)) { - append(stringResource(R.string.pick_created_by_self_1)) - } - append(" ${stringResource(R.string.pick_created_by_self_2)}") - } - - Text( - text = myPickDescription, - modifier = modifier, - color = color, - overflow = TextOverflow.Ellipsis, - maxLines = 1, - style = style - ) -} - -@Composable -fun CreatedByOtherUserText( - userName: String, - modifier: Modifier = Modifier, - showUnderline: Boolean = false, - color: Color = MaterialTheme.colorScheme.onSurface, - style: TextStyle = MaterialTheme.typography.bodyMedium -) { - Text( - text = userName, - modifier = modifier, - color = color, - textDecoration = if (showUnderline) TextDecoration.Underline else null, - overflow = TextOverflow.Ellipsis, - maxLines = 1, - style = style - ) - - Text( - text = stringResource(id = R.string.map_info_window_pick_user), - color = color, - style = style, - ) -} diff --git a/app/src/main/java/com/squirtles/musicroad/common/DefaultTopAppBar.kt b/app/src/main/java/com/squirtles/musicroad/common/DefaultTopAppBar.kt deleted file mode 100644 index 4dd71ca5..00000000 --- a/app/src/main/java/com/squirtles/musicroad/common/DefaultTopAppBar.kt +++ /dev/null @@ -1,56 +0,0 @@ -package com.squirtles.musicroad.common - -import androidx.compose.foundation.layout.RowScope -import androidx.compose.foundation.layout.displayCutoutPadding -import androidx.compose.material.icons.Icons -import androidx.compose.material.icons.automirrored.filled.ArrowBack -import androidx.compose.material3.CenterAlignedTopAppBar -import androidx.compose.material3.ExperimentalMaterial3Api -import androidx.compose.material3.Icon -import androidx.compose.material3.IconButton -import androidx.compose.material3.MaterialTheme -import androidx.compose.material3.Text -import androidx.compose.material3.TopAppBarDefaults -import androidx.compose.runtime.Composable -import androidx.compose.ui.Modifier -import androidx.compose.ui.graphics.Color -import androidx.compose.ui.res.stringResource -import androidx.compose.ui.text.TextStyle -import androidx.compose.ui.text.font.FontWeight -import com.squirtles.musicroad.R -import com.squirtles.musicroad.ui.theme.White - -@OptIn(ExperimentalMaterial3Api::class) -@Composable -fun DefaultTopAppBar( - title: String, - titleStyle: TextStyle = MaterialTheme.typography.titleLarge.copy(fontWeight = FontWeight.Bold), - onBackClick: () -> Unit, - actions: @Composable RowScope.() -> Unit = {}, -) { - CenterAlignedTopAppBar( - title = { - Text( - text = title, - style = titleStyle - ) - }, - modifier = Modifier.displayCutoutPadding(), - navigationIcon = { - IconButton( - onClick = onBackClick - ) { - Icon( - imageVector = Icons.AutoMirrored.Filled.ArrowBack, - contentDescription = stringResource(R.string.top_app_bar_back_description), - tint = White - ) - } - }, - actions = actions, - colors = TopAppBarDefaults.centerAlignedTopAppBarColors().copy( - containerColor = Color.Transparent, - titleContentColor = White - ) - ) -} diff --git a/app/src/main/java/com/squirtles/musicroad/common/MessageAlertDialog.kt b/app/src/main/java/com/squirtles/musicroad/common/MessageAlertDialog.kt deleted file mode 100644 index b49c6a3c..00000000 --- a/app/src/main/java/com/squirtles/musicroad/common/MessageAlertDialog.kt +++ /dev/null @@ -1,129 +0,0 @@ -package com.squirtles.musicroad.common - -import androidx.compose.foundation.layout.Arrangement -import androidx.compose.foundation.layout.Column -import androidx.compose.foundation.layout.Row -import androidx.compose.foundation.layout.RowScope -import androidx.compose.foundation.layout.fillMaxWidth -import androidx.compose.foundation.layout.padding -import androidx.compose.foundation.shape.RoundedCornerShape -import androidx.compose.material3.BasicAlertDialog -import androidx.compose.material3.ButtonDefaults -import androidx.compose.material3.ExperimentalMaterial3Api -import androidx.compose.material3.MaterialTheme -import androidx.compose.material3.Surface -import androidx.compose.material3.Text -import androidx.compose.material3.TextButton -import androidx.compose.runtime.Composable -import androidx.compose.ui.Alignment -import androidx.compose.ui.Modifier -import androidx.compose.ui.graphics.Color -import androidx.compose.ui.res.stringResource -import androidx.compose.ui.text.font.FontWeight -import androidx.compose.ui.tooling.preview.Preview -import androidx.compose.ui.unit.dp -import com.squirtles.musicroad.R -import com.squirtles.musicroad.ui.theme.Black -import com.squirtles.musicroad.ui.theme.MusicRoadTheme -import com.squirtles.musicroad.ui.theme.Primary -import com.squirtles.musicroad.ui.theme.White - -@OptIn(ExperimentalMaterial3Api::class) -@Composable -internal fun MessageAlertDialog( - title: String, - body: String, - onDismissRequest: () -> Unit, - showBody: Boolean = true, - buttons: @Composable RowScope.() -> Unit, -) { - BasicAlertDialog( - onDismissRequest = { onDismissRequest() }, - ) { - Surface( - modifier = Modifier.fillMaxWidth(), - shape = RoundedCornerShape(16.dp), - color = White - ) { - Column( - modifier = Modifier.padding(24.dp), - verticalArrangement = Arrangement.Center - ) { - Text( - text = title, - color = Black, - fontWeight = FontWeight.Bold, - style = MaterialTheme.typography.bodyLarge - ) - - if (showBody) { - VerticalSpacer(8) - - Text( - text = body, - color = Black, - style = MaterialTheme.typography.bodyLarge - ) - } - - VerticalSpacer(24) - - Row( - modifier = Modifier.fillMaxWidth(), - horizontalArrangement = Arrangement.End, - verticalAlignment = Alignment.CenterVertically, - content = buttons - ) - } - } - } -} - -@Composable -internal fun DialogTextButton( - onClick: () -> Unit, - text: String, - textColor: Color = Black, - buttonColor: Color = Color.Transparent, - fontWeight: FontWeight? = null, -) { - TextButton( - onClick = onClick, - colors = ButtonDefaults.buttonColors().copy( - containerColor = buttonColor, - contentColor = textColor - ) - ) { - Text( - text = text, - fontWeight = fontWeight, - ) - } -} - -@Preview(showBackground = true) -@Composable -private fun DeletePickDialogPreview() { - MusicRoadTheme { - MessageAlertDialog( - onDismissRequest = {}, - title = stringResource(R.string.delete_pick_dialog_title), - body = stringResource(R.string.delete_pick_dialog_body), - buttons = { - DialogTextButton( - onClick = {}, - text = "취소" - ) - - HorizontalSpacer(8) - - DialogTextButton( - onClick = {}, - text = "삭제하기", - textColor = Primary, - fontWeight = FontWeight.Bold - ) - } - ) - } -} diff --git a/app/src/main/java/com/squirtles/musicroad/common/PickInfoText.kt b/app/src/main/java/com/squirtles/musicroad/common/PickInfoText.kt deleted file mode 100644 index 8d40d760..00000000 --- a/app/src/main/java/com/squirtles/musicroad/common/PickInfoText.kt +++ /dev/null @@ -1,102 +0,0 @@ -package com.squirtles.musicroad.common - -import androidx.compose.material3.Icon -import androidx.compose.material3.MaterialTheme -import androidx.compose.material3.Text -import androidx.compose.runtime.Composable -import androidx.compose.ui.Modifier -import androidx.compose.ui.graphics.Color -import androidx.compose.ui.res.painterResource -import androidx.compose.ui.res.stringResource -import androidx.compose.ui.text.SpanStyle -import androidx.compose.ui.text.TextStyle -import androidx.compose.ui.text.buildAnnotatedString -import androidx.compose.ui.text.font.FontWeight -import androidx.compose.ui.text.style.TextOverflow -import androidx.compose.ui.text.withStyle -import com.squirtles.musicroad.R -import com.squirtles.musicroad.ui.theme.Primary - -@Composable -fun SongInfoText( - songInfo: String, - color: Color = MaterialTheme.colorScheme.onSurface -) { - Text( - text = songInfo, - color = color, - overflow = TextOverflow.Ellipsis, - maxLines = 1, - style = MaterialTheme.typography.titleMedium, - ) -} - -@Composable -fun FavoriteCountText( - favoriteCount: Int, - iconTint: Color = Primary, - color: Color = MaterialTheme.colorScheme.onSurface, - style: TextStyle = MaterialTheme.typography.bodyMedium -) { - Icon( - painter = painterResource(id = R.drawable.ic_favorite), - contentDescription = stringResource(R.string.map_info_window_favorite_count_icon_description), - tint = iconTint - ) - - Text( - text = " $favoriteCount", - color = color, - style = style, - ) -} - -@Composable -fun CommentText( - comment: String, - color: Color = MaterialTheme.colorScheme.onSecondary, - overflow: TextOverflow = TextOverflow.Ellipsis, - maxLines: Int = 1, - style: TextStyle = MaterialTheme.typography.bodyMedium -) { - Text( - text = comment, - color = color, - overflow = overflow, - maxLines = maxLines, - style = style, - ) -} - -@Composable -fun CountText( - totalCount: Int, - modifier: Modifier = Modifier, - countLabel: String = stringResource(R.string.total_count_text), - defaultColor: Color = MaterialTheme.colorScheme.onSurface, - pointColor: Color = MaterialTheme.colorScheme.primary, - style: TextStyle = MaterialTheme.typography.titleMedium -) { - Text( - text = buildAnnotatedString { - withStyle( - SpanStyle( - color = defaultColor, - fontWeight = FontWeight.Bold - ) - ) { - append("$countLabel ") - } - withStyle( - SpanStyle( - color = pointColor, - fontWeight = FontWeight.Bold - ) - ) { - append("$totalCount") - } - }, - modifier = modifier, - style = style - ) -} diff --git a/app/src/main/java/com/squirtles/musicroad/common/SignInAlertDialog.kt b/app/src/main/java/com/squirtles/musicroad/common/SignInAlertDialog.kt deleted file mode 100644 index 7141b4df..00000000 --- a/app/src/main/java/com/squirtles/musicroad/common/SignInAlertDialog.kt +++ /dev/null @@ -1,135 +0,0 @@ -package com.squirtles.musicroad.common - -import android.content.res.Configuration.UI_MODE_NIGHT_YES -import androidx.compose.foundation.BorderStroke -import androidx.compose.foundation.Image -import androidx.compose.foundation.clickable -import androidx.compose.foundation.isSystemInDarkTheme -import androidx.compose.foundation.layout.Arrangement -import androidx.compose.foundation.layout.Column -import androidx.compose.foundation.layout.PaddingValues -import androidx.compose.foundation.layout.Row -import androidx.compose.foundation.layout.fillMaxWidth -import androidx.compose.foundation.layout.height -import androidx.compose.foundation.layout.padding -import androidx.compose.foundation.layout.size -import androidx.compose.foundation.shape.RoundedCornerShape -import androidx.compose.material3.BasicAlertDialog -import androidx.compose.material3.Button -import androidx.compose.material3.ButtonDefaults -import androidx.compose.material3.ExperimentalMaterial3Api -import androidx.compose.material3.MaterialTheme -import androidx.compose.material3.Surface -import androidx.compose.material3.Text -import androidx.compose.runtime.Composable -import androidx.compose.ui.Alignment -import androidx.compose.ui.Modifier -import androidx.compose.ui.res.painterResource -import androidx.compose.ui.res.stringResource -import androidx.compose.ui.text.font.FontWeight -import androidx.compose.ui.text.style.TextAlign -import androidx.compose.ui.tooling.preview.Preview -import androidx.compose.ui.unit.dp -import com.squirtles.musicroad.R -import com.squirtles.musicroad.ui.theme.Black -import com.squirtles.musicroad.ui.theme.DarkGray -import com.squirtles.musicroad.ui.theme.MusicRoadTheme -import com.squirtles.musicroad.ui.theme.SignInButtonDarkBackground -import com.squirtles.musicroad.ui.theme.SignInButtonDarkStroke -import com.squirtles.musicroad.ui.theme.SignInButtonLightStroke -import com.squirtles.musicroad.ui.theme.White - -@OptIn(ExperimentalMaterial3Api::class) -@Composable -internal fun SignInAlertDialog( - onDismissRequest: () -> Unit, - onGoogleSignInClick: () -> Unit, - description: String -) { - BasicAlertDialog( - onDismissRequest = onDismissRequest, - ) { - Surface( - modifier = Modifier.fillMaxWidth(), - shape = RoundedCornerShape(16.dp), - color = MaterialTheme.colorScheme.surface - ) { - Column( - modifier = Modifier.padding(24.dp), - horizontalAlignment = Alignment.CenterHorizontally, - verticalArrangement = Arrangement.Center - ) { - Text( - text = description, - color = MaterialTheme.colorScheme.onSurface, - fontWeight = FontWeight.Bold, - textAlign = TextAlign.Center, - style = MaterialTheme.typography.bodyLarge - ) - - VerticalSpacer(height = 40) - - GoogleSignInButton(onClick = onGoogleSignInClick) - - VerticalSpacer(height = 20) - - Text( - text = stringResource(R.string.sign_in_dialog_dismiss), - modifier = Modifier.clickable(onClick = onDismissRequest), - color = DarkGray, - textAlign = TextAlign.Center, - style = MaterialTheme.typography.bodyMedium - ) - } - } - } -} - -@Composable -fun GoogleSignInButton( - onClick: () -> Unit, - modifier: Modifier = Modifier -) { - Button( - onClick = onClick, - modifier = modifier.height(40.dp), - shape = RoundedCornerShape(50), - colors = ButtonDefaults.buttonColors(containerColor = if (isSystemInDarkTheme()) SignInButtonDarkBackground else White), - border = BorderStroke(1.dp, if (isSystemInDarkTheme()) SignInButtonDarkStroke else SignInButtonLightStroke), - contentPadding = PaddingValues(vertical = 10.dp, horizontal = 12.dp) - ) { - Row(verticalAlignment = Alignment.CenterVertically) { - Image( - painter = painterResource(id = R.drawable.img_google_logo), - contentDescription = stringResource(id = R.string.profile_google_icon), - modifier = Modifier.size(20.dp) - ) - HorizontalSpacer(10) - Text( - stringResource( - id = R.string.profile_sign_in_google - ), - color = if (isSystemInDarkTheme()) White else Black - ) - } - } -} - -@Preview(name = "Light") -@Preview(name = "Dark", uiMode = UI_MODE_NIGHT_YES) -@Composable -private fun LoginAlertDialogPreview() { - MusicRoadTheme { - SignInAlertDialog({}, {}, stringResource(id = R.string.sign_in_dialog_title_default)) - } -} - - -@Preview(name = "Light") -@Preview(name = "Dark", uiMode = UI_MODE_NIGHT_YES) -@Composable -private fun PreviewGoogleSignInButton() { - MusicRoadTheme { - GoogleSignInButton({}) - } -} diff --git a/app/src/main/java/com/squirtles/musicroad/common/Spacer.kt b/app/src/main/java/com/squirtles/musicroad/common/Spacer.kt deleted file mode 100644 index 645717bb..00000000 --- a/app/src/main/java/com/squirtles/musicroad/common/Spacer.kt +++ /dev/null @@ -1,14 +0,0 @@ -package com.squirtles.musicroad.common - -import androidx.compose.foundation.layout.Spacer -import androidx.compose.foundation.layout.height -import androidx.compose.foundation.layout.width -import androidx.compose.runtime.Composable -import androidx.compose.ui.Modifier -import androidx.compose.ui.unit.dp - -@Composable -fun VerticalSpacer(height: Int) = Spacer(Modifier.height(height.dp)) - -@Composable -fun HorizontalSpacer(width: Int) = Spacer(Modifier.width(width.dp)) diff --git a/app/src/main/java/com/squirtles/musicroad/create/CreatePickScreen.kt b/app/src/main/java/com/squirtles/musicroad/create/CreatePickScreen.kt index f622810f..dbdd8505 100644 --- a/app/src/main/java/com/squirtles/musicroad/create/CreatePickScreen.kt +++ b/app/src/main/java/com/squirtles/musicroad/create/CreatePickScreen.kt @@ -56,14 +56,14 @@ import androidx.core.graphics.toColorInt import androidx.core.view.WindowInsetsControllerCompat import androidx.hilt.navigation.compose.hiltViewModel import androidx.lifecycle.compose.collectAsStateWithLifecycle +import com.squirtles.common.ui.AlbumImage +import com.squirtles.common.ui.VerticalSpacer +import com.squirtles.common.ui.theme.Black +import com.squirtles.common.ui.theme.Dark +import com.squirtles.common.ui.theme.Gray +import com.squirtles.common.ui.theme.White import com.squirtles.model.Song import com.squirtles.musicroad.R -import com.squirtles.musicroad.common.AlbumImage -import com.squirtles.musicroad.common.VerticalSpacer -import com.squirtles.musicroad.ui.theme.Black -import com.squirtles.musicroad.ui.theme.Dark -import com.squirtles.musicroad.ui.theme.Gray -import com.squirtles.musicroad.ui.theme.White @Composable fun CreatePickScreen( diff --git a/app/src/main/java/com/squirtles/musicroad/detail/PickDetailScreen.kt b/app/src/main/java/com/squirtles/musicroad/detail/PickDetailScreen.kt index f3acfe07..818a194d 100644 --- a/app/src/main/java/com/squirtles/musicroad/detail/PickDetailScreen.kt +++ b/app/src/main/java/com/squirtles/musicroad/detail/PickDetailScreen.kt @@ -54,28 +54,28 @@ import androidx.lifecycle.Lifecycle import androidx.lifecycle.compose.LocalLifecycleOwner import androidx.lifecycle.compose.collectAsStateWithLifecycle import androidx.lifecycle.flowWithLifecycle +import com.squirtles.common.ui.DialogTextButton +import com.squirtles.common.ui.HorizontalSpacer +import com.squirtles.common.ui.MessageAlertDialog +import com.squirtles.common.ui.SignInAlertDialog +import com.squirtles.common.ui.VerticalSpacer +import com.squirtles.common.ui.theme.Black +import com.squirtles.common.ui.theme.Primary +import com.squirtles.common.ui.theme.White import com.squirtles.model.Pick import com.squirtles.musicplayer.PlayerServiceViewModel import com.squirtles.musicroad.R import com.squirtles.musicroad.account.AccountViewModel import com.squirtles.musicroad.account.GoogleId -import com.squirtles.musicroad.common.DialogTextButton -import com.squirtles.musicroad.common.HorizontalSpacer -import com.squirtles.musicroad.common.MessageAlertDialog -import com.squirtles.musicroad.common.SignInAlertDialog -import com.squirtles.musicroad.common.VerticalSpacer import com.squirtles.musicroad.detail.DetailViewModel.Companion.DEFAULT_PICK import com.squirtles.musicroad.detail.components.CircleAlbumCover -import com.squirtles.musicroad.detail.components.CommentText +import com.squirtles.musicroad.detail.components.PickDatailCommentText import com.squirtles.musicroad.detail.components.DetailPickTopAppBar import com.squirtles.musicroad.detail.components.MusicVideoKnob import com.squirtles.musicroad.detail.components.PickInformation import com.squirtles.musicroad.detail.components.SongInfo import com.squirtles.musicroad.detail.components.music.MusicPlayer import com.squirtles.musicroad.detail.videoplayer.MusicVideoScreen -import com.squirtles.musicroad.ui.theme.Black -import com.squirtles.musicroad.ui.theme.Primary -import com.squirtles.musicroad.ui.theme.White import kotlinx.coroutines.launch import kotlin.math.absoluteValue @@ -488,7 +488,7 @@ private fun PickDetailContents( favoriteCount = favoriteCount ) - CommentText(comment = pick.comment) + PickDatailCommentText(comment = pick.comment) VerticalSpacer(height = 8) } diff --git a/app/src/main/java/com/squirtles/musicroad/detail/components/DetailPickTopAppBar.kt b/app/src/main/java/com/squirtles/musicroad/detail/components/DetailPickTopAppBar.kt index a4e85e96..1f777ce3 100644 --- a/app/src/main/java/com/squirtles/musicroad/detail/components/DetailPickTopAppBar.kt +++ b/app/src/main/java/com/squirtles/musicroad/detail/components/DetailPickTopAppBar.kt @@ -19,9 +19,9 @@ import androidx.compose.ui.graphics.Color import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.font.FontWeight +import com.squirtles.common.ui.CreatedByOtherUserText +import com.squirtles.common.ui.CreatedBySelfText import com.squirtles.musicroad.R -import com.squirtles.musicroad.common.CreatedByOtherUserText -import com.squirtles.musicroad.common.CreatedBySelfText @OptIn(ExperimentalMaterial3Api::class) @Composable diff --git a/app/src/main/java/com/squirtles/musicroad/detail/components/MusicVideoKnob.kt b/app/src/main/java/com/squirtles/musicroad/detail/components/MusicVideoKnob.kt index 8d7ca3b0..e50178e3 100644 --- a/app/src/main/java/com/squirtles/musicroad/detail/components/MusicVideoKnob.kt +++ b/app/src/main/java/com/squirtles/musicroad/detail/components/MusicVideoKnob.kt @@ -26,9 +26,9 @@ import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp import coil3.compose.AsyncImage import coil3.request.ImageRequest +import com.squirtles.common.ui.theme.White +import com.squirtles.common.ui.toImageUrlWithSize import com.squirtles.musicroad.R -import com.squirtles.musicroad.common.toImageUrlWithSize -import com.squirtles.musicroad.ui.theme.White @Composable internal fun MusicVideoKnob( diff --git a/app/src/main/java/com/squirtles/musicroad/detail/components/CommentText.kt b/app/src/main/java/com/squirtles/musicroad/detail/components/PickDatailCommentText.kt similarity index 85% rename from app/src/main/java/com/squirtles/musicroad/detail/components/CommentText.kt rename to app/src/main/java/com/squirtles/musicroad/detail/components/PickDatailCommentText.kt index bf3a830e..0d9f21c8 100644 --- a/app/src/main/java/com/squirtles/musicroad/detail/components/CommentText.kt +++ b/app/src/main/java/com/squirtles/musicroad/detail/components/PickDatailCommentText.kt @@ -16,13 +16,13 @@ import androidx.compose.ui.draw.clip import androidx.compose.ui.res.stringResource import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp +import com.squirtles.common.ui.theme.Dark +import com.squirtles.common.ui.theme.Gray +import com.squirtles.common.ui.theme.White import com.squirtles.musicroad.R -import com.squirtles.musicroad.ui.theme.Dark -import com.squirtles.musicroad.ui.theme.Gray -import com.squirtles.musicroad.ui.theme.White @Composable -internal fun CommentText( +internal fun PickDatailCommentText( comment: String, modifier: Modifier = Modifier ) { @@ -44,11 +44,11 @@ internal fun CommentText( @Composable private fun CommentTextPreview() { Column(verticalArrangement = Arrangement.spacedBy(10.dp)) { - CommentText(comment = "") + PickDatailCommentText(comment = "") - CommentText(comment = "노래가 좋아서 추천합니다.") + PickDatailCommentText(comment = "노래가 좋아서 추천합니다.") - CommentText( + PickDatailCommentText( comment = "노래가 너무너무너무너무너무너무너무너무너무너무너무너무너무너무너무너무너무너무너무너무너무너무너무너무너무너무" + "너무너무너무너무너무너무너무너무너무너무너무너무너무너무너무너무너무너무너무너무너무너무 좋아요" ) diff --git a/app/src/main/java/com/squirtles/musicroad/detail/components/PickInformation.kt b/app/src/main/java/com/squirtles/musicroad/detail/components/PickInformation.kt index 51a6b925..6f727683 100644 --- a/app/src/main/java/com/squirtles/musicroad/detail/components/PickInformation.kt +++ b/app/src/main/java/com/squirtles/musicroad/detail/components/PickInformation.kt @@ -13,8 +13,8 @@ import androidx.compose.ui.Modifier import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.dp +import com.squirtles.common.ui.theme.Gray import com.squirtles.musicroad.R -import com.squirtles.musicroad.ui.theme.Gray @Composable internal fun PickInformation(formattedDate: String, favoriteCount: Int) { diff --git a/app/src/main/java/com/squirtles/musicroad/detail/components/PlayCircularProgressIndicator.kt b/app/src/main/java/com/squirtles/musicroad/detail/components/PlayCircularProgressIndicator.kt index d9d668a0..82081c47 100644 --- a/app/src/main/java/com/squirtles/musicroad/detail/components/PlayCircularProgressIndicator.kt +++ b/app/src/main/java/com/squirtles/musicroad/detail/components/PlayCircularProgressIndicator.kt @@ -12,7 +12,7 @@ import androidx.compose.ui.graphics.StrokeCap import androidx.compose.ui.input.pointer.pointerInput import androidx.compose.ui.unit.Dp import androidx.compose.ui.unit.dp -import com.squirtles.musicroad.ui.theme.White +import com.squirtles.common.ui.theme.White import kotlin.math.atan2 import kotlin.math.hypot diff --git a/app/src/main/java/com/squirtles/musicroad/detail/components/SwipeUpIcon.kt b/app/src/main/java/com/squirtles/musicroad/detail/components/SwipeUpIcon.kt index c9b4f063..2f44b375 100644 --- a/app/src/main/java/com/squirtles/musicroad/detail/components/SwipeUpIcon.kt +++ b/app/src/main/java/com/squirtles/musicroad/detail/components/SwipeUpIcon.kt @@ -10,8 +10,8 @@ import androidx.compose.ui.Modifier import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.dp +import com.squirtles.common.ui.theme.White import com.squirtles.musicroad.R -import com.squirtles.musicroad.ui.theme.White @Composable internal fun SwipeUpIcon( diff --git a/app/src/main/java/com/squirtles/musicroad/detail/components/music/MusicPlayer.kt b/app/src/main/java/com/squirtles/musicroad/detail/components/music/MusicPlayer.kt index 376f4c7f..9a2aa993 100644 --- a/app/src/main/java/com/squirtles/musicroad/detail/components/music/MusicPlayer.kt +++ b/app/src/main/java/com/squirtles/musicroad/detail/components/music/MusicPlayer.kt @@ -9,10 +9,10 @@ import androidx.compose.runtime.Composable import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.unit.dp +import com.squirtles.common.ui.Constants.DEFAULT_PADDING +import com.squirtles.common.ui.theme.PlayerBackground import com.squirtles.model.PlayerState import com.squirtles.model.Song -import com.squirtles.musicroad.common.Constants.DEFAULT_PADDING -import com.squirtles.musicroad.ui.theme.PlayerBackground @Composable fun MusicPlayer( diff --git a/app/src/main/java/com/squirtles/musicroad/detail/components/music/PlayBar.kt b/app/src/main/java/com/squirtles/musicroad/detail/components/music/PlayBar.kt index e4d91cf3..26227450 100644 --- a/app/src/main/java/com/squirtles/musicroad/detail/components/music/PlayBar.kt +++ b/app/src/main/java/com/squirtles/musicroad/detail/components/music/PlayBar.kt @@ -11,8 +11,8 @@ import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp -import com.squirtles.musicroad.ui.theme.Gray -import com.squirtles.musicroad.ui.theme.MusicRoadTheme +import com.squirtles.common.ui.theme.Gray +import com.squirtles.common.ui.theme.MusicRoadTheme import java.util.concurrent.TimeUnit @Composable diff --git a/app/src/main/java/com/squirtles/musicroad/detail/components/music/PlayProgressIndicator.kt b/app/src/main/java/com/squirtles/musicroad/detail/components/music/PlayProgressIndicator.kt index b0586e02..ac79cf0a 100644 --- a/app/src/main/java/com/squirtles/musicroad/detail/components/music/PlayProgressIndicator.kt +++ b/app/src/main/java/com/squirtles/musicroad/detail/components/music/PlayProgressIndicator.kt @@ -10,8 +10,8 @@ import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color import androidx.compose.ui.input.pointer.pointerInput import androidx.compose.ui.unit.dp -import com.squirtles.musicroad.ui.theme.DarkGray -import com.squirtles.musicroad.ui.theme.White +import com.squirtles.common.ui.theme.DarkGray +import com.squirtles.common.ui.theme.White @Composable internal fun PlayProgressIndicator( diff --git a/app/src/main/java/com/squirtles/musicroad/detail/components/music/PlayerControls.kt b/app/src/main/java/com/squirtles/musicroad/detail/components/music/PlayerControls.kt index d24f0d45..4d581c19 100644 --- a/app/src/main/java/com/squirtles/musicroad/detail/components/music/PlayerControls.kt +++ b/app/src/main/java/com/squirtles/musicroad/detail/components/music/PlayerControls.kt @@ -16,9 +16,9 @@ import androidx.compose.ui.Modifier import androidx.compose.ui.res.stringResource import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp +import com.squirtles.common.ui.theme.MusicRoadTheme +import com.squirtles.common.ui.theme.White import com.squirtles.musicroad.R -import com.squirtles.musicroad.ui.theme.MusicRoadTheme -import com.squirtles.musicroad.ui.theme.White @Composable internal fun PlayerControls( diff --git a/app/src/main/java/com/squirtles/musicroad/detail/videoplayer/VideoPlayerOverlay.kt b/app/src/main/java/com/squirtles/musicroad/detail/videoplayer/VideoPlayerOverlay.kt index cccca824..38fb2264 100644 --- a/app/src/main/java/com/squirtles/musicroad/detail/videoplayer/VideoPlayerOverlay.kt +++ b/app/src/main/java/com/squirtles/musicroad/detail/videoplayer/VideoPlayerOverlay.kt @@ -47,15 +47,15 @@ import androidx.compose.ui.unit.sp import androidx.core.graphics.toColorInt import androidx.hilt.navigation.compose.hiltViewModel import androidx.lifecycle.compose.collectAsStateWithLifecycle +import com.squirtles.common.ui.VerticalSpacer +import com.squirtles.common.ui.theme.Black +import com.squirtles.common.ui.theme.Gray +import com.squirtles.common.ui.theme.White import com.squirtles.model.Creator import com.squirtles.model.LocationPoint import com.squirtles.model.Pick import com.squirtles.model.Song import com.squirtles.musicroad.R -import com.squirtles.musicroad.common.VerticalSpacer -import com.squirtles.musicroad.ui.theme.Black -import com.squirtles.musicroad.ui.theme.Gray -import com.squirtles.musicroad.ui.theme.White @OptIn(ExperimentalMaterial3Api::class) @Composable diff --git a/app/src/main/java/com/squirtles/musicroad/main/MainActivity.kt b/app/src/main/java/com/squirtles/musicroad/main/MainActivity.kt index 94d61eb7..d18b76c8 100644 --- a/app/src/main/java/com/squirtles/musicroad/main/MainActivity.kt +++ b/app/src/main/java/com/squirtles/musicroad/main/MainActivity.kt @@ -24,11 +24,11 @@ import androidx.lifecycle.lifecycleScope import androidx.lifecycle.repeatOnLifecycle import androidx.navigation.compose.rememberNavController import com.google.firebase.auth.FirebaseAuth +import com.squirtles.common.ui.theme.MusicRoadTheme import com.squirtles.musicroad.R import com.squirtles.musicroad.main.navigation.MainNavHost import com.squirtles.musicroad.main.navigation.MainNavigator import com.squirtles.musicroad.main.navigation.rememberMainNavigator -import com.squirtles.musicroad.ui.theme.MusicRoadTheme import dagger.hilt.android.AndroidEntryPoint import kotlinx.coroutines.cancel import kotlinx.coroutines.launch diff --git a/app/src/main/java/com/squirtles/musicroad/main/NeedPermissionDialog.kt b/app/src/main/java/com/squirtles/musicroad/main/NeedPermissionDialog.kt index a61c2119..662ec410 100644 --- a/app/src/main/java/com/squirtles/musicroad/main/NeedPermissionDialog.kt +++ b/app/src/main/java/com/squirtles/musicroad/main/NeedPermissionDialog.kt @@ -21,13 +21,13 @@ import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp +import com.squirtles.common.ui.HorizontalSpacer +import com.squirtles.common.ui.VerticalSpacer +import com.squirtles.common.ui.theme.Black +import com.squirtles.common.ui.theme.DarkGray +import com.squirtles.common.ui.theme.MusicRoadTheme +import com.squirtles.common.ui.theme.White import com.squirtles.musicroad.R -import com.squirtles.musicroad.common.HorizontalSpacer -import com.squirtles.musicroad.common.VerticalSpacer -import com.squirtles.musicroad.ui.theme.Black -import com.squirtles.musicroad.ui.theme.DarkGray -import com.squirtles.musicroad.ui.theme.MusicRoadTheme -import com.squirtles.musicroad.ui.theme.White @OptIn(ExperimentalMaterial3Api::class) @Composable diff --git a/app/src/main/java/com/squirtles/musicroad/main/PermissionBar.kt b/app/src/main/java/com/squirtles/musicroad/main/PermissionBar.kt index 9f9f46ee..ae7755c6 100644 --- a/app/src/main/java/com/squirtles/musicroad/main/PermissionBar.kt +++ b/app/src/main/java/com/squirtles/musicroad/main/PermissionBar.kt @@ -21,8 +21,8 @@ import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.unit.dp +import com.squirtles.common.ui.Constants.DEFAULT_PADDING import com.squirtles.musicroad.R -import com.squirtles.musicroad.common.Constants.DEFAULT_PADDING @Composable fun PermissionBar( diff --git a/app/src/main/java/com/squirtles/musicroad/map/MapScreen.kt b/app/src/main/java/com/squirtles/musicroad/map/MapScreen.kt index c7c46739..199163c9 100644 --- a/app/src/main/java/com/squirtles/musicroad/map/MapScreen.kt +++ b/app/src/main/java/com/squirtles/musicroad/map/MapScreen.kt @@ -33,19 +33,19 @@ import androidx.lifecycle.Lifecycle import androidx.lifecycle.compose.LocalLifecycleOwner import androidx.lifecycle.compose.collectAsStateWithLifecycle import androidx.lifecycle.flowWithLifecycle +import com.squirtles.common.ui.SignInAlertDialog +import com.squirtles.common.ui.VerticalSpacer +import com.squirtles.common.ui.theme.Black import com.squirtles.musicplayer.PlayerServiceViewModel import com.squirtles.musicroad.R import com.squirtles.musicroad.account.AccountViewModel import com.squirtles.musicroad.account.GoogleId -import com.squirtles.musicroad.common.SignInAlertDialog -import com.squirtles.musicroad.common.VerticalSpacer import com.squirtles.musicroad.main.MainActivity import com.squirtles.musicroad.map.components.ClusterBottomSheet import com.squirtles.musicroad.map.components.InfoWindow import com.squirtles.musicroad.map.components.LoadingDialog import com.squirtles.musicroad.map.components.MapBottomNavBar import com.squirtles.musicroad.map.components.PickNotificationBanner -import com.squirtles.musicroad.ui.theme.Black import kotlinx.coroutines.launch @Composable diff --git a/app/src/main/java/com/squirtles/musicroad/map/NaverMap.kt b/app/src/main/java/com/squirtles/musicroad/map/NaverMap.kt index 26f7b17c..4ee4c10b 100644 --- a/app/src/main/java/com/squirtles/musicroad/map/NaverMap.kt +++ b/app/src/main/java/com/squirtles/musicroad/map/NaverMap.kt @@ -41,11 +41,11 @@ import com.naver.maps.map.overlay.CircleOverlay import com.naver.maps.map.overlay.LocationOverlay import com.naver.maps.map.overlay.OverlayImage import com.naver.maps.map.util.FusedLocationSource +import com.squirtles.common.ui.theme.Primary +import com.squirtles.common.ui.theme.Purple15 import com.squirtles.musicroad.R import com.squirtles.musicroad.map.marker.MarkerKey import com.squirtles.musicroad.map.marker.buildClusterer -import com.squirtles.musicroad.ui.theme.Primary -import com.squirtles.musicroad.ui.theme.Purple15 import kotlinx.coroutines.launch import kotlin.coroutines.resume import kotlin.coroutines.suspendCoroutine diff --git a/app/src/main/java/com/squirtles/musicroad/map/components/ClusterBottomSheet.kt b/app/src/main/java/com/squirtles/musicroad/map/components/ClusterBottomSheet.kt index 716bfdff..db3c548e 100644 --- a/app/src/main/java/com/squirtles/musicroad/map/components/ClusterBottomSheet.kt +++ b/app/src/main/java/com/squirtles/musicroad/map/components/ClusterBottomSheet.kt @@ -21,20 +21,20 @@ import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clip import androidx.compose.ui.unit.dp +import com.squirtles.common.ui.AlbumImage +import com.squirtles.common.ui.CommentText +import com.squirtles.common.ui.Constants.DEFAULT_PADDING +import com.squirtles.common.ui.Constants.REQUEST_IMAGE_SIZE_DEFAULT +import com.squirtles.common.ui.CountText +import com.squirtles.common.ui.CreatedByOtherUserText +import com.squirtles.common.ui.CreatedBySelfText +import com.squirtles.common.ui.FavoriteCountText +import com.squirtles.common.ui.HorizontalSpacer +import com.squirtles.common.ui.SongInfoText +import com.squirtles.common.ui.VerticalSpacer import com.squirtles.model.LocationPoint import com.squirtles.model.Pick import com.squirtles.model.Song -import com.squirtles.musicroad.common.AlbumImage -import com.squirtles.musicroad.common.CommentText -import com.squirtles.musicroad.common.Constants.DEFAULT_PADDING -import com.squirtles.musicroad.common.Constants.REQUEST_IMAGE_SIZE_DEFAULT -import com.squirtles.musicroad.common.CountText -import com.squirtles.musicroad.common.CreatedByOtherUserText -import com.squirtles.musicroad.common.CreatedBySelfText -import com.squirtles.musicroad.common.FavoriteCountText -import com.squirtles.musicroad.common.HorizontalSpacer -import com.squirtles.musicroad.common.SongInfoText -import com.squirtles.musicroad.common.VerticalSpacer import kotlinx.coroutines.launch @OptIn(ExperimentalMaterial3Api::class) diff --git a/app/src/main/java/com/squirtles/musicroad/map/components/InfoWindowCard.kt b/app/src/main/java/com/squirtles/musicroad/map/components/InfoWindowCard.kt index 564d6c36..9e8606e1 100644 --- a/app/src/main/java/com/squirtles/musicroad/map/components/InfoWindowCard.kt +++ b/app/src/main/java/com/squirtles/musicroad/map/components/InfoWindowCard.kt @@ -24,18 +24,18 @@ import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp import androidx.core.graphics.toColorInt +import com.squirtles.common.ui.AlbumImage +import com.squirtles.common.ui.CreatedByOtherUserText +import com.squirtles.common.ui.CreatedBySelfText +import com.squirtles.common.ui.FavoriteCountText +import com.squirtles.common.ui.HorizontalSpacer +import com.squirtles.common.ui.SongInfoText +import com.squirtles.common.ui.theme.Gray +import com.squirtles.common.ui.theme.MusicRoadTheme import com.squirtles.model.Creator import com.squirtles.model.LocationPoint import com.squirtles.model.Pick import com.squirtles.model.Song -import com.squirtles.musicroad.common.AlbumImage -import com.squirtles.musicroad.common.CreatedByOtherUserText -import com.squirtles.musicroad.common.CreatedBySelfText -import com.squirtles.musicroad.common.FavoriteCountText -import com.squirtles.musicroad.common.HorizontalSpacer -import com.squirtles.musicroad.common.SongInfoText -import com.squirtles.musicroad.ui.theme.Gray -import com.squirtles.musicroad.ui.theme.MusicRoadTheme @Composable fun InfoWindow( diff --git a/app/src/main/java/com/squirtles/musicroad/map/components/LoadingDialog.kt b/app/src/main/java/com/squirtles/musicroad/map/components/LoadingDialog.kt index 7eda5d40..d90a7f0d 100644 --- a/app/src/main/java/com/squirtles/musicroad/map/components/LoadingDialog.kt +++ b/app/src/main/java/com/squirtles/musicroad/map/components/LoadingDialog.kt @@ -20,13 +20,13 @@ import androidx.compose.ui.graphics.Color import androidx.compose.ui.res.stringResource import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp +import com.squirtles.common.ui.VerticalSpacer +import com.squirtles.common.ui.theme.Black +import com.squirtles.common.ui.theme.DarkGray +import com.squirtles.common.ui.theme.MusicRoadTheme +import com.squirtles.common.ui.theme.Primary +import com.squirtles.common.ui.theme.White import com.squirtles.musicroad.R -import com.squirtles.musicroad.common.VerticalSpacer -import com.squirtles.musicroad.ui.theme.Black -import com.squirtles.musicroad.ui.theme.DarkGray -import com.squirtles.musicroad.ui.theme.MusicRoadTheme -import com.squirtles.musicroad.ui.theme.Primary -import com.squirtles.musicroad.ui.theme.White @OptIn(ExperimentalMaterial3Api::class) @Composable diff --git a/app/src/main/java/com/squirtles/musicroad/map/components/MapBottomNavBar.kt b/app/src/main/java/com/squirtles/musicroad/map/components/MapBottomNavBar.kt index e50daaab..9c04a92f 100644 --- a/app/src/main/java/com/squirtles/musicroad/map/components/MapBottomNavBar.kt +++ b/app/src/main/java/com/squirtles/musicroad/map/components/MapBottomNavBar.kt @@ -23,12 +23,12 @@ import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.stringResource import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp +import com.squirtles.common.ui.theme.MusicRoadTheme +import com.squirtles.common.ui.theme.Primary import com.squirtles.musicroad.R import com.squirtles.musicroad.map.BottomNavigationIconSize import com.squirtles.musicroad.map.BottomNavigationSize import com.squirtles.musicroad.map.navigation.NavTab -import com.squirtles.musicroad.ui.theme.MusicRoadTheme -import com.squirtles.musicroad.ui.theme.Primary @Composable internal fun MapBottomNavBar( diff --git a/app/src/main/java/com/squirtles/musicroad/map/components/PickNotificationBanner.kt b/app/src/main/java/com/squirtles/musicroad/map/components/PickNotificationBanner.kt index af3b953f..9c87dab9 100644 --- a/app/src/main/java/com/squirtles/musicroad/map/components/PickNotificationBanner.kt +++ b/app/src/main/java/com/squirtles/musicroad/map/components/PickNotificationBanner.kt @@ -23,12 +23,12 @@ import androidx.compose.ui.platform.LocalConfiguration import androidx.compose.ui.res.stringResource import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp +import com.squirtles.common.ui.theme.Black +import com.squirtles.common.ui.theme.MusicRoadTheme +import com.squirtles.common.ui.theme.White import com.squirtles.model.Pick import com.squirtles.musicroad.R import com.squirtles.musicroad.detail.DetailViewModel.Companion.DEFAULT_PICK -import com.squirtles.musicroad.ui.theme.Black -import com.squirtles.musicroad.ui.theme.MusicRoadTheme -import com.squirtles.musicroad.ui.theme.White @Composable fun PickNotificationBanner( diff --git a/app/src/main/java/com/squirtles/musicroad/map/marker/Clusterer.kt b/app/src/main/java/com/squirtles/musicroad/map/marker/Clusterer.kt index 1a530bcf..d1f6f242 100644 --- a/app/src/main/java/com/squirtles/musicroad/map/marker/Clusterer.kt +++ b/app/src/main/java/com/squirtles/musicroad/map/marker/Clusterer.kt @@ -15,14 +15,14 @@ import com.naver.maps.map.overlay.Align import com.naver.maps.map.overlay.Marker import com.naver.maps.map.overlay.Overlay import com.naver.maps.map.overlay.OverlayImage -import com.squirtles.musicroad.common.Constants.REQUEST_IMAGE_SIZE_DEFAULT +import com.squirtles.common.ui.Constants.REQUEST_IMAGE_SIZE_DEFAULT +import com.squirtles.common.ui.theme.Black +import com.squirtles.common.ui.theme.Blue +import com.squirtles.common.ui.theme.Primary +import com.squirtles.common.ui.theme.White import com.squirtles.musicroad.map.DEFAULT_MARKER_Z_INDEX import com.squirtles.musicroad.map.MapViewModel import com.squirtles.musicroad.map.setCameraToMarker -import com.squirtles.musicroad.ui.theme.Black -import com.squirtles.musicroad.ui.theme.Blue -import com.squirtles.musicroad.ui.theme.Primary -import com.squirtles.musicroad.ui.theme.White internal fun buildClusterer( context: Context, diff --git a/app/src/main/java/com/squirtles/musicroad/map/marker/DensityType.kt b/app/src/main/java/com/squirtles/musicroad/map/marker/DensityType.kt index eea0e7f6..90606120 100644 --- a/app/src/main/java/com/squirtles/musicroad/map/marker/DensityType.kt +++ b/app/src/main/java/com/squirtles/musicroad/map/marker/DensityType.kt @@ -1,9 +1,9 @@ package com.squirtles.musicroad.map.marker import androidx.compose.ui.graphics.toArgb -import com.squirtles.musicroad.ui.theme.Primary20 -import com.squirtles.musicroad.ui.theme.Primary50 -import com.squirtles.musicroad.ui.theme.Primary80 +import com.squirtles.common.ui.theme.Primary20 +import com.squirtles.common.ui.theme.Primary50 +import com.squirtles.common.ui.theme.Primary80 enum class DensityType(val offset: Int, val color: Int) { LOW(4, Primary80.toArgb()), diff --git a/app/src/main/java/com/squirtles/musicroad/search/SearchMusicScreen.kt b/app/src/main/java/com/squirtles/musicroad/search/SearchMusicScreen.kt index e81cbea1..00efc935 100644 --- a/app/src/main/java/com/squirtles/musicroad/search/SearchMusicScreen.kt +++ b/app/src/main/java/com/squirtles/musicroad/search/SearchMusicScreen.kt @@ -55,16 +55,16 @@ import androidx.lifecycle.compose.collectAsStateWithLifecycle import androidx.paging.LoadState import androidx.paging.compose.LazyPagingItems import androidx.paging.compose.collectAsLazyPagingItems +import com.squirtles.common.ui.AlbumImage +import com.squirtles.common.ui.Constants.COLOR_STOPS +import com.squirtles.common.ui.Constants.REQUEST_IMAGE_SIZE_DEFAULT +import com.squirtles.common.ui.HorizontalSpacer +import com.squirtles.common.ui.VerticalSpacer +import com.squirtles.common.ui.theme.Gray +import com.squirtles.common.ui.theme.White import com.squirtles.model.Song import com.squirtles.musicroad.R -import com.squirtles.musicroad.common.AlbumImage -import com.squirtles.musicroad.common.Constants.COLOR_STOPS -import com.squirtles.musicroad.common.Constants.REQUEST_IMAGE_SIZE_DEFAULT -import com.squirtles.musicroad.common.HorizontalSpacer -import com.squirtles.musicroad.common.VerticalSpacer import com.squirtles.musicroad.create.SearchUiState -import com.squirtles.musicroad.ui.theme.Gray -import com.squirtles.musicroad.ui.theme.White @Composable fun SearchMusicScreen( diff --git a/app/src/main/java/com/squirtles/musicroad/ui/theme/Color.kt b/app/src/main/java/com/squirtles/musicroad/ui/theme/Color.kt index 6ed2ce1a..f2755f13 100644 --- a/app/src/main/java/com/squirtles/musicroad/ui/theme/Color.kt +++ b/app/src/main/java/com/squirtles/musicroad/ui/theme/Color.kt @@ -1,25 +1,25 @@ -package com.squirtles.musicroad.ui.theme - -import androidx.compose.ui.graphics.Color - -val Primary = Color(0xFFFF5F61) -val Primary80 = Color(0xFFFFB3B0) -val Primary50 = Color(0xFFAD625F) -val Primary20 = Color(0xFF571D1E) -val Blue = Color(0xFF6B84FF) - -val Purple = Color(0xFFBB8280) -val Purple15 = Color(0x26BB8280) -val PurpleGrey = Color(0xFFB4AEAE) - -val Black = Color(0xFF000000) -val Dark = Color(0xFF151515) -val DarkGray = Color(0xFF646464) -val Gray = Color(0xFFAAAAAA) -val White = Color(0xFFFFFFFF) - -val PlayerBackground = Color(0xFF353535) - -val SignInButtonDarkBackground = Color(0xFF131314) -val SignInButtonLightStroke = Color(0xFF747775) -val SignInButtonDarkStroke = Color(0xFF8E918F) +//package com.squirtles.musicroad.ui.theme +// +//import androidx.compose.ui.graphics.Color +// +//val Primary = Color(0xFFFF5F61) +//val Primary80 = Color(0xFFFFB3B0) +//val Primary50 = Color(0xFFAD625F) +//val Primary20 = Color(0xFF571D1E) +//val Blue = Color(0xFF6B84FF) +// +//val Purple = Color(0xFFBB8280) +//val Purple15 = Color(0x26BB8280) +//val PurpleGrey = Color(0xFFB4AEAE) +// +//val Black = Color(0xFF000000) +//val Dark = Color(0xFF151515) +//val DarkGray = Color(0xFF646464) +//val Gray = Color(0xFFAAAAAA) +//val White = Color(0xFFFFFFFF) +// +//val PlayerBackground = Color(0xFF353535) +// +//val SignInButtonDarkBackground = Color(0xFF131314) +//val SignInButtonLightStroke = Color(0xFF747775) +//val SignInButtonDarkStroke = Color(0xFF8E918F) diff --git a/app/src/main/java/com/squirtles/musicroad/ui/theme/Theme.kt b/app/src/main/java/com/squirtles/musicroad/ui/theme/Theme.kt index dc6797f4..8f9539f5 100644 --- a/app/src/main/java/com/squirtles/musicroad/ui/theme/Theme.kt +++ b/app/src/main/java/com/squirtles/musicroad/ui/theme/Theme.kt @@ -1,45 +1,45 @@ -package com.squirtles.musicroad.ui.theme - -import androidx.compose.foundation.isSystemInDarkTheme -import androidx.compose.material3.MaterialTheme -import androidx.compose.material3.darkColorScheme -import androidx.compose.material3.lightColorScheme -import androidx.compose.runtime.Composable - -private val DarkColorScheme = darkColorScheme( - primary = Primary, - onPrimary = Black, - primaryContainer = White, - onPrimaryContainer = Black, - secondary = Blue, - tertiary = Purple, - surface = Black, - onSurface = White, - onSurfaceVariant = DarkGray, - onSecondary = Gray -) - -private val LightColorScheme = lightColorScheme( - primary = Primary, - onPrimary = White, - primaryContainer = Dark, - onPrimaryContainer = White, - secondary = Blue, - tertiary = Purple, - surface = White, - onSurface = Black, - onSurfaceVariant = Gray, - onSecondary = DarkGray -) - -@Composable -fun MusicRoadTheme( - darkTheme: Boolean = isSystemInDarkTheme(), - content: @Composable () -> Unit -) { - MaterialTheme( - colorScheme = if (darkTheme) DarkColorScheme else LightColorScheme, - typography = Typography, - content = content - ) -} \ No newline at end of file +//package com.squirtles.musicroad.ui.theme +// +//import androidx.compose.foundation.isSystemInDarkTheme +//import androidx.compose.material3.MaterialTheme +//import androidx.compose.material3.darkColorScheme +//import androidx.compose.material3.lightColorScheme +//import androidx.compose.runtime.Composable +// +//private val DarkColorScheme = darkColorScheme( +// primary = Primary, +// onPrimary = Black, +// primaryContainer = White, +// onPrimaryContainer = Black, +// secondary = Blue, +// tertiary = Purple, +// surface = Black, +// onSurface = White, +// onSurfaceVariant = DarkGray, +// onSecondary = Gray +//) +// +//private val LightColorScheme = lightColorScheme( +// primary = Primary, +// onPrimary = White, +// primaryContainer = Dark, +// onPrimaryContainer = White, +// secondary = Blue, +// tertiary = Purple, +// surface = White, +// onSurface = Black, +// onSurfaceVariant = Gray, +// onSecondary = DarkGray +//) +// +//@Composable +//fun MusicRoadTheme( +// darkTheme: Boolean = isSystemInDarkTheme(), +// content: @Composable () -> Unit +//) { +// MaterialTheme( +// colorScheme = if (darkTheme) DarkColorScheme else LightColorScheme, +// typography = Typography, +// content = content +// ) +//} diff --git a/app/src/main/java/com/squirtles/musicroad/ui/theme/Type.kt b/app/src/main/java/com/squirtles/musicroad/ui/theme/Type.kt index 24437693..a4695e1c 100644 --- a/app/src/main/java/com/squirtles/musicroad/ui/theme/Type.kt +++ b/app/src/main/java/com/squirtles/musicroad/ui/theme/Type.kt @@ -1,34 +1,34 @@ -package com.squirtles.musicroad.ui.theme - -import androidx.compose.material3.Typography -import androidx.compose.ui.text.TextStyle -import androidx.compose.ui.text.font.FontFamily -import androidx.compose.ui.text.font.FontWeight -import androidx.compose.ui.unit.sp - -// Set of Material typography styles to start with -val Typography = Typography( - bodyLarge = TextStyle( - fontFamily = FontFamily.Default, - fontWeight = FontWeight.Normal, - fontSize = 16.sp, - lineHeight = 24.sp, - letterSpacing = 0.5.sp - ) - /* Other default text styles to override - titleLarge = TextStyle( - fontFamily = FontFamily.Default, - fontWeight = FontWeight.Normal, - fontSize = 22.sp, - lineHeight = 28.sp, - letterSpacing = 0.sp - ), - labelSmall = TextStyle( - fontFamily = FontFamily.Default, - fontWeight = FontWeight.Medium, - fontSize = 11.sp, - lineHeight = 16.sp, - letterSpacing = 0.5.sp - ) - */ -) \ No newline at end of file +//package com.squirtles.musicroad.ui.theme +// +//import androidx.compose.material3.Typography +//import androidx.compose.ui.text.TextStyle +//import androidx.compose.ui.text.font.FontFamily +//import androidx.compose.ui.text.font.FontWeight +//import androidx.compose.ui.unit.sp +// +//// Set of Material typography styles to start with +//val Typography = Typography( +// bodyLarge = TextStyle( +// fontFamily = FontFamily.Default, +// fontWeight = FontWeight.Normal, +// fontSize = 16.sp, +// lineHeight = 24.sp, +// letterSpacing = 0.5.sp +// ) +// /* Other default text styles to override +// titleLarge = TextStyle( +// fontFamily = FontFamily.Default, +// fontWeight = FontWeight.Normal, +// fontSize = 22.sp, +// lineHeight = 28.sp, +// letterSpacing = 0.sp +// ), +// labelSmall = TextStyle( +// fontFamily = FontFamily.Default, +// fontWeight = FontWeight.Medium, +// fontSize = 11.sp, +// lineHeight = 16.sp, +// letterSpacing = 0.5.sp +// ) +// */ +//) diff --git a/app/src/main/java/com/squirtles/musicroad/userinfo/components/MenuItem.kt b/app/src/main/java/com/squirtles/musicroad/userinfo/components/MenuItem.kt index ffa4050b..eb447dce 100644 --- a/app/src/main/java/com/squirtles/musicroad/userinfo/components/MenuItem.kt +++ b/app/src/main/java/com/squirtles/musicroad/userinfo/components/MenuItem.kt @@ -2,7 +2,7 @@ package com.squirtles.musicroad.userinfo.components import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.vector.ImageVector -import com.squirtles.musicroad.ui.theme.White +import com.squirtles.common.ui.theme.White data class MenuItem( val imageVector: ImageVector, diff --git a/app/src/main/java/com/squirtles/musicroad/userinfo/components/UserInfoMenus.kt b/app/src/main/java/com/squirtles/musicroad/userinfo/components/UserInfoMenus.kt index c8bb9e32..54f6c7b3 100644 --- a/app/src/main/java/com/squirtles/musicroad/userinfo/components/UserInfoMenus.kt +++ b/app/src/main/java/com/squirtles/musicroad/userinfo/components/UserInfoMenus.kt @@ -24,11 +24,11 @@ import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.TextStyle import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.unit.dp +import com.squirtles.common.ui.Constants.DEFAULT_PADDING +import com.squirtles.common.ui.VerticalSpacer +import com.squirtles.common.ui.theme.Gray +import com.squirtles.common.ui.theme.White import com.squirtles.musicroad.R -import com.squirtles.musicroad.common.Constants.DEFAULT_PADDING -import com.squirtles.musicroad.common.VerticalSpacer -import com.squirtles.musicroad.ui.theme.Gray -import com.squirtles.musicroad.ui.theme.White @Composable internal fun UserInfoMenus( diff --git a/app/src/main/java/com/squirtles/musicroad/userinfo/screen/EditNotificationSettingScreen.kt b/app/src/main/java/com/squirtles/musicroad/userinfo/screen/EditNotificationSettingScreen.kt index fb3a55c3..6ce013da 100644 --- a/app/src/main/java/com/squirtles/musicroad/userinfo/screen/EditNotificationSettingScreen.kt +++ b/app/src/main/java/com/squirtles/musicroad/userinfo/screen/EditNotificationSettingScreen.kt @@ -12,11 +12,11 @@ import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Brush import androidx.compose.ui.res.stringResource import androidx.wear.compose.material.Text +import com.squirtles.common.ui.Constants.COLOR_STOPS +import com.squirtles.common.ui.Constants.DEFAULT_PADDING +import com.squirtles.common.ui.DefaultTopAppBar +import com.squirtles.common.ui.theme.White import com.squirtles.musicroad.R -import com.squirtles.musicroad.common.Constants.COLOR_STOPS -import com.squirtles.musicroad.common.Constants.DEFAULT_PADDING -import com.squirtles.musicroad.common.DefaultTopAppBar -import com.squirtles.musicroad.ui.theme.White @Composable internal fun EditNotificationSettingScreen( diff --git a/app/src/main/java/com/squirtles/musicroad/userinfo/screen/EditProfileScreen.kt b/app/src/main/java/com/squirtles/musicroad/userinfo/screen/EditProfileScreen.kt index c2df7708..be42c533 100644 --- a/app/src/main/java/com/squirtles/musicroad/userinfo/screen/EditProfileScreen.kt +++ b/app/src/main/java/com/squirtles/musicroad/userinfo/screen/EditProfileScreen.kt @@ -51,19 +51,19 @@ import androidx.hilt.navigation.compose.hiltViewModel import androidx.lifecycle.Lifecycle import androidx.lifecycle.compose.LocalLifecycleOwner import androidx.lifecycle.flowWithLifecycle +import com.squirtles.common.ui.Constants.COLOR_STOPS +import com.squirtles.common.ui.DialogTextButton +import com.squirtles.common.ui.HorizontalSpacer +import com.squirtles.common.ui.MessageAlertDialog +import com.squirtles.common.ui.theme.Black +import com.squirtles.common.ui.theme.DarkGray +import com.squirtles.common.ui.theme.Gray +import com.squirtles.common.ui.theme.MusicRoadTheme +import com.squirtles.common.ui.theme.Primary +import com.squirtles.common.ui.theme.White import com.squirtles.musicroad.R import com.squirtles.musicroad.account.AccountViewModel import com.squirtles.musicroad.account.GoogleId -import com.squirtles.musicroad.common.Constants.COLOR_STOPS -import com.squirtles.musicroad.common.DialogTextButton -import com.squirtles.musicroad.common.HorizontalSpacer -import com.squirtles.musicroad.common.MessageAlertDialog -import com.squirtles.musicroad.ui.theme.Black -import com.squirtles.musicroad.ui.theme.DarkGray -import com.squirtles.musicroad.ui.theme.Gray -import com.squirtles.musicroad.ui.theme.MusicRoadTheme -import com.squirtles.musicroad.ui.theme.Primary -import com.squirtles.musicroad.ui.theme.White import com.squirtles.musicroad.userinfo.UserInfoViewModel import kotlinx.coroutines.delay import kotlinx.coroutines.launch diff --git a/app/src/main/java/com/squirtles/musicroad/userinfo/screen/UserInfoScreen.kt b/app/src/main/java/com/squirtles/musicroad/userinfo/screen/UserInfoScreen.kt index 4b8540dc..977f460b 100644 --- a/app/src/main/java/com/squirtles/musicroad/userinfo/screen/UserInfoScreen.kt +++ b/app/src/main/java/com/squirtles/musicroad/userinfo/screen/UserInfoScreen.kt @@ -51,18 +51,18 @@ import androidx.lifecycle.flowWithLifecycle import coil3.compose.AsyncImage import coil3.request.ImageRequest import coil3.request.crossfade +import com.squirtles.common.ui.Constants.COLOR_STOPS +import com.squirtles.common.ui.DefaultTopAppBar +import com.squirtles.common.ui.DialogTextButton +import com.squirtles.common.ui.HorizontalSpacer +import com.squirtles.common.ui.MessageAlertDialog +import com.squirtles.common.ui.VerticalSpacer +import com.squirtles.common.ui.theme.Black +import com.squirtles.common.ui.theme.Primary +import com.squirtles.common.ui.theme.White import com.squirtles.musicroad.R import com.squirtles.musicroad.account.AccountViewModel import com.squirtles.musicroad.account.GoogleId -import com.squirtles.musicroad.common.Constants.COLOR_STOPS -import com.squirtles.musicroad.common.DefaultTopAppBar -import com.squirtles.musicroad.common.DialogTextButton -import com.squirtles.musicroad.common.HorizontalSpacer -import com.squirtles.musicroad.common.MessageAlertDialog -import com.squirtles.musicroad.common.VerticalSpacer -import com.squirtles.musicroad.ui.theme.Black -import com.squirtles.musicroad.ui.theme.Primary -import com.squirtles.musicroad.ui.theme.White import com.squirtles.musicroad.userinfo.UserInfoViewModel import com.squirtles.musicroad.userinfo.components.MenuItem import com.squirtles.musicroad.userinfo.components.UserInfoMenus diff --git a/audio_visualizer/src/main/AndroidManifest.xml b/audio_visualizer/src/main/AndroidManifest.xml deleted file mode 100644 index 8bdb7e14..00000000 --- a/audio_visualizer/src/main/AndroidManifest.xml +++ /dev/null @@ -1,4 +0,0 @@ - - - - From 073ab0f790f6943bedb581a3e4c1986bb0318ddf Mon Sep 17 00:00:00 2001 From: miller198 Date: Tue, 22 Apr 2025 20:11:47 +0900 Subject: [PATCH 47/62] =?UTF-8?q?[refactor]=20=ED=95=84=EC=9A=94=EC=97=86?= =?UTF-8?q?=EB=8A=94=20=EC=BD=94=EB=93=9C=20=EC=82=AD=EC=A0=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../musicroad/detail/PickDetailScreen.kt | 4 +- ...ommentText.kt => PickDetailCommentText.kt} | 8 +- .../musicroad/media/PlayerServiceViewModel.kt | 113 --------- .../musicroad/media/PlayerUiState.kt | 12 - .../musicroad/media/PlayerViewModel.kt | 228 ------------------ .../com/squirtles/musicroad/ui/theme/Color.kt | 25 -- .../com/squirtles/musicroad/ui/theme/Theme.kt | 45 ---- .../com/squirtles/musicroad/ui/theme/Type.kt | 34 --- 8 files changed, 6 insertions(+), 463 deletions(-) rename app/src/main/java/com/squirtles/musicroad/detail/components/{PickDatailCommentText.kt => PickDetailCommentText.kt} (92%) delete mode 100644 app/src/main/java/com/squirtles/musicroad/media/PlayerServiceViewModel.kt delete mode 100644 app/src/main/java/com/squirtles/musicroad/media/PlayerUiState.kt delete mode 100644 app/src/main/java/com/squirtles/musicroad/media/PlayerViewModel.kt delete mode 100644 app/src/main/java/com/squirtles/musicroad/ui/theme/Color.kt delete mode 100644 app/src/main/java/com/squirtles/musicroad/ui/theme/Theme.kt delete mode 100644 app/src/main/java/com/squirtles/musicroad/ui/theme/Type.kt diff --git a/app/src/main/java/com/squirtles/musicroad/detail/PickDetailScreen.kt b/app/src/main/java/com/squirtles/musicroad/detail/PickDetailScreen.kt index 818a194d..2a5ffe92 100644 --- a/app/src/main/java/com/squirtles/musicroad/detail/PickDetailScreen.kt +++ b/app/src/main/java/com/squirtles/musicroad/detail/PickDetailScreen.kt @@ -69,7 +69,7 @@ import com.squirtles.musicroad.account.AccountViewModel import com.squirtles.musicroad.account.GoogleId import com.squirtles.musicroad.detail.DetailViewModel.Companion.DEFAULT_PICK import com.squirtles.musicroad.detail.components.CircleAlbumCover -import com.squirtles.musicroad.detail.components.PickDatailCommentText +import com.squirtles.musicroad.detail.components.PickDetailCommentText import com.squirtles.musicroad.detail.components.DetailPickTopAppBar import com.squirtles.musicroad.detail.components.MusicVideoKnob import com.squirtles.musicroad.detail.components.PickInformation @@ -488,7 +488,7 @@ private fun PickDetailContents( favoriteCount = favoriteCount ) - PickDatailCommentText(comment = pick.comment) + PickDetailCommentText(comment = pick.comment) VerticalSpacer(height = 8) } diff --git a/app/src/main/java/com/squirtles/musicroad/detail/components/PickDatailCommentText.kt b/app/src/main/java/com/squirtles/musicroad/detail/components/PickDetailCommentText.kt similarity index 92% rename from app/src/main/java/com/squirtles/musicroad/detail/components/PickDatailCommentText.kt rename to app/src/main/java/com/squirtles/musicroad/detail/components/PickDetailCommentText.kt index 0d9f21c8..38606fa1 100644 --- a/app/src/main/java/com/squirtles/musicroad/detail/components/PickDatailCommentText.kt +++ b/app/src/main/java/com/squirtles/musicroad/detail/components/PickDetailCommentText.kt @@ -22,7 +22,7 @@ import com.squirtles.common.ui.theme.White import com.squirtles.musicroad.R @Composable -internal fun PickDatailCommentText( +internal fun PickDetailCommentText( comment: String, modifier: Modifier = Modifier ) { @@ -44,11 +44,11 @@ internal fun PickDatailCommentText( @Composable private fun CommentTextPreview() { Column(verticalArrangement = Arrangement.spacedBy(10.dp)) { - PickDatailCommentText(comment = "") + PickDetailCommentText(comment = "") - PickDatailCommentText(comment = "노래가 좋아서 추천합니다.") + PickDetailCommentText(comment = "노래가 좋아서 추천합니다.") - PickDatailCommentText( + PickDetailCommentText( comment = "노래가 너무너무너무너무너무너무너무너무너무너무너무너무너무너무너무너무너무너무너무너무너무너무너무너무너무너무" + "너무너무너무너무너무너무너무너무너무너무너무너무너무너무너무너무너무너무너무너무너무너무 좋아요" ) diff --git a/app/src/main/java/com/squirtles/musicroad/media/PlayerServiceViewModel.kt b/app/src/main/java/com/squirtles/musicroad/media/PlayerServiceViewModel.kt deleted file mode 100644 index 189a4c0b..00000000 --- a/app/src/main/java/com/squirtles/musicroad/media/PlayerServiceViewModel.kt +++ /dev/null @@ -1,113 +0,0 @@ -//package com.squirtles.musicroad.media -// -//import androidx.lifecycle.ViewModel -//import androidx.lifecycle.viewModelScope -//import com.squirtles.domain.model.Pick -//import com.squirtles.domain.model.PlayerState -//import com.squirtles.domain.model.Song -//import com.squirtles.domain.player.MediaPlayerListenerUseCase -//import com.squirtles.domain.player.MediaPlayerUseCase -//import dagger.hilt.android.lifecycle.HiltViewModel -//import kotlinx.coroutines.async -//import kotlinx.coroutines.flow.SharingStarted -//import kotlinx.coroutines.flow.StateFlow -//import kotlinx.coroutines.flow.stateIn -//import kotlinx.coroutines.launch -//import javax.inject.Inject -// -//@HiltViewModel -//class PlayerServiceViewModel @Inject constructor( -// private val mediaPlayerUseCase: MediaPlayerUseCase, -// private val mediaPlayerListenerUseCase: MediaPlayerListenerUseCase, -//) : ViewModel() { -// -// val playerState: StateFlow = mediaPlayerListenerUseCase.playerStateFlow() -// .stateIn( -// scope = viewModelScope, -// started = SharingStarted.WhileSubscribed(5_000), -// initialValue = PlayerState() -// ) -// -// val audioSessionId get() = mediaPlayerUseCase.audioSessionId -// -// suspend fun readyPlayer() { -// viewModelScope.async { -// mediaPlayerUseCase.readyPlayer() -// }.await() -// } -// -// fun setMediaItem(pick: Pick) { -// viewModelScope.launch { -// if (playerState.value.id == pick.id) { -// mediaPlayerUseCase.changeRepeatMode(false) -// } else { -// mediaPlayerUseCase.setMediaItem(pick) -// } -// } -// } -// -// fun setMediaItems(picks: List) { -// mediaPlayerUseCase.setMediaItems(picks) -// } -// -// private fun onPlay() { -// mediaPlayerUseCase.play() -// } -// -// fun onPause() { -// mediaPlayerUseCase.pause() -// } -// -// fun onStop() { -// mediaPlayerUseCase.stop() -// } -// -// fun onRelease() { -// mediaPlayerUseCase.release() -// } -// -// fun onPrevious() { -// mediaPlayerUseCase.previous() -// } -// -// fun onNext() { -// mediaPlayerUseCase.next() -// } -// -// fun onAdvanceBy() { -// mediaPlayerUseCase.advanceBy() -// } -// -// fun shuffleNext(pick: Pick) { -// if (playerState.value.isPlaying) { -// onPause() -// } else { -// setMediaItem(pick) -// onPlay() -// } -// } -// -// fun togglePlayPause(song: Song) { -// if (playerState.value.isPlaying) { -// onPause() -// } else { -// onPlay() -// } -// } -// -// fun onRewindBy() { -// mediaPlayerUseCase.rewindBy() -// } -// -// fun onSeekingStarted() { -// mediaPlayerUseCase.onSeekingStarted() -// } -// -// fun onSeekingFinished(time: Long) { -// mediaPlayerUseCase.onSeekingFinished(time) -// } -// -// fun onAddToQueue(pick: Pick) { -// mediaPlayerUseCase.addMediaItem(pick) -// } -//} diff --git a/app/src/main/java/com/squirtles/musicroad/media/PlayerUiState.kt b/app/src/main/java/com/squirtles/musicroad/media/PlayerUiState.kt deleted file mode 100644 index 76525f22..00000000 --- a/app/src/main/java/com/squirtles/musicroad/media/PlayerUiState.kt +++ /dev/null @@ -1,12 +0,0 @@ -//package com.squirtles.musicroad.media -// -//data class PlayerUiState( -// val isReady: Boolean = true, -// val isPlaying: Boolean = false, -// val currentPosition: Long = 0L, -//) { -// companion object { -// val PLAYER_STATE_INITIAL = PlayerUiState(isReady = false) -// val PLAYER_STATE_STOP = PlayerUiState(isReady = true, isPlaying = false) -// } -//} diff --git a/app/src/main/java/com/squirtles/musicroad/media/PlayerViewModel.kt b/app/src/main/java/com/squirtles/musicroad/media/PlayerViewModel.kt deleted file mode 100644 index 4b5c0ab0..00000000 --- a/app/src/main/java/com/squirtles/musicroad/media/PlayerViewModel.kt +++ /dev/null @@ -1,228 +0,0 @@ -//package com.squirtles.musicroad.media -// -//import android.content.Context -//import android.util.Log -//import androidx.annotation.OptIn -//import androidx.lifecycle.ViewModel -//import androidx.lifecycle.viewModelScope -//import androidx.media3.common.C.TIME_UNSET -//import androidx.media3.common.MediaItem -//import androidx.media3.common.PlaybackException -//import androidx.media3.common.Player -//import androidx.media3.common.util.UnstableApi -//import androidx.media3.exoplayer.ExoPlayer -//import com.squirtles.musicroad.media.PlayerUiState.Companion.PLAYER_STATE_INITIAL -//import com.squirtles.musicroad.media.PlayerUiState.Companion.PLAYER_STATE_STOP -//import dagger.hilt.android.lifecycle.HiltViewModel -//import kotlinx.coroutines.delay -//import kotlinx.coroutines.flow.MutableStateFlow -//import kotlinx.coroutines.flow.StateFlow -//import kotlinx.coroutines.launch -//import javax.inject.Inject -// -//@HiltViewModel -//class PlayerViewModel @Inject constructor( -//) : ViewModel() { -// -// private var player: ExoPlayer? = null -// -// private var _audioSessionId = 0 -// val audioSessionId get() = _audioSessionId -// -// private val _playerState = MutableStateFlow(PLAYER_STATE_INITIAL) -// val playerUiState: StateFlow = _playerState -// -// private val _bufferPercentage = MutableStateFlow(0) -// val bufferPercentage: StateFlow = _bufferPercentage -// -// private val _duration = MutableStateFlow(30_000L) -// val duration: StateFlow = _duration -// -// @OptIn(UnstableApi::class) -// private fun initializePlayer(context: Context) { -// val exoPlayer = ExoPlayer.Builder(context).build().also { -// it.addListener(object : Player.Listener { -// override fun onPlayerError(error: PlaybackException) { -// handleError(error) -// } -// -// override fun onPlaybackStateChanged(playbackState: Int) { -// if (playbackState == Player.STATE_ENDED) { -// it.seekTo(0) -// it.pause() -// } -// } -// }) -// it.volume = 0.8f -// } -// this.player = exoPlayer -// _audioSessionId = exoPlayer.audioSessionId -// } -// -// fun readyPlayer(context: Context, sourceUrl: String) { -// if (player != null) return -// -// initializePlayer(context) -// -// player?.let { -// val mediaItem = MediaItem.fromUri(sourceUrl) -// it.setMediaItem(mediaItem) -// it.prepare() -// it.playWhenReady = false -// it.seekTo(_playerState.value.currentPosition) -// -// _playerState.value = -// PlayerUiState(isReady = true, currentPosition = _playerState.value.currentPosition) -// -// _duration.value = -// if (it.duration == TIME_UNSET) 30_000L else it.duration -// -// updatePlayerStatePeriodically(it) -// } -// } -// -// fun readyPlayerSetList(context: Context, sourceUrls: List) { -// if (player != null) return -// -// initializePlayer(context) -// -// player?.let { -// it.setMediaItems(sourceUrls.map { url -> -// MediaItem.fromUri(url) -// }) -// it.prepare() -// it.playWhenReady = false -// it.repeatMode = Player.REPEAT_MODE_ALL -// } -// } -// -// private fun updatePlayerStatePeriodically(exoPlayer: ExoPlayer) { -// viewModelScope.launch { -// while (_playerState.value.isReady) { -// _playerState.value = _playerState.value.copy( -// isPlaying = exoPlayer.isPlaying, -// currentPosition = exoPlayer.currentPosition, -// ) -// _bufferPercentage.value = exoPlayer.bufferedPercentage -// delay(1000) -// } -// } -// } -// -// fun shuffleNextItem() { -// viewModelScope.launch { -// player?.let { -// if (!it.isPlaying) it.seekToNextMediaItem() -// togglePlayPause(it) -// } -// } -// } -// -// fun replayForward(sec: Long) { -// player?.let { -// it.seekTo(it.currentPosition + sec) -// viewModelScope.launch { -// _playerState.value = -// _playerState.value.copy(currentPosition = it.currentPosition) -// } -// } -// } -// -// fun togglePlayPause() { -// player?.let { -// togglePlayPause(it) -// } -// } -// -// private fun togglePlayPause(exoPlayer: ExoPlayer) { -// if (exoPlayer.isPlaying) pause(exoPlayer) -// else play(exoPlayer) -// } -// -// fun play() { -// player?.let { -// play(it) -// } -// } -// -// private fun play(exoPlayer: ExoPlayer) { -// viewModelScope.launch { -// _playerState.value = _playerState.value.copy(isPlaying = true) -// } -// exoPlayer.play() -// } -// -// fun pause() { -// player?.let { -// pause(it) -// } -// } -// -// private fun pause(exoPlayer: ExoPlayer) { -// viewModelScope.launch { -// _playerState.value = _playerState.value.copy(isPlaying = false) -// } -// exoPlayer.pause() -// } -// -// fun stop() { -// player?.let { -// viewModelScope.launch { -// _playerState.value = PLAYER_STATE_STOP -// } -// it.stop() -// } -// } -// -// fun playerSeekTo(sec: Long) { -// viewModelScope.launch { -// player?.let { -// _playerState.value = _playerState.value.copy(currentPosition = sec) -// it.seekTo(sec) -// } -// } -// } -// -// fun savePlayerState() { -// player?.let { -// _playerState.value = _playerState.value.copy( -// isReady = false, -// isPlaying = false, -// currentPosition = it.currentPosition, -// ) -// } -// } -// -// private fun releasePlayer() { -// player?.release() -// } -// -// private fun handleError(error: PlaybackException) { -// when (error.errorCode) { -// PlaybackException.ERROR_CODE_IO_NETWORK_CONNECTION_FAILED -> { -// // TODO: Handle network connection error -// Log.d("PlayerViewModel", "Network connection error") -// } -// -// PlaybackException.ERROR_CODE_IO_FILE_NOT_FOUND -> { -// // TODO: Handle file not found error -// Log.d("PlayerViewModel", "File not found") -// } -// -// PlaybackException.ERROR_CODE_DECODER_INIT_FAILED -> { -// // TODO: Handle decoder initialization error -// Log.d("PlayerViewModel", "Decoder initialization error") -// } -// -// else -> { -// // TODO: Handle other types of errors -// Log.d("PlayerViewModel", "${error.message}") -// } -// } -// } -// -// override fun onCleared() { -// releasePlayer() -// super.onCleared() -// } -//} diff --git a/app/src/main/java/com/squirtles/musicroad/ui/theme/Color.kt b/app/src/main/java/com/squirtles/musicroad/ui/theme/Color.kt deleted file mode 100644 index f2755f13..00000000 --- a/app/src/main/java/com/squirtles/musicroad/ui/theme/Color.kt +++ /dev/null @@ -1,25 +0,0 @@ -//package com.squirtles.musicroad.ui.theme -// -//import androidx.compose.ui.graphics.Color -// -//val Primary = Color(0xFFFF5F61) -//val Primary80 = Color(0xFFFFB3B0) -//val Primary50 = Color(0xFFAD625F) -//val Primary20 = Color(0xFF571D1E) -//val Blue = Color(0xFF6B84FF) -// -//val Purple = Color(0xFFBB8280) -//val Purple15 = Color(0x26BB8280) -//val PurpleGrey = Color(0xFFB4AEAE) -// -//val Black = Color(0xFF000000) -//val Dark = Color(0xFF151515) -//val DarkGray = Color(0xFF646464) -//val Gray = Color(0xFFAAAAAA) -//val White = Color(0xFFFFFFFF) -// -//val PlayerBackground = Color(0xFF353535) -// -//val SignInButtonDarkBackground = Color(0xFF131314) -//val SignInButtonLightStroke = Color(0xFF747775) -//val SignInButtonDarkStroke = Color(0xFF8E918F) diff --git a/app/src/main/java/com/squirtles/musicroad/ui/theme/Theme.kt b/app/src/main/java/com/squirtles/musicroad/ui/theme/Theme.kt deleted file mode 100644 index 8f9539f5..00000000 --- a/app/src/main/java/com/squirtles/musicroad/ui/theme/Theme.kt +++ /dev/null @@ -1,45 +0,0 @@ -//package com.squirtles.musicroad.ui.theme -// -//import androidx.compose.foundation.isSystemInDarkTheme -//import androidx.compose.material3.MaterialTheme -//import androidx.compose.material3.darkColorScheme -//import androidx.compose.material3.lightColorScheme -//import androidx.compose.runtime.Composable -// -//private val DarkColorScheme = darkColorScheme( -// primary = Primary, -// onPrimary = Black, -// primaryContainer = White, -// onPrimaryContainer = Black, -// secondary = Blue, -// tertiary = Purple, -// surface = Black, -// onSurface = White, -// onSurfaceVariant = DarkGray, -// onSecondary = Gray -//) -// -//private val LightColorScheme = lightColorScheme( -// primary = Primary, -// onPrimary = White, -// primaryContainer = Dark, -// onPrimaryContainer = White, -// secondary = Blue, -// tertiary = Purple, -// surface = White, -// onSurface = Black, -// onSurfaceVariant = Gray, -// onSecondary = DarkGray -//) -// -//@Composable -//fun MusicRoadTheme( -// darkTheme: Boolean = isSystemInDarkTheme(), -// content: @Composable () -> Unit -//) { -// MaterialTheme( -// colorScheme = if (darkTheme) DarkColorScheme else LightColorScheme, -// typography = Typography, -// content = content -// ) -//} diff --git a/app/src/main/java/com/squirtles/musicroad/ui/theme/Type.kt b/app/src/main/java/com/squirtles/musicroad/ui/theme/Type.kt deleted file mode 100644 index a4695e1c..00000000 --- a/app/src/main/java/com/squirtles/musicroad/ui/theme/Type.kt +++ /dev/null @@ -1,34 +0,0 @@ -//package com.squirtles.musicroad.ui.theme -// -//import androidx.compose.material3.Typography -//import androidx.compose.ui.text.TextStyle -//import androidx.compose.ui.text.font.FontFamily -//import androidx.compose.ui.text.font.FontWeight -//import androidx.compose.ui.unit.sp -// -//// Set of Material typography styles to start with -//val Typography = Typography( -// bodyLarge = TextStyle( -// fontFamily = FontFamily.Default, -// fontWeight = FontWeight.Normal, -// fontSize = 16.sp, -// lineHeight = 24.sp, -// letterSpacing = 0.5.sp -// ) -// /* Other default text styles to override -// titleLarge = TextStyle( -// fontFamily = FontFamily.Default, -// fontWeight = FontWeight.Normal, -// fontSize = 22.sp, -// lineHeight = 28.sp, -// letterSpacing = 0.sp -// ), -// labelSmall = TextStyle( -// fontFamily = FontFamily.Default, -// fontWeight = FontWeight.Medium, -// fontSize = 11.sp, -// lineHeight = 16.sp, -// letterSpacing = 0.5.sp -// ) -// */ -//) From 6cbd050be62ea629be769fbfcfe69a9317b85b40 Mon Sep 17 00:00:00 2001 From: miller198 Date: Tue, 22 Apr 2025 20:20:16 +0900 Subject: [PATCH 48/62] =?UTF-8?q?[refactor]=20core:account=20=EC=A0=81?= =?UTF-8?q?=EC=9A=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/build.gradle.kts | 1 + .../musicroad/account/AccountViewModel.kt | 76 ----------------- .../squirtles/musicroad/account/GoogleId.kt | 82 ------------------- .../musicroad/detail/PickDetailScreen.kt | 4 +- .../com/squirtles/musicroad/map/MapScreen.kt | 4 +- .../userinfo/screen/EditProfileScreen.kt | 4 +- .../userinfo/screen/UserInfoScreen.kt | 4 +- .../com/squirtles/detail/PickDetailScreen.kt | 1 - 8 files changed, 9 insertions(+), 167 deletions(-) delete mode 100644 app/src/main/java/com/squirtles/musicroad/account/AccountViewModel.kt delete mode 100644 app/src/main/java/com/squirtles/musicroad/account/GoogleId.kt diff --git a/app/build.gradle.kts b/app/build.gradle.kts index ae7b57fd..139726f0 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -102,6 +102,7 @@ dependencies { // implementation(projects.data) // implementation(projects.mediaservice) implementation(projects.audioVisualizer) + implementation(projects.core.account) implementation(projects.core.musicplayer) implementation(projects.core.model) implementation(projects.core.common) diff --git a/app/src/main/java/com/squirtles/musicroad/account/AccountViewModel.kt b/app/src/main/java/com/squirtles/musicroad/account/AccountViewModel.kt deleted file mode 100644 index 6f58f37a..00000000 --- a/app/src/main/java/com/squirtles/musicroad/account/AccountViewModel.kt +++ /dev/null @@ -1,76 +0,0 @@ -package com.squirtles.musicroad.account - -import android.util.Log -import androidx.lifecycle.ViewModel -import androidx.lifecycle.viewModelScope -import com.google.android.libraries.identity.googleid.GoogleIdTokenCredential -import com.squirtles.user.usecase.SignOutUseCase -import com.squirtles.user.usecase.CreateGoogleIdUserUseCase -import com.squirtles.user.usecase.DeleteAccountUseCase -import com.squirtles.user.usecase.FetchUserByIdUseCase -import dagger.hilt.android.lifecycle.HiltViewModel -import kotlinx.coroutines.flow.MutableSharedFlow -import kotlinx.coroutines.flow.asSharedFlow -import kotlinx.coroutines.launch -import javax.inject.Inject - -@HiltViewModel -class AccountViewModel @Inject constructor( - private val fetchUserByIdUseCase: FetchUserByIdUseCase, - private val createGoogleIdUserUseCase: CreateGoogleIdUserUseCase, - private val signOutUseCase: SignOutUseCase, - private val deleteAccountUseCase: DeleteAccountUseCase -) : ViewModel() { - - private val _signInSuccess = MutableSharedFlow() - val signInSuccess = _signInSuccess.asSharedFlow() - - private val _signOutSuccess = MutableSharedFlow() - val signOutSuccess = _signOutSuccess.asSharedFlow() - - private val _deleteAccountSuccess = MutableSharedFlow() - val deleteAccountSuccess = _deleteAccountSuccess.asSharedFlow() - - fun signIn(uid: String, credential: GoogleIdTokenCredential) { - viewModelScope.launch { - fetchUserByIdUseCase(uid) - .onSuccess { - Log.d("SignIn", "기존 계정 로그인 email : ${it.email} 로그인") - _signInSuccess.emit(true) - } - .onFailure { - createGoogleIdUser(uid, credential) - } - } - } - - private fun createGoogleIdUser(uid: String, credential: GoogleIdTokenCredential) { - viewModelScope.launch { - createGoogleIdUserUseCase( - uid = uid, - email = credential.id, - userName = credential.displayName, - userProfileImage = credential.profilePictureUri.toString() - ).onSuccess { - Log.d("SignIn", "새로운 계정 로그인 email : ${it.email}") - _signInSuccess.emit(true) - }.onFailure { - _signInSuccess.emit(false) - } - } - } - - fun signOut() { - viewModelScope.launch { - signOutUseCase() - _signOutSuccess.emit(true) - } - } - - fun deleteAccount() { - viewModelScope.launch { - deleteAccountUseCase() - _deleteAccountSuccess.emit(true) - } - } -} diff --git a/app/src/main/java/com/squirtles/musicroad/account/GoogleId.kt b/app/src/main/java/com/squirtles/musicroad/account/GoogleId.kt deleted file mode 100644 index e177ba65..00000000 --- a/app/src/main/java/com/squirtles/musicroad/account/GoogleId.kt +++ /dev/null @@ -1,82 +0,0 @@ -package com.squirtles.musicroad.account - -import android.content.Context -import android.util.Log -import android.widget.Toast -import androidx.credentials.ClearCredentialStateRequest -import androidx.credentials.CredentialManager -import androidx.credentials.CustomCredential -import androidx.credentials.GetCredentialRequest -import androidx.credentials.GetCredentialResponse -import androidx.credentials.exceptions.NoCredentialException -import com.google.android.libraries.identity.googleid.GetGoogleIdOption -import com.google.android.libraries.identity.googleid.GoogleIdTokenCredential -import com.google.firebase.auth.FirebaseAuth -import com.google.firebase.auth.GoogleAuthProvider -import com.squirtles.musicroad.BuildConfig -import com.squirtles.musicroad.R -import kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.launch - -class GoogleId(private val context: Context) { - private val credentialManager = CredentialManager.create(context) - - private val googleIdOption: GetGoogleIdOption = GetGoogleIdOption.Builder() - .setFilterByAuthorizedAccounts(false) - .setServerClientId(BuildConfig.GOOGLE_CLIENT_ID) - .setAutoSelectEnabled(true) - .build() - - private val request = GetCredentialRequest.Builder() - .addCredentialOption(googleIdOption) - .build() - - private fun signInWithGoogle(result: GetCredentialResponse, onSuccess: (String, GoogleIdTokenCredential) -> Unit) { - when (val data = result.credential) { - is CustomCredential -> { - if (data.type == GoogleIdTokenCredential.TYPE_GOOGLE_ID_TOKEN_CREDENTIAL) { - val googleIdTokenCredential = GoogleIdTokenCredential.createFrom(data.data) - Log.d("SignIn", "token : ${googleIdTokenCredential.idToken}") - signInWithFirebase(googleIdTokenCredential, onSuccess) - } - } - } - } - - private fun signInWithFirebase(googleIdTokenCredential: GoogleIdTokenCredential, onSuccess: (String, GoogleIdTokenCredential) -> Unit) { - val credential = GoogleAuthProvider.getCredential(googleIdTokenCredential.idToken, null) - FirebaseAuth.getInstance().signInWithCredential(credential) - .addOnSuccessListener { authResult -> - authResult.user?.uid?.let { uid -> - Log.d("SignIn", "Firebase 인증 uid : $uid") - onSuccess(uid, googleIdTokenCredential) - } - } - .addOnFailureListener { exception -> - Log.e("SignIn", "Firebase 인증 실패", exception) - } - } - - fun signIn(onSuccess: (String, GoogleIdTokenCredential) -> Unit) { - CoroutineScope(Dispatchers.Main).launch { - runCatching { - val result = credentialManager.getCredential(context, request) - signInWithGoogle(result, onSuccess) - }.onFailure { exception -> - when (exception) { - is NoCredentialException -> Toast.makeText(context, context.getString(R.string.google_id_no_credential_exception_message), Toast.LENGTH_SHORT).show() - } - Log.e("SignIn", "Google SignIn Error : $exception") - } - } - } - - fun signOut() { - CoroutineScope(Dispatchers.Main).launch { - credentialManager.clearCredentialState( - request = ClearCredentialStateRequest() - ) - } - } -} diff --git a/app/src/main/java/com/squirtles/musicroad/detail/PickDetailScreen.kt b/app/src/main/java/com/squirtles/musicroad/detail/PickDetailScreen.kt index 2a5ffe92..6b08aee3 100644 --- a/app/src/main/java/com/squirtles/musicroad/detail/PickDetailScreen.kt +++ b/app/src/main/java/com/squirtles/musicroad/detail/PickDetailScreen.kt @@ -54,6 +54,8 @@ import androidx.lifecycle.Lifecycle import androidx.lifecycle.compose.LocalLifecycleOwner import androidx.lifecycle.compose.collectAsStateWithLifecycle import androidx.lifecycle.flowWithLifecycle +import com.squirtles.account.AccountViewModel +import com.squirtles.account.GoogleId import com.squirtles.common.ui.DialogTextButton import com.squirtles.common.ui.HorizontalSpacer import com.squirtles.common.ui.MessageAlertDialog @@ -65,8 +67,6 @@ import com.squirtles.common.ui.theme.White import com.squirtles.model.Pick import com.squirtles.musicplayer.PlayerServiceViewModel import com.squirtles.musicroad.R -import com.squirtles.musicroad.account.AccountViewModel -import com.squirtles.musicroad.account.GoogleId import com.squirtles.musicroad.detail.DetailViewModel.Companion.DEFAULT_PICK import com.squirtles.musicroad.detail.components.CircleAlbumCover import com.squirtles.musicroad.detail.components.PickDetailCommentText diff --git a/app/src/main/java/com/squirtles/musicroad/map/MapScreen.kt b/app/src/main/java/com/squirtles/musicroad/map/MapScreen.kt index 199163c9..5e410658 100644 --- a/app/src/main/java/com/squirtles/musicroad/map/MapScreen.kt +++ b/app/src/main/java/com/squirtles/musicroad/map/MapScreen.kt @@ -33,13 +33,13 @@ import androidx.lifecycle.Lifecycle import androidx.lifecycle.compose.LocalLifecycleOwner import androidx.lifecycle.compose.collectAsStateWithLifecycle import androidx.lifecycle.flowWithLifecycle +import com.squirtles.account.AccountViewModel +import com.squirtles.account.GoogleId import com.squirtles.common.ui.SignInAlertDialog import com.squirtles.common.ui.VerticalSpacer import com.squirtles.common.ui.theme.Black import com.squirtles.musicplayer.PlayerServiceViewModel import com.squirtles.musicroad.R -import com.squirtles.musicroad.account.AccountViewModel -import com.squirtles.musicroad.account.GoogleId import com.squirtles.musicroad.main.MainActivity import com.squirtles.musicroad.map.components.ClusterBottomSheet import com.squirtles.musicroad.map.components.InfoWindow diff --git a/app/src/main/java/com/squirtles/musicroad/userinfo/screen/EditProfileScreen.kt b/app/src/main/java/com/squirtles/musicroad/userinfo/screen/EditProfileScreen.kt index be42c533..28ed562b 100644 --- a/app/src/main/java/com/squirtles/musicroad/userinfo/screen/EditProfileScreen.kt +++ b/app/src/main/java/com/squirtles/musicroad/userinfo/screen/EditProfileScreen.kt @@ -51,6 +51,8 @@ import androidx.hilt.navigation.compose.hiltViewModel import androidx.lifecycle.Lifecycle import androidx.lifecycle.compose.LocalLifecycleOwner import androidx.lifecycle.flowWithLifecycle +import com.squirtles.account.AccountViewModel +import com.squirtles.account.GoogleId import com.squirtles.common.ui.Constants.COLOR_STOPS import com.squirtles.common.ui.DialogTextButton import com.squirtles.common.ui.HorizontalSpacer @@ -62,8 +64,6 @@ import com.squirtles.common.ui.theme.MusicRoadTheme import com.squirtles.common.ui.theme.Primary import com.squirtles.common.ui.theme.White import com.squirtles.musicroad.R -import com.squirtles.musicroad.account.AccountViewModel -import com.squirtles.musicroad.account.GoogleId import com.squirtles.musicroad.userinfo.UserInfoViewModel import kotlinx.coroutines.delay import kotlinx.coroutines.launch diff --git a/app/src/main/java/com/squirtles/musicroad/userinfo/screen/UserInfoScreen.kt b/app/src/main/java/com/squirtles/musicroad/userinfo/screen/UserInfoScreen.kt index 977f460b..78b806fe 100644 --- a/app/src/main/java/com/squirtles/musicroad/userinfo/screen/UserInfoScreen.kt +++ b/app/src/main/java/com/squirtles/musicroad/userinfo/screen/UserInfoScreen.kt @@ -51,6 +51,8 @@ import androidx.lifecycle.flowWithLifecycle import coil3.compose.AsyncImage import coil3.request.ImageRequest import coil3.request.crossfade +import com.squirtles.account.AccountViewModel +import com.squirtles.account.GoogleId import com.squirtles.common.ui.Constants.COLOR_STOPS import com.squirtles.common.ui.DefaultTopAppBar import com.squirtles.common.ui.DialogTextButton @@ -61,8 +63,6 @@ import com.squirtles.common.ui.theme.Black import com.squirtles.common.ui.theme.Primary import com.squirtles.common.ui.theme.White import com.squirtles.musicroad.R -import com.squirtles.musicroad.account.AccountViewModel -import com.squirtles.musicroad.account.GoogleId import com.squirtles.musicroad.userinfo.UserInfoViewModel import com.squirtles.musicroad.userinfo.components.MenuItem import com.squirtles.musicroad.userinfo.components.UserInfoMenus diff --git a/feature/detail/src/main/java/com/squirtles/detail/PickDetailScreen.kt b/feature/detail/src/main/java/com/squirtles/detail/PickDetailScreen.kt index 80c7499b..e3bd69f7 100644 --- a/feature/detail/src/main/java/com/squirtles/detail/PickDetailScreen.kt +++ b/feature/detail/src/main/java/com/squirtles/detail/PickDetailScreen.kt @@ -133,7 +133,6 @@ fun PickDetailScreen( } is PickDetailUiState.Success -> { - val lifecycleOwner = LocalLifecycleOwner.current val pick = (uiState as PickDetailUiState.Success).pick val isFavorite = (uiState as PickDetailUiState.Success).isFavorite val isNonMember = detailViewModel.getUid() == null From 1d9b46965db2c15f4401b9b2ebce234d7aef9ace Mon Sep 17 00:00:00 2001 From: miller198 Date: Tue, 22 Apr 2025 20:35:20 +0900 Subject: [PATCH 49/62] =?UTF-8?q?[refactor]=20feature:userinfo=20=EC=A0=81?= =?UTF-8?q?=EC=9A=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/build.gradle.kts | 1 + .../musicroad/main/navigation/MainNavHost.kt | 2 +- .../main/navigation/MainNavigator.kt | 6 +- .../musicroad/userinfo/UserInfoViewModel.kt | 54 --- .../musicroad/userinfo/components/MenuItem.kt | 13 - .../userinfo/components/UserInfoMenus.kt | 104 ------ .../userinfo/navigation/UserInfoNavigation.kt | 62 ---- .../screen/EditNotificationSettingScreen.kt | 49 --- .../userinfo/screen/EditProfileScreen.kt | 328 ------------------ .../userinfo/screen/UserInfoScreen.kt | 273 --------------- 10 files changed, 5 insertions(+), 887 deletions(-) delete mode 100644 app/src/main/java/com/squirtles/musicroad/userinfo/UserInfoViewModel.kt delete mode 100644 app/src/main/java/com/squirtles/musicroad/userinfo/components/MenuItem.kt delete mode 100644 app/src/main/java/com/squirtles/musicroad/userinfo/components/UserInfoMenus.kt delete mode 100644 app/src/main/java/com/squirtles/musicroad/userinfo/navigation/UserInfoNavigation.kt delete mode 100644 app/src/main/java/com/squirtles/musicroad/userinfo/screen/EditNotificationSettingScreen.kt delete mode 100644 app/src/main/java/com/squirtles/musicroad/userinfo/screen/EditProfileScreen.kt delete mode 100644 app/src/main/java/com/squirtles/musicroad/userinfo/screen/UserInfoScreen.kt diff --git a/app/build.gradle.kts b/app/build.gradle.kts index 139726f0..141aab7f 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -124,6 +124,7 @@ dependencies { implementation(projects.data.favorite) implementation(projects.data.location) implementation(projects.data.order) + implementation(projects.feature.userinfo) implementation(libs.androidx.core.ktx) implementation(libs.androidx.lifecycle.runtime.ktx) diff --git a/app/src/main/java/com/squirtles/musicroad/main/navigation/MainNavHost.kt b/app/src/main/java/com/squirtles/musicroad/main/navigation/MainNavHost.kt index 725acfaf..0f21f9fa 100644 --- a/app/src/main/java/com/squirtles/musicroad/main/navigation/MainNavHost.kt +++ b/app/src/main/java/com/squirtles/musicroad/main/navigation/MainNavHost.kt @@ -12,7 +12,7 @@ import com.squirtles.musicroad.map.MapViewModel import com.squirtles.musicroad.map.navigation.mapNavGraph import com.squirtles.musicroad.mypick.navigation.myPickNavGraph import com.squirtles.musicroad.search.navigation.searchNavGraph -import com.squirtles.musicroad.userinfo.navigation.userInfoNavGraph +import com.squirtles.userinfo.navigation.userInfoNavGraph @Composable internal fun MainNavHost( diff --git a/app/src/main/java/com/squirtles/musicroad/main/navigation/MainNavigator.kt b/app/src/main/java/com/squirtles/musicroad/main/navigation/MainNavigator.kt index 48ad2a2b..1f958973 100644 --- a/app/src/main/java/com/squirtles/musicroad/main/navigation/MainNavigator.kt +++ b/app/src/main/java/com/squirtles/musicroad/main/navigation/MainNavigator.kt @@ -15,10 +15,10 @@ import com.squirtles.musicroad.favorite.navigation.navigateFavorite import com.squirtles.musicroad.map.navigation.navigateMap import com.squirtles.musicroad.mypick.navigation.navigateMyPicks import com.squirtles.musicroad.search.navigation.navigateSearch -import com.squirtles.musicroad.userinfo.navigation.navigateEditNotificationSetting -import com.squirtles.musicroad.userinfo.navigation.navigateEditProfile -import com.squirtles.musicroad.userinfo.navigation.navigateUserInfo import com.squirtles.navigation.Route +import com.squirtles.userinfo.navigation.navigateEditNotificationSetting +import com.squirtles.userinfo.navigation.navigateEditProfile +import com.squirtles.userinfo.navigation.navigateUserInfo internal class MainNavigator( val navController: NavHostController diff --git a/app/src/main/java/com/squirtles/musicroad/userinfo/UserInfoViewModel.kt b/app/src/main/java/com/squirtles/musicroad/userinfo/UserInfoViewModel.kt deleted file mode 100644 index f18152f8..00000000 --- a/app/src/main/java/com/squirtles/musicroad/userinfo/UserInfoViewModel.kt +++ /dev/null @@ -1,54 +0,0 @@ -package com.squirtles.musicroad.userinfo - -import androidx.lifecycle.ViewModel -import androidx.lifecycle.viewModelScope -import com.squirtles.model.User -import com.squirtles.user.usecase.FetchUserByIdUseCase -import com.squirtles.user.usecase.GetCurrentUidUseCase -import com.squirtles.user.usecase.UpdateUserNameUseCase -import dagger.hilt.android.lifecycle.HiltViewModel -import kotlinx.coroutines.flow.MutableSharedFlow -import kotlinx.coroutines.flow.MutableStateFlow -import kotlinx.coroutines.flow.SharedFlow -import kotlinx.coroutines.flow.asSharedFlow -import kotlinx.coroutines.flow.asStateFlow -import kotlinx.coroutines.launch -import javax.inject.Inject - -@HiltViewModel -class UserInfoViewModel @Inject constructor( - private val getCurrentUidUseCase: GetCurrentUidUseCase, - private val fetchUserByIdUseCase: FetchUserByIdUseCase, - private val updateUserNameUseCase: UpdateUserNameUseCase, -) : ViewModel() { - - private val _profileUser = MutableStateFlow(DEFAULT_USER) - val profileUser = _profileUser.asStateFlow() - - val currentUid get() = getCurrentUidUseCase() - - private val _updateSuccess = MutableSharedFlow() - val updateSuccess = _updateSuccess.asSharedFlow() - - fun getUserById(uid: String) { - viewModelScope.launch { - val user = fetchUserByIdUseCase(uid).getOrDefault(DEFAULT_USER) - _profileUser.emit(user) - } - } - - fun updateUsername(newUserName: String) { - viewModelScope.launch { - currentUid?.let { uid -> - val result = runCatching { - updateUserNameUseCase(uid, newUserName).getOrThrow() - fetchUserByIdUseCase(uid).getOrThrow() - } - _updateSuccess.emit(result.isSuccess) - } - } - } -} - -val DEFAULT_USER = User("", "", "", null, listOf()) - diff --git a/app/src/main/java/com/squirtles/musicroad/userinfo/components/MenuItem.kt b/app/src/main/java/com/squirtles/musicroad/userinfo/components/MenuItem.kt deleted file mode 100644 index eb447dce..00000000 --- a/app/src/main/java/com/squirtles/musicroad/userinfo/components/MenuItem.kt +++ /dev/null @@ -1,13 +0,0 @@ -package com.squirtles.musicroad.userinfo.components - -import androidx.compose.ui.graphics.Color -import androidx.compose.ui.graphics.vector.ImageVector -import com.squirtles.common.ui.theme.White - -data class MenuItem( - val imageVector: ImageVector, - val contentDescription: String, - val iconColor: Color = White, - val menuTitle: String, - val onMenuClick: () -> Unit -) diff --git a/app/src/main/java/com/squirtles/musicroad/userinfo/components/UserInfoMenus.kt b/app/src/main/java/com/squirtles/musicroad/userinfo/components/UserInfoMenus.kt deleted file mode 100644 index 54f6c7b3..00000000 --- a/app/src/main/java/com/squirtles/musicroad/userinfo/components/UserInfoMenus.kt +++ /dev/null @@ -1,104 +0,0 @@ -package com.squirtles.musicroad.userinfo.components - -import androidx.compose.foundation.clickable -import androidx.compose.foundation.interaction.MutableInteractionSource -import androidx.compose.foundation.layout.Arrangement -import androidx.compose.foundation.layout.Column -import androidx.compose.foundation.layout.Row -import androidx.compose.foundation.layout.fillMaxWidth -import androidx.compose.foundation.layout.padding -import androidx.compose.foundation.layout.size -import androidx.compose.material.icons.Icons -import androidx.compose.material.icons.automirrored.filled.ArrowForwardIos -import androidx.compose.material3.HorizontalDivider -import androidx.compose.material3.Icon -import androidx.compose.material3.MaterialTheme -import androidx.compose.material3.Text -import androidx.compose.material3.ripple -import androidx.compose.runtime.Composable -import androidx.compose.runtime.remember -import androidx.compose.ui.Alignment -import androidx.compose.ui.Modifier -import androidx.compose.ui.graphics.Color -import androidx.compose.ui.res.stringResource -import androidx.compose.ui.text.TextStyle -import androidx.compose.ui.text.font.FontWeight -import androidx.compose.ui.unit.dp -import com.squirtles.common.ui.Constants.DEFAULT_PADDING -import com.squirtles.common.ui.VerticalSpacer -import com.squirtles.common.ui.theme.Gray -import com.squirtles.common.ui.theme.White -import com.squirtles.musicroad.R - -@Composable -internal fun UserInfoMenus( - title: String, - titleTextColor: Color = White, - titleTextStyle: TextStyle = MaterialTheme.typography.titleMedium, - menus: List, - menuTextColor: Color = White, - menuTextStyle: TextStyle = MaterialTheme.typography.bodyLarge -) { - Column { - Text( - text = title, - modifier = Modifier - .fillMaxWidth() - .padding( - horizontal = MENU_PADDING_HORIZONTAL, - vertical = MENU_PADDING_VERTICAL - ), - color = titleTextColor, - fontWeight = FontWeight.Bold, - style = titleTextStyle - ) - - HorizontalDivider( - modifier = Modifier - .fillMaxWidth() - .padding(horizontal = MENU_PADDING_HORIZONTAL), - color = Gray - ) - - VerticalSpacer(8) - - for (menu in menus) { - Row( - modifier = Modifier - .fillMaxWidth() - .padding(horizontal = MENU_PADDING_HORIZONTAL) - .clickable( - interactionSource = remember { MutableInteractionSource() }, - indication = ripple(color = White), - onClick = menu.onMenuClick - ) - .padding(vertical = MENU_PADDING_VERTICAL), - horizontalArrangement = Arrangement.spacedBy(DEFAULT_PADDING), - verticalAlignment = Alignment.CenterVertically - ) { - Icon( - imageVector = menu.imageVector, - contentDescription = menu.contentDescription, - tint = menu.iconColor - ) - Text( - text = menu.menuTitle, - modifier = Modifier.weight(1f), - color = menuTextColor, - style = menuTextStyle - ) - Icon( - imageVector = Icons.AutoMirrored.Filled.ArrowForwardIos, - contentDescription = stringResource(R.string.user_info_navigate_to_menu), - modifier = Modifier.size(16.dp), - tint = Gray - ) - } - } - - VerticalSpacer(20) - } -} - -private val MENU_PADDING_HORIZONTAL = 24.dp -private val MENU_PADDING_VERTICAL = 8.dp diff --git a/app/src/main/java/com/squirtles/musicroad/userinfo/navigation/UserInfoNavigation.kt b/app/src/main/java/com/squirtles/musicroad/userinfo/navigation/UserInfoNavigation.kt deleted file mode 100644 index 02b9d871..00000000 --- a/app/src/main/java/com/squirtles/musicroad/userinfo/navigation/UserInfoNavigation.kt +++ /dev/null @@ -1,62 +0,0 @@ -package com.squirtles.musicroad.userinfo.navigation - -import androidx.navigation.NavController -import androidx.navigation.NavGraphBuilder -import androidx.navigation.NavOptions -import androidx.navigation.compose.composable -import androidx.navigation.toRoute -import com.squirtles.musicroad.userinfo.screen.EditNotificationSettingScreen -import com.squirtles.musicroad.userinfo.screen.EditProfileScreen -import com.squirtles.musicroad.userinfo.screen.UserInfoScreen -import com.squirtles.navigation.MainRoute -import com.squirtles.navigation.UserInfoRoute - -fun NavController.navigateUserInfo(uid: String, navOptions: NavOptions? = null) { - navigate(MainRoute.UserInfo(uid), navOptions) -} - -fun NavController.navigateEditProfile(userName: String, navOptions: NavOptions? = null) { - navigate(UserInfoRoute.EditProfile(userName), navOptions) -} - -fun NavController.navigateEditNotificationSetting(navOptions: NavOptions? = null) { - navigate(UserInfoRoute.EditNotification, navOptions) -} - -fun NavGraphBuilder.userInfoNavGraph( - onBackClick: () -> Unit, - onBackToMapClick: () -> Unit, - onFavoritePicksClick: (String) -> Unit, - onMyPicksClick: (String) -> Unit, - onEditProfileClick: (String) -> Unit, - onEditNotificationClick: () -> Unit, -) { - composable { backStackEntry -> - val uid = backStackEntry.toRoute().uid - - UserInfoScreen( - uid = uid, - onBackClick = onBackClick, - onBackToMapClick = onBackToMapClick, - onFavoritePicksClick = onFavoritePicksClick, - onMyPicksClick = onMyPicksClick, - onEditProfileClick = onEditProfileClick, - onEditNotificationClick = onEditNotificationClick, - ) - } - - composable { backStackEntry -> - val userName = backStackEntry.toRoute().userName - EditProfileScreen( - currentUserName = userName, - onBackToMapClick = onBackToMapClick, - onBackClick = onBackClick, - ) - } - - composable { - EditNotificationSettingScreen( - onBackClick = onBackClick - ) - } -} diff --git a/app/src/main/java/com/squirtles/musicroad/userinfo/screen/EditNotificationSettingScreen.kt b/app/src/main/java/com/squirtles/musicroad/userinfo/screen/EditNotificationSettingScreen.kt deleted file mode 100644 index 6ce013da..00000000 --- a/app/src/main/java/com/squirtles/musicroad/userinfo/screen/EditNotificationSettingScreen.kt +++ /dev/null @@ -1,49 +0,0 @@ -package com.squirtles.musicroad.userinfo.screen - -import androidx.compose.foundation.background -import androidx.compose.foundation.layout.Box -import androidx.compose.foundation.layout.fillMaxSize -import androidx.compose.foundation.layout.padding -import androidx.compose.material3.MaterialTheme -import androidx.compose.material3.Scaffold -import androidx.compose.runtime.Composable -import androidx.compose.ui.Alignment -import androidx.compose.ui.Modifier -import androidx.compose.ui.graphics.Brush -import androidx.compose.ui.res.stringResource -import androidx.wear.compose.material.Text -import com.squirtles.common.ui.Constants.COLOR_STOPS -import com.squirtles.common.ui.Constants.DEFAULT_PADDING -import com.squirtles.common.ui.DefaultTopAppBar -import com.squirtles.common.ui.theme.White -import com.squirtles.musicroad.R - -@Composable -internal fun EditNotificationSettingScreen( - onBackClick: () -> Unit -) { - Scaffold( - topBar = { - DefaultTopAppBar( - title = stringResource(id = R.string.setting_notification_title), - onBackClick = onBackClick - ) - } - ) { innerPadding -> - Box( - modifier = Modifier - .fillMaxSize() - .background(Brush.verticalGradient(colorStops = COLOR_STOPS)) - .padding(innerPadding) - ) { - Text( - text = stringResource(id = R.string.setting_notification_description), - modifier = Modifier - .padding(horizontal = DEFAULT_PADDING) - .align(Alignment.Center), - color = White, - style = MaterialTheme.typography.bodyLarge - ) - } - } -} diff --git a/app/src/main/java/com/squirtles/musicroad/userinfo/screen/EditProfileScreen.kt b/app/src/main/java/com/squirtles/musicroad/userinfo/screen/EditProfileScreen.kt deleted file mode 100644 index 28ed562b..00000000 --- a/app/src/main/java/com/squirtles/musicroad/userinfo/screen/EditProfileScreen.kt +++ /dev/null @@ -1,328 +0,0 @@ -package com.squirtles.musicroad.userinfo.screen - -import android.content.Context -import android.widget.Toast -import androidx.activity.compose.BackHandler -import androidx.compose.foundation.background -import androidx.compose.foundation.clickable -import androidx.compose.foundation.interaction.MutableInteractionSource -import androidx.compose.foundation.layout.Arrangement -import androidx.compose.foundation.layout.Box -import androidx.compose.foundation.layout.Column -import androidx.compose.foundation.layout.fillMaxSize -import androidx.compose.foundation.layout.fillMaxWidth -import androidx.compose.foundation.layout.padding -import androidx.compose.foundation.layout.wrapContentHeight -import androidx.compose.material.icons.Icons -import androidx.compose.material.icons.automirrored.filled.ArrowBack -import androidx.compose.material.icons.filled.Check -import androidx.compose.material3.CenterAlignedTopAppBar -import androidx.compose.material3.CircularProgressIndicator -import androidx.compose.material3.ExperimentalMaterial3Api -import androidx.compose.material3.Icon -import androidx.compose.material3.IconButton -import androidx.compose.material3.MaterialTheme -import androidx.compose.material3.OutlinedTextField -import androidx.compose.material3.OutlinedTextFieldDefaults -import androidx.compose.material3.Scaffold -import androidx.compose.material3.Text -import androidx.compose.material3.TopAppBarDefaults -import androidx.compose.runtime.Composable -import androidx.compose.runtime.LaunchedEffect -import androidx.compose.runtime.MutableState -import androidx.compose.runtime.getValue -import androidx.compose.runtime.mutableStateOf -import androidx.compose.runtime.remember -import androidx.compose.runtime.saveable.rememberSaveable -import androidx.compose.runtime.setValue -import androidx.compose.ui.Alignment -import androidx.compose.ui.Modifier -import androidx.compose.ui.graphics.Brush -import androidx.compose.ui.graphics.Color -import androidx.compose.ui.platform.LocalContext -import androidx.compose.ui.platform.LocalFocusManager -import androidx.compose.ui.res.stringResource -import androidx.compose.ui.text.font.FontWeight -import androidx.compose.ui.text.style.TextAlign -import androidx.compose.ui.tooling.preview.Preview -import androidx.compose.ui.unit.dp -import androidx.compose.ui.unit.sp -import androidx.hilt.navigation.compose.hiltViewModel -import androidx.lifecycle.Lifecycle -import androidx.lifecycle.compose.LocalLifecycleOwner -import androidx.lifecycle.flowWithLifecycle -import com.squirtles.account.AccountViewModel -import com.squirtles.account.GoogleId -import com.squirtles.common.ui.Constants.COLOR_STOPS -import com.squirtles.common.ui.DialogTextButton -import com.squirtles.common.ui.HorizontalSpacer -import com.squirtles.common.ui.MessageAlertDialog -import com.squirtles.common.ui.theme.Black -import com.squirtles.common.ui.theme.DarkGray -import com.squirtles.common.ui.theme.Gray -import com.squirtles.common.ui.theme.MusicRoadTheme -import com.squirtles.common.ui.theme.Primary -import com.squirtles.common.ui.theme.White -import com.squirtles.musicroad.R -import com.squirtles.musicroad.userinfo.UserInfoViewModel -import kotlinx.coroutines.delay -import kotlinx.coroutines.launch -import java.util.regex.Pattern - -@Composable -internal fun EditProfileScreen( - currentUserName: String, - onBackToMapClick: () -> Unit, - onBackClick: () -> Unit, - userInfoViewModel: UserInfoViewModel = hiltViewModel(), - accountViewModel: AccountViewModel = hiltViewModel() -) { - val context = LocalContext.current - val lifecycleOwner = LocalLifecycleOwner.current - val focusManager = LocalFocusManager.current - val userName = remember { mutableStateOf(currentUserName) } - val nickNameErrorMessage = remember { mutableStateOf("") } - var showLoadingIndicator by rememberSaveable { mutableStateOf(false) } - var showDeleteAccountDialog by remember { mutableStateOf(false) } - - val onDeleteAccountClick: () -> Unit = { - GoogleId(context).signOut() - accountViewModel.deleteAccount() - } - - BackHandler(enabled = showLoadingIndicator) { } - - LaunchedEffect(Unit) { - launch { - userInfoViewModel.updateSuccess - .flowWithLifecycle(lifecycleOwner.lifecycle, Lifecycle.State.STARTED) - .collect { isSuccess -> - focusManager.clearFocus() - delay(100) - if (isSuccess) { - Toast.makeText( - context, - context.getString(R.string.setting_profile_update_nickname_success), - Toast.LENGTH_SHORT - ).show() - onBackClick() - } else { - showLoadingIndicator = false - Toast.makeText( - context, - context.getString(R.string.setting_profile_update_nickname_failure), - Toast.LENGTH_SHORT - ).show() - } - } - } - - launch { - accountViewModel.deleteAccountSuccess - .flowWithLifecycle(lifecycleOwner.lifecycle, Lifecycle.State.STARTED) - .collect { isSuccess -> - if (isSuccess) { - showLoadingIndicator = false - onBackToMapClick() - } - } - } - } - - Scaffold( - topBar = { - EditProfileAppBar( - confirmEnabled = nickNameErrorMessage.value.isEmpty() && - currentUserName != userName.value, - onConfirmClick = { - showLoadingIndicator = true - userInfoViewModel.updateUsername(userName.value) - }, - onBackClick = onBackClick - ) - } - ) { innerPadding -> - Box( - modifier = Modifier - .fillMaxSize() - .background(Brush.verticalGradient(colorStops = COLOR_STOPS)) - .padding(innerPadding) - ) { - // 프로필 수정 - EditProfileContents(userName, nickNameErrorMessage) - - // 회원 탈퇴 - Text( - text = stringResource(id = R.string.user_info_setting_delete_user_account), - modifier = Modifier - .padding(vertical = 20.dp) - .clickable { showDeleteAccountDialog = true } - .align(Alignment.BottomCenter), - color = DarkGray, - textAlign = TextAlign.Center, - style = MaterialTheme.typography.bodyMedium - ) - } - } - - if (showLoadingIndicator) { - Box( - modifier = Modifier - .fillMaxSize() - .background(Black.copy(alpha = 0.5F)) - .clickable( - interactionSource = remember { MutableInteractionSource() }, - indication = null, - onClick = {} - ), - contentAlignment = Alignment.Center - ) { - CircularProgressIndicator() - } - } - - if (showDeleteAccountDialog) { - MessageAlertDialog( - onDismissRequest = { - showDeleteAccountDialog = false - }, - title = stringResource(R.string.delete_account_dialog_title), - body = stringResource(R.string.delete_account_dialog_description), - showBody = true - ) { - DialogTextButton( - onClick = { - showDeleteAccountDialog = false - }, - text = stringResource(R.string.delete_account_dialog_dismiss) - ) - - HorizontalSpacer(8) - - DialogTextButton( - onClick = { - showLoadingIndicator = true - showDeleteAccountDialog = false - onDeleteAccountClick() - }, - text = stringResource(R.string.delete_account_dialog_confirm), - textColor = Primary, - fontWeight = FontWeight.Bold - ) - } - } -} - -@OptIn(ExperimentalMaterial3Api::class) -@Composable -private fun EditProfileAppBar( - confirmEnabled: Boolean, - onConfirmClick: () -> Unit, - onBackClick: () -> Unit -) { - CenterAlignedTopAppBar( - title = { - Text( - text = stringResource(id = R.string.setting_profile_title), - style = MaterialTheme.typography.titleLarge.copy(fontWeight = FontWeight.Bold) - ) - }, - navigationIcon = { - IconButton( - onClick = onBackClick - ) { - Icon( - imageVector = Icons.AutoMirrored.Filled.ArrowBack, - contentDescription = stringResource(R.string.top_app_bar_back_description), - tint = White - ) - } - }, - actions = { - IconButton( - onClick = onConfirmClick, - enabled = confirmEnabled - ) { - Icon( - imageVector = Icons.Default.Check, - contentDescription = stringResource(R.string.setting_profile_confirm_icon_description), - tint = if (confirmEnabled) White else Gray - ) - } - }, - colors = TopAppBarDefaults.centerAlignedTopAppBarColors().copy( - containerColor = Color.Transparent, - titleContentColor = White - ) - ) -} - -private fun validateUserName(userName: String, context: Context) = when { - userName.length < 2 -> context.getString(R.string.setting_profile_nickname_message_length_fail_min) - userName.length > 10 -> context.getString(R.string.setting_profile_nickname_message_length_fail_max) - Pattern.matches(USERNAME_PATTERN, userName).not() -> context.getString(R.string.setting_profile_nickname_message_format_fail) - else -> "" -} - -@Composable -private fun EditProfileContents( - userName: MutableState, - nickNameErrorMessage: MutableState -) { - val context = LocalContext.current - Column( - modifier = Modifier - .wrapContentHeight() - .padding(vertical = 30.dp, horizontal = 30.dp), - verticalArrangement = Arrangement.spacedBy(12.dp) - ) { - Text( - text = stringResource(id = R.string.setting_profile_nickname), - fontSize = 20.sp, - fontWeight = FontWeight.Bold, - color = White, - style = MaterialTheme.typography.titleLarge - ) - - OutlinedTextField( - value = userName.value, - onValueChange = { - nickNameErrorMessage.value = validateUserName(it, context) - userName.value = it - }, - modifier = Modifier.fillMaxWidth(), - singleLine = true, - supportingText = { Text(nickNameErrorMessage.value) }, - isError = nickNameErrorMessage.value.isNotEmpty(), - colors = OutlinedTextFieldDefaults.colors( - focusedTextColor = White, - unfocusedTextColor = White, - errorTextColor = White, - focusedContainerColor = Color.Transparent, - unfocusedContainerColor = Color.Transparent, - cursorColor = White, - focusedBorderColor = Gray, - unfocusedBorderColor = Gray, - ) - ) - } -} - -private const val USERNAME_PATTERN = "^[ㄱ-ㅎ|ㅏ-ㅣ가-힣a-zA-Z0-9]+$" - -@Preview -@Composable -private fun EditProfileAppBarPreview() { - EditProfileAppBar(false, {}, {}) -} - -@Preview -@Composable -private fun EditProfileContentPreview() { - MusicRoadTheme { - EditProfileContents( - remember { mutableStateOf("짱구") }, - remember { mutableStateOf("") } - ) - } -} diff --git a/app/src/main/java/com/squirtles/musicroad/userinfo/screen/UserInfoScreen.kt b/app/src/main/java/com/squirtles/musicroad/userinfo/screen/UserInfoScreen.kt deleted file mode 100644 index 78b806fe..00000000 --- a/app/src/main/java/com/squirtles/musicroad/userinfo/screen/UserInfoScreen.kt +++ /dev/null @@ -1,273 +0,0 @@ -package com.squirtles.musicroad.userinfo.screen - -import androidx.activity.compose.BackHandler -import androidx.compose.foundation.background -import androidx.compose.foundation.clickable -import androidx.compose.foundation.interaction.MutableInteractionSource -import androidx.compose.foundation.layout.Box -import androidx.compose.foundation.layout.Column -import androidx.compose.foundation.layout.fillMaxSize -import androidx.compose.foundation.layout.padding -import androidx.compose.foundation.layout.size -import androidx.compose.foundation.layout.wrapContentWidth -import androidx.compose.foundation.rememberScrollState -import androidx.compose.foundation.shape.CircleShape -import androidx.compose.foundation.verticalScroll -import androidx.compose.material.icons.Icons -import androidx.compose.material.icons.automirrored.outlined.Logout -import androidx.compose.material.icons.filled.MusicNote -import androidx.compose.material.icons.outlined.Archive -import androidx.compose.material.icons.outlined.Map -import androidx.compose.material.icons.outlined.Notifications -import androidx.compose.material.icons.outlined.SwitchAccount -import androidx.compose.material3.CircularProgressIndicator -import androidx.compose.material3.ExtendedFloatingActionButton -import androidx.compose.material3.Icon -import androidx.compose.material3.MaterialTheme -import androidx.compose.material3.Scaffold -import androidx.compose.material3.Text -import androidx.compose.runtime.Composable -import androidx.compose.runtime.LaunchedEffect -import androidx.compose.runtime.getValue -import androidx.compose.runtime.mutableStateOf -import androidx.compose.runtime.remember -import androidx.compose.runtime.saveable.rememberSaveable -import androidx.compose.runtime.setValue -import androidx.compose.ui.Alignment -import androidx.compose.ui.Modifier -import androidx.compose.ui.draw.clip -import androidx.compose.ui.graphics.Brush -import androidx.compose.ui.layout.ContentScale -import androidx.compose.ui.platform.LocalContext -import androidx.compose.ui.res.painterResource -import androidx.compose.ui.res.stringResource -import androidx.compose.ui.text.font.FontWeight -import androidx.compose.ui.unit.dp -import androidx.hilt.navigation.compose.hiltViewModel -import androidx.lifecycle.Lifecycle -import androidx.lifecycle.compose.LocalLifecycleOwner -import androidx.lifecycle.compose.collectAsStateWithLifecycle -import androidx.lifecycle.flowWithLifecycle -import coil3.compose.AsyncImage -import coil3.request.ImageRequest -import coil3.request.crossfade -import com.squirtles.account.AccountViewModel -import com.squirtles.account.GoogleId -import com.squirtles.common.ui.Constants.COLOR_STOPS -import com.squirtles.common.ui.DefaultTopAppBar -import com.squirtles.common.ui.DialogTextButton -import com.squirtles.common.ui.HorizontalSpacer -import com.squirtles.common.ui.MessageAlertDialog -import com.squirtles.common.ui.VerticalSpacer -import com.squirtles.common.ui.theme.Black -import com.squirtles.common.ui.theme.Primary -import com.squirtles.common.ui.theme.White -import com.squirtles.musicroad.R -import com.squirtles.musicroad.userinfo.UserInfoViewModel -import com.squirtles.musicroad.userinfo.components.MenuItem -import com.squirtles.musicroad.userinfo.components.UserInfoMenus -import kotlinx.coroutines.launch - -@Composable -fun UserInfoScreen( - uid: String, - onBackClick: () -> Unit, - onBackToMapClick: () -> Unit, - onFavoritePicksClick: (String) -> Unit, - onMyPicksClick: (String) -> Unit, - onEditProfileClick: (String) -> Unit, - onEditNotificationClick: () -> Unit, - userInfoViewModel: UserInfoViewModel = hiltViewModel(), - accountViewModel: AccountViewModel = hiltViewModel() -) { - val context = LocalContext.current - val lifecycleOwner = LocalLifecycleOwner.current - val scrollState = rememberScrollState() - val user by userInfoViewModel.profileUser.collectAsStateWithLifecycle() - - var showLogOutDialog by remember { mutableStateOf(false) } - var showLoadingIndicator by rememberSaveable { mutableStateOf(false) } - - val onSignOutClick: () -> Unit = { - GoogleId(context).signOut() - accountViewModel.signOut() - } - - BackHandler(enabled = showLoadingIndicator) { } - - LaunchedEffect(Unit) { - uid.let { - userInfoViewModel.getUserById(uid) - } - - launch { - accountViewModel.signOutSuccess - .flowWithLifecycle(lifecycleOwner.lifecycle, Lifecycle.State.STARTED) - .collect { isSuccess -> - if (isSuccess) { - showLoadingIndicator = false - onBackToMapClick() - } - } - } - } - - Scaffold( - topBar = { - DefaultTopAppBar( - title = user.userName, - onBackClick = onBackClick - ) - } - ) { innerPadding -> - Box( - modifier = Modifier - .fillMaxSize() - .background(Brush.verticalGradient(colorStops = COLOR_STOPS)) - .padding(innerPadding), - ) { - Column( - modifier = Modifier - .fillMaxSize() - .verticalScroll(scrollState) - .padding(bottom = 96.dp), - horizontalAlignment = Alignment.CenterHorizontally - ) { - VerticalSpacer(16) - - AsyncImage( - model = ImageRequest.Builder(LocalContext.current) - .data(user.userProfileImage) - .crossfade(true) - .build(), - contentDescription = stringResource(R.string.user_info_profile_image), - modifier = Modifier - .size(180.dp) - .clip(CircleShape), - placeholder = painterResource(R.drawable.img_user_default_profile), - error = painterResource(R.drawable.img_user_default_profile), - contentScale = ContentScale.Crop, - ) - - VerticalSpacer(40) - - UserInfoMenus( - title = stringResource(R.string.user_info_pick_category_title), - menus = listOf( - MenuItem( - imageVector = Icons.Outlined.Archive, - contentDescription = stringResource(R.string.user_info_favorite_menu_icon_description), - menuTitle = stringResource(R.string.user_info_favorite_menu_title), - onMenuClick = { onFavoritePicksClick(uid) } - ), - MenuItem( - imageVector = Icons.Default.MusicNote, - contentDescription = stringResource(R.string.user_info_created_by_self_menu_icon_description), - menuTitle = stringResource(R.string.user_info_created_by_self_menu_title), - onMenuClick = { onMyPicksClick(uid) } - ) - ) - ) - - if (uid == userInfoViewModel.currentUid) { - UserInfoMenus( - title = stringResource(R.string.user_info_setting_category_title), - menus = listOf( - MenuItem( - imageVector = Icons.Outlined.SwitchAccount, - contentDescription = stringResource(R.string.user_info_setting_profile_menu_icon_description), - menuTitle = stringResource(R.string.user_info_setting_profile_menu_title), - onMenuClick = { onEditProfileClick(user.userName) } - ), - MenuItem( - imageVector = Icons.Outlined.Notifications, - contentDescription = stringResource(R.string.user_info_setting_notification_menu_icon_description), - menuTitle = stringResource(R.string.user_info_setting_notification_menu_title), - onMenuClick = onEditNotificationClick - ), - MenuItem( - imageVector = Icons.AutoMirrored.Outlined.Logout, - contentDescription = stringResource(R.string.user_info_setting_sign_out_menu_icon_description), - menuTitle = stringResource(R.string.user_info_setting_sign_out_menu_title), - onMenuClick = { showLogOutDialog = true } - ) - ) - ) - } - } - - ExtendedFloatingActionButton( - onClick = onBackToMapClick, - modifier = Modifier - .wrapContentWidth() - .padding(horizontal = 8.dp) - .padding(bottom = 48.dp) - .align(Alignment.BottomCenter), - shape = CircleShape, - containerColor = Primary, - contentColor = White, - ) { - Icon( - imageVector = Icons.Outlined.Map, - contentDescription = stringResource(R.string.user_info_icon_map_description), - tint = White - ) - - HorizontalSpacer(8) - - Text( - text = stringResource(R.string.user_info_back_to_map_button_text), - color = White, - style = MaterialTheme.typography.bodyLarge - ) - } - - if (showLogOutDialog) { - MessageAlertDialog( - onDismissRequest = { - showLogOutDialog = false - }, - title = stringResource(R.string.sign_out_dialog_title), - body = "", - showBody = false - ) { - DialogTextButton( - onClick = { - showLogOutDialog = false - }, - text = stringResource(R.string.sign_out_dialog_dismiss) - ) - - HorizontalSpacer(8) - - DialogTextButton( - onClick = { - showLogOutDialog = false - showLoadingIndicator = true - onSignOutClick() - }, - text = stringResource(R.string.sign_out_dialog_confirm), - textColor = Primary, - fontWeight = FontWeight.Bold - ) - } - } - - if (showLoadingIndicator) { - Box( - modifier = Modifier - .fillMaxSize() - .background(Black.copy(alpha = 0.5F)) - .clickable( - interactionSource = remember { MutableInteractionSource() }, - indication = null, - onClick = {} - ), - contentAlignment = Alignment.Center - ) { - CircularProgressIndicator() - } - } - } - } -} From ea3891f51f598d28fe32d334611b16e01b9af32c Mon Sep 17 00:00:00 2001 From: miller198 Date: Tue, 22 Apr 2025 20:59:26 +0900 Subject: [PATCH 50/62] =?UTF-8?q?[refactor]=20feature:search=20=EC=A0=81?= =?UTF-8?q?=EC=9A=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/build.gradle.kts | 5 +- .../musicroad/main/navigation/MainNavHost.kt | 2 +- .../main/navigation/MainNavigator.kt | 2 +- .../musicroad/search/SearchMusicScreen.kt | 341 ------------------ .../musicroad/search/SearchViewModel.kt | 64 ---- .../search/navigation/SearchNavigation.kt | 25 -- 6 files changed, 3 insertions(+), 436 deletions(-) delete mode 100644 app/src/main/java/com/squirtles/musicroad/search/SearchMusicScreen.kt delete mode 100644 app/src/main/java/com/squirtles/musicroad/search/SearchViewModel.kt delete mode 100644 app/src/main/java/com/squirtles/musicroad/search/navigation/SearchNavigation.kt diff --git a/app/build.gradle.kts b/app/build.gradle.kts index 141aab7f..4453c1a3 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -125,6 +125,7 @@ dependencies { implementation(projects.data.location) implementation(projects.data.order) implementation(projects.feature.userinfo) + implementation(projects.feature.search) implementation(libs.androidx.core.ktx) implementation(libs.androidx.lifecycle.runtime.ktx) @@ -176,10 +177,6 @@ dependencies { // ExoPlayer implementation(libs.bundles.exoplayer) - // Paging - implementation(libs.androidx.paging.runtime) - implementation(libs.androidx.paging.compose) - // Serialization implementation(libs.kotlinx.serialization.json) diff --git a/app/src/main/java/com/squirtles/musicroad/main/navigation/MainNavHost.kt b/app/src/main/java/com/squirtles/musicroad/main/navigation/MainNavHost.kt index 0f21f9fa..ce1d7110 100644 --- a/app/src/main/java/com/squirtles/musicroad/main/navigation/MainNavHost.kt +++ b/app/src/main/java/com/squirtles/musicroad/main/navigation/MainNavHost.kt @@ -11,7 +11,7 @@ import com.squirtles.musicroad.favorite.navigation.favoriteNavGraph import com.squirtles.musicroad.map.MapViewModel import com.squirtles.musicroad.map.navigation.mapNavGraph import com.squirtles.musicroad.mypick.navigation.myPickNavGraph -import com.squirtles.musicroad.search.navigation.searchNavGraph +import com.squirtles.search.navigation.searchNavGraph import com.squirtles.userinfo.navigation.userInfoNavGraph @Composable diff --git a/app/src/main/java/com/squirtles/musicroad/main/navigation/MainNavigator.kt b/app/src/main/java/com/squirtles/musicroad/main/navigation/MainNavigator.kt index 1f958973..6a5b4ad5 100644 --- a/app/src/main/java/com/squirtles/musicroad/main/navigation/MainNavigator.kt +++ b/app/src/main/java/com/squirtles/musicroad/main/navigation/MainNavigator.kt @@ -14,8 +14,8 @@ import com.squirtles.model.Song import com.squirtles.musicroad.favorite.navigation.navigateFavorite import com.squirtles.musicroad.map.navigation.navigateMap import com.squirtles.musicroad.mypick.navigation.navigateMyPicks -import com.squirtles.musicroad.search.navigation.navigateSearch import com.squirtles.navigation.Route +import com.squirtles.search.navigation.navigateSearch import com.squirtles.userinfo.navigation.navigateEditNotificationSetting import com.squirtles.userinfo.navigation.navigateEditProfile import com.squirtles.userinfo.navigation.navigateUserInfo diff --git a/app/src/main/java/com/squirtles/musicroad/search/SearchMusicScreen.kt b/app/src/main/java/com/squirtles/musicroad/search/SearchMusicScreen.kt deleted file mode 100644 index 00efc935..00000000 --- a/app/src/main/java/com/squirtles/musicroad/search/SearchMusicScreen.kt +++ /dev/null @@ -1,341 +0,0 @@ -package com.squirtles.musicroad.search - -import androidx.compose.foundation.background -import androidx.compose.foundation.clickable -import androidx.compose.foundation.gestures.detectTapGestures -import androidx.compose.foundation.interaction.MutableInteractionSource -import androidx.compose.foundation.layout.Box -import androidx.compose.foundation.layout.Column -import androidx.compose.foundation.layout.Row -import androidx.compose.foundation.layout.WindowInsets -import androidx.compose.foundation.layout.asPaddingValues -import androidx.compose.foundation.layout.fillMaxSize -import androidx.compose.foundation.layout.fillMaxWidth -import androidx.compose.foundation.layout.height -import androidx.compose.foundation.layout.navigationBars -import androidx.compose.foundation.layout.padding -import androidx.compose.foundation.layout.size -import androidx.compose.foundation.layout.statusBars -import androidx.compose.foundation.lazy.LazyColumn -import androidx.compose.foundation.shape.CircleShape -import androidx.compose.foundation.shape.RoundedCornerShape -import androidx.compose.foundation.text.KeyboardActions -import androidx.compose.foundation.text.KeyboardOptions -import androidx.compose.material.icons.Icons -import androidx.compose.material.icons.automirrored.filled.ArrowBack -import androidx.compose.material3.Icon -import androidx.compose.material3.IconButton -import androidx.compose.material3.LinearProgressIndicator -import androidx.compose.material3.MaterialTheme -import androidx.compose.material3.OutlinedTextField -import androidx.compose.material3.Scaffold -import androidx.compose.material3.Text -import androidx.compose.material3.TextFieldDefaults -import androidx.compose.material3.ripple -import androidx.compose.runtime.Composable -import androidx.compose.runtime.getValue -import androidx.compose.runtime.remember -import androidx.compose.ui.Alignment -import androidx.compose.ui.Modifier -import androidx.compose.ui.draw.clip -import androidx.compose.ui.focus.FocusManager -import androidx.compose.ui.graphics.Brush -import androidx.compose.ui.graphics.Color -import androidx.compose.ui.input.pointer.pointerInput -import androidx.compose.ui.platform.LocalFocusManager -import androidx.compose.ui.res.stringResource -import androidx.compose.ui.text.TextStyle -import androidx.compose.ui.text.input.ImeAction -import androidx.compose.ui.text.input.KeyboardType -import androidx.compose.ui.text.style.TextOverflow -import androidx.compose.ui.tooling.preview.Preview -import androidx.compose.ui.unit.dp -import androidx.hilt.navigation.compose.hiltViewModel -import androidx.lifecycle.compose.collectAsStateWithLifecycle -import androidx.paging.LoadState -import androidx.paging.compose.LazyPagingItems -import androidx.paging.compose.collectAsLazyPagingItems -import com.squirtles.common.ui.AlbumImage -import com.squirtles.common.ui.Constants.COLOR_STOPS -import com.squirtles.common.ui.Constants.REQUEST_IMAGE_SIZE_DEFAULT -import com.squirtles.common.ui.HorizontalSpacer -import com.squirtles.common.ui.VerticalSpacer -import com.squirtles.common.ui.theme.Gray -import com.squirtles.common.ui.theme.White -import com.squirtles.model.Song -import com.squirtles.musicroad.R -import com.squirtles.musicroad.create.SearchUiState - -@Composable -fun SearchMusicScreen( - onBackClick: () -> Unit, - onItemClick: (Song) -> Unit, - searchViewModel: SearchViewModel = hiltViewModel(), -) { - val focusManager = LocalFocusManager.current - val searchText by searchViewModel.searchText.collectAsStateWithLifecycle() - val searchUiState by searchViewModel.searchUiState.collectAsStateWithLifecycle() - val searchResult = searchViewModel.searchResult.collectAsLazyPagingItems() - - Scaffold( - contentWindowInsets = WindowInsets.navigationBars, - topBar = { - Box( - modifier = Modifier - .fillMaxWidth() - .padding(WindowInsets.statusBars.asPaddingValues()) - .padding(top = 16.dp) - ) { - SearchTopBar( - keyword = searchText, - onValueChange = searchViewModel::onSearchTextChange, - onBackClick = onBackClick, - focusManager = focusManager - ) - } - }, - ) { innerPadding -> - - Box( - modifier = Modifier - .fillMaxSize() - .background(Brush.verticalGradient(colorStops = COLOR_STOPS)) - .padding(innerPadding) - .addFocusCleaner(focusManager) - ) { - if (searchResult.loadState.refresh is LoadState.Loading || - searchResult.loadState.append is LoadState.Loading - ) { - LinearProgressIndicator( - modifier = Modifier - .fillMaxWidth() - .padding(top = 10.dp), - trackColor = Gray - ) - } - - // 검색 결과 - when (searchUiState) { - is SearchUiState.HotResult -> { - // TODO HOT 리스트 - } - - is SearchUiState.SearchResult -> { - SearchResult( - searchResult = searchResult, - onItemClick = { song -> - focusManager.clearFocus() - onItemClick(song) - } - ) - } - } - } - } -} - -@Composable -private fun SearchTopBar( - keyword: String, - onValueChange: (String) -> Unit, - onBackClick: () -> Unit, - focusManager: FocusManager -) { - - Row( - modifier = Modifier - .fillMaxWidth() - .height(SearchBarHeight) - .padding(end = DefaultPadding), - verticalAlignment = Alignment.CenterVertically - ) { - IconButton( - onClick = { - onBackClick() - }, - modifier = Modifier.padding(start = 4.dp) - ) { - Icon( - imageVector = Icons.AutoMirrored.Filled.ArrowBack, - contentDescription = stringResource(id = R.string.top_app_bar_back_description), - tint = White - ) - } - HorizontalSpacer(8) - - OutlinedTextField( - value = keyword, - onValueChange = onValueChange, - modifier = Modifier.weight(1f), - placeholder = { - Text(stringResource(id = R.string.search)) - }, - // 키보드 완료버튼 -> Search로 변경, 누르면 Search 동작 실행 - keyboardOptions = KeyboardOptions( - keyboardType = KeyboardType.Text, - imeAction = ImeAction.Search - ), - keyboardActions = KeyboardActions( - onSearch = { - focusManager.clearFocus() - } - ), - singleLine = true, - shape = CircleShape, - colors = TextFieldDefaults.colors( - focusedTextColor = White, - unfocusedTextColor = White, - focusedContainerColor = Color.Transparent, - unfocusedContainerColor = Color.Transparent, - cursorColor = White, - focusedIndicatorColor = White, - unfocusedIndicatorColor = White, - focusedPlaceholderColor = Gray, - unfocusedPlaceholderColor = White - ) - ) - } -} - -@Composable -private fun SearchResult( - searchResult: LazyPagingItems, - onItemClick: (Song) -> Unit -) { - Column( - modifier = Modifier.fillMaxSize() - ) { - VerticalSpacer(20) - - TextWithColorAndStyle( - text = stringResource(id = R.string.result), - textColor = White, - textStyle = MaterialTheme.typography.titleLarge, - modifier = Modifier.padding(start = DefaultPadding) - ) - VerticalSpacer(20) - - LazyColumn(modifier = Modifier.padding(bottom = DefaultPadding)) { - items(searchResult.itemCount) { index -> - searchResult[index]?.let { - SongItem(it) { - onItemClick(it) - } - } - } - } - - if (searchResult.loadState.refresh is LoadState.NotLoading) { - if (searchResult.itemCount == 0) { - Text( - text = stringResource(id = R.string.search_music_empty_description), - modifier = Modifier.padding(horizontal = DefaultPadding), - color = White - ) - } - } else if (searchResult.loadState.refresh is LoadState.Error) { - Text( - text = stringResource(id = R.string.search_music_error_description), - modifier = Modifier.padding(horizontal = DefaultPadding), - color = White - ) - } - } -} - -@Composable -private fun SongItem( - song: Song, - onItemClick: () -> Unit, -) { - Row( - modifier = Modifier - .fillMaxWidth() - .clickable( - interactionSource = remember { MutableInteractionSource() }, - indication = ripple(color = White), - ) { - onItemClick() - } - .padding(horizontal = DefaultPadding, vertical = ItemSpacing / 2), - verticalAlignment = Alignment.CenterVertically - ) { - AlbumImage( - imageUrl = song.getImageUrlWithSize(REQUEST_IMAGE_SIZE_DEFAULT.width, REQUEST_IMAGE_SIZE_DEFAULT.height), - modifier = Modifier - .size(ImageSize) - .clip(RoundedCornerShape(16.dp)) - ) - - HorizontalSpacer(16) - Column { - TextWithColorAndStyle( - text = song.songName, - textColor = White, - textStyle = MaterialTheme.typography.bodyLarge - ) - TextWithColorAndStyle( - text = song.artistName, - textColor = Gray, - textStyle = MaterialTheme.typography.bodyMedium - ) - TextWithColorAndStyle( - text = song.albumName, - textColor = Gray, - textStyle = MaterialTheme.typography.bodyMedium - ) - } - } -} - -@Composable -fun TextWithColorAndStyle( - text: String, - textColor: Color, - textStyle: TextStyle, - modifier: Modifier = Modifier -) { - Text( - text = text, - modifier = modifier, - color = textColor, - overflow = TextOverflow.Ellipsis, - maxLines = 1, - style = textStyle - ) -} - -fun Modifier.addFocusCleaner(focusManager: FocusManager, doOnClear: () -> Unit = {}): Modifier { - return this.pointerInput(Unit) { - detectTapGestures( - onTap = { - doOnClear() - focusManager.clearFocus() - } - ) - } -} - -@Preview -@Composable -fun SongItemPreview() { - val song = Song( - id = "1", - songName = "Ditto", - artistName = "String", - albumName = "Ditto", - imageUrl = "https://i.scdn.co/image/ab67616d0000b2733d98a0ae7c78a3a9babaf8af", - genreNames = listOf(), - bgColor = android.graphics.Color.RED, - externalUrl = "", - previewUrl = "", - ) - SongItem(song) { - - } -} - -private val SearchBarHeight = 56.dp -private val DefaultPadding = 16.dp -private val ItemSpacing = 24.dp -private val ImageSize = 56.dp diff --git a/app/src/main/java/com/squirtles/musicroad/search/SearchViewModel.kt b/app/src/main/java/com/squirtles/musicroad/search/SearchViewModel.kt deleted file mode 100644 index ce6670bf..00000000 --- a/app/src/main/java/com/squirtles/musicroad/search/SearchViewModel.kt +++ /dev/null @@ -1,64 +0,0 @@ -package com.squirtles.musicroad.search - -import androidx.lifecycle.ViewModel -import androidx.lifecycle.viewModelScope -import androidx.paging.PagingData -import androidx.paging.cachedIn -import com.squirtles.applemusic.usecase.FetchSongsUseCase -import com.squirtles.model.Song -import com.squirtles.musicroad.create.SearchUiState -import dagger.hilt.android.lifecycle.HiltViewModel -import kotlinx.coroutines.FlowPreview -import kotlinx.coroutines.Job -import kotlinx.coroutines.flow.MutableStateFlow -import kotlinx.coroutines.flow.asStateFlow -import kotlinx.coroutines.flow.collectLatest -import kotlinx.coroutines.flow.debounce -import kotlinx.coroutines.launch -import javax.inject.Inject - -@OptIn(FlowPreview::class) -@HiltViewModel -class SearchViewModel @Inject constructor( - private val fetchSongsUseCase: FetchSongsUseCase, -) : ViewModel() { - - private val _searchUiState = MutableStateFlow(SearchUiState.HotResult) - val searchUiState = _searchUiState.asStateFlow() - - private val _searchResult = MutableStateFlow>(PagingData.empty()) - val searchResult = _searchResult.asStateFlow() - - private val _searchText = MutableStateFlow("") - val searchText = _searchText.asStateFlow() - - private var searchJob: Job? = null - - init { - viewModelScope.launch { - _searchText - .debounce(300) - .collect { searchKeyword -> - searchJob?.cancel() - if (searchKeyword.isNotBlank()) { - searchJob = launch { searchSongs(searchKeyword) } - } else { - _searchUiState.emit(SearchUiState.HotResult) - } - } - } - } - - private suspend fun searchSongs(searchKeyword: String) { - fetchSongsUseCase(searchKeyword) - .cachedIn(viewModelScope) - .collectLatest { - _searchResult.emit(it) - _searchUiState.emit(SearchUiState.SearchResult) - } - } - - fun onSearchTextChange(text: String) { - _searchText.value = text - } -} diff --git a/app/src/main/java/com/squirtles/musicroad/search/navigation/SearchNavigation.kt b/app/src/main/java/com/squirtles/musicroad/search/navigation/SearchNavigation.kt deleted file mode 100644 index 7f5eacfe..00000000 --- a/app/src/main/java/com/squirtles/musicroad/search/navigation/SearchNavigation.kt +++ /dev/null @@ -1,25 +0,0 @@ -package com.squirtles.musicroad.search.navigation - -import androidx.navigation.NavController -import androidx.navigation.NavGraphBuilder -import androidx.navigation.NavOptions -import androidx.navigation.compose.composable -import com.squirtles.model.Song -import com.squirtles.musicroad.search.SearchMusicScreen -import com.squirtles.navigation.MainRoute - -fun NavController.navigateSearch(navOptions: NavOptions? = null) { - navigate(MainRoute.Search, navOptions) -} - -fun NavGraphBuilder.searchNavGraph( - onBackClick: () -> Unit, - onItemClick: (Song) -> Unit, -) { - composable { - SearchMusicScreen( - onBackClick = onBackClick, - onItemClick = onItemClick, // Create 이동 - ) - } -} From e91027abf1110cf0a7692314e0eb71630170f5ff Mon Sep 17 00:00:00 2001 From: miller198 Date: Tue, 22 Apr 2025 21:20:46 +0900 Subject: [PATCH 51/62] =?UTF-8?q?[refactor]=20feature:favorite=20=EC=A0=81?= =?UTF-8?q?=EC=9A=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/build.gradle.kts | 2 + .../musicroad/create/CreatePickScreen.kt | 350 ------------------ .../musicroad/create/CreatePickUiState.kt | 12 - .../musicroad/create/CreatePickViewModel.kt | 122 ------ .../create/navigation/CreateNavigation.kt | 41 -- .../favorite/FavoriteListViewModel.kt | 25 -- .../musicroad/favorite/FavoriteScreen.kt | 47 --- .../favorite/navigation/FavoriteNavigation.kt | 28 -- .../musicroad/main/navigation/MainNavHost.kt | 2 +- .../main/navigation/MainNavigator.kt | 2 +- .../FirebaseFavoriteDataSourceImpl.kt | 2 +- .../firebase/FirebaseDataSourceConstants.kt | 2 +- .../pick/FirebasePickDataSourceImpl.kt | 2 +- 13 files changed, 7 insertions(+), 630 deletions(-) delete mode 100644 app/src/main/java/com/squirtles/musicroad/create/CreatePickScreen.kt delete mode 100644 app/src/main/java/com/squirtles/musicroad/create/CreatePickUiState.kt delete mode 100644 app/src/main/java/com/squirtles/musicroad/create/CreatePickViewModel.kt delete mode 100644 app/src/main/java/com/squirtles/musicroad/create/navigation/CreateNavigation.kt delete mode 100644 app/src/main/java/com/squirtles/musicroad/favorite/FavoriteListViewModel.kt delete mode 100644 app/src/main/java/com/squirtles/musicroad/favorite/FavoriteScreen.kt delete mode 100644 app/src/main/java/com/squirtles/musicroad/favorite/navigation/FavoriteNavigation.kt diff --git a/app/build.gradle.kts b/app/build.gradle.kts index 4453c1a3..6148203f 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -126,6 +126,8 @@ dependencies { implementation(projects.data.order) implementation(projects.feature.userinfo) implementation(projects.feature.search) + implementation(projects.feature.create) + implementation(projects.feature.favorite) implementation(libs.androidx.core.ktx) implementation(libs.androidx.lifecycle.runtime.ktx) diff --git a/app/src/main/java/com/squirtles/musicroad/create/CreatePickScreen.kt b/app/src/main/java/com/squirtles/musicroad/create/CreatePickScreen.kt deleted file mode 100644 index dbdd8505..00000000 --- a/app/src/main/java/com/squirtles/musicroad/create/CreatePickScreen.kt +++ /dev/null @@ -1,350 +0,0 @@ -package com.squirtles.musicroad.create - -import android.app.Activity -import android.util.Size -import androidx.activity.compose.BackHandler -import androidx.compose.foundation.background -import androidx.compose.foundation.clickable -import androidx.compose.foundation.interaction.MutableInteractionSource -import androidx.compose.foundation.layout.Arrangement -import androidx.compose.foundation.layout.Box -import androidx.compose.foundation.layout.Column -import androidx.compose.foundation.layout.aspectRatio -import androidx.compose.foundation.layout.fillMaxSize -import androidx.compose.foundation.layout.fillMaxWidth -import androidx.compose.foundation.layout.height -import androidx.compose.foundation.layout.padding -import androidx.compose.foundation.layout.statusBarsPadding -import androidx.compose.foundation.rememberScrollState -import androidx.compose.foundation.shape.RoundedCornerShape -import androidx.compose.foundation.verticalScroll -import androidx.compose.material.icons.Icons -import androidx.compose.material.icons.automirrored.filled.ArrowBack -import androidx.compose.material.icons.filled.Check -import androidx.compose.material3.CenterAlignedTopAppBar -import androidx.compose.material3.CircularProgressIndicator -import androidx.compose.material3.ExperimentalMaterial3Api -import androidx.compose.material3.Icon -import androidx.compose.material3.IconButton -import androidx.compose.material3.MaterialTheme -import androidx.compose.material3.OutlinedTextField -import androidx.compose.material3.OutlinedTextFieldDefaults -import androidx.compose.material3.Scaffold -import androidx.compose.material3.Text -import androidx.compose.material3.TopAppBarDefaults -import androidx.compose.runtime.Composable -import androidx.compose.runtime.LaunchedEffect -import androidx.compose.runtime.SideEffect -import androidx.compose.runtime.getValue -import androidx.compose.runtime.mutableStateOf -import androidx.compose.runtime.remember -import androidx.compose.runtime.saveable.rememberSaveable -import androidx.compose.runtime.setValue -import androidx.compose.ui.Alignment -import androidx.compose.ui.Modifier -import androidx.compose.ui.draw.clip -import androidx.compose.ui.graphics.Brush -import androidx.compose.ui.graphics.Color -import androidx.compose.ui.graphics.luminance -import androidx.compose.ui.platform.LocalView -import androidx.compose.ui.res.stringResource -import androidx.compose.ui.text.font.FontWeight -import androidx.compose.ui.text.style.TextAlign -import androidx.compose.ui.tooling.preview.Preview -import androidx.compose.ui.unit.dp -import androidx.core.graphics.toColorInt -import androidx.core.view.WindowInsetsControllerCompat -import androidx.hilt.navigation.compose.hiltViewModel -import androidx.lifecycle.compose.collectAsStateWithLifecycle -import com.squirtles.common.ui.AlbumImage -import com.squirtles.common.ui.VerticalSpacer -import com.squirtles.common.ui.theme.Black -import com.squirtles.common.ui.theme.Dark -import com.squirtles.common.ui.theme.Gray -import com.squirtles.common.ui.theme.White -import com.squirtles.model.Song -import com.squirtles.musicroad.R - -@Composable -fun CreatePickScreen( - song: Song, - onBackClick: () -> Unit, - onCreateClick: (String) -> Unit, - createPickViewModel: CreatePickViewModel = hiltViewModel(), -) { - - val comment = createPickViewModel.comment.collectAsStateWithLifecycle() - val uiState by createPickViewModel.createPickUiState.collectAsStateWithLifecycle() - var showCreateIndicator by rememberSaveable { mutableStateOf(false) } - - val dynamicBackgroundColor = Color(song.bgColor) - val dynamicOnBackgroundColor = if (dynamicBackgroundColor.luminance() >= 0.5f) Black else White - val view = LocalView.current - - if (!view.isInEditMode) { - SideEffect { - val window = (view.context as Activity).window - val windowInsetsController = WindowInsetsControllerCompat(window, view) - val isLightStatusBar = dynamicBackgroundColor.luminance() >= 0.5f - windowInsetsController.isAppearanceLightStatusBars = isLightStatusBar - } - } - - // 생성 중 인디케이터가 표시되고 있을 때는 시스템의 뒤로 가기 버튼 클릭을 무시 - BackHandler(enabled = showCreateIndicator) { } - - when (uiState) { - CreateUiState.Default -> { - CreatePickDisplay( - song = song, - comment = comment.value, - dynamicBackgroundColor = dynamicBackgroundColor, - dynamicOnBackgroundColor = dynamicOnBackgroundColor, - onBackClick = { - createPickViewModel.resetComment() - onBackClick() - }, - onCreateClick = { - createPickViewModel.onCreatePickClick() - showCreateIndicator = true - }, - onCommentChange = createPickViewModel::onCommentChange - ) - } - - is CreateUiState.Success -> { - LaunchedEffect(Unit) { - showCreateIndicator = false - val pickId = (uiState as CreateUiState.Success).data - onCreateClick(pickId) - } - } - - CreateUiState.Error -> { - // TODO() - showCreateIndicator = false - Text("생성 오류") - } - } - - if (showCreateIndicator) { - Box( - modifier = Modifier - .fillMaxSize() - .background(Black.copy(alpha = 0.5F)) - .clickable( // 클릭 효과 제거 및 클릭 이벤트 무시 - interactionSource = remember { MutableInteractionSource() }, - indication = null, - onClick = {} - ), - contentAlignment = Alignment.Center - ) { - CircularProgressIndicator() - } - } -} - -@Composable -private fun CreatePickDisplay( - song: Song, - comment: String, - dynamicBackgroundColor: Color, - dynamicOnBackgroundColor: Color, - onCommentChange: (String) -> Unit, - onBackClick: () -> Unit, - onCreateClick: () -> Unit, -) { - Scaffold( - containerColor = dynamicBackgroundColor, - topBar = { - CreatePickScreenTopBar( - dynamicOnBackgroundColor = dynamicOnBackgroundColor, - onBackClick = onBackClick, - onCreateClick = onCreateClick - ) - } - ) { innerPadding -> - Box( - modifier = Modifier - .padding(innerPadding) - .fillMaxSize() - .background( - brush = Brush.verticalGradient( - colorStops = arrayOf( - 0.0f to dynamicBackgroundColor, - 0.47f to Black - ) - ) - ) - ) { - CreatePickContent( - song = song, - comment = comment, - onValueChange = onCommentChange, - dynamicOnBackgroundColor = dynamicOnBackgroundColor, - ) - } - } -} - -@Composable -private fun CreatePickContent( - song: Song, - comment: String, - onValueChange: (String) -> Unit, - dynamicOnBackgroundColor: Color, -) { - val scrollState = rememberScrollState() - - Column( - modifier = Modifier - .fillMaxSize() - .verticalScroll(scrollState) - .padding(vertical = 10.dp), - horizontalAlignment = Alignment.CenterHorizontally, - verticalArrangement = Arrangement.spacedBy(10.dp), - ) { - Text( - text = song.songName, - color = dynamicOnBackgroundColor, - textAlign = TextAlign.Center, - style = MaterialTheme.typography.titleLarge.copy(fontWeight = FontWeight.Bold) - ) - - Text( - text = song.artistName, - color = dynamicOnBackgroundColor, - textAlign = TextAlign.Center, - style = MaterialTheme.typography.bodyLarge - ) - - AlbumImage( - imageUrl = song.getImageUrlWithSize(RequestImageSize.width, RequestImageSize.height), - modifier = Modifier - .fillMaxWidth() - .padding(horizontal = 30.dp) - .aspectRatio(1f) - .clip(RoundedCornerShape(20.dp)), - contentDescription = song.albumName + stringResource(id = R.string.pick_album_description) - ) - - VerticalSpacer(40) - - CommentTextBox( - comment = comment, - onValueChange = onValueChange, - ) - } -} - -@Composable -private fun CommentTextBox( - comment: String, - onValueChange: (String) -> Unit, -) { - OutlinedTextField( - value = comment, - onValueChange = { textValue -> - onValueChange(textValue) - }, - modifier = Modifier - .fillMaxWidth() - .height(100.dp) - .padding(horizontal = 30.dp), - textStyle = MaterialTheme.typography.bodyLarge.copy(White), - placeholder = { - Text( - text = stringResource(id = R.string.pick_comment_placeholder), - style = MaterialTheme.typography.bodyLarge.copy(Gray) - ) - }, - shape = RoundedCornerShape(10.dp), - colors = OutlinedTextFieldDefaults.colors( - unfocusedContainerColor = Dark, - focusedContainerColor = Dark, - focusedBorderColor = Color.Transparent, - unfocusedBorderColor = Color.Transparent - ) - ) -} - -@OptIn(ExperimentalMaterial3Api::class) -@Composable -private fun CreatePickScreenTopBar( - dynamicOnBackgroundColor: Color, - onBackClick: () -> Unit, - onCreateClick: () -> Unit -) { - CenterAlignedTopAppBar( - modifier = Modifier.statusBarsPadding(), - title = { - Text( - text = stringResource(id = R.string.pick_app_bar_title), - color = dynamicOnBackgroundColor, - style = MaterialTheme.typography.titleLarge.copy(fontWeight = FontWeight.Bold) - ) - }, - navigationIcon = { - IconButton(onClick = onBackClick) { - Icon( - imageVector = Icons.AutoMirrored.Filled.ArrowBack, - contentDescription = stringResource(id = R.string.top_app_bar_back_description), - tint = dynamicOnBackgroundColor - ) - } - }, - actions = { - IconButton(onClick = onCreateClick) { - Icon( - imageVector = Icons.Filled.Check, - contentDescription = stringResource(id = R.string.pick_app_bar_upload_description), - tint = dynamicOnBackgroundColor - ) - } - }, - colors = TopAppBarDefaults.centerAlignedTopAppBarColors( - containerColor = Color.Transparent - ) - ) -} - -@Preview -@Composable -private fun CreatePickScreenPreview() { - CreatePickDisplay( - song = Song( - id = "1778132734", - songName = "Super Shy", - artistName = "뉴진스", - albumName = "NewJeans 'Super Shy' - Single", - imageUrl = "https://i.scdn.co/image/ab67616d0000b2733d98a0ae7c78a3a9babaf8af", - genreNames = listOf("K-Pop"), - bgColor = "#8FC1E2".toColorInt(), - externalUrl = "", - previewUrl = "" - ), - onBackClick = {}, - comment = "TEST COMMENT", - dynamicBackgroundColor = Color.White, - dynamicOnBackgroundColor = Color.Gray, - onCommentChange = { - - }, - onCreateClick = { - - } - ) -} - -private val RequestImageSize = Size(720, 720) -private val DEFAULT_SONG = Song( - id = "1778132734", - songName = "", - artistName = "", - albumName = "", - imageUrl = "", - genreNames = listOf("K-Pop"), - bgColor = "#8FC1E2".toColorInt(), - externalUrl = "", - previewUrl = "" -) diff --git a/app/src/main/java/com/squirtles/musicroad/create/CreatePickUiState.kt b/app/src/main/java/com/squirtles/musicroad/create/CreatePickUiState.kt deleted file mode 100644 index 7ec0c6b6..00000000 --- a/app/src/main/java/com/squirtles/musicroad/create/CreatePickUiState.kt +++ /dev/null @@ -1,12 +0,0 @@ -package com.squirtles.musicroad.create - -sealed class SearchUiState { - data object HotResult : SearchUiState() - data object SearchResult : SearchUiState() -} - -sealed class CreateUiState { - data object Default : CreateUiState() - data class Success(val data: T) : CreateUiState() - data object Error : CreateUiState() -} diff --git a/app/src/main/java/com/squirtles/musicroad/create/CreatePickViewModel.kt b/app/src/main/java/com/squirtles/musicroad/create/CreatePickViewModel.kt deleted file mode 100644 index 18cf510d..00000000 --- a/app/src/main/java/com/squirtles/musicroad/create/CreatePickViewModel.kt +++ /dev/null @@ -1,122 +0,0 @@ -package com.squirtles.musicroad.create - -import android.location.Location -import android.util.Log -import androidx.lifecycle.SavedStateHandle -import androidx.lifecycle.ViewModel -import androidx.lifecycle.viewModelScope -import androidx.navigation.toRoute -import com.squirtles.applemusic.usecase.FetchMusicVideoUseCase -import com.squirtles.domain.pick.usecase.CreatePickUseCase -import com.squirtles.location.usecase.GetLastLocationUseCase -import com.squirtles.model.Creator -import com.squirtles.model.LocationPoint -import com.squirtles.model.Pick -import com.squirtles.model.Song -import com.squirtles.navigation.SearchRoute -import com.squirtles.user.usecase.FetchUserByIdUseCase -import com.squirtles.user.usecase.GetCurrentUidUseCase -import com.squirtles.util.serializableType -import com.squirtles.util.throttleFirst -import dagger.hilt.android.lifecycle.HiltViewModel -import kotlinx.coroutines.flow.MutableSharedFlow -import kotlinx.coroutines.flow.MutableStateFlow -import kotlinx.coroutines.flow.asStateFlow -import kotlinx.coroutines.launch -import javax.inject.Inject -import kotlin.reflect.typeOf - -@HiltViewModel -class CreatePickViewModel @Inject constructor( - savedStateHandle: SavedStateHandle, - getLastLocationUseCase: GetLastLocationUseCase, - private val fetchMusicVideoUseCase: FetchMusicVideoUseCase, - private val createPickUseCase: CreatePickUseCase, - private val getCurrentUidUseCase: GetCurrentUidUseCase, - private val fetchUserByIdUseCase: FetchUserByIdUseCase -) : ViewModel() { - private val songTypeMap = mapOf(typeOf() to serializableType()) - private val song = savedStateHandle.toRoute(songTypeMap).song - - private val _createPickUiState = MutableStateFlow>(CreateUiState.Default) - val createPickUiState = _createPickUiState.asStateFlow() - - private val _comment = MutableStateFlow("") - val comment get() = _comment - - private var lastLocation: Location? = null - private val createPickClick = MutableSharedFlow() - - init { - // 데이터소스의 위치값을 계속 collect하며 curLocation 변수에 저장 - viewModelScope.launch { - getLastLocationUseCase().collect { location -> - lastLocation = location - } - } - - // 등록 버튼 클릭 후 3초 이내의 클릭은 무시하고 픽 생성하기 - viewModelScope.launch { - createPickClick - .throttleFirst(3000) - .collect { - createPick(song) - } - } - } - - fun onCommentChange(text: String) { - _comment.value = text - } - - fun resetComment() { - _comment.value = "" - } - - fun onCreatePickClick() { - viewModelScope.launch { - createPickClick.emit(Unit) - } - } - - private fun createPick(song: Song) { - viewModelScope.launch { - if (lastLocation == null) { - /* TODO: DEFAULT 인 경우 -> LocalDataSource 위치 데이터 못 불러옴 */ - return@launch - } - - val musicVideo = fetchMusicVideoUseCase(song) - val currentUid = getCurrentUidUseCase() - /* 등록 결과 - pick ID 담긴 Result */ - if (currentUid != null) { - fetchUserByIdUseCase(currentUid) - .onSuccess { user -> - val createResult = createPickUseCase( - Pick( - id = "", - song = song, - comment = _comment.value, - createdAt = "", - createdBy = Creator( - uid = user.uid, - userName = user.userName - ), - location = LocationPoint(lastLocation!!.latitude, lastLocation!!.longitude), - musicVideoUrl = musicVideo?.previewUrl ?: "", - musicVideoThumbnailUrl = musicVideo?.thumbnailUrl ?: "" - ) - ) - - createResult.onSuccess { pickId -> - _createPickUiState.emit(CreateUiState.Success(pickId)) - }.onFailure { - /* TODO: Firestore 등록 실패처리 */ - _createPickUiState.emit(CreateUiState.Error) - Log.d("CreatePickViewModel", createResult.exceptionOrNull()?.message.toString()) - } - } - } - } - } -} diff --git a/app/src/main/java/com/squirtles/musicroad/create/navigation/CreateNavigation.kt b/app/src/main/java/com/squirtles/musicroad/create/navigation/CreateNavigation.kt deleted file mode 100644 index 437c5a99..00000000 --- a/app/src/main/java/com/squirtles/musicroad/create/navigation/CreateNavigation.kt +++ /dev/null @@ -1,41 +0,0 @@ -package com.squirtles.create.navigation - -import androidx.navigation.NavController -import androidx.navigation.NavGraphBuilder -import androidx.navigation.NavOptions -import androidx.navigation.compose.composable -import androidx.navigation.toRoute -import com.squirtles.model.Song -import com.squirtles.musicroad.create.CreatePickScreen -import com.squirtles.navigation.SearchRoute -import com.squirtles.util.serializableType -import java.net.URLEncoder -import java.nio.charset.StandardCharsets -import kotlin.reflect.typeOf - -fun NavController.navigateCreate(song: Song, navOptions: NavOptions? = null) { - val encodedSong = song.copy( - previewUrl = URLEncoder.encode(song.previewUrl, StandardCharsets.UTF_8.toString()), - externalUrl = URLEncoder.encode(song.externalUrl, StandardCharsets.UTF_8.toString()), - genreNames = song.genreNames.map { URLEncoder.encode(it, StandardCharsets.UTF_8.toString()) }, - imageUrl = URLEncoder.encode(song.imageUrl, StandardCharsets.UTF_8.toString()) - ) - navigate(SearchRoute.Create(encodedSong), navOptions) -} - -fun NavGraphBuilder.createNavGraph( - onBackClick: () -> Unit, - onCreateClick: (String) -> Unit -) { - composable( - typeMap = mapOf(typeOf() to serializableType()) - ) { backStackEntry -> - val song = backStackEntry.toRoute().song - - CreatePickScreen( - song = song, - onBackClick = onBackClick, - onCreateClick = onCreateClick, - ) - } -} diff --git a/app/src/main/java/com/squirtles/musicroad/favorite/FavoriteListViewModel.kt b/app/src/main/java/com/squirtles/musicroad/favorite/FavoriteListViewModel.kt deleted file mode 100644 index d77453e4..00000000 --- a/app/src/main/java/com/squirtles/musicroad/favorite/FavoriteListViewModel.kt +++ /dev/null @@ -1,25 +0,0 @@ -package com.squirtles.musicroad.favorite - -import com.squirtles.domain.pick.usecase.FetchFavoritePicksUseCase -import com.squirtles.favorite.usecase.DeleteFavoriteUseCase -import com.squirtles.order.usecase.GetFavoriteListOrderUseCase -import com.squirtles.order.usecase.SaveFavoriteListOrderUseCase -import com.squirtles.picklist.PickListViewModel -import com.squirtles.user.usecase.GetCurrentUidUseCase -import dagger.hilt.android.lifecycle.HiltViewModel -import javax.inject.Inject - -@HiltViewModel -class FavoriteListViewModel @Inject constructor( - fetchFavoritePicksUseCase: FetchFavoritePicksUseCase, - getFavoriteListOrderUseCase: GetFavoriteListOrderUseCase, - saveFavoriteListOrderUseCase: SaveFavoriteListOrderUseCase, - deleteFavoriteUseCase: DeleteFavoriteUseCase, - getCurrentUidUseCase: GetCurrentUidUseCase -) : PickListViewModel( - fetchPickListUseCase = fetchFavoritePicksUseCase, - getPickListOrderUseCase = getFavoriteListOrderUseCase, - savePickListOrderUseCase = saveFavoriteListOrderUseCase, - removePickUseCase = deleteFavoriteUseCase, - getCurrentUidUseCase = getCurrentUidUseCase -) diff --git a/app/src/main/java/com/squirtles/musicroad/favorite/FavoriteScreen.kt b/app/src/main/java/com/squirtles/musicroad/favorite/FavoriteScreen.kt deleted file mode 100644 index 0926803f..00000000 --- a/app/src/main/java/com/squirtles/musicroad/favorite/FavoriteScreen.kt +++ /dev/null @@ -1,47 +0,0 @@ -package com.squirtles.musicroad.favorite - -import androidx.compose.runtime.Composable -import androidx.compose.runtime.LaunchedEffect -import androidx.compose.runtime.getValue -import androidx.compose.runtime.mutableStateOf -import androidx.compose.runtime.saveable.rememberSaveable -import androidx.compose.runtime.setValue -import androidx.hilt.navigation.compose.hiltViewModel -import androidx.lifecycle.compose.collectAsStateWithLifecycle -import com.squirtles.picklist.PickListScreenContents -import com.squirtles.picklist.PickListType - -@Composable -fun FavoriteScreen( - uid: String, - onBackClick: () -> Unit, - onItemClick: (String) -> Unit, - favoriteListViewModel: FavoriteListViewModel = hiltViewModel() -) { - val uiState by favoriteListViewModel.pickListUiState.collectAsStateWithLifecycle() - val selectedPicksId by favoriteListViewModel.selectedPicksId.collectAsStateWithLifecycle() - var showOrderBottomSheet by rememberSaveable { mutableStateOf(false) } - - LaunchedEffect(Unit) { - favoriteListViewModel.fetchPickList(uid) - } - - PickListScreenContents( - uid = uid, - showOrderBottomSheet = showOrderBottomSheet, - selectedPicksId = selectedPicksId, - pickListType = PickListType.FAVORITE, - uiState = uiState, - onBackClick = onBackClick, - onItemClick = onItemClick, - setListOrder = favoriteListViewModel::setListOrder, - setOrderBottomSheetVisibility = { showOrderBottomSheet = it }, - selectAllPicks = favoriteListViewModel::selectAllPicks, - deselectAllPicks = favoriteListViewModel::deselectAllPicks, - toggleSelectedPick = favoriteListViewModel::toggleSelectedPick, - deleteSelectedPicks = favoriteListViewModel::deleteSelectedPicks, - getUid = { - favoriteListViewModel.getUid().toString() - } - ) -} diff --git a/app/src/main/java/com/squirtles/musicroad/favorite/navigation/FavoriteNavigation.kt b/app/src/main/java/com/squirtles/musicroad/favorite/navigation/FavoriteNavigation.kt deleted file mode 100644 index 9af63728..00000000 --- a/app/src/main/java/com/squirtles/musicroad/favorite/navigation/FavoriteNavigation.kt +++ /dev/null @@ -1,28 +0,0 @@ -package com.squirtles.musicroad.favorite.navigation - -import androidx.navigation.NavController -import androidx.navigation.NavGraphBuilder -import androidx.navigation.NavOptions -import androidx.navigation.compose.composable -import androidx.navigation.toRoute -import com.squirtles.musicroad.favorite.FavoriteScreen -import com.squirtles.navigation.MainRoute - -fun NavController.navigateFavorite(uid: String, navOptions: NavOptions? = null) { - navigate(MainRoute.Favorite(uid), navOptions) -} - -fun NavGraphBuilder.favoriteNavGraph( - onBackClick: () -> Unit, - onItemClick: (String) -> Unit, -) { - composable { backStackEntry -> - val uid = backStackEntry.toRoute().uid - - FavoriteScreen( - uid = uid, - onBackClick = onBackClick, - onItemClick = onItemClick, - ) - } -} diff --git a/app/src/main/java/com/squirtles/musicroad/main/navigation/MainNavHost.kt b/app/src/main/java/com/squirtles/musicroad/main/navigation/MainNavHost.kt index ce1d7110..39f5ac18 100644 --- a/app/src/main/java/com/squirtles/musicroad/main/navigation/MainNavHost.kt +++ b/app/src/main/java/com/squirtles/musicroad/main/navigation/MainNavHost.kt @@ -6,8 +6,8 @@ import androidx.hilt.navigation.compose.hiltViewModel import androidx.navigation.compose.NavHost import com.squirtles.create.navigation.createNavGraph import com.squirtles.detail.navigation.detailNavGraph +import com.squirtles.favorite.navigation.favoriteNavGraph import com.squirtles.musicplayer.PlayerServiceViewModel -import com.squirtles.musicroad.favorite.navigation.favoriteNavGraph import com.squirtles.musicroad.map.MapViewModel import com.squirtles.musicroad.map.navigation.mapNavGraph import com.squirtles.musicroad.mypick.navigation.myPickNavGraph diff --git a/app/src/main/java/com/squirtles/musicroad/main/navigation/MainNavigator.kt b/app/src/main/java/com/squirtles/musicroad/main/navigation/MainNavigator.kt index 6a5b4ad5..d4014d8a 100644 --- a/app/src/main/java/com/squirtles/musicroad/main/navigation/MainNavigator.kt +++ b/app/src/main/java/com/squirtles/musicroad/main/navigation/MainNavigator.kt @@ -10,8 +10,8 @@ import androidx.navigation.compose.rememberNavController import androidx.navigation.navOptions import com.squirtles.create.navigation.navigateCreate import com.squirtles.detail.navigation.navigatePickDetail +import com.squirtles.favorite.navigation.navigateFavorite import com.squirtles.model.Song -import com.squirtles.musicroad.favorite.navigation.navigateFavorite import com.squirtles.musicroad.map.navigation.navigateMap import com.squirtles.musicroad.mypick.navigation.navigateMyPicks import com.squirtles.navigation.Route diff --git a/data/favorite/src/main/java/com/squirtles/favorite/FirebaseFavoriteDataSourceImpl.kt b/data/favorite/src/main/java/com/squirtles/favorite/FirebaseFavoriteDataSourceImpl.kt index 2f0ab501..e1675995 100644 --- a/data/favorite/src/main/java/com/squirtles/favorite/FirebaseFavoriteDataSourceImpl.kt +++ b/data/favorite/src/main/java/com/squirtles/favorite/FirebaseFavoriteDataSourceImpl.kt @@ -56,7 +56,7 @@ class FirebaseFavoriteDataSourceImpl @Inject constructor( private suspend fun queryFavoriteByPickIdAndUserId(pickId: String, userId: String): Result = queryDocumentsEquals( collection = FirebaseCollections.Favorites, - fields = listOf(FirebaseDocumentFields.PickId, FirebaseDocumentFields.UserId), + fields = listOf(FirebaseDocumentFields.PickId, FirebaseDocumentFields.Uid), values = listOf(pickId, userId) ) diff --git a/data/firebase/src/main/java/com/squirtles/firebase/FirebaseDataSourceConstants.kt b/data/firebase/src/main/java/com/squirtles/firebase/FirebaseDataSourceConstants.kt index 26d40d7e..51036de1 100644 --- a/data/firebase/src/main/java/com/squirtles/firebase/FirebaseDataSourceConstants.kt +++ b/data/firebase/src/main/java/com/squirtles/firebase/FirebaseDataSourceConstants.kt @@ -9,7 +9,7 @@ sealed class FirebaseCollections(val name: String) { sealed class FirebaseDocumentFields(val name: String) { data object AddedAt: FirebaseDocumentFields("addedAt") data object PickId: FirebaseDocumentFields("pickId") - data object UserId: FirebaseDocumentFields("userId") + data object Uid: FirebaseDocumentFields("uid") data object MyPicks: FirebaseDocumentFields("myPicks") data object Name: FirebaseDocumentFields("name") data object Location: FirebaseDocumentFields("location") diff --git a/data/pick/src/main/java/com/squirtles/pick/FirebasePickDataSourceImpl.kt b/data/pick/src/main/java/com/squirtles/pick/FirebasePickDataSourceImpl.kt index f79933dc..07af1b92 100644 --- a/data/pick/src/main/java/com/squirtles/pick/FirebasePickDataSourceImpl.kt +++ b/data/pick/src/main/java/com/squirtles/pick/FirebasePickDataSourceImpl.kt @@ -159,7 +159,7 @@ class FirebasePickDataSourceImpl @Inject constructor( private suspend fun fetchFavoritesByUserId(userId: String): List { return queryDocumentsEquals( collection = FirebaseCollections.Favorites, - fields = listOf(FirebaseDocumentFields.UserId), + fields = listOf(FirebaseDocumentFields.Uid), values = listOf(userId) ).getOrThrow().documents } From 5ebf440069d8124ac5b17e800a223d53d09a0832 Mon Sep 17 00:00:00 2001 From: miller198 Date: Tue, 22 Apr 2025 21:27:29 +0900 Subject: [PATCH 52/62] =?UTF-8?q?[refactor]=20feature:mypick=20=EC=A0=81?= =?UTF-8?q?=EC=9A=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/build.gradle.kts | 1 + .../musicroad/main/navigation/MainNavHost.kt | 2 +- .../main/navigation/MainNavigator.kt | 2 +- .../musicroad/mypick/MyPickListViewModel.kt | 25 ----------- .../musicroad/mypick/MyPickScreen.kt | 45 ------------------- .../mypick/navigation/MyPickNavigation.kt | 28 ------------ 6 files changed, 3 insertions(+), 100 deletions(-) delete mode 100644 app/src/main/java/com/squirtles/musicroad/mypick/MyPickListViewModel.kt delete mode 100644 app/src/main/java/com/squirtles/musicroad/mypick/MyPickScreen.kt delete mode 100644 app/src/main/java/com/squirtles/musicroad/mypick/navigation/MyPickNavigation.kt diff --git a/app/build.gradle.kts b/app/build.gradle.kts index 6148203f..f4bd7676 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -128,6 +128,7 @@ dependencies { implementation(projects.feature.search) implementation(projects.feature.create) implementation(projects.feature.favorite) + implementation(projects.feature.mypick) implementation(libs.androidx.core.ktx) implementation(libs.androidx.lifecycle.runtime.ktx) diff --git a/app/src/main/java/com/squirtles/musicroad/main/navigation/MainNavHost.kt b/app/src/main/java/com/squirtles/musicroad/main/navigation/MainNavHost.kt index 39f5ac18..94121b81 100644 --- a/app/src/main/java/com/squirtles/musicroad/main/navigation/MainNavHost.kt +++ b/app/src/main/java/com/squirtles/musicroad/main/navigation/MainNavHost.kt @@ -10,7 +10,7 @@ import com.squirtles.favorite.navigation.favoriteNavGraph import com.squirtles.musicplayer.PlayerServiceViewModel import com.squirtles.musicroad.map.MapViewModel import com.squirtles.musicroad.map.navigation.mapNavGraph -import com.squirtles.musicroad.mypick.navigation.myPickNavGraph +import com.squirtles.mypick.navigation.myPickNavGraph import com.squirtles.search.navigation.searchNavGraph import com.squirtles.userinfo.navigation.userInfoNavGraph diff --git a/app/src/main/java/com/squirtles/musicroad/main/navigation/MainNavigator.kt b/app/src/main/java/com/squirtles/musicroad/main/navigation/MainNavigator.kt index d4014d8a..36d30ce6 100644 --- a/app/src/main/java/com/squirtles/musicroad/main/navigation/MainNavigator.kt +++ b/app/src/main/java/com/squirtles/musicroad/main/navigation/MainNavigator.kt @@ -13,7 +13,7 @@ import com.squirtles.detail.navigation.navigatePickDetail import com.squirtles.favorite.navigation.navigateFavorite import com.squirtles.model.Song import com.squirtles.musicroad.map.navigation.navigateMap -import com.squirtles.musicroad.mypick.navigation.navigateMyPicks +import com.squirtles.mypick.navigation.navigateMyPicks import com.squirtles.navigation.Route import com.squirtles.search.navigation.navigateSearch import com.squirtles.userinfo.navigation.navigateEditNotificationSetting diff --git a/app/src/main/java/com/squirtles/musicroad/mypick/MyPickListViewModel.kt b/app/src/main/java/com/squirtles/musicroad/mypick/MyPickListViewModel.kt deleted file mode 100644 index a5e7cc8a..00000000 --- a/app/src/main/java/com/squirtles/musicroad/mypick/MyPickListViewModel.kt +++ /dev/null @@ -1,25 +0,0 @@ -package com.squirtles.musicroad.mypick - -import com.squirtles.domain.pick.usecase.DeletePickUseCase -import com.squirtles.domain.pick.usecase.FetchMyPicksUseCase -import com.squirtles.order.usecase.GetMyPickListOrderUseCase -import com.squirtles.order.usecase.SaveMyPickListOrderUseCase -import com.squirtles.picklist.PickListViewModel -import com.squirtles.user.usecase.GetCurrentUidUseCase -import dagger.hilt.android.lifecycle.HiltViewModel -import javax.inject.Inject - -@HiltViewModel -class MyPickListViewModel @Inject constructor( - fetchMyPicksUseCase: FetchMyPicksUseCase, - getMyPickListOrderUseCase: GetMyPickListOrderUseCase, - saveMyPickListOrderUseCase: SaveMyPickListOrderUseCase, - deletePickUseCase: DeletePickUseCase, - getCurrentUidUseCase: GetCurrentUidUseCase -) : PickListViewModel( - fetchPickListUseCase = fetchMyPicksUseCase, - getPickListOrderUseCase = getMyPickListOrderUseCase, - savePickListOrderUseCase = saveMyPickListOrderUseCase, - removePickUseCase = deletePickUseCase, - getCurrentUidUseCase = getCurrentUidUseCase -) diff --git a/app/src/main/java/com/squirtles/musicroad/mypick/MyPickScreen.kt b/app/src/main/java/com/squirtles/musicroad/mypick/MyPickScreen.kt deleted file mode 100644 index 34a45159..00000000 --- a/app/src/main/java/com/squirtles/musicroad/mypick/MyPickScreen.kt +++ /dev/null @@ -1,45 +0,0 @@ -package com.squirtles.musicroad.mypick - -import androidx.compose.runtime.Composable -import androidx.compose.runtime.LaunchedEffect -import androidx.compose.runtime.getValue -import androidx.compose.runtime.mutableStateOf -import androidx.compose.runtime.saveable.rememberSaveable -import androidx.compose.runtime.setValue -import androidx.hilt.navigation.compose.hiltViewModel -import androidx.lifecycle.compose.collectAsStateWithLifecycle -import com.squirtles.picklist.PickListScreenContents -import com.squirtles.picklist.PickListType - -@Composable -fun MyPickScreen( - uid: String, - onBackClick: () -> Unit, - onItemClick: (String) -> Unit, - myPickListViewModel: MyPickListViewModel = hiltViewModel() -) { - val uiState by myPickListViewModel.pickListUiState.collectAsStateWithLifecycle() - val selectedPicksId by myPickListViewModel.selectedPicksId.collectAsStateWithLifecycle() - var showOrderBottomSheet by rememberSaveable { mutableStateOf(false) } - - LaunchedEffect(Unit) { - myPickListViewModel.fetchPickList(uid) - } - - PickListScreenContents( - uid = uid, - showOrderBottomSheet = showOrderBottomSheet, - selectedPicksId = selectedPicksId, - pickListType = PickListType.CREATED, - uiState = uiState, - onBackClick = onBackClick, - onItemClick = onItemClick, - setListOrder = myPickListViewModel::setListOrder, - setOrderBottomSheetVisibility = { showOrderBottomSheet = it }, - selectAllPicks = myPickListViewModel::selectAllPicks, - deselectAllPicks = myPickListViewModel::deselectAllPicks, - toggleSelectedPick = myPickListViewModel::toggleSelectedPick, - deleteSelectedPicks = myPickListViewModel::deleteSelectedPicks, - getUid = { myPickListViewModel.getUid().toString() }, - ) -} diff --git a/app/src/main/java/com/squirtles/musicroad/mypick/navigation/MyPickNavigation.kt b/app/src/main/java/com/squirtles/musicroad/mypick/navigation/MyPickNavigation.kt deleted file mode 100644 index 60f621f0..00000000 --- a/app/src/main/java/com/squirtles/musicroad/mypick/navigation/MyPickNavigation.kt +++ /dev/null @@ -1,28 +0,0 @@ -package com.squirtles.musicroad.mypick.navigation - -import androidx.navigation.NavController -import androidx.navigation.NavGraphBuilder -import androidx.navigation.NavOptions -import androidx.navigation.compose.composable -import androidx.navigation.toRoute -import com.squirtles.musicroad.mypick.MyPickScreen -import com.squirtles.navigation.UserInfoRoute - -fun NavController.navigateMyPicks(uid: String, navOptions: NavOptions) { - navigate(UserInfoRoute.MyPicks(uid), navOptions) -} - -fun NavGraphBuilder.myPickNavGraph( - onBackClick: () -> Unit, - onItemClick: (String) -> Unit, -) { - composable { backStackEntry -> - val uid = backStackEntry.toRoute().uid - - MyPickScreen( - uid = uid, - onBackClick = onBackClick, - onItemClick = onItemClick - ) - } -} From f894d9f0906bb83f438ed0d7372aea89bd30b046 Mon Sep 17 00:00:00 2001 From: miller198 Date: Wed, 23 Apr 2025 01:55:39 +0900 Subject: [PATCH 53/62] =?UTF-8?q?[refactor]=20feature:detail=20=EC=A0=81?= =?UTF-8?q?=EC=9A=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/build.gradle.kts | 5 +- .../musicroad/detail/DetailViewModel.kt | 175 ------ .../musicroad/detail/FavoriteAction.kt | 5 - .../musicroad/detail/PickDetailScreen.kt | 542 ------------------ .../musicroad/detail/PickDetailUiState.kt | 10 - .../detail/components/CircleAlbumCover.kt | 82 --- .../detail/components/DetailPickTopAppBar.kt | 104 ---- .../detail/components/MusicVideoKnob.kt | 80 --- .../components/PickDetailCommentText.kt | 56 -- .../detail/components/PickInformation.kt | 39 -- .../PlayCircularProgressIndicator.kt | 65 --- .../musicroad/detail/components/SongInfo.kt | 39 -- .../detail/components/SwipeUpIcon.kt | 32 -- .../detail/components/music/MusicPlayer.kt | 55 -- .../detail/components/music/PlayBar.kt | 99 ---- .../components/music/PlayProgressIndicator.kt | 55 -- .../detail/components/music/PlayerControls.kt | 101 ---- .../detail/navigation/PickDetailNavigation.kt | 34 -- .../detail/videoplayer/MusicVideoPlayer.kt | 112 ---- .../detail/videoplayer/MusicVideoScreen.kt | 37 -- .../detail/videoplayer/VideoPlayerOverlay.kt | 234 -------- .../detail/videoplayer/VideoPlayerState.kt | 5 - .../videoplayer/VideoPlayerViewModel.kt | 135 ----- .../map/components/PickNotificationBanner.kt | 2 +- .../main/java/com/squirtles/map/NaverMap.kt | 1 - .../map/components/ClusterBottomSheet.kt | 2 +- .../com/squirtles/map/marker/Clusterer.kt | 9 +- .../map/marker/LeafMarkerIconView.kt | 5 +- 28 files changed, 12 insertions(+), 2108 deletions(-) delete mode 100644 app/src/main/java/com/squirtles/musicroad/detail/DetailViewModel.kt delete mode 100644 app/src/main/java/com/squirtles/musicroad/detail/FavoriteAction.kt delete mode 100644 app/src/main/java/com/squirtles/musicroad/detail/PickDetailScreen.kt delete mode 100644 app/src/main/java/com/squirtles/musicroad/detail/PickDetailUiState.kt delete mode 100644 app/src/main/java/com/squirtles/musicroad/detail/components/CircleAlbumCover.kt delete mode 100644 app/src/main/java/com/squirtles/musicroad/detail/components/DetailPickTopAppBar.kt delete mode 100644 app/src/main/java/com/squirtles/musicroad/detail/components/MusicVideoKnob.kt delete mode 100644 app/src/main/java/com/squirtles/musicroad/detail/components/PickDetailCommentText.kt delete mode 100644 app/src/main/java/com/squirtles/musicroad/detail/components/PickInformation.kt delete mode 100644 app/src/main/java/com/squirtles/musicroad/detail/components/PlayCircularProgressIndicator.kt delete mode 100644 app/src/main/java/com/squirtles/musicroad/detail/components/SongInfo.kt delete mode 100644 app/src/main/java/com/squirtles/musicroad/detail/components/SwipeUpIcon.kt delete mode 100644 app/src/main/java/com/squirtles/musicroad/detail/components/music/MusicPlayer.kt delete mode 100644 app/src/main/java/com/squirtles/musicroad/detail/components/music/PlayBar.kt delete mode 100644 app/src/main/java/com/squirtles/musicroad/detail/components/music/PlayProgressIndicator.kt delete mode 100644 app/src/main/java/com/squirtles/musicroad/detail/components/music/PlayerControls.kt delete mode 100644 app/src/main/java/com/squirtles/musicroad/detail/navigation/PickDetailNavigation.kt delete mode 100644 app/src/main/java/com/squirtles/musicroad/detail/videoplayer/MusicVideoPlayer.kt delete mode 100644 app/src/main/java/com/squirtles/musicroad/detail/videoplayer/MusicVideoScreen.kt delete mode 100644 app/src/main/java/com/squirtles/musicroad/detail/videoplayer/VideoPlayerOverlay.kt delete mode 100644 app/src/main/java/com/squirtles/musicroad/detail/videoplayer/VideoPlayerState.kt delete mode 100644 app/src/main/java/com/squirtles/musicroad/detail/videoplayer/VideoPlayerViewModel.kt diff --git a/app/build.gradle.kts b/app/build.gradle.kts index f4bd7676..efe62ca7 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -98,10 +98,6 @@ android { } dependencies { -// implementation(projects.domain) -// implementation(projects.data) -// implementation(projects.mediaservice) - implementation(projects.audioVisualizer) implementation(projects.core.account) implementation(projects.core.musicplayer) implementation(projects.core.model) @@ -129,6 +125,7 @@ dependencies { implementation(projects.feature.create) implementation(projects.feature.favorite) implementation(projects.feature.mypick) + implementation(projects.feature.detail) implementation(libs.androidx.core.ktx) implementation(libs.androidx.lifecycle.runtime.ktx) diff --git a/app/src/main/java/com/squirtles/musicroad/detail/DetailViewModel.kt b/app/src/main/java/com/squirtles/musicroad/detail/DetailViewModel.kt deleted file mode 100644 index f0068c7a..00000000 --- a/app/src/main/java/com/squirtles/musicroad/detail/DetailViewModel.kt +++ /dev/null @@ -1,175 +0,0 @@ -package com.squirtles.musicroad.detail - -import androidx.core.graphics.toColorInt -import androidx.lifecycle.SavedStateHandle -import androidx.lifecycle.ViewModel -import androidx.lifecycle.viewModelScope -import com.squirtles.domain.pick.usecase.DeletePickUseCase -import com.squirtles.domain.pick.usecase.FetchPickUseCase -import com.squirtles.favorite.usecase.CreateFavoriteUseCase -import com.squirtles.favorite.usecase.DeleteFavoriteUseCase -import com.squirtles.favorite.usecase.FetchIsFavoriteUseCase -import com.squirtles.model.Creator -import com.squirtles.model.LocationPoint -import com.squirtles.model.Pick -import com.squirtles.model.Song -import com.squirtles.user.usecase.GetCurrentUidUseCase -import com.squirtles.util.throttleFirst -import dagger.hilt.android.lifecycle.HiltViewModel -import kotlinx.coroutines.async -import kotlinx.coroutines.flow.MutableSharedFlow -import kotlinx.coroutines.flow.MutableStateFlow -import kotlinx.coroutines.flow.asSharedFlow -import kotlinx.coroutines.flow.asStateFlow -import kotlinx.coroutines.launch -import javax.inject.Inject - -@HiltViewModel -class DetailViewModel @Inject constructor( - savedStateHandle: SavedStateHandle, - private val fetchPickUseCase: FetchPickUseCase, - private val fetchIsFavoriteUseCase: FetchIsFavoriteUseCase, - private val getCurrentUidUseCase: GetCurrentUidUseCase, - private val deletePickUseCase: DeletePickUseCase, - private val createFavoriteUseCase: CreateFavoriteUseCase, - private val deleteFavoriteUseCase: DeleteFavoriteUseCase, -) : ViewModel() { - - private val _pickDetailUiState = MutableStateFlow(PickDetailUiState.Loading) - val pickDetailUiState = _pickDetailUiState.asStateFlow() - - private var _currentTab = DETAIL_PICK_TAB - val currentTab get() = _currentTab - - private val _favoriteAction = MutableSharedFlow() - val favoriteAction = _favoriteAction.asSharedFlow() - - private val actionClick = MutableSharedFlow>() - - init { - viewModelScope.launch { - actionClick - .throttleFirst(1000) - .collect { (pickId, isAdding) -> - getUid()?.let { uid -> - if (isAdding) { - addFavorite(pickId, uid) - } else { - deleteFavorite(pickId, uid) - } - } - } - } - } - - fun getUid() = getCurrentUidUseCase() - - fun fetchPick(pickId: String) { - viewModelScope.launch { - _pickDetailUiState.emit(PickDetailUiState.Loading) - - val fetchPick = async { fetchPickUseCase(pickId) } - - val fetchIsFavoriteResult: Result = getUid()?.let { - async { fetchIsFavoriteUseCase(pickId, it) }.await() - } ?: Result.success(false) // 비회원일시 항상 false - val fetchPickResult = fetchPick.await() - - when { - fetchPickResult.isSuccess && fetchIsFavoriteResult.isSuccess -> { - _pickDetailUiState.emit( - PickDetailUiState.Success( - pick = fetchPickResult.getOrDefault(DEFAULT_PICK), - isFavorite = fetchIsFavoriteResult.getOrDefault(false) - ) - ) - } - - else -> { - _pickDetailUiState.emit(PickDetailUiState.Error) - } - } - } - } - - fun toggleFavoritePick(pickId: String, isAdding: Boolean) { - viewModelScope.launch { - actionClick.emit(pickId to isAdding) - } - } - - fun deletePick(pickId: String) { - viewModelScope.launch { - getUid()?.let { uid -> - _pickDetailUiState.emit(PickDetailUiState.Loading) - deletePickUseCase(pickId, uid) - .onSuccess { - _pickDetailUiState.emit(PickDetailUiState.Deleted) - } - .onFailure { - _pickDetailUiState.emit(PickDetailUiState.Error) - } - } - } - } - - private fun addFavorite(pickId: String, uid: String) { - viewModelScope.launch { - createFavoriteUseCase(pickId, uid) - .onSuccess { - _favoriteAction.emit(FavoriteAction.ADDED) - val currentUiState = _pickDetailUiState.value as? PickDetailUiState.Success - currentUiState?.let { successState -> - _pickDetailUiState.emit(successState.copy(isFavorite = true)) - } - } - .onFailure { - _pickDetailUiState.emit(PickDetailUiState.Error) - } - } - } - - private fun deleteFavorite(pickId: String, uid: String) { - viewModelScope.launch { - deleteFavoriteUseCase(pickId, uid) - .onSuccess { - _favoriteAction.emit(FavoriteAction.DELETED) - val currentUiState = _pickDetailUiState.value as? PickDetailUiState.Success - currentUiState?.let { successState -> - _pickDetailUiState.emit(successState.copy(isFavorite = false)) - } - } - .onFailure { - _pickDetailUiState.emit(PickDetailUiState.Error) - } - } - } - - fun setCurrentTab(index: Int) { - _currentTab = index - } - - companion object { - val DEFAULT_PICK = - Pick( - id = "", - song = Song( - id = "", - songName = "", - artistName = "", - albumName = "", - imageUrl = "", - genreNames = listOf(), - bgColor = "#000000".toColorInt(), - externalUrl = "", - previewUrl = "" - ), - comment = "", - createdAt = "", - createdBy = Creator(uid = "", userName = "짱구"), - favoriteCount = 0, - location = LocationPoint(1.0, 1.0), - musicVideoUrl = "", - ) - } -} diff --git a/app/src/main/java/com/squirtles/musicroad/detail/FavoriteAction.kt b/app/src/main/java/com/squirtles/musicroad/detail/FavoriteAction.kt deleted file mode 100644 index 8169095b..00000000 --- a/app/src/main/java/com/squirtles/musicroad/detail/FavoriteAction.kt +++ /dev/null @@ -1,5 +0,0 @@ -package com.squirtles.musicroad.detail - -enum class FavoriteAction { - ADDED, DELETED -} diff --git a/app/src/main/java/com/squirtles/musicroad/detail/PickDetailScreen.kt b/app/src/main/java/com/squirtles/musicroad/detail/PickDetailScreen.kt deleted file mode 100644 index 6b08aee3..00000000 --- a/app/src/main/java/com/squirtles/musicroad/detail/PickDetailScreen.kt +++ /dev/null @@ -1,542 +0,0 @@ -package com.squirtles.musicroad.detail - -import android.app.Activity -import android.content.Context -import android.widget.Toast -import androidx.activity.compose.BackHandler -import androidx.compose.foundation.background -import androidx.compose.foundation.clickable -import androidx.compose.foundation.interaction.MutableInteractionSource -import androidx.compose.foundation.layout.Arrangement -import androidx.compose.foundation.layout.Box -import androidx.compose.foundation.layout.Column -import androidx.compose.foundation.layout.fillMaxSize -import androidx.compose.foundation.layout.fillMaxWidth -import androidx.compose.foundation.layout.height -import androidx.compose.foundation.layout.padding -import androidx.compose.foundation.layout.size -import androidx.compose.foundation.layout.statusBarsPadding -import androidx.compose.foundation.pager.HorizontalPager -import androidx.compose.foundation.pager.rememberPagerState -import androidx.compose.foundation.rememberScrollState -import androidx.compose.foundation.verticalScroll -import androidx.compose.material3.CircularProgressIndicator -import androidx.compose.material3.Scaffold -import androidx.compose.runtime.Composable -import androidx.compose.runtime.DisposableEffect -import androidx.compose.runtime.LaunchedEffect -import androidx.compose.runtime.SideEffect -import androidx.compose.runtime.getValue -import androidx.compose.runtime.mutableIntStateOf -import androidx.compose.runtime.mutableStateOf -import androidx.compose.runtime.remember -import androidx.compose.runtime.rememberCoroutineScope -import androidx.compose.runtime.saveable.rememberSaveable -import androidx.compose.runtime.setValue -import androidx.compose.ui.Alignment -import androidx.compose.ui.Modifier -import androidx.compose.ui.graphics.Brush -import androidx.compose.ui.graphics.Color -import androidx.compose.ui.graphics.graphicsLayer -import androidx.compose.ui.graphics.luminance -import androidx.compose.ui.platform.LocalContext -import androidx.compose.ui.platform.LocalView -import androidx.compose.ui.res.stringResource -import androidx.compose.ui.text.font.FontWeight -import androidx.compose.ui.tooling.preview.Preview -import androidx.compose.ui.unit.dp -import androidx.compose.ui.util.lerp -import androidx.compose.ui.zIndex -import androidx.core.content.ContextCompat.getString -import androidx.core.view.WindowInsetsControllerCompat -import androidx.hilt.navigation.compose.hiltViewModel -import androidx.lifecycle.Lifecycle -import androidx.lifecycle.compose.LocalLifecycleOwner -import androidx.lifecycle.compose.collectAsStateWithLifecycle -import androidx.lifecycle.flowWithLifecycle -import com.squirtles.account.AccountViewModel -import com.squirtles.account.GoogleId -import com.squirtles.common.ui.DialogTextButton -import com.squirtles.common.ui.HorizontalSpacer -import com.squirtles.common.ui.MessageAlertDialog -import com.squirtles.common.ui.SignInAlertDialog -import com.squirtles.common.ui.VerticalSpacer -import com.squirtles.common.ui.theme.Black -import com.squirtles.common.ui.theme.Primary -import com.squirtles.common.ui.theme.White -import com.squirtles.model.Pick -import com.squirtles.musicplayer.PlayerServiceViewModel -import com.squirtles.musicroad.R -import com.squirtles.musicroad.detail.DetailViewModel.Companion.DEFAULT_PICK -import com.squirtles.musicroad.detail.components.CircleAlbumCover -import com.squirtles.musicroad.detail.components.PickDetailCommentText -import com.squirtles.musicroad.detail.components.DetailPickTopAppBar -import com.squirtles.musicroad.detail.components.MusicVideoKnob -import com.squirtles.musicroad.detail.components.PickInformation -import com.squirtles.musicroad.detail.components.SongInfo -import com.squirtles.musicroad.detail.components.music.MusicPlayer -import com.squirtles.musicroad.detail.videoplayer.MusicVideoScreen -import kotlinx.coroutines.launch -import kotlin.math.absoluteValue - -@Composable -fun PickDetailScreen( - pickId: String, - onUserInfoClick: (String) -> Unit, - onBackClick: () -> Unit, - onDeleted: (Context) -> Unit, - playerServiceViewModel: PlayerServiceViewModel, - detailViewModel: DetailViewModel = hiltViewModel(), - accountViewModel: AccountViewModel = hiltViewModel() -) { - val context = LocalContext.current - val lifecycleOwner = LocalLifecycleOwner.current - val uiState by detailViewModel.pickDetailUiState.collectAsStateWithLifecycle() - var showDeletePickDialog by rememberSaveable { mutableStateOf(false) } - var showProcessIndicator by rememberSaveable { mutableStateOf(false) } - var isMusicVideoAvailable by remember { mutableStateOf(false) } - - // Sign In Dialog - var showSignInDialog by remember { mutableStateOf(false) } - var signInDialogDescription by remember { mutableStateOf("") } - - BackHandler { - if (showProcessIndicator.not()) { - onBackClick() - } - } - - LaunchedEffect(Unit) { - detailViewModel.fetchPick(pickId) - - launch { - accountViewModel.signInSuccess - .flowWithLifecycle(lifecycleOwner.lifecycle, Lifecycle.State.STARTED) - .collect { isSuccess -> - if (isSuccess) { - showProcessIndicator = false - detailViewModel.fetchPick(pickId) - } - } - } - } - - when (uiState) { - PickDetailUiState.Loading -> { - Box( - modifier = Modifier - .fillMaxSize() - .background(Black), - contentAlignment = Alignment.Center - ) { - CircularProgressIndicator() - } - } - - is PickDetailUiState.Success -> { - val lifecycleOwner = LocalLifecycleOwner.current - val pick = (uiState as PickDetailUiState.Success).pick - val isFavorite = (uiState as PickDetailUiState.Success).isFavorite - val isNonMember = detailViewModel.getUid() == null - val isCreatedBySelf = detailViewModel.getUid() == pick.createdBy.uid - var favoriteCount by rememberSaveable { mutableIntStateOf(pick.favoriteCount) } - val onActionClick: () -> Unit = { - when { - isNonMember -> { - signInDialogDescription = getString(context, R.string.sign_in_dialog_title_favorite) - showSignInDialog = true - } - - isCreatedBySelf -> { - playerServiceViewModel.onPause() - showDeletePickDialog = true - } - - isFavorite -> { - showProcessIndicator = true - detailViewModel.toggleFavoritePick( - pickId = pickId, - isAdding = false - ) - } - - else -> { - showProcessIndicator = true - detailViewModel.toggleFavoritePick( - pickId = pickId, - isAdding = true - ) - } - } - } - - val scrollScope = rememberCoroutineScope() - val pagerState = rememberPagerState( - pageCount = { if (isMusicVideoAvailable) 2 else 1 } - ) - - LaunchedEffect(Unit) { - detailViewModel.favoriteAction - .flowWithLifecycle(lifecycleOwner.lifecycle, Lifecycle.State.STARTED) - .collect { action -> - when (action) { - FavoriteAction.ADDED -> { - showProcessIndicator = false - favoriteCount += 1 - context.showShortToast(context.getString(R.string.success_add_to_favorite)) - } - - FavoriteAction.DELETED -> { - showProcessIndicator = false - favoriteCount -= 1 - context.showShortToast(context.getString(R.string.success_delete_at_favorite)) - } - } - } - } - - // 비디오 플레이어 설정 - LaunchedEffect(pick) { - playerServiceViewModel.readyPlayer() - playerServiceViewModel.setMediaItem(pick) - isMusicVideoAvailable = pick.musicVideoUrl.isNotEmpty() - } - - LaunchedEffect(pagerState) { - pagerState.scrollToPage(page = detailViewModel.currentTab) - } - - DisposableEffect(Unit) { - onDispose { - detailViewModel.setCurrentTab(pagerState.currentPage) - } - } - - HorizontalPager( - state = pagerState - ) { page -> - when (page) { - DETAIL_PICK_TAB -> { - PickDetailContents( - pick = pick, - currentUid = detailViewModel.getUid(), - isFavorite = isFavorite, - pickUid = pick.createdBy.uid, - pickUserName = pick.createdBy.userName, - favoriteCount = favoriteCount, - isMusicVideoAvailable = isMusicVideoAvailable, - onUserInfoClick = onUserInfoClick, - playerServiceViewModel = playerServiceViewModel, - onBackClick = { - onBackClick() - }, - onActionClick = onActionClick, - ) - } - - MUSIC_VIDEO_TAB -> { - MusicVideoScreen( - pick = pick, - modifier = Modifier - .background(Black) - .graphicsLayer { - val pageOffset = ( - (pagerState.currentPage - page) + pagerState - .currentPageOffsetFraction - ).absoluteValue - alpha = lerp( - start = 0.5f, - stop = 1f, - fraction = 1f - pageOffset.coerceIn(0f, 1f) - ) - }, - onBackClick = { - scrollScope.launch { - pagerState.animateScrollToPage(page = DETAIL_PICK_TAB) - } - }, - ) - } - } - - // 페이지 전환에 따른 음원과 뮤비 재생 상태 - if (page != DETAIL_PICK_TAB) playerServiceViewModel.onPause() - } - } - - PickDetailUiState.Deleted -> { - LaunchedEffect(Unit) { - onBackClick() - onDeleted(context) - Toast.makeText( - context, - context.getString(R.string.success_delete_pick), - Toast.LENGTH_SHORT - ).show() - } - } - - PickDetailUiState.Error -> { - LaunchedEffect(Unit) { - Toast.makeText( - context, - context.getString(R.string.error_loading_pick_list), - Toast.LENGTH_SHORT - ).show() - } - - // Show default pick - PickDetailContents( - pick = DEFAULT_PICK, - currentUid = null, - isFavorite = false, - pickUid = "", - pickUserName = "", - favoriteCount = 0, - isMusicVideoAvailable = false, - playerServiceViewModel = playerServiceViewModel, - onUserInfoClick = onUserInfoClick, - onBackClick = onBackClick, - onActionClick = { } - ) - } - } - - if (showDeletePickDialog) { - MessageAlertDialog( - onDismissRequest = { - showDeletePickDialog = false - }, - title = stringResource(R.string.delete_pick_dialog_title), - body = stringResource(R.string.delete_pick_dialog_body), - buttons = { - DialogTextButton( - onClick = { - showDeletePickDialog = false - }, - text = stringResource(R.string.delete_pick_dialog_cancel) - ) - - HorizontalSpacer(8) - - DialogTextButton( - onClick = { - showDeletePickDialog = false - detailViewModel.deletePick(pickId) - }, - text = stringResource(R.string.delete_pick_dialog_delete), - textColor = Primary, - fontWeight = FontWeight.Bold - ) - }, - ) - } - - if (showProcessIndicator) { - Box( - modifier = Modifier - .fillMaxSize() - .background(Black.copy(alpha = 0.5F)) - .clickable( // 클릭 효과 제거 및 클릭 이벤트 무시 - interactionSource = remember { MutableInteractionSource() }, - indication = null, - onClick = {} - ), - contentAlignment = Alignment.Center - ) { - CircularProgressIndicator() - } - } - - if (showSignInDialog) { - SignInAlertDialog( - onDismissRequest = { showSignInDialog = false }, - onGoogleSignInClick = { - showSignInDialog = false - showProcessIndicator = true - GoogleId(context).signIn( - onSuccess = { uid, credential -> - accountViewModel.signIn(uid, credential) - } - ) - }, - description = signInDialogDescription - ) - } -} - -@Composable -private fun PickDetailContents( - pick: Pick, - isFavorite: Boolean, - currentUid: String?, - pickUid: String, - pickUserName: String, - favoriteCount: Int, - isMusicVideoAvailable: Boolean, - playerServiceViewModel: PlayerServiceViewModel, - onUserInfoClick: (String) -> Unit, - onBackClick: () -> Unit, - onActionClick: () -> Unit -) { - val isCreatedBySelf = remember { currentUid == pickUid } - val scrollState = rememberScrollState() - val dynamicBackgroundColor = Color(pick.song.bgColor) - val onDynamicBackgroundColor = if (dynamicBackgroundColor.luminance() >= 0.5f) Black else White - val view = LocalView.current - - val audioEffectColor = dynamicBackgroundColor.copy( - red = (dynamicBackgroundColor.red + 0.2f).coerceAtMost(1.0f), - green = (dynamicBackgroundColor.green + 0.2f).coerceAtMost(1.0f), - blue = (dynamicBackgroundColor.blue + 0.2f).coerceAtMost(1.0f), - ) - - val playerUiState by playerServiceViewModel.playerState.collectAsStateWithLifecycle() - val audioSessionId by playerServiceViewModel.audioSessionId.collectAsStateWithLifecycle(0) - - if (!view.isInEditMode) { - SideEffect { - val window = (view.context as Activity).window - val windowInsetsController = WindowInsetsControllerCompat(window, view) - val isLightStatusBar = dynamicBackgroundColor.luminance() >= 0.5f - - windowInsetsController.isAppearanceLightStatusBars = isLightStatusBar - } - } - - Scaffold( - topBar = { - DetailPickTopAppBar( - modifier = Modifier.statusBarsPadding(), - isCreatedBySelf = isCreatedBySelf, - isFavorite = isFavorite, - uid = pickUid, - userName = pickUserName, - onDynamicBackgroundColor = onDynamicBackgroundColor, - onUserInfoClick = onUserInfoClick, - onBackClick = { - onBackClick() - }, - onActionClick = { - onActionClick() - } - ) - } - ) { innerPadding -> - - Column( - modifier = Modifier - .fillMaxSize() - .background( - brush = Brush.verticalGradient( - colorStops = arrayOf( - 0.0f to dynamicBackgroundColor, - 0.47f to Black - ) - ) - ) - .padding(innerPadding) - ) { - Column( - modifier = Modifier - .fillMaxWidth() - .weight(1f) - .verticalScroll(scrollState) - .padding(top = 10.dp), - horizontalAlignment = Alignment.CenterHorizontally, - verticalArrangement = Arrangement.spacedBy(10.dp), - ) { - SongInfo( - song = pick.song, - dynamicOnBackgroundColor = onDynamicBackgroundColor, - modifier = Modifier.zIndex(1f) - ) - - Box( - modifier = Modifier - .fillMaxWidth() - .height(320.dp) - .align(Alignment.CenterHorizontally) - .zIndex(0f) - ) { - if (audioSessionId != 0) { - CircleAlbumCover( - modifier = Modifier - .size(320.dp) - .align(Alignment.Center), - song = pick.song, - currentPosition = { playerUiState.currentPosition }, - duration = { playerUiState.duration }, - audioEffectColor = audioEffectColor, - audioSessionId = audioSessionId, - onSeekChanged = { timeMs -> - playerServiceViewModel.onSeekingFinished(timeMs) - }, - ) - } - - if (isMusicVideoAvailable) { - MusicVideoKnob( - thumbnail = pick.musicVideoThumbnailUrl, - modifier = Modifier.align(Alignment.CenterEnd) - ) - } - } - - PickInformation( - formattedDate = pick.createdAt, - favoriteCount = favoriteCount - ) - - PickDetailCommentText(comment = pick.comment) - - VerticalSpacer(height = 8) - } - - if (pick.song.previewUrl.isBlank().not()) { - MusicPlayer( - song = pick.song, - playerState = playerUiState, - onSeekChanged = { timeMs -> - playerServiceViewModel.onSeekingFinished(timeMs) - }, - onReplayForwardClick = { isForward -> - if (isForward) { - playerServiceViewModel.onAdvanceBy() - } else { - playerServiceViewModel.onRewindBy() - } - }, - onPauseToggle = { song -> - playerServiceViewModel.togglePlayPause(song) - }, - ) - } - } - } -} - -fun Context.showShortToast(message: String) { - Toast.makeText(this, message, Toast.LENGTH_SHORT).show() -} - -@Preview -@Composable -private fun PickDetailPreview() { - PickDetailContents( - pick = DEFAULT_PICK, - currentUid = null, - isFavorite = false, - pickUid = "", - pickUserName = "짱구", - favoriteCount = 0, - isMusicVideoAvailable = true, - onUserInfoClick = {}, - playerServiceViewModel = hiltViewModel(), - onBackClick = {}, - onActionClick = {}, - ) -} - -internal const val DETAIL_PICK_TAB = 0 -internal const val MUSIC_VIDEO_TAB = 1 diff --git a/app/src/main/java/com/squirtles/musicroad/detail/PickDetailUiState.kt b/app/src/main/java/com/squirtles/musicroad/detail/PickDetailUiState.kt deleted file mode 100644 index 0292db04..00000000 --- a/app/src/main/java/com/squirtles/musicroad/detail/PickDetailUiState.kt +++ /dev/null @@ -1,10 +0,0 @@ -package com.squirtles.musicroad.detail - -import com.squirtles.model.Pick - -sealed class PickDetailUiState { - data object Loading : PickDetailUiState() - data class Success(val pick: Pick, val isFavorite: Boolean) : PickDetailUiState() - data object Deleted : PickDetailUiState() - data object Error : PickDetailUiState() -} diff --git a/app/src/main/java/com/squirtles/musicroad/detail/components/CircleAlbumCover.kt b/app/src/main/java/com/squirtles/musicroad/detail/components/CircleAlbumCover.kt deleted file mode 100644 index c0b3cc86..00000000 --- a/app/src/main/java/com/squirtles/musicroad/detail/components/CircleAlbumCover.kt +++ /dev/null @@ -1,82 +0,0 @@ -package com.squirtles.musicroad.detail.components - -import androidx.compose.foundation.layout.Box -import androidx.compose.foundation.layout.aspectRatio -import androidx.compose.foundation.layout.fillMaxSize -import androidx.compose.foundation.layout.fillMaxWidth -import androidx.compose.foundation.layout.padding -import androidx.compose.foundation.shape.CircleShape -import androidx.compose.runtime.Composable -import androidx.compose.ui.Alignment -import androidx.compose.ui.Modifier -import androidx.compose.ui.draw.clip -import androidx.compose.ui.graphics.Color -import androidx.compose.ui.layout.ContentScale -import androidx.compose.ui.res.stringResource -import androidx.compose.ui.unit.dp -import coil3.compose.AsyncImage -import com.miller198.audiovisualizer.configs.GradientConfig -import com.miller198.audiovisualizer.configs.VisualizerConfig -import com.miller198.audiovisualizer.soundeffect.SoundEffects -import com.miller198.audiovisualizer.ui.CircleVisualizer -import com.squirtles.model.Song -import com.squirtles.musicroad.R - -@Composable -internal fun CircleAlbumCover( - audioSessionId: Int, - song: Song, - currentPosition: () -> Long, - duration: () -> Long, - audioEffectColor: Color, - onSeekChanged: (Long) -> Unit, - modifier: Modifier = Modifier, -) { - Box( - modifier = modifier - ) { - CircleVisualizer( - audioSessionId = audioSessionId, - soundEffects = SoundEffects.BAR, - visualizerConfig = VisualizerConfig.FftCaptureConfig.Default, - gradientConfig = GradientConfig.Enabled( - color = audioEffectColor.mixedWhite(), - duration = 2500 - ), - color = audioEffectColor, - modifier = modifier.align(Alignment.Center) - ) - - PlayCircularProgressIndicator( - modifier = modifier - .fillMaxSize() - .padding(10.dp) - .align(Alignment.Center), - currentTime = currentPosition().toFloat(), - duration = duration().toFloat(), - strokeWidth = 5.dp, - onSeekChanged = { timeMs -> - onSeekChanged(timeMs.toLong()) - }, - ) - - AsyncImage( - model = song.getImageUrlWithSize(400, 400), - contentDescription = song.albumName + stringResource(id = R.string.pick_album_description), - modifier = Modifier - .fillMaxWidth() - .padding(horizontal = 30.dp) - .aspectRatio(1f) - .clip(CircleShape) - .align(Alignment.Center), - contentScale = ContentScale.Crop - ) - } -} - -private fun Color.mixedWhite(): Color = Color( - red = (Color.White.red + this.red) / 2, - green = (Color.White.green + this.green) / 2, - blue = (Color.White.blue + this.blue) / 2, - alpha = 0.9f -) diff --git a/app/src/main/java/com/squirtles/musicroad/detail/components/DetailPickTopAppBar.kt b/app/src/main/java/com/squirtles/musicroad/detail/components/DetailPickTopAppBar.kt deleted file mode 100644 index 1f777ce3..00000000 --- a/app/src/main/java/com/squirtles/musicroad/detail/components/DetailPickTopAppBar.kt +++ /dev/null @@ -1,104 +0,0 @@ -package com.squirtles.musicroad.detail.components - -import androidx.compose.foundation.clickable -import androidx.compose.foundation.interaction.MutableInteractionSource -import androidx.compose.foundation.layout.Row -import androidx.compose.material.icons.Icons -import androidx.compose.material.icons.automirrored.filled.ArrowBack -import androidx.compose.material3.CenterAlignedTopAppBar -import androidx.compose.material3.ExperimentalMaterial3Api -import androidx.compose.material3.Icon -import androidx.compose.material3.IconButton -import androidx.compose.material3.MaterialTheme -import androidx.compose.material3.TopAppBarDefaults -import androidx.compose.runtime.Composable -import androidx.compose.runtime.remember -import androidx.compose.ui.Alignment -import androidx.compose.ui.Modifier -import androidx.compose.ui.graphics.Color -import androidx.compose.ui.res.painterResource -import androidx.compose.ui.res.stringResource -import androidx.compose.ui.text.font.FontWeight -import com.squirtles.common.ui.CreatedByOtherUserText -import com.squirtles.common.ui.CreatedBySelfText -import com.squirtles.musicroad.R - -@OptIn(ExperimentalMaterial3Api::class) -@Composable -fun DetailPickTopAppBar( - modifier: Modifier = Modifier, - isCreatedBySelf: Boolean, - isFavorite: Boolean, - uid: String, - userName: String, - onDynamicBackgroundColor: Color, - onUserInfoClick: (String) -> Unit, - onBackClick: () -> Unit, - onActionClick: () -> Unit, -) { - CenterAlignedTopAppBar( - title = { - Row( - modifier = Modifier.clickable( - interactionSource = remember { MutableInteractionSource() }, - indication = null, - onClick = { onUserInfoClick(uid) } - ), - verticalAlignment = Alignment.CenterVertically - ) { - if (isCreatedBySelf) { - CreatedBySelfText( - showUnderline = true, - color = onDynamicBackgroundColor, - style = MaterialTheme.typography.titleLarge.copy(fontWeight = FontWeight.Bold) - ) - } else { - CreatedByOtherUserText( - userName = userName, - modifier = Modifier.weight(weight = 1f, fill = false), - showUnderline = true, - color = onDynamicBackgroundColor, - style = MaterialTheme.typography.titleLarge.copy(fontWeight = FontWeight.Bold) - ) - } - } - }, - modifier = modifier, - navigationIcon = { - IconButton( - onClick = { onBackClick() } - ) { - Icon( - imageVector = Icons.AutoMirrored.Filled.ArrowBack, - contentDescription = stringResource(id = R.string.top_app_bar_back_description), - tint = onDynamicBackgroundColor - ) - } - }, - actions = { - IconButton( - onClick = { onActionClick() } - ) { - val painterResourceId = when { - isCreatedBySelf -> R.drawable.ic_delete - isFavorite -> R.drawable.ic_favorite_true - else -> R.drawable.ic_favorite_false - } - val stringResourceId = when { - isCreatedBySelf -> R.string.pick_delete_icon_description - isFavorite -> R.string.pick_favorite_true_icon_description - else -> R.string.pick_favorite_false_icon_description - } - - Icon( - painter = painterResource(painterResourceId), - contentDescription = stringResource(stringResourceId), - tint = onDynamicBackgroundColor - ) - } - }, - colors = TopAppBarDefaults.centerAlignedTopAppBarColors( - containerColor = Color.Transparent - ) - ) -} diff --git a/app/src/main/java/com/squirtles/musicroad/detail/components/MusicVideoKnob.kt b/app/src/main/java/com/squirtles/musicroad/detail/components/MusicVideoKnob.kt deleted file mode 100644 index e50178e3..00000000 --- a/app/src/main/java/com/squirtles/musicroad/detail/components/MusicVideoKnob.kt +++ /dev/null @@ -1,80 +0,0 @@ -package com.squirtles.musicroad.detail.components - -import android.util.Size -import androidx.compose.animation.core.FastOutSlowInEasing -import androidx.compose.animation.core.RepeatMode -import androidx.compose.animation.core.animateFloat -import androidx.compose.animation.core.infiniteRepeatable -import androidx.compose.animation.core.rememberInfiniteTransition -import androidx.compose.animation.core.tween -import androidx.compose.foundation.layout.fillMaxSize -import androidx.compose.foundation.layout.offset -import androidx.compose.foundation.layout.padding -import androidx.compose.foundation.layout.size -import androidx.compose.foundation.shape.RoundedCornerShape -import androidx.compose.material3.Surface -import androidx.compose.runtime.Composable -import androidx.compose.runtime.getValue -import androidx.compose.ui.Modifier -import androidx.compose.ui.draw.clip -import androidx.compose.ui.graphics.Color -import androidx.compose.ui.graphics.painter.ColorPainter -import androidx.compose.ui.layout.ContentScale -import androidx.compose.ui.platform.LocalContext -import androidx.compose.ui.res.stringResource -import androidx.compose.ui.tooling.preview.Preview -import androidx.compose.ui.unit.dp -import coil3.compose.AsyncImage -import coil3.request.ImageRequest -import com.squirtles.common.ui.theme.White -import com.squirtles.common.ui.toImageUrlWithSize -import com.squirtles.musicroad.R - -@Composable -internal fun MusicVideoKnob( - thumbnail: String, - modifier: Modifier -) { - val infiniteTransition = rememberInfiniteTransition(label = "infinite repeatable") - val offsetX by infiniteTransition.animateFloat( - initialValue = 8f, - targetValue = 0f, - animationSpec = infiniteRepeatable( - animation = tween(durationMillis = 600, easing = FastOutSlowInEasing), - repeatMode = RepeatMode.Reverse - ), - label = "infinite repeatable" - ) - - Surface( - modifier = modifier - .size(width = 16.dp, height = 320.dp) - .offset(x = offsetX.dp), - color = White, - shape = RoundedCornerShape(topStart = 30.dp, bottomStart = 30.dp) - ) { - AsyncImage( - model = ImageRequest.Builder(LocalContext.current) - .data(thumbnail.toImageUrlWithSize(Size(560, 320))) - .build(), - contentDescription = stringResource(R.string.pick_swipe_icon_description), - modifier = Modifier - .fillMaxSize() - .padding(vertical = 2.dp) - .padding(start = 2.dp) - .clip(RoundedCornerShape(topStart = 30.dp, bottomStart = 30.dp)), - placeholder = ColorPainter(Color.Transparent), - error = ColorPainter(Color.Transparent), - contentScale = ContentScale.Crop, - ) - } -} - -@Preview -@Composable -private fun MusicVideoKnobPreview() { - MusicVideoKnob( - thumbnail = "", - modifier = Modifier - ) -} diff --git a/app/src/main/java/com/squirtles/musicroad/detail/components/PickDetailCommentText.kt b/app/src/main/java/com/squirtles/musicroad/detail/components/PickDetailCommentText.kt deleted file mode 100644 index 38606fa1..00000000 --- a/app/src/main/java/com/squirtles/musicroad/detail/components/PickDetailCommentText.kt +++ /dev/null @@ -1,56 +0,0 @@ -package com.squirtles.musicroad.detail.components - -import androidx.compose.foundation.background -import androidx.compose.foundation.layout.Arrangement -import androidx.compose.foundation.layout.Column -import androidx.compose.foundation.layout.fillMaxWidth -import androidx.compose.foundation.layout.heightIn -import androidx.compose.foundation.layout.padding -import androidx.compose.foundation.layout.wrapContentHeight -import androidx.compose.foundation.shape.RoundedCornerShape -import androidx.compose.material3.MaterialTheme -import androidx.compose.material3.Text -import androidx.compose.runtime.Composable -import androidx.compose.ui.Modifier -import androidx.compose.ui.draw.clip -import androidx.compose.ui.res.stringResource -import androidx.compose.ui.tooling.preview.Preview -import androidx.compose.ui.unit.dp -import com.squirtles.common.ui.theme.Dark -import com.squirtles.common.ui.theme.Gray -import com.squirtles.common.ui.theme.White -import com.squirtles.musicroad.R - -@Composable -internal fun PickDetailCommentText( - comment: String, - modifier: Modifier = Modifier -) { - Text( - text = comment.ifEmpty { stringResource(id = R.string.pick_comment_empty) }, - modifier = modifier - .fillMaxWidth() - .wrapContentHeight() - .heightIn(min = 100.dp) - .padding(horizontal = 30.dp) - .clip(shape = RoundedCornerShape(10.dp)) - .background(Dark) - .padding(horizontal = 12.dp, vertical = 8.dp), - style = MaterialTheme.typography.bodyLarge.copy(if (comment.isNotEmpty()) White else Gray) - ) -} - -@Preview -@Composable -private fun CommentTextPreview() { - Column(verticalArrangement = Arrangement.spacedBy(10.dp)) { - PickDetailCommentText(comment = "") - - PickDetailCommentText(comment = "노래가 좋아서 추천합니다.") - - PickDetailCommentText( - comment = "노래가 너무너무너무너무너무너무너무너무너무너무너무너무너무너무너무너무너무너무너무너무너무너무너무너무너무너무" + - "너무너무너무너무너무너무너무너무너무너무너무너무너무너무너무너무너무너무너무너무너무너무 좋아요" - ) - } -} diff --git a/app/src/main/java/com/squirtles/musicroad/detail/components/PickInformation.kt b/app/src/main/java/com/squirtles/musicroad/detail/components/PickInformation.kt deleted file mode 100644 index 6f727683..00000000 --- a/app/src/main/java/com/squirtles/musicroad/detail/components/PickInformation.kt +++ /dev/null @@ -1,39 +0,0 @@ -package com.squirtles.musicroad.detail.components - -import androidx.compose.foundation.layout.Arrangement -import androidx.compose.foundation.layout.Row -import androidx.compose.foundation.layout.fillMaxWidth -import androidx.compose.foundation.layout.padding -import androidx.compose.material3.Icon -import androidx.compose.material3.MaterialTheme -import androidx.compose.material3.Text -import androidx.compose.runtime.Composable -import androidx.compose.ui.Alignment -import androidx.compose.ui.Modifier -import androidx.compose.ui.res.painterResource -import androidx.compose.ui.res.stringResource -import androidx.compose.ui.unit.dp -import com.squirtles.common.ui.theme.Gray -import com.squirtles.musicroad.R - -@Composable -internal fun PickInformation(formattedDate: String, favoriteCount: Int) { - Row( - modifier = Modifier - .fillMaxWidth() - .padding(horizontal = 30.dp, vertical = 4.dp), - horizontalArrangement = Arrangement.spacedBy(4.dp), - verticalAlignment = Alignment.CenterVertically - ) { - if (formattedDate.isNotBlank()) { - Text(text = formattedDate, style = MaterialTheme.typography.titleMedium.copy(Gray)) - Icon( - painter = painterResource(id = R.drawable.ic_favorite), - contentDescription = stringResource(R.string.pick_favorite_count_icon_description), - modifier = Modifier.padding(start = 4.dp), - tint = Gray - ) - Text(text = "$favoriteCount", style = MaterialTheme.typography.titleMedium.copy(Gray)) - } - } -} diff --git a/app/src/main/java/com/squirtles/musicroad/detail/components/PlayCircularProgressIndicator.kt b/app/src/main/java/com/squirtles/musicroad/detail/components/PlayCircularProgressIndicator.kt deleted file mode 100644 index 82081c47..00000000 --- a/app/src/main/java/com/squirtles/musicroad/detail/components/PlayCircularProgressIndicator.kt +++ /dev/null @@ -1,65 +0,0 @@ -package com.squirtles.musicroad.detail.components - -import androidx.compose.foundation.gestures.detectTapGestures -import androidx.compose.foundation.layout.Box -import androidx.compose.foundation.layout.aspectRatio -import androidx.compose.foundation.layout.fillMaxSize -import androidx.compose.material3.CircularProgressIndicator -import androidx.compose.runtime.Composable -import androidx.compose.ui.Modifier -import androidx.compose.ui.graphics.Color -import androidx.compose.ui.graphics.StrokeCap -import androidx.compose.ui.input.pointer.pointerInput -import androidx.compose.ui.unit.Dp -import androidx.compose.ui.unit.dp -import com.squirtles.common.ui.theme.White -import kotlin.math.atan2 -import kotlin.math.hypot - -@Composable -internal fun PlayCircularProgressIndicator( - modifier: Modifier = Modifier, - currentTime: Float, - strokeWidth: Dp, - duration: Float, - innerRadiusRatio: Float = 0.5f, // 터치 비활성 비율 - onSeekChanged: (Float) -> Unit -) { - Box( - modifier = modifier - .aspectRatio(1f) - .pointerInput(Unit) { - detectTapGestures { offset -> - val centerX = size.width / 2 - val centerY = size.height / 2 - - val distanceFromCenter = hypot(offset.x - centerX, offset.y - centerY) // 터치 좌표랑 중심 사이 - val innerRadius = size.width * innerRadiusRatio / 2 - if (distanceFromCenter < innerRadius) return@detectTapGestures // 중앙의 반경 내 터치 무시 - - // 중심 기준 터치좌표의 각도 - val angle = Math - .toDegrees(atan2(offset.y - centerY, offset.x - centerX).toDouble()) - .toFloat() - - // 원의 위쪽 점(12시 방향)을 기준으로 시계 방향 - val normalizedAngle = ((angle + 360) % 360 + 90) % 360 - - // 각도를 재생 시간으로 변환 - val seekTime = (normalizedAngle / 360f) * duration - onSeekChanged(seekTime) - } - } - ) { - CircularProgressIndicator( - modifier = Modifier.fillMaxSize(), - progress = { currentTime / duration }, - color = White, - trackColor = Color.Transparent, - strokeWidth = strokeWidth, - strokeCap = StrokeCap.Round, - gapSize = 0.dp, - ) - } -} - diff --git a/app/src/main/java/com/squirtles/musicroad/detail/components/SongInfo.kt b/app/src/main/java/com/squirtles/musicroad/detail/components/SongInfo.kt deleted file mode 100644 index 2fad5d8f..00000000 --- a/app/src/main/java/com/squirtles/musicroad/detail/components/SongInfo.kt +++ /dev/null @@ -1,39 +0,0 @@ -package com.squirtles.musicroad.detail.components - -import androidx.compose.foundation.layout.Column -import androidx.compose.foundation.layout.fillMaxWidth -import androidx.compose.material3.MaterialTheme -import androidx.compose.material3.Text -import androidx.compose.runtime.Composable -import androidx.compose.ui.Alignment -import androidx.compose.ui.Modifier -import androidx.compose.ui.graphics.Color -import androidx.compose.ui.text.font.FontWeight -import androidx.compose.ui.text.style.TextAlign -import com.squirtles.model.Song - -@Composable -internal fun SongInfo( - song: Song, - dynamicOnBackgroundColor: Color, - modifier: Modifier, -) { - Column( - modifier = modifier.fillMaxWidth(), - horizontalAlignment = Alignment.CenterHorizontally - ) { - Text( - text = song.songName, - color = dynamicOnBackgroundColor, - textAlign = TextAlign.Center, - style = MaterialTheme.typography.titleLarge.copy(fontWeight = FontWeight.Bold) - ) - - Text( - text = song.artistName, - color = dynamicOnBackgroundColor, - textAlign = TextAlign.Center, - style = MaterialTheme.typography.bodyLarge - ) - } -} diff --git a/app/src/main/java/com/squirtles/musicroad/detail/components/SwipeUpIcon.kt b/app/src/main/java/com/squirtles/musicroad/detail/components/SwipeUpIcon.kt deleted file mode 100644 index 2f44b375..00000000 --- a/app/src/main/java/com/squirtles/musicroad/detail/components/SwipeUpIcon.kt +++ /dev/null @@ -1,32 +0,0 @@ -package com.squirtles.musicroad.detail.components - -import androidx.compose.foundation.layout.Box -import androidx.compose.foundation.layout.fillMaxWidth -import androidx.compose.foundation.layout.heightIn -import androidx.compose.material3.Icon -import androidx.compose.runtime.Composable -import androidx.compose.ui.Alignment -import androidx.compose.ui.Modifier -import androidx.compose.ui.res.painterResource -import androidx.compose.ui.res.stringResource -import androidx.compose.ui.unit.dp -import com.squirtles.common.ui.theme.White -import com.squirtles.musicroad.R - -@Composable -internal fun SwipeUpIcon( - swipeableModifier: Modifier -) { - Box( - modifier = swipeableModifier - .fillMaxWidth() - .heightIn(min = 100.dp) - ) { - Icon( - painter = painterResource(id = R.drawable.ic_swipe), - contentDescription = stringResource(id = R.string.pick_swipe_icon_description), - modifier = Modifier.align(Alignment.Center), - tint = White - ) - } -} diff --git a/app/src/main/java/com/squirtles/musicroad/detail/components/music/MusicPlayer.kt b/app/src/main/java/com/squirtles/musicroad/detail/components/music/MusicPlayer.kt deleted file mode 100644 index 9a2aa993..00000000 --- a/app/src/main/java/com/squirtles/musicroad/detail/components/music/MusicPlayer.kt +++ /dev/null @@ -1,55 +0,0 @@ -package com.squirtles.musicroad.detail.components.music - -import androidx.compose.foundation.background -import androidx.compose.foundation.layout.Column -import androidx.compose.foundation.layout.fillMaxWidth -import androidx.compose.foundation.layout.padding -import androidx.compose.foundation.shape.RoundedCornerShape -import androidx.compose.runtime.Composable -import androidx.compose.ui.Alignment -import androidx.compose.ui.Modifier -import androidx.compose.ui.unit.dp -import com.squirtles.common.ui.Constants.DEFAULT_PADDING -import com.squirtles.common.ui.theme.PlayerBackground -import com.squirtles.model.PlayerState -import com.squirtles.model.Song - -@Composable -fun MusicPlayer( - song: Song, - playerState: PlayerState, - onSeekChanged: (Long) -> Unit, - onReplayForwardClick: (Boolean) -> Unit, - onPauseToggle: (Song) -> Unit, -) { - Column( - modifier = Modifier - .fillMaxWidth() - .padding(horizontal = 8.dp, vertical = DEFAULT_PADDING) - .background( - color = PlayerBackground, - shape = RoundedCornerShape(16.dp) - ) - .padding(horizontal = 30.dp, vertical = DEFAULT_PADDING), - horizontalAlignment = Alignment.CenterHorizontally, - ) { - PlayBar( - duration = playerState.duration, - currentTime = playerState.currentPosition, - bufferPercentage = playerState.bufferPercentage, - isPlaying = playerState.isPlaying, - onSeekChanged = { timeMs -> - onSeekChanged(timeMs.toLong()) - }, - onReplayClick = { - onReplayForwardClick(false) - }, - onPauseToggle = { - onPauseToggle(song) - }, - onForwardClick = { - onReplayForwardClick(true) - }, - ) - } -} diff --git a/app/src/main/java/com/squirtles/musicroad/detail/components/music/PlayBar.kt b/app/src/main/java/com/squirtles/musicroad/detail/components/music/PlayBar.kt deleted file mode 100644 index 26227450..00000000 --- a/app/src/main/java/com/squirtles/musicroad/detail/components/music/PlayBar.kt +++ /dev/null @@ -1,99 +0,0 @@ -package com.squirtles.musicroad.detail.components.music - -import androidx.compose.foundation.layout.Arrangement -import androidx.compose.foundation.layout.Box -import androidx.compose.foundation.layout.Column -import androidx.compose.foundation.layout.Row -import androidx.compose.foundation.layout.fillMaxWidth -import androidx.compose.foundation.layout.padding -import androidx.compose.material3.Text -import androidx.compose.runtime.Composable -import androidx.compose.ui.Modifier -import androidx.compose.ui.tooling.preview.Preview -import androidx.compose.ui.unit.dp -import com.squirtles.common.ui.theme.Gray -import com.squirtles.common.ui.theme.MusicRoadTheme -import java.util.concurrent.TimeUnit - -@Composable -internal fun PlayBar( - duration: Long, - currentTime: Long, - bufferPercentage: Int, - isPlaying: Boolean, - onSeekChanged: (timeMs: Float) -> Unit, - onReplayClick: () -> Unit, - onPauseToggle: () -> Unit, - onForwardClick: () -> Unit, - modifier: Modifier = Modifier, -) { - Column(modifier = modifier) { - Box( - modifier = Modifier - .fillMaxWidth() - .padding(top = 12.dp) - ) { - PlayProgressIndicator( - currentTime = currentTime.toFloat(), - duration = duration.toFloat(), - bufferPercentage = bufferPercentage.toFloat(), - onSeekChanged = onSeekChanged - ) - } - - Row( - modifier = Modifier - .fillMaxWidth() - .padding(top = 10.dp), - horizontalArrangement = Arrangement.SpaceBetween - ) { - Text( - text = currentTime.formatMinSec(), - color = Gray - ) - Box(modifier = Modifier.weight(2f)) { - PlayerControls( - modifier = Modifier.fillMaxWidth(), - isPlaying = isPlaying, - onReplayClick = onReplayClick, - onPauseToggle = onPauseToggle, - onForwardClick = onForwardClick - ) - } - Text( - text = duration.formatMinSec(), - color = Gray - ) - } - } -} - -private fun Long.formatMinSec(): String { - return if (this == 0L) { - "00:00" - } else { - val totalSeconds = - TimeUnit.MILLISECONDS.toSeconds(this) + (this % 1000 / 500) // 500ms 이상일 경우 반올림 - val minutes = totalSeconds / 60 - val seconds = totalSeconds % 60 - "${minutes.toString().padStart(2, '0')}:${seconds.toString().padStart(2, '0')}" - } -} - -@Preview -@Composable -private fun PlayBarPreview() { - MusicRoadTheme { - PlayBar( - modifier = Modifier.fillMaxWidth(), - duration = 10000L, - currentTime = 5000L, - bufferPercentage = 50, - onSeekChanged = {}, - isPlaying = true, - onReplayClick = {}, - onPauseToggle = {}, - onForwardClick = {} - ) - } -} diff --git a/app/src/main/java/com/squirtles/musicroad/detail/components/music/PlayProgressIndicator.kt b/app/src/main/java/com/squirtles/musicroad/detail/components/music/PlayProgressIndicator.kt deleted file mode 100644 index ac79cf0a..00000000 --- a/app/src/main/java/com/squirtles/musicroad/detail/components/music/PlayProgressIndicator.kt +++ /dev/null @@ -1,55 +0,0 @@ -package com.squirtles.musicroad.detail.components.music - -import androidx.compose.foundation.gestures.detectTapGestures -import androidx.compose.foundation.layout.Box -import androidx.compose.foundation.layout.fillMaxWidth -import androidx.compose.foundation.layout.height -import androidx.compose.material3.LinearProgressIndicator -import androidx.compose.runtime.Composable -import androidx.compose.ui.Modifier -import androidx.compose.ui.graphics.Color -import androidx.compose.ui.input.pointer.pointerInput -import androidx.compose.ui.unit.dp -import com.squirtles.common.ui.theme.DarkGray -import com.squirtles.common.ui.theme.White - -@Composable -internal fun PlayProgressIndicator( - modifier: Modifier = Modifier, - currentTime: Float, - bufferPercentage: Float, - duration: Float, - onSeekChanged: (Float) -> Unit -) { - Box(modifier = modifier) { - LinearProgressIndicator( - progress = { bufferPercentage / 100f }, - modifier = Modifier.fillMaxWidth(), - color = DarkGray, - trackColor = Color.DarkGray, - gapSize = 0.dp, - drawStopIndicator = { } - ) - - LinearProgressIndicator( - progress = { currentTime / duration }, - modifier = Modifier.fillMaxWidth(), - color = White, - trackColor = Color.Transparent, - gapSize = 0.dp, - drawStopIndicator = { } - ) - - Box( - modifier = modifier - .fillMaxWidth() - .height(3.dp) - .pointerInput(Unit) { - detectTapGestures { offset -> - val newTime = offset.x / size.width * duration - onSeekChanged(newTime) - } - } - ) - } -} diff --git a/app/src/main/java/com/squirtles/musicroad/detail/components/music/PlayerControls.kt b/app/src/main/java/com/squirtles/musicroad/detail/components/music/PlayerControls.kt deleted file mode 100644 index 4d581c19..00000000 --- a/app/src/main/java/com/squirtles/musicroad/detail/components/music/PlayerControls.kt +++ /dev/null @@ -1,101 +0,0 @@ -package com.squirtles.musicroad.detail.components.music - -import androidx.compose.foundation.layout.Box -import androidx.compose.foundation.layout.Row -import androidx.compose.foundation.layout.size -import androidx.compose.material.icons.Icons -import androidx.compose.material.icons.filled.Forward5 -import androidx.compose.material.icons.filled.Pause -import androidx.compose.material.icons.filled.PlayArrow -import androidx.compose.material.icons.filled.Replay5 -import androidx.compose.material3.Icon -import androidx.compose.material3.IconButton -import androidx.compose.runtime.Composable -import androidx.compose.ui.Alignment -import androidx.compose.ui.Modifier -import androidx.compose.ui.res.stringResource -import androidx.compose.ui.tooling.preview.Preview -import androidx.compose.ui.unit.dp -import com.squirtles.common.ui.theme.MusicRoadTheme -import com.squirtles.common.ui.theme.White -import com.squirtles.musicroad.R - -@Composable -internal fun PlayerControls( - isPlaying: Boolean, - onReplayClick: () -> Unit, - onPauseToggle: () -> Unit, - onForwardClick: () -> Unit, - modifier: Modifier = Modifier -) { - - Row( - modifier = modifier, - ) { - Box( - modifier = modifier - .weight(1f), - contentAlignment = Alignment.Center - ) { - IconButton( - onClick = onReplayClick - ) { - Icon( - imageVector = Icons.Default.Replay5, - contentDescription = stringResource(id = R.string.player_replay_description), - modifier = Modifier.size(64.dp), - tint = White - ) - } - } - Box( - modifier = modifier - .weight(1f), - contentAlignment = Alignment.Center - ) { - IconButton( - onClick = onPauseToggle - ) { - Icon( - imageVector = if (isPlaying) { - Icons.Default.Pause - } else { - Icons.Default.PlayArrow - }, - contentDescription = stringResource(id = R.string.player_play_pause_description), - modifier = Modifier.size(64.dp), - tint = White - ) - } - } - - Box( - modifier = modifier - .weight(1f), - contentAlignment = Alignment.Center - ) { - IconButton( - onClick = onForwardClick - ) { - Icon( - imageVector = Icons.Default.Forward5, - contentDescription = stringResource(id = R.string.player_forward_description), - modifier = Modifier.size(64.dp), - tint = White - ) - } - } - } -} - -@Preview -@Composable -private fun PlayerControlsPreview() { - MusicRoadTheme { - PlayerControls( - isPlaying = true, - onReplayClick = {}, - onPauseToggle = {}, - onForwardClick = {}) - } -} diff --git a/app/src/main/java/com/squirtles/musicroad/detail/navigation/PickDetailNavigation.kt b/app/src/main/java/com/squirtles/musicroad/detail/navigation/PickDetailNavigation.kt deleted file mode 100644 index 007c4233..00000000 --- a/app/src/main/java/com/squirtles/musicroad/detail/navigation/PickDetailNavigation.kt +++ /dev/null @@ -1,34 +0,0 @@ -package com.squirtles.detail.navigation - -import android.content.Context -import androidx.navigation.NavController -import androidx.navigation.NavGraphBuilder -import androidx.navigation.NavOptions -import androidx.navigation.compose.composable -import androidx.navigation.toRoute -import com.squirtles.musicplayer.PlayerServiceViewModel -import com.squirtles.musicroad.detail.PickDetailScreen -import com.squirtles.navigation.MapRoute - -fun NavController.navigatePickDetail(pickId: String, navOptions: NavOptions? = null) { - navigate(MapRoute.PickDetail(pickId), navOptions) -} - -fun NavGraphBuilder.detailNavGraph( - playerServiceViewModel: PlayerServiceViewModel, - onUserInfoClick: (String) -> Unit, - onBackClick: () -> Unit, - onDeleted: (Context) -> Unit, -) { - composable { backStackEntry -> - val pickId = backStackEntry.toRoute().pickId - - PickDetailScreen( - pickId = pickId, - playerServiceViewModel = playerServiceViewModel, - onUserInfoClick = onUserInfoClick, - onBackClick = onBackClick, - onDeleted = onDeleted, - ) - } -} diff --git a/app/src/main/java/com/squirtles/musicroad/detail/videoplayer/MusicVideoPlayer.kt b/app/src/main/java/com/squirtles/musicroad/detail/videoplayer/MusicVideoPlayer.kt deleted file mode 100644 index f2d63795..00000000 --- a/app/src/main/java/com/squirtles/musicroad/detail/videoplayer/MusicVideoPlayer.kt +++ /dev/null @@ -1,112 +0,0 @@ -package com.squirtles.musicroad.detail.videoplayer - -import android.graphics.Matrix -import android.graphics.SurfaceTexture -import android.view.Surface -import android.view.TextureView -import androidx.annotation.OptIn -import androidx.compose.runtime.Composable -import androidx.compose.runtime.DisposableEffect -import androidx.compose.runtime.LaunchedEffect -import androidx.compose.runtime.getValue -import androidx.compose.runtime.mutableStateOf -import androidx.compose.runtime.remember -import androidx.compose.runtime.rememberCoroutineScope -import androidx.compose.runtime.setValue -import androidx.compose.ui.platform.LocalContext -import androidx.compose.ui.viewinterop.AndroidView -import androidx.hilt.navigation.compose.hiltViewModel -import androidx.lifecycle.Lifecycle -import androidx.lifecycle.compose.LocalLifecycleOwner -import androidx.lifecycle.compose.collectAsStateWithLifecycle -import androidx.lifecycle.flowWithLifecycle -import androidx.media3.common.util.UnstableApi -import kotlinx.coroutines.launch - -@OptIn(UnstableApi::class) -@Composable -fun MusicVideoPlayer( - musicVideoUrl: String, - videoPlayerViewModel: VideoPlayerViewModel = hiltViewModel() -) { - val context = LocalContext.current - val lifecycleOwner = LocalLifecycleOwner.current - val coroutineScope = rememberCoroutineScope() - - val textureView = remember { TextureView(context) } - var currentSurfaceTexture by remember { mutableStateOf(null) } - val videoSize by videoPlayerViewModel.videoSize.collectAsStateWithLifecycle() - - DisposableEffect(Unit) { - onDispose { - videoPlayerViewModel.pause() - videoPlayerViewModel.setLastPosition() - textureView.surfaceTexture?.release() - textureView.surfaceTextureListener = null - } - } - - LaunchedEffect(currentSurfaceTexture) { - if (currentSurfaceTexture != null) { - val surface = Surface(currentSurfaceTexture) - videoPlayerViewModel.setSurface(surface) - } - } - - AndroidView( - factory = { - videoPlayerViewModel.initializePlayer(context, musicVideoUrl) - textureView.apply { - surfaceTextureListener = object : TextureView.SurfaceTextureListener { - override fun onSurfaceTextureAvailable(surfaceTexture: SurfaceTexture, width: Int, height: Int) { - currentSurfaceTexture = surfaceTexture - coroutineScope.launch { - videoPlayerViewModel.videoSize - .flowWithLifecycle(lifecycleOwner.lifecycle, Lifecycle.State.STARTED) - .collect { - setVideoSize(textureView, it.width, it.height) - } - } - } - - override fun onSurfaceTextureSizeChanged(surfaceTexture: SurfaceTexture, width: Int, height: Int) { - - } - - override fun onSurfaceTextureDestroyed(surfaceTexture: SurfaceTexture): Boolean { - videoPlayerViewModel.setSurface(null) - return true - } - - override fun onSurfaceTextureUpdated(surfaceTexture: SurfaceTexture) { - // TODO - } - } - } - } - ) -} - -private fun setVideoSize(textureView: TextureView, videoWidth: Int, videoHeight: Int) { - // 비율 조정 - val viewWidth = textureView.width.toFloat() - val viewHeight = textureView.height.toFloat() - val videoAspect = videoWidth.toFloat() / videoHeight - val viewAspect = viewWidth / viewHeight - val scaleX: Float - val scaleY: Float - - if (videoAspect > viewAspect) { - scaleX = videoAspect / viewAspect - scaleY = 1f - } else { - scaleX = 1f - scaleY = viewAspect / videoAspect - } - - // 중앙 정렬 - val matrix = Matrix() - matrix.setScale(scaleX, scaleY, viewWidth / 2, viewHeight / 2) - - textureView.setTransform(matrix) -} diff --git a/app/src/main/java/com/squirtles/musicroad/detail/videoplayer/MusicVideoScreen.kt b/app/src/main/java/com/squirtles/musicroad/detail/videoplayer/MusicVideoScreen.kt deleted file mode 100644 index ebd1d74d..00000000 --- a/app/src/main/java/com/squirtles/musicroad/detail/videoplayer/MusicVideoScreen.kt +++ /dev/null @@ -1,37 +0,0 @@ -package com.squirtles.musicroad.detail.videoplayer - -import androidx.activity.compose.BackHandler -import androidx.annotation.OptIn -import androidx.compose.foundation.layout.Box -import androidx.compose.foundation.layout.fillMaxSize -import androidx.compose.material3.CircularProgressIndicator -import androidx.compose.runtime.Composable -import androidx.compose.runtime.getValue -import androidx.compose.ui.Alignment -import androidx.compose.ui.Modifier -import androidx.hilt.navigation.compose.hiltViewModel -import androidx.lifecycle.compose.collectAsStateWithLifecycle -import androidx.media3.common.util.UnstableApi -import com.squirtles.model.Pick - -@OptIn(UnstableApi::class) -@Composable -fun MusicVideoScreen( - pick: Pick, - modifier: Modifier, - onBackClick: () -> Unit, - videoPlayerViewModel: VideoPlayerViewModel = hiltViewModel() -) { - val isLoading by videoPlayerViewModel.isLoading.collectAsStateWithLifecycle() - - BackHandler { onBackClick() } - - Box(modifier = modifier.fillMaxSize()) { - MusicVideoPlayer(pick.musicVideoUrl) - VideoPlayerOverlay(pick, onBackClick) - - if (isLoading) { - CircularProgressIndicator(Modifier.align(Alignment.Center)) - } - } -} diff --git a/app/src/main/java/com/squirtles/musicroad/detail/videoplayer/VideoPlayerOverlay.kt b/app/src/main/java/com/squirtles/musicroad/detail/videoplayer/VideoPlayerOverlay.kt deleted file mode 100644 index 38fb2264..00000000 --- a/app/src/main/java/com/squirtles/musicroad/detail/videoplayer/VideoPlayerOverlay.kt +++ /dev/null @@ -1,234 +0,0 @@ -package com.squirtles.musicroad.detail.videoplayer - -import androidx.compose.animation.core.Animatable -import androidx.compose.animation.core.tween -import androidx.compose.foundation.background -import androidx.compose.foundation.gestures.detectTapGestures -import androidx.compose.foundation.layout.Arrangement -import androidx.compose.foundation.layout.Box -import androidx.compose.foundation.layout.Column -import androidx.compose.foundation.layout.Row -import androidx.compose.foundation.layout.displayCutoutPadding -import androidx.compose.foundation.layout.fillMaxSize -import androidx.compose.foundation.layout.fillMaxWidth -import androidx.compose.foundation.layout.navigationBarsPadding -import androidx.compose.foundation.layout.padding -import androidx.compose.foundation.layout.size -import androidx.compose.foundation.layout.statusBarsPadding -import androidx.compose.foundation.shape.CircleShape -import androidx.compose.foundation.shape.RoundedCornerShape -import androidx.compose.material.icons.Icons -import androidx.compose.material.icons.automirrored.filled.ArrowBack -import androidx.compose.material.icons.filled.Pause -import androidx.compose.material.icons.filled.PlayArrow -import androidx.compose.material.icons.filled.Replay -import androidx.compose.material3.CenterAlignedTopAppBar -import androidx.compose.material3.ExperimentalMaterial3Api -import androidx.compose.material3.Icon -import androidx.compose.material3.IconButton -import androidx.compose.material3.MaterialTheme -import androidx.compose.material3.Text -import androidx.compose.material3.TopAppBarDefaults -import androidx.compose.runtime.Composable -import androidx.compose.runtime.LaunchedEffect -import androidx.compose.runtime.remember -import androidx.compose.ui.Alignment -import androidx.compose.ui.Modifier -import androidx.compose.ui.graphics.Color -import androidx.compose.ui.graphics.graphicsLayer -import androidx.compose.ui.input.pointer.pointerInput -import androidx.compose.ui.res.stringResource -import androidx.compose.ui.text.font.FontWeight -import androidx.compose.ui.text.style.TextAlign -import androidx.compose.ui.text.style.TextOverflow -import androidx.compose.ui.tooling.preview.Preview -import androidx.compose.ui.unit.dp -import androidx.compose.ui.unit.sp -import androidx.core.graphics.toColorInt -import androidx.hilt.navigation.compose.hiltViewModel -import androidx.lifecycle.compose.collectAsStateWithLifecycle -import com.squirtles.common.ui.VerticalSpacer -import com.squirtles.common.ui.theme.Black -import com.squirtles.common.ui.theme.Gray -import com.squirtles.common.ui.theme.White -import com.squirtles.model.Creator -import com.squirtles.model.LocationPoint -import com.squirtles.model.Pick -import com.squirtles.model.Song -import com.squirtles.musicroad.R - -@OptIn(ExperimentalMaterial3Api::class) -@Composable -fun VideoPlayerOverlay( - pick: Pick, - onBackClick: () -> Unit, - videoPlayerViewModel: VideoPlayerViewModel = hiltViewModel() -) { - val playerState = videoPlayerViewModel.playerState.collectAsStateWithLifecycle(VideoPlayerState.Playing) - val alpha = remember { Animatable(0f) } - - LaunchedEffect(Unit) { - videoPlayerViewModel.playerState.collect { - when (it) { - VideoPlayerState.Playing -> alpha.animateTo(0f, animationSpec = tween(durationMillis = 300)) - else -> alpha.animateTo(1.0f, animationSpec = tween(durationMillis = 300)) - } - } - } - - Box( - modifier = Modifier - .fillMaxSize() - .graphicsLayer { this.alpha = alpha.value } - .background(Black.copy(alpha = alpha.value.coerceAtMost(0.5f))) - .displayCutoutPadding() - .pointerInput(Unit) { - detectTapGestures( - onTap = { - when (playerState.value) { - VideoPlayerState.Pause -> videoPlayerViewModel.setPlayState(VideoPlayerState.Playing) - VideoPlayerState.Playing -> videoPlayerViewModel.setPlayState(VideoPlayerState.Pause) - VideoPlayerState.Replay -> videoPlayerViewModel.setPlayState(VideoPlayerState.Playing) - } - } - ) - } - ) { - CenterAlignedTopAppBar( - title = { - Text( - text = pick.createdBy.userName + stringResource(id = R.string.pick_app_bar_title_user), - color = White, - style = MaterialTheme.typography.titleLarge.copy(fontWeight = FontWeight.Bold), - ) - }, - modifier = Modifier - .statusBarsPadding() - .align(Alignment.TopCenter), - navigationIcon = { - IconButton( - onClick = onBackClick, - enabled = alpha.value > 0.5f - ) { - Icon( - imageVector = Icons.AutoMirrored.Filled.ArrowBack, - contentDescription = stringResource(id = R.string.pick_app_bar_back_description), - tint = White - ) - } - }, - colors = TopAppBarDefaults.centerAlignedTopAppBarColors( - containerColor = Color.Transparent - ) - ) - - Box( - modifier = Modifier - .size(70.dp) - .align(Alignment.Center) - .background(Black.copy(0.5f), shape = CircleShape) - ) { - Icon( - imageVector = when (playerState.value) { - VideoPlayerState.Pause -> Icons.Default.PlayArrow - VideoPlayerState.Playing -> Icons.Default.Pause - VideoPlayerState.Replay -> Icons.Default.Replay - }, - contentDescription = stringResource(id = R.string.player_play_pause_description), - modifier = Modifier - .align(Alignment.Center) - .size(40.dp), - tint = White - ) - } - - Column( - modifier = Modifier - .fillMaxWidth() - .padding(horizontal = 16.dp) - .padding(bottom = 40.dp) - .navigationBarsPadding() - .align(Alignment.BottomCenter) - ) { - Text( - text = pick.song.songName, - color = White, - fontSize = 20.sp, - fontWeight = FontWeight.Bold, - overflow = TextOverflow.Ellipsis, - maxLines = 1, - style = MaterialTheme.typography.titleLarge - ) - - VerticalSpacer(8) - - Text( - text = pick.song.artistName, - color = Gray, - fontSize = 20.sp, - overflow = TextOverflow.Ellipsis, - maxLines = 1, - style = MaterialTheme.typography.titleLarge - ) - - VerticalSpacer(24) - - if (pick.comment.isNotEmpty()) { - Text( - text = pick.comment, - color = White, - fontSize = 16.sp, - overflow = TextOverflow.Ellipsis, - maxLines = 2, - style = MaterialTheme.typography.titleLarge - ) - - VerticalSpacer(24) - } - - Row( - modifier = Modifier.fillMaxWidth(), - horizontalArrangement = Arrangement.End - ) { - Text( - text = stringResource(id = R.string.video_quality_description), - modifier = Modifier - .background(Gray.copy(0.3f), RoundedCornerShape(8.dp)) - .padding(vertical = 4.dp, horizontal = 8.dp), - color = White, - fontSize = 16.sp, - textAlign = TextAlign.Right, - style = MaterialTheme.typography.titleSmall - ) - } - } - } -} - -@Preview -@Composable -private fun VideoPlayerOverlayPreview() { - VideoPlayerOverlay( - pick = Pick( - id = "", - song = Song( - id = "", - songName = "Super Shy", - artistName = "뉴진스", - albumName = "NewJeans 'Super Shy' - Single", - imageUrl = "https://i.scdn.co/image/ab67616d0000b2733d98a0ae7c78a3a9babaf8af", - genreNames = listOf("KPop", "R&B", "Rap"), - bgColor = "#8fc1e2".toColorInt(), - externalUrl = "", - previewUrl = "" - ), - comment = "강남역 거리는 Super Shy 듣기 좋네요 ^-^!", - createdAt = "2024.11.02", - createdBy = Creator(uid = "", userName = "짱구"), - favoriteCount = 100, - location = LocationPoint(1.0, 1.0), - musicVideoUrl = "", - ), - onBackClick = {} - ) -} diff --git a/app/src/main/java/com/squirtles/musicroad/detail/videoplayer/VideoPlayerState.kt b/app/src/main/java/com/squirtles/musicroad/detail/videoplayer/VideoPlayerState.kt deleted file mode 100644 index 5103a1a8..00000000 --- a/app/src/main/java/com/squirtles/musicroad/detail/videoplayer/VideoPlayerState.kt +++ /dev/null @@ -1,5 +0,0 @@ -package com.squirtles.musicroad.detail.videoplayer - -enum class VideoPlayerState { - Playing, Pause, Replay -} diff --git a/app/src/main/java/com/squirtles/musicroad/detail/videoplayer/VideoPlayerViewModel.kt b/app/src/main/java/com/squirtles/musicroad/detail/videoplayer/VideoPlayerViewModel.kt deleted file mode 100644 index 5f4acc52..00000000 --- a/app/src/main/java/com/squirtles/musicroad/detail/videoplayer/VideoPlayerViewModel.kt +++ /dev/null @@ -1,135 +0,0 @@ -package com.squirtles.musicroad.detail.videoplayer - -import android.content.Context -import android.util.Size -import android.view.Surface -import android.widget.Toast -import androidx.core.content.ContextCompat.getString -import androidx.lifecycle.ViewModel -import androidx.lifecycle.viewModelScope -import androidx.media3.common.MediaItem -import androidx.media3.common.Player -import androidx.media3.common.VideoSize -import androidx.media3.exoplayer.ExoPlayer -import com.squirtles.musicroad.R -import dagger.hilt.android.lifecycle.HiltViewModel -import kotlinx.coroutines.flow.MutableStateFlow -import kotlinx.coroutines.flow.asStateFlow -import kotlinx.coroutines.flow.combine -import kotlinx.coroutines.launch -import javax.inject.Inject - -@HiltViewModel -class VideoPlayerViewModel @Inject constructor() : ViewModel() { - - private var player: ExoPlayer? = null - - private var _playerState = MutableStateFlow(VideoPlayerState.Playing) // 현재 플레이어의 상태 - val playerState = _playerState.asStateFlow() - - private var _videoSize = MutableStateFlow(Size(0, 0)) // 영상 크기 - val videoSize = _videoSize.asStateFlow() - - private val _isLoading = MutableStateFlow(true) - val isLoading = _isLoading.asStateFlow() - - private var lastPosition = 0L - - fun initializePlayer(context: Context, url: String) { - if (player != null) { - setPlayer() - return - } - - val exoPlayer = ExoPlayer.Builder(context).build().apply { - playWhenReady = false - addListener(object : Player.Listener { - override fun onVideoSizeChanged(videoSize: VideoSize) { - viewModelScope.launch { - _videoSize.emit(Size(videoSize.width, videoSize.height)) - } - } - - override fun onPlaybackStateChanged(state: Int) { - viewModelScope.launch { - when (state) { - Player.STATE_IDLE, - Player.STATE_BUFFERING -> { - _isLoading.emit(true) - } - - Player.STATE_READY -> { - _isLoading.emit(false) - } - - Player.STATE_ENDED -> { - Toast.makeText(context, getString(context, R.string.video_player_ended_message), Toast.LENGTH_SHORT).show() - _playerState.emit(VideoPlayerState.Replay) - seekTo(0) - } - } - } - } - }) - } - player = exoPlayer - - setPlayerSource(url) - } - - fun play() { - player?.play() - } - - fun pause() { - player?.pause() - } - - private fun setPlayerSource(url: String) { - player?.run { - val mediaItem = MediaItem.fromUri(url) - setMediaItem(mediaItem) - prepare() - seekTo(lastPosition) - } - setPlayer() - } - - private fun releasePlayer() { - player?.release() - player = null - } - - fun setPlayState(playerState: VideoPlayerState) { - viewModelScope.launch { - _playerState.emit(playerState) - } - } - - fun setLastPosition() { - lastPosition = player?.currentPosition ?: 0 - } - - fun setSurface(surface: Surface?) { - player?.setVideoSurface(surface) - } - - private fun setPlayer() { - viewModelScope.launch { - combine(isLoading, playerState) { isLoading, playState -> - !isLoading && (playState == VideoPlayerState.Playing) - }.collect { shouldPlay -> - if (shouldPlay) { - player?.play() - } else { - player?.pause() - } - } - } - } - - override fun onCleared() { - releasePlayer() - super.onCleared() - } -} diff --git a/app/src/main/java/com/squirtles/musicroad/map/components/PickNotificationBanner.kt b/app/src/main/java/com/squirtles/musicroad/map/components/PickNotificationBanner.kt index 9c87dab9..39826e8b 100644 --- a/app/src/main/java/com/squirtles/musicroad/map/components/PickNotificationBanner.kt +++ b/app/src/main/java/com/squirtles/musicroad/map/components/PickNotificationBanner.kt @@ -26,9 +26,9 @@ import androidx.compose.ui.unit.dp import com.squirtles.common.ui.theme.Black import com.squirtles.common.ui.theme.MusicRoadTheme import com.squirtles.common.ui.theme.White +import com.squirtles.detail.DetailViewModel.Companion.DEFAULT_PICK import com.squirtles.model.Pick import com.squirtles.musicroad.R -import com.squirtles.musicroad.detail.DetailViewModel.Companion.DEFAULT_PICK @Composable fun PickNotificationBanner( diff --git a/feature/map/src/main/java/com/squirtles/map/NaverMap.kt b/feature/map/src/main/java/com/squirtles/map/NaverMap.kt index ff8b36b6..c9ec485b 100644 --- a/feature/map/src/main/java/com/squirtles/map/NaverMap.kt +++ b/feature/map/src/main/java/com/squirtles/map/NaverMap.kt @@ -185,7 +185,6 @@ private fun NaverMap.initLocationOverlay( } } -@SuppressLint("MissingPermission") private fun NaverMap.initDeviceLocation( context: Context, circleOverlay: CircleOverlay, diff --git a/feature/map/src/main/java/com/squirtles/map/components/ClusterBottomSheet.kt b/feature/map/src/main/java/com/squirtles/map/components/ClusterBottomSheet.kt index 5b86075b..3dfa3af0 100644 --- a/feature/map/src/main/java/com/squirtles/map/components/ClusterBottomSheet.kt +++ b/feature/map/src/main/java/com/squirtles/map/components/ClusterBottomSheet.kt @@ -63,7 +63,7 @@ fun ClusterBottomSheet( .fillMaxWidth() .padding(start = DEFAULT_PADDING) ) - + VerticalSpacer(height = 8) LazyColumn { diff --git a/feature/map/src/main/java/com/squirtles/map/marker/Clusterer.kt b/feature/map/src/main/java/com/squirtles/map/marker/Clusterer.kt index 4456a438..81aabc42 100644 --- a/feature/map/src/main/java/com/squirtles/map/marker/Clusterer.kt +++ b/feature/map/src/main/java/com/squirtles/map/marker/Clusterer.kt @@ -15,6 +15,7 @@ import com.naver.maps.map.overlay.Align import com.naver.maps.map.overlay.Marker import com.naver.maps.map.overlay.Overlay import com.naver.maps.map.overlay.OverlayImage +import com.squirtles.common.ui.Constants.REQUEST_IMAGE_SIZE_DEFAULT import com.squirtles.common.ui.theme.Black import com.squirtles.common.ui.theme.Blue import com.squirtles.common.ui.theme.Primary @@ -23,7 +24,6 @@ import com.squirtles.map.DEFAULT_MARKER_Z_INDEX import com.squirtles.map.MapViewModel import com.squirtles.map.setCameraToMarker - internal fun buildClusterer( context: Context, mapViewModel: MapViewModel, @@ -116,7 +116,12 @@ internal fun buildClusterer( val color = if (pick.createdBy.uid == mapViewModel.getUid()) Blue else Primary setPaintColor(color.toArgb()) } - leafMarkerIconView.setLeafMarkerIcon(pick) { + leafMarkerIconView.setLeafMarkerIcon( + pick.song.getImageUrlWithSize( + REQUEST_IMAGE_SIZE_DEFAULT.width, + REQUEST_IMAGE_SIZE_DEFAULT.height + ) + ) { marker.icon = OverlayImage.fromView(leafMarkerIconView) marker.setOnClickListener { marker.map?.let { map -> diff --git a/feature/map/src/main/java/com/squirtles/map/marker/LeafMarkerIconView.kt b/feature/map/src/main/java/com/squirtles/map/marker/LeafMarkerIconView.kt index 318144ca..26496465 100644 --- a/feature/map/src/main/java/com/squirtles/map/marker/LeafMarkerIconView.kt +++ b/feature/map/src/main/java/com/squirtles/map/marker/LeafMarkerIconView.kt @@ -70,9 +70,8 @@ class LeafMarkerIconView( strokePaint.color = color } - fun setLeafMarkerIcon(pick: Pick, onImageLoaded: () -> Unit) { - val song = pick.song - loadImage(song.getImageUrlWithSize(REQUEST_IMAGE_SIZE_DEFAULT.width, REQUEST_IMAGE_SIZE_DEFAULT.height)) { + fun setLeafMarkerIcon(imgUrl: String?, onImageLoaded: () -> Unit) { + loadImage(imgUrl) { onImageLoaded() } } From dc8a7cff5c0ac7d33805049532c1123e286bd405 Mon Sep 17 00:00:00 2001 From: miller198 Date: Wed, 23 Apr 2025 02:01:43 +0900 Subject: [PATCH 54/62] =?UTF-8?q?[refactor]=20feature:map=20=EC=A0=81?= =?UTF-8?q?=EC=9A=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/build.gradle.kts | 1 + .../squirtles/musicroad/main/MainActivity.kt | 1 + .../musicroad/main/navigation/MainNavHost.kt | 6 +- .../main/navigation/MainNavigator.kt | 2 +- .../com/squirtles/musicroad/map/Constants.kt | 16 - .../com/squirtles/musicroad/map/MapScreen.kt | 275 ----------------- .../squirtles/musicroad/map/MapViewModel.kt | 195 ------------ .../com/squirtles/musicroad/map/NaverMap.kt | 285 ------------------ .../map/components/ClusterBottomSheet.kt | 158 ---------- .../map/components/InfoWindowCard.kt | 157 ---------- .../musicroad/map/components/LoadingDialog.kt | 87 ------ .../map/components/MapBottomNavBar.kt | 140 --------- .../map/components/PickNotificationBanner.kt | 84 ------ .../map/marker/ClusterMarkerIconView.kt | 55 ---- .../musicroad/map/marker/Clusterer.kt | 148 --------- .../musicroad/map/marker/DensityType.kt | 12 - .../map/marker/LeafMarkerIconView.kt | 103 ------- .../musicroad/map/marker/MarkerKey.kt | 9 - .../musicroad/map/navigation/MapNavigation.kt | 34 --- .../musicroad/map/navigation/NavTab.kt | 34 --- 20 files changed, 7 insertions(+), 1795 deletions(-) delete mode 100644 app/src/main/java/com/squirtles/musicroad/map/Constants.kt delete mode 100644 app/src/main/java/com/squirtles/musicroad/map/MapScreen.kt delete mode 100644 app/src/main/java/com/squirtles/musicroad/map/MapViewModel.kt delete mode 100644 app/src/main/java/com/squirtles/musicroad/map/NaverMap.kt delete mode 100644 app/src/main/java/com/squirtles/musicroad/map/components/ClusterBottomSheet.kt delete mode 100644 app/src/main/java/com/squirtles/musicroad/map/components/InfoWindowCard.kt delete mode 100644 app/src/main/java/com/squirtles/musicroad/map/components/LoadingDialog.kt delete mode 100644 app/src/main/java/com/squirtles/musicroad/map/components/MapBottomNavBar.kt delete mode 100644 app/src/main/java/com/squirtles/musicroad/map/components/PickNotificationBanner.kt delete mode 100644 app/src/main/java/com/squirtles/musicroad/map/marker/ClusterMarkerIconView.kt delete mode 100644 app/src/main/java/com/squirtles/musicroad/map/marker/Clusterer.kt delete mode 100644 app/src/main/java/com/squirtles/musicroad/map/marker/DensityType.kt delete mode 100644 app/src/main/java/com/squirtles/musicroad/map/marker/LeafMarkerIconView.kt delete mode 100644 app/src/main/java/com/squirtles/musicroad/map/marker/MarkerKey.kt delete mode 100644 app/src/main/java/com/squirtles/musicroad/map/navigation/MapNavigation.kt delete mode 100644 app/src/main/java/com/squirtles/musicroad/map/navigation/NavTab.kt diff --git a/app/build.gradle.kts b/app/build.gradle.kts index efe62ca7..30f56257 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -126,6 +126,7 @@ dependencies { implementation(projects.feature.favorite) implementation(projects.feature.mypick) implementation(projects.feature.detail) + implementation(projects.feature.map) implementation(libs.androidx.core.ktx) implementation(libs.androidx.lifecycle.runtime.ktx) diff --git a/app/src/main/java/com/squirtles/musicroad/main/MainActivity.kt b/app/src/main/java/com/squirtles/musicroad/main/MainActivity.kt index d18b76c8..e60b8794 100644 --- a/app/src/main/java/com/squirtles/musicroad/main/MainActivity.kt +++ b/app/src/main/java/com/squirtles/musicroad/main/MainActivity.kt @@ -145,6 +145,7 @@ class MainActivity : AppCompatActivity() { MainNavHost( navigator = navigator, + finishActivity = { this.finish() }, ) } } diff --git a/app/src/main/java/com/squirtles/musicroad/main/navigation/MainNavHost.kt b/app/src/main/java/com/squirtles/musicroad/main/navigation/MainNavHost.kt index 94121b81..b7dfd2d3 100644 --- a/app/src/main/java/com/squirtles/musicroad/main/navigation/MainNavHost.kt +++ b/app/src/main/java/com/squirtles/musicroad/main/navigation/MainNavHost.kt @@ -7,9 +7,9 @@ import androidx.navigation.compose.NavHost import com.squirtles.create.navigation.createNavGraph import com.squirtles.detail.navigation.detailNavGraph import com.squirtles.favorite.navigation.favoriteNavGraph +import com.squirtles.map.MapViewModel +import com.squirtles.map.navigation.mapNavGraph import com.squirtles.musicplayer.PlayerServiceViewModel -import com.squirtles.musicroad.map.MapViewModel -import com.squirtles.musicroad.map.navigation.mapNavGraph import com.squirtles.mypick.navigation.myPickNavGraph import com.squirtles.search.navigation.searchNavGraph import com.squirtles.userinfo.navigation.userInfoNavGraph @@ -18,6 +18,7 @@ import com.squirtles.userinfo.navigation.userInfoNavGraph internal fun MainNavHost( modifier: Modifier = Modifier, navigator: MainNavigator, + finishActivity: () -> Unit, mapViewModel: MapViewModel = hiltViewModel(), playerServiceViewModel: PlayerServiceViewModel = hiltViewModel(), ) { @@ -32,6 +33,7 @@ internal fun MainNavHost( onCenterClick = navigator::navigateSearch, onUserInfoClick = navigator::navigateUserInfo, onPickSummaryClick = navigator::navigatePickDetail, + onLoadingDialogCloseClick = finishActivity ) searchNavGraph( diff --git a/app/src/main/java/com/squirtles/musicroad/main/navigation/MainNavigator.kt b/app/src/main/java/com/squirtles/musicroad/main/navigation/MainNavigator.kt index 36d30ce6..54e76206 100644 --- a/app/src/main/java/com/squirtles/musicroad/main/navigation/MainNavigator.kt +++ b/app/src/main/java/com/squirtles/musicroad/main/navigation/MainNavigator.kt @@ -11,8 +11,8 @@ import androidx.navigation.navOptions import com.squirtles.create.navigation.navigateCreate import com.squirtles.detail.navigation.navigatePickDetail import com.squirtles.favorite.navigation.navigateFavorite +import com.squirtles.map.navigation.navigateMap import com.squirtles.model.Song -import com.squirtles.musicroad.map.navigation.navigateMap import com.squirtles.mypick.navigation.navigateMyPicks import com.squirtles.navigation.Route import com.squirtles.search.navigation.navigateSearch diff --git a/app/src/main/java/com/squirtles/musicroad/map/Constants.kt b/app/src/main/java/com/squirtles/musicroad/map/Constants.kt deleted file mode 100644 index 92fad232..00000000 --- a/app/src/main/java/com/squirtles/musicroad/map/Constants.kt +++ /dev/null @@ -1,16 +0,0 @@ -package com.squirtles.musicroad.map - -internal enum class BottomNavigationSize( - val size: Int -) { - WIDTH(245), - HEIGHT(50), - HORIZONTAL_PADDING(32) -} - -internal enum class BottomNavigationIconSize( - val size: Int, -) { - CENTER(82), - CENTER_ICON(34) -} diff --git a/app/src/main/java/com/squirtles/musicroad/map/MapScreen.kt b/app/src/main/java/com/squirtles/musicroad/map/MapScreen.kt deleted file mode 100644 index 5e410658..00000000 --- a/app/src/main/java/com/squirtles/musicroad/map/MapScreen.kt +++ /dev/null @@ -1,275 +0,0 @@ -package com.squirtles.musicroad.map - -import androidx.activity.compose.BackHandler -import androidx.compose.foundation.background -import androidx.compose.foundation.clickable -import androidx.compose.foundation.interaction.MutableInteractionSource -import androidx.compose.foundation.layout.Arrangement -import androidx.compose.foundation.layout.Box -import androidx.compose.foundation.layout.Column -import androidx.compose.foundation.layout.WindowInsets -import androidx.compose.foundation.layout.asPaddingValues -import androidx.compose.foundation.layout.fillMaxHeight -import androidx.compose.foundation.layout.fillMaxSize -import androidx.compose.foundation.layout.navigationBars -import androidx.compose.foundation.layout.padding -import androidx.compose.foundation.layout.statusBars -import androidx.compose.material3.CircularProgressIndicator -import androidx.compose.material3.Scaffold -import androidx.compose.runtime.Composable -import androidx.compose.runtime.LaunchedEffect -import androidx.compose.runtime.getValue -import androidx.compose.runtime.mutableStateOf -import androidx.compose.runtime.remember -import androidx.compose.runtime.saveable.rememberSaveable -import androidx.compose.runtime.setValue -import androidx.compose.ui.Alignment -import androidx.compose.ui.Modifier -import androidx.compose.ui.platform.LocalContext -import androidx.compose.ui.unit.dp -import androidx.core.content.ContextCompat.getString -import androidx.hilt.navigation.compose.hiltViewModel -import androidx.lifecycle.Lifecycle -import androidx.lifecycle.compose.LocalLifecycleOwner -import androidx.lifecycle.compose.collectAsStateWithLifecycle -import androidx.lifecycle.flowWithLifecycle -import com.squirtles.account.AccountViewModel -import com.squirtles.account.GoogleId -import com.squirtles.common.ui.SignInAlertDialog -import com.squirtles.common.ui.VerticalSpacer -import com.squirtles.common.ui.theme.Black -import com.squirtles.musicplayer.PlayerServiceViewModel -import com.squirtles.musicroad.R -import com.squirtles.musicroad.main.MainActivity -import com.squirtles.musicroad.map.components.ClusterBottomSheet -import com.squirtles.musicroad.map.components.InfoWindow -import com.squirtles.musicroad.map.components.LoadingDialog -import com.squirtles.musicroad.map.components.MapBottomNavBar -import com.squirtles.musicroad.map.components.PickNotificationBanner -import kotlinx.coroutines.launch - -@Composable -fun MapScreen( - mapViewModel: MapViewModel, - playerServiceViewModel: PlayerServiceViewModel, - onFavoriteClick: (String) -> Unit, - onCenterClick: () -> Unit, - onUserInfoClick: (String) -> Unit, - onPickSummaryClick: (String) -> Unit, - accountViewModel: AccountViewModel = hiltViewModel() -) { - val nearPicks by mapViewModel.nearPicks.collectAsStateWithLifecycle() - val lastLocation by mapViewModel.lastLocation.collectAsStateWithLifecycle() - - val clickedMarkerState by mapViewModel.clickedMarkerState.collectAsStateWithLifecycle() - val playerState by playerServiceViewModel.playerState.collectAsStateWithLifecycle() - - val context = LocalContext.current - val lifecycleOwner = LocalLifecycleOwner.current - var showBottomSheet by remember { mutableStateOf(false) } - var showLocationLoading by rememberSaveable { mutableStateOf(true) } - var isPlaying: Boolean by remember { mutableStateOf(false) } - - // Sign In Dialog - var showSignInDialog by remember { mutableStateOf(false) } - var signInDialogDescription by remember { mutableStateOf("") } - var onSignInSuccess by remember { mutableStateOf<(String) -> Unit>({}) } - var showLoadingIndicator by rememberSaveable { mutableStateOf(false) } - - BackHandler(enabled = showLoadingIndicator) { } - - LaunchedEffect(Unit) { - playerServiceViewModel.readyPlayer() - - launch { - accountViewModel.signInSuccess - .flowWithLifecycle(lifecycleOwner.lifecycle, Lifecycle.State.STARTED) - .collect { isSuccess -> - if (isSuccess) { - showLoadingIndicator = false - mapViewModel.getUid()?.let { uid -> - onSignInSuccess(uid) - } - } - } - } - } - - LaunchedEffect(playerState) { - isPlaying = playerState.isPlaying - } - - LaunchedEffect(lastLocation) { - showLocationLoading = lastLocation == null - } - - Scaffold( - contentWindowInsets = WindowInsets.navigationBars - ) { innerPadding -> - Box( - modifier = Modifier - .fillMaxSize() - .padding(innerPadding) - ) { - NaverMap( - mapViewModel = mapViewModel, - lastLocation = lastLocation - ) - - if (nearPicks.isNotEmpty()) { - PickNotificationBanner( - nearPicks = nearPicks, - isPlaying = isPlaying, - onClick = { - playerServiceViewModel.shuffleNext( - if (nearPicks.size == 1) nearPicks.first() - else nearPicks.filter { it.id != playerState.id }.random() - ) - } - ) - } - - Column( - modifier = Modifier.fillMaxSize(), - verticalArrangement = Arrangement.Bottom, - horizontalAlignment = Alignment.CenterHorizontally - ) { - clickedMarkerState.prevClickedMarker?.let { - if (clickedMarkerState.curPickId != null) { // 단말 마커 클릭 시 - showBottomSheet = false - mapViewModel.picks[clickedMarkerState.curPickId]?.let { pick -> - InfoWindow( - pick = pick, - uid = mapViewModel.getUid(), - navigateToPick = { pickId -> - onPickSummaryClick(pickId) - }, - calculateDistance = { lat, lng -> - mapViewModel.calculateDistance(lat, lng).let { distance -> - when { - distance >= 1000.0 -> "%.1fkm".format(distance / 1000.0) - distance >= 0 -> "%.0fm".format(distance) - else -> "" - } - } - } - ) - } - } else { // 클러스터 마커 클릭 시 - showBottomSheet = true - } - } - - VerticalSpacer(16) - - MapBottomNavBar( - modifier = Modifier.padding(bottom = 16.dp), - lastLocation = lastLocation, - onFavoriteClick = { - mapViewModel.getUid()?.let { uid -> - onFavoriteClick(uid) - } ?: run { - signInDialogDescription = getString(context, R.string.sign_in_dialog_title_favorite_picks) - showSignInDialog = true - onSignInSuccess = onFavoriteClick - } - }, - onCenterClick = { - mapViewModel.getUid()?.let { - onCenterClick() - mapViewModel.saveCurLocationForced() - } ?: run { - signInDialogDescription = getString(context, R.string.sign_in_dialog_title_add_pick) - showSignInDialog = true - onSignInSuccess = { - onCenterClick() - mapViewModel.saveCurLocationForced() - } - } - }, - onUserInfoClick = { - mapViewModel.getUid()?.let { uid -> - onUserInfoClick(uid) - } ?: run { - signInDialogDescription = getString(context, R.string.sign_in_dialog) - showSignInDialog = true - onSignInSuccess = onUserInfoClick - } - } - ) - } - - if (showBottomSheet) { - ClusterBottomSheet( - onDismissRequest = { - showBottomSheet = false - mapViewModel.resetClickedMarkerState(context) - }, - modifier = Modifier - .fillMaxHeight() - .padding(WindowInsets.statusBars.asPaddingValues()), - clusterPickList = clickedMarkerState.clusterPickList, - uid = mapViewModel.getUid(), - calculateDistance = { lat, lng -> - mapViewModel.calculateDistance(lat, lng).let { distance -> - when { - distance >= 1000.0 -> "%.1fkm".format(distance / 1000.0) - distance >= 0 -> "%.0fm".format(distance) - else -> "" - } - } - - }, - onClickItem = { pickId -> - onPickSummaryClick(pickId) - } - ) - } - - if (showLocationLoading) { - Box( - modifier = Modifier.fillMaxSize(), - contentAlignment = Alignment.Center - ) { - LoadingDialog( - onCloseClick = { - (context as MainActivity).finish() - } - ) - } - } - } - } - - if (showSignInDialog) { - SignInAlertDialog( - onDismissRequest = { showSignInDialog = false }, - onGoogleSignInClick = { - showSignInDialog = false - showLoadingIndicator = true - GoogleId(context).signIn( - onSuccess = { uid, credential -> - accountViewModel.signIn(uid, credential) - } - ) - }, - description = signInDialogDescription - ) - } - - if (showLoadingIndicator) { - Box( - modifier = Modifier - .fillMaxSize() - .background(Black.copy(alpha = 0.5F)) - .clickable( - interactionSource = remember { MutableInteractionSource() }, - indication = null, - onClick = {} - ), - contentAlignment = Alignment.Center - ) { - CircularProgressIndicator() - } - } -} diff --git a/app/src/main/java/com/squirtles/musicroad/map/MapViewModel.kt b/app/src/main/java/com/squirtles/musicroad/map/MapViewModel.kt deleted file mode 100644 index 0f33e2fd..00000000 --- a/app/src/main/java/com/squirtles/musicroad/map/MapViewModel.kt +++ /dev/null @@ -1,195 +0,0 @@ -package com.squirtles.musicroad.map - -import android.content.Context -import android.location.Location -import android.util.Log -import androidx.lifecycle.ViewModel -import androidx.lifecycle.viewModelScope -import com.naver.maps.geometry.LatLng -import com.naver.maps.map.CameraPosition -import com.naver.maps.map.clustering.Clusterer -import com.naver.maps.map.overlay.Marker -import com.squirtles.domain.pick.usecase.FetchPickUseCase -import com.squirtles.location.usecase.GetLastLocationUseCase -import com.squirtles.location.usecase.SaveLastLocationUseCase -import com.squirtles.model.Pick -import com.squirtles.musicroad.map.marker.MarkerKey -import com.squirtles.user.usecase.GetCurrentUidUseCase -import dagger.hilt.android.lifecycle.HiltViewModel -import kotlinx.coroutines.flow.MutableStateFlow -import kotlinx.coroutines.flow.StateFlow -import kotlinx.coroutines.flow.asStateFlow -import kotlinx.coroutines.launch -import javax.inject.Inject - -data class MarkerState( - val prevClickedMarker: Marker? = null, // 이전에 클릭한 마커(클러스터 마커 & 단말 마커) - val clusterPickList: List? = null, // 클러스터 마커의 픽 정보 - val curPickId: String? = null // 현재 선택한 마커의 pick id -) - -@HiltViewModel -class MapViewModel @Inject constructor( - getLastLocationUseCase: GetLastLocationUseCase, - private val saveLastLocationUseCase: SaveLastLocationUseCase, - private val fetchPickUseCase: FetchPickUseCase, - private val getCurrentUidUseCase: GetCurrentUidUseCase -) : ViewModel() { - - private val _centerLatLng: MutableStateFlow = MutableStateFlow(null) - val centerLatLng = _centerLatLng.asStateFlow() - - private var _lastCameraPosition: CameraPosition? = null - val lastCameraPosition get() = _lastCameraPosition - - private val _picks: MutableMap = mutableMapOf() // key: pickId, value: Pick - val picks: Map get() = _picks - - private val _nearPicks = MutableStateFlow>(emptyList()) - val nearPicks = _nearPicks.asStateFlow() - - private val _clickedMarkerState = MutableStateFlow(MarkerState()) - val clickedMarkerState = _clickedMarkerState.asStateFlow() - - // FIXME : 네이버맵의 LocationChangeListener에서 실시간으로 변하는 위치 정보 -> 더 나은 방법이 있으면 고쳐주세요 - private var _currentLocation: Location? = null - - // LocalDataSource에 저장되는 위치 정보 - // Firestore 데이터 쿼리 작업 최소화 및 위치데이터 공유 용도 - val lastLocation: StateFlow = getLastLocationUseCase() - - fun getUid() = getCurrentUidUseCase() - - fun setLastCameraPosition(cameraPosition: CameraPosition) { - _lastCameraPosition = cameraPosition - } - - fun updateCurLocation(location: Location) { - _currentLocation = location - - if (lastLocation.value == null - || calculateDistance(location.latitude, location.longitude) > 5.0 - ) { - saveCurLocation(location) - } - } - - private fun saveCurLocation(location: Location) { - viewModelScope.launch { - saveLastLocationUseCase(location) - } - } - - fun saveCurLocationForced() { - _currentLocation?.let { location -> - saveCurLocation(location) - } - } - - fun calculateDistance( - lat: Double, - lng: Double, - from: Location? = lastLocation.value, - ): Double { - return from?.let { - val location = Location("pickLocation").apply { - latitude = lat - longitude = lng - } - from.distanceTo(location).toDouble() - } ?: -1.0 - } - - fun updateCenterLatLng(latLng: LatLng) { - _centerLatLng.value = latLng - } - - // FIXME: 인자로 Context 받는 것 수정하기 - fun setClickedMarker(context: Context, marker: Marker) { - viewModelScope.launch { - marker.toggleSizeByClick(context, true) - _clickedMarkerState.emit(_clickedMarkerState.value.copy(prevClickedMarker = marker)) - } - } - - fun setClickedMarkerState( - context: Context, - marker: Marker, - clusterTag: String? = null, - pickId: String? = null - ) { - viewModelScope.launch { - val prevClickedMarker = _clickedMarkerState.value.prevClickedMarker - if (prevClickedMarker == marker) return@launch - - prevClickedMarker?.toggleSizeByClick(context, false) - marker.toggleSizeByClick(context, true) - val pickList = clusterTag?.split(",")?.mapNotNull { id -> picks[id] } - _clickedMarkerState.emit(MarkerState(marker, pickList, pickId)) - } - } - - fun resetClickedMarkerState(context: Context) { - viewModelScope.launch { - val prevClickedMarker = _clickedMarkerState.value.prevClickedMarker - prevClickedMarker?.toggleSizeByClick(context, false) - _clickedMarkerState.emit(MarkerState(null, null, null)) - } - } - - fun fetchPicksInBounds(leftTop: LatLng, clusterer: Clusterer?) { - viewModelScope.launch { - _centerLatLng.value?.run { - val radiusInM = leftTop.distanceTo(this) - fetchPickUseCase(this.latitude, this.longitude, radiusInM) - .onSuccess { pickList -> - val newKeyTagMap: MutableMap = mutableMapOf() - pickList.forEach { pick -> - newKeyTagMap[MarkerKey(pick)] = pick.id - _picks[pick.id] = pick - } - _clickedMarkerState.value.clusterPickList?.let { clusterPickList -> // 클러스터 마커가 선택되어 있는 경우 - val updatedPickList = mutableListOf() - clusterPickList.forEach { pick -> - _picks[pick.id]?.let { updatedPick -> - updatedPickList.add(updatedPick) - } - } - _clickedMarkerState.emit(_clickedMarkerState.value.copy(clusterPickList = updatedPickList.toList())) // 최신 픽 정보로 clusterPickList 업데이트 - } - clusterer?.addAll(newKeyTagMap) - } - .onFailure { - // TODO: NoSuchPickInRadiusException일 때 - Log.e("MapViewModel", "${it.message}") - } - } - } - } - - fun requestPickNotificationArea(location: Location, notiRadius: Double) { - viewModelScope.launch { - fetchPickUseCase(location.latitude, location.longitude, notiRadius) - .onSuccess { - _nearPicks.emit(it) - }.onFailure { - _nearPicks.emit(emptyList()) - } - } - } - - private fun Marker.toggleSizeByClick(context: Context, isClicked: Boolean) { - val defaultIconWidth = this.icon.getIntrinsicWidth(context) - val defaultIconHeight = this.icon.getIntrinsicHeight(context) - - zIndex = if (isClicked) CLICKED_MARKER_Z_INDEX else DEFAULT_MARKER_Z_INDEX - this.width = - if (isClicked) (defaultIconWidth * MARKER_SCALE).toInt() else defaultIconWidth - this.height = - if (isClicked) (defaultIconHeight * MARKER_SCALE).toInt() else defaultIconHeight - } - - companion object { - private const val MARKER_SCALE = 1.5 - } -} diff --git a/app/src/main/java/com/squirtles/musicroad/map/NaverMap.kt b/app/src/main/java/com/squirtles/musicroad/map/NaverMap.kt deleted file mode 100644 index 4ee4c10b..00000000 --- a/app/src/main/java/com/squirtles/musicroad/map/NaverMap.kt +++ /dev/null @@ -1,285 +0,0 @@ -package com.squirtles.musicroad.map - -import android.Manifest -import android.app.Activity -import android.content.Context -import android.graphics.PointF -import android.location.Location -import androidx.compose.foundation.isSystemInDarkTheme -import androidx.compose.foundation.layout.fillMaxSize -import androidx.compose.runtime.Composable -import androidx.compose.runtime.DisposableEffect -import androidx.compose.runtime.LaunchedEffect -import androidx.compose.runtime.MutableState -import androidx.compose.runtime.getValue -import androidx.compose.runtime.mutableStateOf -import androidx.compose.runtime.remember -import androidx.compose.runtime.rememberCoroutineScope -import androidx.compose.runtime.setValue -import androidx.compose.ui.Modifier -import androidx.compose.ui.graphics.toArgb -import androidx.compose.ui.platform.LocalContext -import androidx.compose.ui.viewinterop.AndroidView -import androidx.core.content.PermissionChecker -import androidx.lifecycle.Lifecycle -import androidx.lifecycle.LifecycleEventObserver -import androidx.lifecycle.compose.LocalLifecycleOwner -import androidx.lifecycle.compose.collectAsStateWithLifecycle -import com.google.android.gms.location.FusedLocationProviderClient -import com.google.android.gms.location.LocationServices -import com.naver.maps.geometry.LatLng -import com.naver.maps.geometry.LatLngBounds -import com.naver.maps.map.CameraAnimation -import com.naver.maps.map.CameraPosition -import com.naver.maps.map.CameraUpdate -import com.naver.maps.map.LocationTrackingMode -import com.naver.maps.map.MapView -import com.naver.maps.map.NaverMap -import com.naver.maps.map.UiSettings -import com.naver.maps.map.clustering.Clusterer -import com.naver.maps.map.overlay.CircleOverlay -import com.naver.maps.map.overlay.LocationOverlay -import com.naver.maps.map.overlay.OverlayImage -import com.naver.maps.map.util.FusedLocationSource -import com.squirtles.common.ui.theme.Primary -import com.squirtles.common.ui.theme.Purple15 -import com.squirtles.musicroad.R -import com.squirtles.musicroad.map.marker.MarkerKey -import com.squirtles.musicroad.map.marker.buildClusterer -import kotlinx.coroutines.launch -import kotlin.coroutines.resume -import kotlin.coroutines.suspendCoroutine - -@Composable -fun NaverMap( - mapViewModel: MapViewModel, - lastLocation: Location? -) { - val context = LocalContext.current - val mapView = remember { MapView(context) } - val naverMap = remember { mutableStateOf(null) } - - val fusedLocationClient = remember { LocationServices.getFusedLocationProviderClient(context) } - val locationSource = - remember { FusedLocationSource(context as Activity, LOCATION_PERMISSION_REQUEST_CODE) } - val locationOverlay = remember { mutableStateOf(null) } - val circleOverlay = remember { CircleOverlay() } - var clusterer by remember { mutableStateOf?>(null) } - - val lifecycleOwner = LocalLifecycleOwner.current - val coroutineScope = rememberCoroutineScope() - - val centerLatLng by mapViewModel.centerLatLng.collectAsStateWithLifecycle() - - LaunchedEffect(lastLocation) { - // 현재 위치와 마지막 위치가 5미터 이상 차이가 날때만 현위치 기준 반경 100m 픽 정보 개수 불러오기 - lastLocation?.let { - mapViewModel.requestPickNotificationArea(lastLocation, CIRCLE_RADIUS_METER) - } - } - - LaunchedEffect(centerLatLng) { - naverMap.value?.projection?.fromScreenLocation(PointF(0F, 0F))?.run { - mapViewModel.fetchPicksInBounds( - leftTop = this, - clusterer = clusterer - ) - } - } - - DisposableEffect(Unit) { - clusterer = buildClusterer(context, mapViewModel) - - onDispose { - clusterer?.clear() - } - } - - DisposableEffect(lifecycleOwner) { - val mapLifecycleObserver = LifecycleEventObserver { _, event -> - when (event) { - Lifecycle.Event.ON_CREATE -> mapView.onCreate(null) - Lifecycle.Event.ON_START -> mapView.onStart() - Lifecycle.Event.ON_RESUME -> mapView.onResume() - Lifecycle.Event.ON_PAUSE -> mapView.onPause() - Lifecycle.Event.ON_STOP -> mapView.onStop() - Lifecycle.Event.ON_DESTROY -> mapView.onDestroy() - else -> throw IllegalStateException() - } - } - - lifecycleOwner.lifecycle.addObserver(mapLifecycleObserver) - - onDispose { - naverMap.value?.let { - mapViewModel.setLastCameraPosition(it.cameraPosition) - } - lifecycleOwner.lifecycle.removeObserver(mapLifecycleObserver) - mapView.onDestroy() - } - } - - AndroidView( - factory = { - mapView.apply { - coroutineScope.launch { - naverMap.value = suspendCoroutine { continuation -> - getMapAsync { - continuation.resume(it) - } - } - - naverMap.value?.run { - mapType = NaverMap.MapType.Navi - initMapSettings() - initDeviceLocation( - context = context, - circleOverlay = circleOverlay, - fusedLocationClient = fusedLocationClient, - lastCameraPosition = mapViewModel.lastCameraPosition - ) { - mapViewModel.fetchPicksInBounds( - leftTop = this.projection.fromScreenLocation(PointF(0F, 0F)), - clusterer = clusterer - ) - } - initLocationOverlay(locationSource, locationOverlay) - setLocationChangeListener(circleOverlay, mapViewModel) - setMapClickListener { mapViewModel.resetClickedMarkerState(context) } - setCameraIdleListener { centerLatLng -> - mapViewModel.updateCenterLatLng(centerLatLng) - } - clusterer?.map = this - } - } - } - }, - modifier = Modifier.fillMaxSize() - ) - - if (isSystemInDarkTheme()) { - naverMap.value?.isNightModeEnabled = true - } -} - -internal fun setCameraToMarker( - map: NaverMap, - clickedMarkerPosition: LatLng -) { - val cameraUpdate = CameraUpdate - .scrollTo(clickedMarkerPosition) - .animate(CameraAnimation.Easing) - map.moveCamera(cameraUpdate) -} - -private fun NaverMap.initLocationOverlay( - currentLocationSource: FusedLocationSource, - currentLocationOverlay: MutableState -) { - locationSource = currentLocationSource - locationTrackingMode = LocationTrackingMode.Follow - currentLocationOverlay.value = locationOverlay - currentLocationOverlay.value?.run { - isVisible = true - icon = OverlayImage.fromResource(R.drawable.ic_location) - } -} - -private fun NaverMap.initDeviceLocation( - context: Context, - circleOverlay: CircleOverlay, - fusedLocationClient: FusedLocationProviderClient, - lastCameraPosition: CameraPosition?, - fetchPicksInBounds: () -> Unit, -) { - if (checkSelfPermission(context)) { - fusedLocationClient.lastLocation.addOnSuccessListener { location -> - if (location != null) { - locationOverlay.position = LatLng(location) - setCircleOverlay(circleOverlay, location) - lastCameraPosition?.let { - moveCamera(CameraUpdate.toCameraPosition(it)) - fetchPicksInBounds() - } ?: run { - moveCamera(CameraUpdate.scrollTo(LatLng(location))) - moveCamera(CameraUpdate.zoomTo(INITIAL_CAMERA_ZOOM)) - } - } - } - } -} - -private fun NaverMap.setLocationChangeListener( - circleOverlay: CircleOverlay, - mapViewModel: MapViewModel -) { - addOnLocationChangeListener { location -> - this.setCircleOverlay(circleOverlay, location) - mapViewModel.updateCurLocation(location) - } -} - -private fun NaverMap.setCircleOverlay(circleOverlay: CircleOverlay, location: Location) { - circleOverlay.center = LatLng(location.latitude, location.longitude) - circleOverlay.color = Purple15.toArgb() - circleOverlay.outlineColor = Primary.toArgb() - circleOverlay.outlineWidth = 3 - circleOverlay.radius = CIRCLE_RADIUS_METER - circleOverlay.map = this -} - -private fun NaverMap.initMapSettings() { - extent = LatLngBounds(SOUTHWEST, NORTHEAST) - setCameraZoomLimit() - uiSettings.setNaverMapMapUi() -} - -private fun UiSettings.setNaverMapMapUi() { - isLocationButtonEnabled = true - isZoomControlEnabled = false - isTiltGesturesEnabled = false -} - -private fun NaverMap.setCameraZoomLimit() { - minZoom = MIN_ZOOM_LEVEL - maxZoom = MAX_ZOOM_LEVEL -} - -// 지도 클릭 이벤트 설정 -private fun NaverMap.setMapClickListener( - resetSelectedMarkerAndPick: () -> Unit -) { - this.setOnMapClickListener { _, _ -> - resetSelectedMarkerAndPick() - } -} - -// 카메라 대기 이벤트 설정 -private fun NaverMap.setCameraIdleListener( - updateCenterLatLng: (LatLng) -> Unit -) { - addOnCameraIdleListener { - updateCenterLatLng(cameraPosition.target) - } -} - -private fun checkSelfPermission(context: Context): Boolean { - return PermissionChecker.checkSelfPermission(context, PERMISSIONS[0]) == - PermissionChecker.PERMISSION_GRANTED && - PermissionChecker.checkSelfPermission(context, PERMISSIONS[1]) == - PermissionChecker.PERMISSION_GRANTED -} - -private val SOUTHWEST = LatLng(33.011268, 124.344361) -private val NORTHEAST = LatLng(39.346507, 130.826372) -private const val LOCATION_PERMISSION_REQUEST_CODE = 1000 -private const val CIRCLE_RADIUS_METER = 100.0 -private const val INITIAL_CAMERA_ZOOM = 16.5 -private const val MIN_ZOOM_LEVEL = 6.0 -private const val MAX_ZOOM_LEVEL = 18.0 -private val PERMISSIONS = arrayOf( - Manifest.permission.ACCESS_FINE_LOCATION, - Manifest.permission.ACCESS_COARSE_LOCATION -) -internal const val DEFAULT_MARKER_Z_INDEX = 0 -internal const val CLICKED_MARKER_Z_INDEX = 100 diff --git a/app/src/main/java/com/squirtles/musicroad/map/components/ClusterBottomSheet.kt b/app/src/main/java/com/squirtles/musicroad/map/components/ClusterBottomSheet.kt deleted file mode 100644 index db3c548e..00000000 --- a/app/src/main/java/com/squirtles/musicroad/map/components/ClusterBottomSheet.kt +++ /dev/null @@ -1,158 +0,0 @@ -package com.squirtles.musicroad.map.components - -import androidx.compose.foundation.clickable -import androidx.compose.foundation.layout.Arrangement -import androidx.compose.foundation.layout.Column -import androidx.compose.foundation.layout.Row -import androidx.compose.foundation.layout.fillMaxWidth -import androidx.compose.foundation.layout.padding -import androidx.compose.foundation.layout.size -import androidx.compose.foundation.lazy.LazyColumn -import androidx.compose.foundation.lazy.items -import androidx.compose.foundation.shape.RoundedCornerShape -import androidx.compose.material3.ExperimentalMaterial3Api -import androidx.compose.material3.MaterialTheme -import androidx.compose.material3.ModalBottomSheet -import androidx.compose.material3.Text -import androidx.compose.material3.rememberModalBottomSheetState -import androidx.compose.runtime.Composable -import androidx.compose.runtime.rememberCoroutineScope -import androidx.compose.ui.Alignment -import androidx.compose.ui.Modifier -import androidx.compose.ui.draw.clip -import androidx.compose.ui.unit.dp -import com.squirtles.common.ui.AlbumImage -import com.squirtles.common.ui.CommentText -import com.squirtles.common.ui.Constants.DEFAULT_PADDING -import com.squirtles.common.ui.Constants.REQUEST_IMAGE_SIZE_DEFAULT -import com.squirtles.common.ui.CountText -import com.squirtles.common.ui.CreatedByOtherUserText -import com.squirtles.common.ui.CreatedBySelfText -import com.squirtles.common.ui.FavoriteCountText -import com.squirtles.common.ui.HorizontalSpacer -import com.squirtles.common.ui.SongInfoText -import com.squirtles.common.ui.VerticalSpacer -import com.squirtles.model.LocationPoint -import com.squirtles.model.Pick -import com.squirtles.model.Song -import kotlinx.coroutines.launch - -@OptIn(ExperimentalMaterial3Api::class) -@Composable -fun ClusterBottomSheet( - onDismissRequest: () -> Unit, - modifier: Modifier = Modifier, - clusterPickList: List?, - uid: String?, - calculateDistance: (Double, Double) -> String, - onClickItem: (String) -> Unit -) { - val sheetState = rememberModalBottomSheetState() - val scope = rememberCoroutineScope() - - ModalBottomSheet( - onDismissRequest = { onDismissRequest() }, - modifier = modifier, - sheetState = sheetState, - containerColor = MaterialTheme.colorScheme.surface - ) { - clusterPickList?.let { pickList -> - CountText( - totalCount = pickList.size, - modifier = Modifier - .fillMaxWidth() - .padding(start = DEFAULT_PADDING) - ) - - VerticalSpacer(height = 8) - - LazyColumn { - items( - items = pickList, - key = { it.id } - ) { pick -> - BottomSheetItem( - song = pick.song, - pickLocation = pick.location, - createdUserName = pick.createdBy.userName.takeIf { pick.createdBy.uid != uid }, - comment = pick.comment, - favoriteCount = pick.favoriteCount, - calculateDistance = calculateDistance, - onClickItem = { - scope - .launch { sheetState.hide() } - .invokeOnCompletion { onClickItem(pick.id) } - } - ) - } - } - } - } -} - -@Composable -fun BottomSheetItem( - song: Song, - pickLocation: LocationPoint, - createdUserName: String?, - comment: String, - favoriteCount: Int, - calculateDistance: (Double, Double) -> String, - onClickItem: () -> Unit -) { - Row( - modifier = Modifier - .fillMaxWidth() - .clickable { onClickItem() } - .padding(horizontal = DEFAULT_PADDING, vertical = DEFAULT_PADDING / 2), - horizontalArrangement = Arrangement.spacedBy(DEFAULT_PADDING), - verticalAlignment = Alignment.CenterVertically - ) { - AlbumImage( - imageUrl = song.getImageUrlWithSize(REQUEST_IMAGE_SIZE_DEFAULT.width, REQUEST_IMAGE_SIZE_DEFAULT.height), - modifier = Modifier - .size(45.dp) - .clip(RoundedCornerShape(4.dp)) - ) - - Column( - modifier = Modifier.weight(1f) - ) { - SongInfoText( - songInfo = "${song.songName} - ${song.artistName}" - ) - - Row( - verticalAlignment = Alignment.CenterVertically - ) { - createdUserName?.let { userName -> - CreatedByOtherUserText( - userName = userName, - modifier = Modifier.weight(weight = 1f, fill = false) - ) - } ?: run { - CreatedBySelfText( - modifier = Modifier.weight(weight = 1f, fill = false) - ) - } - - HorizontalSpacer(8) - - FavoriteCountText( - favoriteCount = favoriteCount - ) - } - - CommentText( - comment = comment - ) - } - - Text( - text = calculateDistance(pickLocation.latitude, pickLocation.longitude), - modifier = Modifier.align(Alignment.Top), - color = MaterialTheme.colorScheme.onSurfaceVariant, - style = MaterialTheme.typography.bodyMedium - ) - } -} diff --git a/app/src/main/java/com/squirtles/musicroad/map/components/InfoWindowCard.kt b/app/src/main/java/com/squirtles/musicroad/map/components/InfoWindowCard.kt deleted file mode 100644 index 9e8606e1..00000000 --- a/app/src/main/java/com/squirtles/musicroad/map/components/InfoWindowCard.kt +++ /dev/null @@ -1,157 +0,0 @@ -package com.squirtles.musicroad.map.components - -import android.content.res.Configuration.UI_MODE_NIGHT_YES -import android.util.Size -import androidx.compose.foundation.background -import androidx.compose.foundation.layout.Arrangement -import androidx.compose.foundation.layout.Box -import androidx.compose.foundation.layout.Column -import androidx.compose.foundation.layout.Row -import androidx.compose.foundation.layout.fillMaxHeight -import androidx.compose.foundation.layout.fillMaxWidth -import androidx.compose.foundation.layout.height -import androidx.compose.foundation.layout.padding -import androidx.compose.foundation.layout.size -import androidx.compose.foundation.shape.RoundedCornerShape -import androidx.compose.material3.ElevatedCard -import androidx.compose.material3.MaterialTheme -import androidx.compose.material3.Text -import androidx.compose.runtime.Composable -import androidx.compose.ui.Alignment -import androidx.compose.ui.Modifier -import androidx.compose.ui.draw.clip -import androidx.compose.ui.text.style.TextOverflow -import androidx.compose.ui.tooling.preview.Preview -import androidx.compose.ui.unit.dp -import androidx.core.graphics.toColorInt -import com.squirtles.common.ui.AlbumImage -import com.squirtles.common.ui.CreatedByOtherUserText -import com.squirtles.common.ui.CreatedBySelfText -import com.squirtles.common.ui.FavoriteCountText -import com.squirtles.common.ui.HorizontalSpacer -import com.squirtles.common.ui.SongInfoText -import com.squirtles.common.ui.theme.Gray -import com.squirtles.common.ui.theme.MusicRoadTheme -import com.squirtles.model.Creator -import com.squirtles.model.LocationPoint -import com.squirtles.model.Pick -import com.squirtles.model.Song - -@Composable -fun InfoWindow( - pick: Pick, - uid: String?, - navigateToPick: (String) -> Unit, - calculateDistance: (Double, Double) -> String -) { - ElevatedCard( - onClick = { navigateToPick(pick.id) }, - modifier = Modifier - .fillMaxWidth() - .height(122.dp) - .padding(horizontal = 16.dp) - ) { - Row( - modifier = Modifier - .background(MaterialTheme.colorScheme.surface) - .padding(16.dp), - horizontalArrangement = Arrangement.spacedBy(16.dp) - ) { - AlbumImage( - imageUrl = pick.song.getImageUrlWithSize(RequestImageSize.width, RequestImageSize.height), - modifier = Modifier - .size(90.dp) - .clip(RoundedCornerShape(4.dp)) - ) - - Column( - modifier = Modifier.weight(1f) - ) { - SongInfoText( - songInfo = "${pick.song.songName} - ${pick.song.artistName}" - ) - - Row( - verticalAlignment = Alignment.CenterVertically - ) { - if (pick.createdBy.uid != uid) { - CreatedByOtherUserText( - userName = pick.createdBy.userName, - modifier = Modifier.weight(weight = 1f, fill = false), - style = MaterialTheme.typography.bodyLarge - ) - } else { - CreatedBySelfText( - modifier = Modifier.weight(weight = 1f, fill = false), - style = MaterialTheme.typography.bodyLarge - ) - } - - HorizontalSpacer(8) - - FavoriteCountText( - favoriteCount = pick.favoriteCount, - style = MaterialTheme.typography.bodyLarge - ) - } - - Box( - modifier = Modifier - .weight(1f) - .fillMaxHeight(), - contentAlignment = Alignment.Center - ) { - Text( - text = pick.comment, - color = MaterialTheme.colorScheme.onSurface, - overflow = TextOverflow.Ellipsis, - maxLines = 1, - style = MaterialTheme.typography.bodyMedium, - ) - } - } - - Text( - text = calculateDistance(pick.location.latitude, pick.location.longitude), - style = MaterialTheme.typography.bodyMedium.copy(Gray) - ) - } - } -} - -@Preview("info window card") -@Preview("info window card (dark)", uiMode = UI_MODE_NIGHT_YES) -@Composable -private fun InfoWindowPreview() { - MusicRoadTheme { - InfoWindow( - pick = Pick( - id = "", - song = Song( - id = "", - songName = "Ditto", - artistName = "NewJeans", - albumName = "Ditto", - imageUrl = "https://i.scdn.co/image/ab67616d0000b2733d98a0ae7c78a3a9babaf8af", - genreNames = listOf("KPop", "R&B", "Rap"), - bgColor = "#000000".toColorInt(), - externalUrl = "", - previewUrl = "" - ), - comment = "강남역 거리는 ditto 듣기 좋네요 ^-^!", - createdAt = "1970.01.21", - createdBy = Creator(uid = "", userName = "짱구"), - favoriteCount = 100, - location = LocationPoint(1.0, 1.0), - musicVideoUrl = "", - ), - uid = "", - navigateToPick = { }, - calculateDistance = { _, _ -> - TODO() - } - ) - } -} - -private val RequestImageSize = Size(400, 400) diff --git a/app/src/main/java/com/squirtles/musicroad/map/components/LoadingDialog.kt b/app/src/main/java/com/squirtles/musicroad/map/components/LoadingDialog.kt deleted file mode 100644 index d90a7f0d..00000000 --- a/app/src/main/java/com/squirtles/musicroad/map/components/LoadingDialog.kt +++ /dev/null @@ -1,87 +0,0 @@ -package com.squirtles.musicroad.map.components - -import androidx.compose.foundation.layout.Arrangement -import androidx.compose.foundation.layout.Column -import androidx.compose.foundation.layout.fillMaxWidth -import androidx.compose.foundation.layout.padding -import androidx.compose.foundation.shape.RoundedCornerShape -import androidx.compose.material3.BasicAlertDialog -import androidx.compose.material3.ButtonDefaults -import androidx.compose.material3.CircularProgressIndicator -import androidx.compose.material3.ExperimentalMaterial3Api -import androidx.compose.material3.MaterialTheme -import androidx.compose.material3.Surface -import androidx.compose.material3.Text -import androidx.compose.material3.TextButton -import androidx.compose.runtime.Composable -import androidx.compose.ui.Alignment -import androidx.compose.ui.Modifier -import androidx.compose.ui.graphics.Color -import androidx.compose.ui.res.stringResource -import androidx.compose.ui.tooling.preview.Preview -import androidx.compose.ui.unit.dp -import com.squirtles.common.ui.VerticalSpacer -import com.squirtles.common.ui.theme.Black -import com.squirtles.common.ui.theme.DarkGray -import com.squirtles.common.ui.theme.MusicRoadTheme -import com.squirtles.common.ui.theme.Primary -import com.squirtles.common.ui.theme.White -import com.squirtles.musicroad.R - -@OptIn(ExperimentalMaterial3Api::class) -@Composable -fun LoadingDialog( - onCloseClick: () -> Unit, -) { - BasicAlertDialog( - onDismissRequest = {}, - ) { - Surface( - modifier = Modifier.fillMaxWidth(), - shape = RoundedCornerShape(16.dp), - color = White - ) { - Column( - modifier = Modifier.padding(top = 48.dp, bottom = 16.dp), - horizontalAlignment = Alignment.CenterHorizontally, - verticalArrangement = Arrangement.Center - ) { - CircularProgressIndicator( - color = Primary - ) - - VerticalSpacer(16) - - Text( - text = stringResource(R.string.loading_current_location_dialog_text), - color = Black, - style = MaterialTheme.typography.bodyLarge - ) - - VerticalSpacer(24) - - TextButton( - onClick = { onCloseClick() }, - colors = ButtonDefaults.buttonColors().copy( - containerColor = Color.Transparent, - contentColor = DarkGray - ) - ) { - Text( - text = stringResource(R.string.close_loading_current_location_dialog) - ) - } - } - } - } -} - -@Preview -@Composable -fun LoadingDialogPreview() { - MusicRoadTheme { - LoadingDialog( - onCloseClick = {} - ) - } -} diff --git a/app/src/main/java/com/squirtles/musicroad/map/components/MapBottomNavBar.kt b/app/src/main/java/com/squirtles/musicroad/map/components/MapBottomNavBar.kt deleted file mode 100644 index 9c04a92f..00000000 --- a/app/src/main/java/com/squirtles/musicroad/map/components/MapBottomNavBar.kt +++ /dev/null @@ -1,140 +0,0 @@ -package com.squirtles.musicroad.map.components - -import android.content.res.Configuration -import android.location.Location -import androidx.compose.foundation.background -import androidx.compose.foundation.clickable -import androidx.compose.foundation.layout.Box -import androidx.compose.foundation.layout.Row -import androidx.compose.foundation.layout.fillMaxHeight -import androidx.compose.foundation.layout.padding -import androidx.compose.foundation.layout.size -import androidx.compose.foundation.shape.CircleShape -import androidx.compose.material3.Icon -import androidx.compose.material3.MaterialTheme -import androidx.compose.runtime.Composable -import androidx.compose.ui.Alignment -import androidx.compose.ui.Modifier -import androidx.compose.ui.draw.clip -import androidx.compose.ui.graphics.Color -import androidx.compose.ui.graphics.painter.Painter -import androidx.compose.ui.graphics.vector.rememberVectorPainter -import androidx.compose.ui.res.painterResource -import androidx.compose.ui.res.stringResource -import androidx.compose.ui.tooling.preview.Preview -import androidx.compose.ui.unit.dp -import com.squirtles.common.ui.theme.MusicRoadTheme -import com.squirtles.common.ui.theme.Primary -import com.squirtles.musicroad.R -import com.squirtles.musicroad.map.BottomNavigationIconSize -import com.squirtles.musicroad.map.BottomNavigationSize -import com.squirtles.musicroad.map.navigation.NavTab - -@Composable -internal fun MapBottomNavBar( - modifier: Modifier = Modifier, - lastLocation: Location?, - onFavoriteClick: () -> Unit, - onCenterClick: () -> Unit, - onUserInfoClick: () -> Unit, -) { - Box( - modifier = modifier, - contentAlignment = Alignment.Center - ) { - Row( - modifier = Modifier - .size(BottomNavigationSize.WIDTH.size.dp, BottomNavigationSize.HEIGHT.size.dp) - .clip(CircleShape) - .background(color = MaterialTheme.colorScheme.surface) - ) { - // 왼쪽 버튼 - MapBottomNavigationItem( - modifier = Modifier - .weight(1f) - .fillMaxHeight() - .padding(end = BottomNavigationSize.HORIZONTAL_PADDING.size.dp), - tab = NavTab.FAVORITE, - painter = null, - tint = Primary, - onClick = onFavoriteClick - ) - - // 오른쪽 버튼 - MapBottomNavigationItem( - modifier = Modifier - .weight(1f) - .fillMaxHeight() - .padding(start = BottomNavigationSize.HORIZONTAL_PADDING.size.dp), - tab = NavTab.MYPAGE, - painter = null, - tint = Primary, - onClick = onUserInfoClick - ) - } - - // 중앙 버튼 - MapBottomNavigationItem( - modifier = Modifier - .size(BottomNavigationIconSize.CENTER.size.dp) - .clip(CircleShape) - .background( - color = lastLocation?.let { - MaterialTheme.colorScheme.primary - } ?: Color.Gray - ), - tab = NavTab.SEARCH, - painter = painterResource(R.drawable.ic_musical_note_64), - tint = MaterialTheme.colorScheme.onPrimary, - onClick = onCenterClick - ) - } -} - -@Composable -private fun MapBottomNavigationItem( - modifier: Modifier = Modifier, - tab: NavTab, - painter: Painter?, - tint: Color, - onClick: () -> Unit -) { - Box( - modifier = modifier - .clickable { onClick() }, - contentAlignment = Alignment.Center, - ) { - Icon( - modifier = if (tab.iconSize != null) Modifier.size(tab.iconSize.dp) else Modifier, - painter = painter ?: rememberVectorPainter(tab.icon), - contentDescription = stringResource(tab.contentDescription), - tint = tint - ) - } -} - -@Preview(showBackground = true) -@Composable -fun BottomNavigationLightPreview() { - MusicRoadTheme { - MapBottomNavBar( - onFavoriteClick = {}, - lastLocation = null, - onCenterClick = {}, - onUserInfoClick = {} - ) - } -} - -@Preview(showBackground = true, uiMode = Configuration.UI_MODE_NIGHT_YES) -@Composable -fun BottomNavigationDarkPreview() { - MusicRoadTheme { - MapBottomNavBar( - onFavoriteClick = {}, - lastLocation = null, - onCenterClick = {}, - onUserInfoClick = {} - ) - } -} diff --git a/app/src/main/java/com/squirtles/musicroad/map/components/PickNotificationBanner.kt b/app/src/main/java/com/squirtles/musicroad/map/components/PickNotificationBanner.kt deleted file mode 100644 index 39826e8b..00000000 --- a/app/src/main/java/com/squirtles/musicroad/map/components/PickNotificationBanner.kt +++ /dev/null @@ -1,84 +0,0 @@ -package com.squirtles.musicroad.map.components - -import androidx.compose.animation.core.FastOutSlowInEasing -import androidx.compose.animation.core.RepeatMode -import androidx.compose.animation.core.animateFloat -import androidx.compose.animation.core.infiniteRepeatable -import androidx.compose.animation.core.rememberInfiniteTransition -import androidx.compose.animation.core.tween -import androidx.compose.foundation.background -import androidx.compose.foundation.clickable -import androidx.compose.foundation.layout.Box -import androidx.compose.foundation.layout.fillMaxSize -import androidx.compose.foundation.layout.offset -import androidx.compose.foundation.layout.padding -import androidx.compose.foundation.shape.RoundedCornerShape -import androidx.compose.material3.Text -import androidx.compose.runtime.Composable -import androidx.compose.runtime.getValue -import androidx.compose.ui.Alignment -import androidx.compose.ui.Modifier -import androidx.compose.ui.draw.clip -import androidx.compose.ui.platform.LocalConfiguration -import androidx.compose.ui.res.stringResource -import androidx.compose.ui.tooling.preview.Preview -import androidx.compose.ui.unit.dp -import com.squirtles.common.ui.theme.Black -import com.squirtles.common.ui.theme.MusicRoadTheme -import com.squirtles.common.ui.theme.White -import com.squirtles.detail.DetailViewModel.Companion.DEFAULT_PICK -import com.squirtles.model.Pick -import com.squirtles.musicroad.R - -@Composable -fun PickNotificationBanner( - modifier: Modifier = Modifier, - isPlaying: Boolean = false, - nearPicks: List, - onClick: () -> Unit, -) { - val infiniteTransition = rememberInfiniteTransition(label = "infinite repeatable") - val offsetY by infiniteTransition.animateFloat( - initialValue = 8f, - targetValue = 0f, - animationSpec = infiniteRepeatable( - animation = tween(durationMillis = 600, easing = FastOutSlowInEasing), - repeatMode = RepeatMode.Reverse - ), - label = "infinite repeatable" - ) - - Box( - modifier = modifier - .fillMaxSize() - .offset(y = offsetY.dp), - ) { - Text( - text = stringResource( - id = if (isPlaying) R.string.map_pick_notification_stop else R.string.map_pick_notification_play, - nearPicks.count() - ), - modifier = Modifier - .align(Alignment.TopCenter) - .offset(y = (LocalConfiguration.current.screenHeightDp * 0.08).dp) - .clip(RoundedCornerShape(30.dp)) - .clickable { - onClick() - } - .background(White.copy(alpha = 0.8f)) - .padding(vertical = 10.dp, horizontal = 23.dp), - color = Black, - ) - } -} - -@Preview -@Composable -private fun PickNotificationBannerPreview() { - MusicRoadTheme { - PickNotificationBanner( - nearPicks = listOf(DEFAULT_PICK), - onClick = { } - ) - } -} diff --git a/app/src/main/java/com/squirtles/musicroad/map/marker/ClusterMarkerIconView.kt b/app/src/main/java/com/squirtles/musicroad/map/marker/ClusterMarkerIconView.kt deleted file mode 100644 index 7603c49d..00000000 --- a/app/src/main/java/com/squirtles/musicroad/map/marker/ClusterMarkerIconView.kt +++ /dev/null @@ -1,55 +0,0 @@ -package com.squirtles.musicroad.map.marker - -import android.annotation.SuppressLint -import android.content.Context -import android.graphics.Canvas -import android.graphics.Paint -import android.view.View - -@SuppressLint("ViewConstructor") -class ClusterMarkerIconView( - context: Context, - private val densityType: DensityType -) : View(context) { - - private val fillPaint = Paint().apply { - style = Paint.Style.FILL - } - - override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) { - val width = resolveSize(MARKER_WIDTH.dpToPx(), widthMeasureSpec) - val height = resolveSize(MARKER_HEIGHT.dpToPx(), heightMeasureSpec) - setMeasuredDimension(width, height) - } - - override fun onDraw(canvas: Canvas) { - super.onDraw(canvas) - fillPaint.color = densityType.color - - // back circle - fillPaint.alpha = 64 - canvas.drawCircle( - width / 2f, - height / 2f, - (width / 2f) - densityType.offset, - fillPaint - ) - - // circle - fillPaint.alpha = 255 - canvas.drawCircle( - width / 2f, - height / 2f, - (width / 2f) - OFFSET - densityType.offset, - fillPaint - ) - } - - private fun Int.dpToPx() = (this * resources.displayMetrics.density).toInt() - - companion object { - private const val OFFSET = 8 - private const val MARKER_WIDTH = 40 - private const val MARKER_HEIGHT = 50 - } -} diff --git a/app/src/main/java/com/squirtles/musicroad/map/marker/Clusterer.kt b/app/src/main/java/com/squirtles/musicroad/map/marker/Clusterer.kt deleted file mode 100644 index d1f6f242..00000000 --- a/app/src/main/java/com/squirtles/musicroad/map/marker/Clusterer.kt +++ /dev/null @@ -1,148 +0,0 @@ -package com.squirtles.musicroad.map.marker - -import android.content.Context -import android.graphics.PointF -import android.view.View -import androidx.compose.ui.graphics.toArgb -import com.naver.maps.map.clustering.ClusterMarkerInfo -import com.naver.maps.map.clustering.Clusterer -import com.naver.maps.map.clustering.ClusteringKey -import com.naver.maps.map.clustering.DefaultClusterMarkerUpdater -import com.naver.maps.map.clustering.DefaultLeafMarkerUpdater -import com.naver.maps.map.clustering.DefaultMarkerManager -import com.naver.maps.map.clustering.LeafMarkerInfo -import com.naver.maps.map.overlay.Align -import com.naver.maps.map.overlay.Marker -import com.naver.maps.map.overlay.Overlay -import com.naver.maps.map.overlay.OverlayImage -import com.squirtles.common.ui.Constants.REQUEST_IMAGE_SIZE_DEFAULT -import com.squirtles.common.ui.theme.Black -import com.squirtles.common.ui.theme.Blue -import com.squirtles.common.ui.theme.Primary -import com.squirtles.common.ui.theme.White -import com.squirtles.musicroad.map.DEFAULT_MARKER_Z_INDEX -import com.squirtles.musicroad.map.MapViewModel -import com.squirtles.musicroad.map.setCameraToMarker - -internal fun buildClusterer( - context: Context, - mapViewModel: MapViewModel, -): Clusterer { - return Clusterer.ComplexBuilder() - .thresholdStrategy { zoom -> - when { - zoom >= 17.0 -> 15.0 - zoom >= 16.0 -> 16.0 - zoom >= 15.0 -> 17.0 - zoom >= 14.0 -> 20.0 - zoom >= 13.5 -> 25.0 - zoom >= 13.0 -> 30.0 - zoom >= 12.5 -> 35.0 - zoom >= 12.0 -> 40.0 - zoom >= 11.0 -> 45.0 - zoom >= 9.0 -> 40.0 - zoom >= 7.5 -> 21.0 - zoom >= 7.0 -> 18.0 - else -> 15.0 - } - } - .tagMergeStrategy { cluster -> - cluster.children.map { it.tag }.joinToString(",") - } - .markerManager(object : DefaultMarkerManager() { - override fun createMarker(): Marker { - val marker = Marker() - with(marker) { - icon = OverlayImage.fromView(View(context)) - setCaptionAligns(Align.Center) - captionHaloColor = android.graphics.Color.TRANSPARENT - } - return marker - } - }) - .clusterMarkerUpdater(object : DefaultClusterMarkerUpdater() { - override fun updateClusterMarker(info: ClusterMarkerInfo, marker: Marker) { - // 클릭된 마커가 클러스터 마커 안에 포함되면 클릭된 마커 해제 - mapViewModel.clickedMarkerState.value.curPickId?.let { curPickId -> - if (info.tag.toString().contains(curPickId)) { - mapViewModel.resetClickedMarkerState(context) - } - } - val densityType = when { - info.size < 10 -> DensityType.LOW - info.size < 100 -> DensityType.MEDIUM - else -> DensityType.HIGH - } - val captionColor = when { - densityType == DensityType.LOW -> Black - else -> White - } - val clusterMarkerIconView = ClusterMarkerIconView(context, densityType) - marker.icon = OverlayImage.fromView(clusterMarkerIconView) - marker.zIndex = DEFAULT_MARKER_Z_INDEX - marker.anchor = PointF(0.5F, 0.5F) - marker.captionText = info.size.toString() - marker.captionColor = captionColor.toArgb() - marker.onClickListener = Overlay.OnClickListener { - marker.map?.let { map -> - setCameraToMarker( - map = map, - clickedMarkerPosition = marker.position - ) - mapViewModel.setClickedMarkerState( - context = context, - marker = marker, - clusterTag = info.tag.toString() - ) - } - true - } - // 클러스터 마커를 클릭한 채로 configuration change 시 크기 유지 - if (info.tag.toString() - == mapViewModel.clickedMarkerState.value.clusterPickList?.joinToString(",") { it.id } - ) { - mapViewModel.setClickedMarker(context, marker) - } - } - }) - .leafMarkerUpdater(object : DefaultLeafMarkerUpdater() { - override fun updateLeafMarker(info: LeafMarkerInfo, marker: Marker) { - marker.anchor = Marker.DEFAULT_ANCHOR - marker.zIndex = DEFAULT_MARKER_Z_INDEX - marker.captionText = "" - - val pick = (info.key as MarkerKey).pick - val leafMarkerIconView = LeafMarkerIconView(context).apply { - val color = if (pick.createdBy.uid == mapViewModel.getUid()) Blue else Primary - setPaintColor(color.toArgb()) - } - leafMarkerIconView.setLeafMarkerIcon( - pick.song.getImageUrlWithSize( - REQUEST_IMAGE_SIZE_DEFAULT.width, - REQUEST_IMAGE_SIZE_DEFAULT.height - ) - ) { - marker.icon = OverlayImage.fromView(leafMarkerIconView) - marker.setOnClickListener { - marker.map?.let { map -> - setCameraToMarker( - map = map, - clickedMarkerPosition = marker.position - ) - mapViewModel.setClickedMarkerState( - context = context, - marker = marker, - pickId = pick.id - ) - } - true - } - // 단말 마커를 클릭한 채로 configuration change 시 크기 유지 - if (pick.id == mapViewModel.clickedMarkerState.value.curPickId) { - mapViewModel.setClickedMarker(context, marker) - } - } - } - }) - .build() -} diff --git a/app/src/main/java/com/squirtles/musicroad/map/marker/DensityType.kt b/app/src/main/java/com/squirtles/musicroad/map/marker/DensityType.kt deleted file mode 100644 index 90606120..00000000 --- a/app/src/main/java/com/squirtles/musicroad/map/marker/DensityType.kt +++ /dev/null @@ -1,12 +0,0 @@ -package com.squirtles.musicroad.map.marker - -import androidx.compose.ui.graphics.toArgb -import com.squirtles.common.ui.theme.Primary20 -import com.squirtles.common.ui.theme.Primary50 -import com.squirtles.common.ui.theme.Primary80 - -enum class DensityType(val offset: Int, val color: Int) { - LOW(4, Primary80.toArgb()), - MEDIUM(2, Primary50.toArgb()), - HIGH(0, Primary20.toArgb()) -} diff --git a/app/src/main/java/com/squirtles/musicroad/map/marker/LeafMarkerIconView.kt b/app/src/main/java/com/squirtles/musicroad/map/marker/LeafMarkerIconView.kt deleted file mode 100644 index 26735017..00000000 --- a/app/src/main/java/com/squirtles/musicroad/map/marker/LeafMarkerIconView.kt +++ /dev/null @@ -1,103 +0,0 @@ -package com.squirtles.musicroad.map.marker - -import android.content.Context -import android.graphics.Bitmap -import android.graphics.Canvas -import android.graphics.Paint -import android.graphics.Rect -import android.util.AttributeSet -import android.view.View -import androidx.annotation.ColorInt -import coil3.SingletonImageLoader -import coil3.request.ImageRequest -import coil3.request.allowHardware -import coil3.request.transformations -import coil3.toBitmap -import coil3.transform.CircleCropTransformation - -class LeafMarkerIconView( - context: Context, - attrs: AttributeSet? = null -) : View(context, attrs) { - private val fillPaint = Paint().apply { - style = Paint.Style.FILL - } - private val strokePaint = Paint().apply { - style = Paint.Style.STROKE - strokeWidth = STROKE_WIDTH - } - private val imageLoader = SingletonImageLoader.get(context) - private var bitmap: Bitmap? = null - private val bitmapRect = Rect() - - override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) { - val width = resolveSize(MARKER_WIDTH.dpToPx(), widthMeasureSpec) - val height = resolveSize(MARKER_HEIGHT.dpToPx(), heightMeasureSpec) - setMeasuredDimension(width, height) - } - - override fun onDraw(canvas: Canvas) { - super.onDraw(canvas) - - canvas.drawRect( - (width - STROKE_WIDTH) / 2f, - width / 2f, - (width + STROKE_WIDTH) / 2f, - height.toFloat(), - fillPaint - ) - bitmap?.let { - bitmapRect.set(0, 0, width, width) - canvas.drawBitmap(it, null, bitmapRect, null) - canvas.drawCircle( - width / 2f, - width / 2f, - (width - STROKE_WIDTH) / 2f, - strokePaint - ) // bitmap 테두리 그리기 - } ?: canvas.drawCircle( - width / 2f, - width / 2f, - width / 2f, - fillPaint - ) // bitmap이 null이면 이미지 없이 원만 그려지도록 - } - - fun setPaintColor(@ColorInt color: Int) { - fillPaint.color = color - strokePaint.color = color - } - - fun setLeafMarkerIcon(imgUrl: String?, onImageLoaded: () -> Unit) { - loadImage(imgUrl) { - onImageLoaded() - } - } - - private fun loadImage(url: String?, onImageLoaded: () -> Unit) { - val request = ImageRequest.Builder(context) - .data(url) - .allowHardware(false) - .transformations(CircleCropTransformation()) - .listener( - onSuccess = { _, result -> - bitmap = result.image.toBitmap() - onImageLoaded() - }, - onError = { _, error -> - onImageLoaded() - } - ) - .build() - - imageLoader.enqueue(request) - } - - private fun Int.dpToPx() = (this * resources.displayMetrics.density).toInt() - - companion object { - private const val STROKE_WIDTH = 8f - private const val MARKER_WIDTH = 40 - private const val MARKER_HEIGHT = 50 - } -} diff --git a/app/src/main/java/com/squirtles/musicroad/map/marker/MarkerKey.kt b/app/src/main/java/com/squirtles/musicroad/map/marker/MarkerKey.kt deleted file mode 100644 index 7969b5a9..00000000 --- a/app/src/main/java/com/squirtles/musicroad/map/marker/MarkerKey.kt +++ /dev/null @@ -1,9 +0,0 @@ -package com.squirtles.musicroad.map.marker - -import com.naver.maps.geometry.LatLng -import com.naver.maps.map.clustering.ClusteringKey -import com.squirtles.model.Pick - -data class MarkerKey(val pick: Pick) : ClusteringKey { - override fun getPosition() = LatLng(pick.location.latitude, pick.location.longitude) -} diff --git a/app/src/main/java/com/squirtles/musicroad/map/navigation/MapNavigation.kt b/app/src/main/java/com/squirtles/musicroad/map/navigation/MapNavigation.kt deleted file mode 100644 index 721bd213..00000000 --- a/app/src/main/java/com/squirtles/musicroad/map/navigation/MapNavigation.kt +++ /dev/null @@ -1,34 +0,0 @@ -package com.squirtles.musicroad.map.navigation - -import androidx.navigation.NavController -import androidx.navigation.NavGraphBuilder -import androidx.navigation.NavOptions -import androidx.navigation.compose.composable -import com.squirtles.musicplayer.PlayerServiceViewModel -import com.squirtles.musicroad.map.MapScreen -import com.squirtles.musicroad.map.MapViewModel -import com.squirtles.navigation.Route - -fun NavController.navigateMap(navOptions: NavOptions? = null) { - navigate(Route.Map, navOptions) -} - -fun NavGraphBuilder.mapNavGraph( - mapViewModel: MapViewModel, - playerServiceViewModel: PlayerServiceViewModel, - onFavoriteClick: (String) -> Unit, - onCenterClick: () -> Unit, - onUserInfoClick: (String) -> Unit, - onPickSummaryClick: (String) -> Unit, -) { - composable { - MapScreen( - mapViewModel = mapViewModel, - playerServiceViewModel = playerServiceViewModel, - onFavoriteClick = onFavoriteClick, - onCenterClick = onCenterClick, - onUserInfoClick = onUserInfoClick, - onPickSummaryClick = onPickSummaryClick, - ) - } -} diff --git a/app/src/main/java/com/squirtles/musicroad/map/navigation/NavTab.kt b/app/src/main/java/com/squirtles/musicroad/map/navigation/NavTab.kt deleted file mode 100644 index d24b0c21..00000000 --- a/app/src/main/java/com/squirtles/musicroad/map/navigation/NavTab.kt +++ /dev/null @@ -1,34 +0,0 @@ -package com.squirtles.musicroad.map.navigation - -import androidx.annotation.StringRes -import androidx.compose.material.icons.Icons -import androidx.compose.material.icons.filled.FavoriteBorder -import androidx.compose.material.icons.outlined.AccountCircle -import androidx.compose.material.icons.outlined.MusicNote -import androidx.compose.ui.graphics.vector.ImageVector -import com.squirtles.musicroad.R -import com.squirtles.musicroad.map.BottomNavigationIconSize - -internal enum class NavTab( - @StringRes val contentDescription: Int, - val icon: ImageVector, - val iconSize: Int?, -) { - FAVORITE( - contentDescription = R.string.map_navigation_favorite_icon_description, - icon = Icons.Default.FavoriteBorder, - iconSize = null, - ), - - MYPAGE( - contentDescription = R.string.map_navigation_setting_icon_description, - icon = Icons.Outlined.AccountCircle, - iconSize = null, - ), - - SEARCH( - contentDescription = R.string.map_navigation_center_icon_description, - icon = Icons.Outlined.MusicNote, - iconSize = BottomNavigationIconSize.CENTER_ICON.size, - ), -} From 3808a84fe6e7e969b1354c3812d5fbe324b8f7cf Mon Sep 17 00:00:00 2001 From: miller198 Date: Wed, 23 Apr 2025 18:14:16 +0900 Subject: [PATCH 55/62] =?UTF-8?q?[refactor]=20feature:main=20=EC=A0=81?= =?UTF-8?q?=EC=9A=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/build.gradle.kts | 88 +------- app/src/main/AndroidManifest.xml | 18 +- .../squirtles/musicroad/main/LoadingState.kt | 9 - .../squirtles/musicroad/main/MainActivity.kt | 197 ------------------ .../squirtles/musicroad/main/MainViewModel.kt | 61 ------ .../musicroad/main/NeedPermissionDialog.kt | 96 --------- .../squirtles/musicroad/main/PermissionBar.kt | 81 ------- .../musicroad/main/navigation/MainNavHost.kt | 77 ------- .../main/navigation/MainNavigator.kt | 127 ----------- .../convention/AndroidApplicationPlugin.kt | 4 + 10 files changed, 15 insertions(+), 743 deletions(-) delete mode 100644 app/src/main/java/com/squirtles/musicroad/main/LoadingState.kt delete mode 100644 app/src/main/java/com/squirtles/musicroad/main/MainActivity.kt delete mode 100644 app/src/main/java/com/squirtles/musicroad/main/MainViewModel.kt delete mode 100644 app/src/main/java/com/squirtles/musicroad/main/NeedPermissionDialog.kt delete mode 100644 app/src/main/java/com/squirtles/musicroad/main/PermissionBar.kt delete mode 100644 app/src/main/java/com/squirtles/musicroad/main/navigation/MainNavHost.kt delete mode 100644 app/src/main/java/com/squirtles/musicroad/main/navigation/MainNavigator.kt diff --git a/app/build.gradle.kts b/app/build.gradle.kts index 30f56257..f0818862 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -9,38 +9,18 @@ val keystoreProperties = Properties() keystoreProperties.load(FileInputStream(rootProject.file("app/keystore.properties"))) plugins { - alias(libs.plugins.android.application) - alias(libs.plugins.jetbrains.kotlin.android) + alias(libs.plugins.musicroad.android.application) alias(libs.plugins.ksp) alias(libs.plugins.hilt) alias(libs.plugins.google.services) - alias(libs.plugins.kotlin.serialization) alias(libs.plugins.firebase.crashlytics) } android { namespace = "com.squirtles.musicroad" - compileSdk = 34 defaultConfig { - applicationId = "com.squirtles.musicroad" - minSdk = 26 - targetSdk = 34 - versionCode = 10100 - versionName = "1.1.0" - -// testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner" - vectorDrawables { - useSupportLibrary = true - } - addManifestPlaceholders(mapOf("NAVERMAP_CLIENT_ID" to properties.getProperty("NAVERMAP_CLIENT_ID"))) - - buildConfigField( - "String", - "GOOGLE_CLIENT_ID", - "\"${properties.getProperty("GOOGLE_CLIENT_ID")}\"" - ) } signingConfigs { @@ -75,20 +55,9 @@ android { signingConfig = signingConfigs.getByName("signedRelease") } } - compileOptions { - sourceCompatibility = JavaVersion.VERSION_17 - targetCompatibility = JavaVersion.VERSION_17 - } - kotlinOptions { - jvmTarget = "17" - } buildFeatures { viewBinding = true buildConfig = true - compose = true - } - composeOptions { - kotlinCompilerExtensionVersion = "1.5.14" } packaging { resources { @@ -98,21 +67,7 @@ android { } dependencies { - implementation(projects.core.account) - implementation(projects.core.musicplayer) - implementation(projects.core.model) - implementation(projects.core.common) - implementation(projects.core.picklist) implementation(projects.core.navigation) - implementation(projects.core.util) - implementation(projects.domain.applemusic) - implementation(projects.domain.firebase) - implementation(projects.domain.user) - implementation(projects.domain.pick) - implementation(projects.domain.picklist) - implementation(projects.domain.favorite) - implementation(projects.domain.order) - implementation(projects.domain.location) implementation(projects.data.applemusic) implementation(projects.data.firebase) implementation(projects.data.user) @@ -120,6 +75,7 @@ dependencies { implementation(projects.data.favorite) implementation(projects.data.location) implementation(projects.data.order) + implementation(projects.feature.main) implementation(projects.feature.userinfo) implementation(projects.feature.search) implementation(projects.feature.create) @@ -130,59 +86,19 @@ dependencies { implementation(libs.androidx.core.ktx) implementation(libs.androidx.lifecycle.runtime.ktx) - implementation(libs.compose.ui) - implementation(libs.androidx.core.splashscreen) - implementation(libs.material) -// implementation(libs.androidx.compose.animation) testImplementation(libs.junit) androidTestImplementation(libs.androidx.junit) androidTestImplementation(libs.androidx.espresso.core) - androidTestImplementation(libs.compose.ui.test.junit4) - debugImplementation(libs.compose.ui.tooling) - debugImplementation(libs.compose.ui.test.manifest) - implementation(libs.kotlinx.immutable) - - // Compose - implementation(libs.androidx.activity.compose) - implementation(platform(libs.compose.bom)) - implementation(libs.compose.material) - androidTestImplementation(platform(libs.compose.bom)) - implementation(libs.navigation.compose) - implementation(libs.compose.material3) - implementation(libs.compose.material.icons.extended) - implementation(libs.compose.ui.tooling.preview) // Hilt implementation(libs.hilt.android) ksp(libs.hilt.android.compiler) androidTestImplementation(libs.hilt.android.testing) kspAndroidTest(libs.hilt.android.compiler) - implementation(libs.hilt.navigation.compose) // Firebase implementation(platform(libs.firebase.bom)) implementation(libs.firebase.analytics) - implementation(libs.firebase.auth.ktx) implementation(libs.google.firebase.dynamic.module.support) implementation(libs.firebase.crashlytics) - - // Map - implementation(libs.map.sdk) - implementation(libs.play.services.location) - - // Coil - implementation(libs.coil) - implementation(libs.coil.compose) - implementation(libs.coil.network.okhttp) - - // ExoPlayer - implementation(libs.bundles.exoplayer) - - // Serialization - implementation(libs.kotlinx.serialization.json) - - // Credentials - implementation(libs.androidx.credentials) - implementation(libs.androidx.credentials.play.services.auth) - implementation(libs.googleid) } diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 2c314c2a..d1f03844 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -35,16 +35,16 @@ android:name="com.naver.maps.map.CLIENT_ID" android:value="${NAVERMAP_CLIENT_ID}" /> - - - + + + + + + - - - + + + diff --git a/app/src/main/java/com/squirtles/musicroad/main/LoadingState.kt b/app/src/main/java/com/squirtles/musicroad/main/LoadingState.kt deleted file mode 100644 index 0523c309..00000000 --- a/app/src/main/java/com/squirtles/musicroad/main/LoadingState.kt +++ /dev/null @@ -1,9 +0,0 @@ -package com.squirtles.musicroad.main - -sealed class LoadingState { - data object Loading : LoadingState() - data class Success(val uid: String?) : LoadingState() - data class NetworkError(val error: String) : LoadingState() - data class CreatedUserError(val error: String) : LoadingState() - data class UserNotFoundError(val error: String) : LoadingState() -} diff --git a/app/src/main/java/com/squirtles/musicroad/main/MainActivity.kt b/app/src/main/java/com/squirtles/musicroad/main/MainActivity.kt deleted file mode 100644 index e60b8794..00000000 --- a/app/src/main/java/com/squirtles/musicroad/main/MainActivity.kt +++ /dev/null @@ -1,197 +0,0 @@ -package com.squirtles.musicroad.main - -import android.Manifest -import android.content.Context -import android.content.Intent -import android.net.Uri -import android.os.Bundle -import android.provider.Settings -import android.util.Log -import android.widget.Toast -import androidx.activity.compose.setContent -import androidx.activity.enableEdgeToEdge -import androidx.activity.viewModels -import androidx.appcompat.app.AppCompatActivity -import androidx.compose.foundation.layout.Box -import androidx.compose.foundation.layout.fillMaxSize -import androidx.compose.ui.Alignment -import androidx.compose.ui.Modifier -import androidx.core.content.PermissionChecker -import androidx.core.splashscreen.SplashScreen -import androidx.core.splashscreen.SplashScreen.Companion.installSplashScreen -import androidx.lifecycle.Lifecycle -import androidx.lifecycle.lifecycleScope -import androidx.lifecycle.repeatOnLifecycle -import androidx.navigation.compose.rememberNavController -import com.google.firebase.auth.FirebaseAuth -import com.squirtles.common.ui.theme.MusicRoadTheme -import com.squirtles.musicroad.R -import com.squirtles.musicroad.main.navigation.MainNavHost -import com.squirtles.musicroad.main.navigation.MainNavigator -import com.squirtles.musicroad.main.navigation.rememberMainNavigator -import dagger.hilt.android.AndroidEntryPoint -import kotlinx.coroutines.cancel -import kotlinx.coroutines.launch - -@AndroidEntryPoint -class MainActivity : AppCompatActivity() { - private val mainViewModel by viewModels() - - override fun onCreate(savedInstanceState: Bundle?) { - val splashScreen = installSplashScreen() - super.onCreate(savedInstanceState) - - setKeepOnScreenCondition(splashScreen) - enableEdgeToEdge() - - if (!checkSelfPermission()) { - requestPermissions(PERMISSIONS, REQUEST_PERMISSION_CODE) - } else { - setMusicRoadContent() - } - } - - override fun onRequestPermissionsResult( - requestCode: Int, - permissions: Array, - grantResults: IntArray - ) { - super.onRequestPermissionsResult(requestCode, permissions, grantResults) - - val deniedPermission = permissions.filterIndexed { index, _ -> - grantResults[index] == -1 - } - - if (requestCode == REQUEST_PERMISSION_CODE) { - if (deniedPermission.isEmpty()) { // 모든 권한이 허용된 경우 - setMusicRoadContent() - } else { // 권한이 하나라도 거부된 경우 - if (shouldShowRequestPermissionRationale(deniedPermission[0])) { // 권한 요청 가능 시 재요청 - showNeedPermissionDialog(true) { - showNeedPermissionDialog(false) - requestPermissions(deniedPermission.toTypedArray(), REQUEST_PERMISSION_CODE) - } - } else { // 권한 2번 거절 시 - mainViewModel.setCanRequestPermission(false) - showPermissionBar() - } - } - } - } - - override fun onResume() { - super.onResume() - - if (checkSelfPermission()) { - setMusicRoadContent() - } else if (mainViewModel.canRequestPermission.not()) { - showPermissionBar() - } - } - - private fun setKeepOnScreenCondition(splashScreen: SplashScreen) { - lifecycleScope.launch { - repeatOnLifecycle(Lifecycle.State.STARTED) { - mainViewModel.loadingState.collect { state -> - when (state) { - is LoadingState.Loading -> { - splashScreen.setKeepOnScreenCondition { true } - } - - is LoadingState.Success -> { - Log.d("MainActivity", "Success: ${state.uid}") - splashScreen.setKeepOnScreenCondition { false } - cancel() - } - - is LoadingState.UserNotFoundError -> { - FirebaseAuth.getInstance().signOut() - splashScreen.setKeepOnScreenCondition { false } - cancel() - } - - is LoadingState.NetworkError -> { - showToast(getString(R.string.main_network_error_message)) - finish() - } - - is LoadingState.CreatedUserError -> { - showToast(getString(R.string.main_create_user_fail_message)) - finish() - } - } - } - } - } - } - - private fun checkSelfPermission(): Boolean { - return PERMISSIONS.all { permission -> - PermissionChecker.checkSelfPermission(this, permission) == - PermissionChecker.PERMISSION_GRANTED - } - } - - private fun Context.showToast(message: String) { - Toast.makeText(this, message, Toast.LENGTH_LONG).show() - } - - private fun setMusicRoadContent() { - setContent { - val navigator: MainNavigator = rememberMainNavigator() - - MusicRoadTheme { - val navController = rememberNavController() - - MainNavHost( - navigator = navigator, - finishActivity = { this.finish() }, - ) - } - } - } - - private fun showNeedPermissionDialog( - showDialog: Boolean, - onConfirmClick: () -> Unit = {}, - ) { - setContent { - MusicRoadTheme { - Box( - modifier = Modifier.fillMaxSize(), - contentAlignment = Alignment.Center - ) { - NeedPermissionDialog( - showDialog = showDialog, - onConfirmClick = onConfirmClick - ) - } - } - } - } - - private fun showPermissionBar() { - setContent { - MusicRoadTheme { - PermissionBar( - onClick = { - val intent = - Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS) - val uri = Uri.fromParts("package", packageName, null) - intent.data = uri - startActivity(intent) - }, - ) - } - } - } - - companion object { - private val PERMISSIONS = arrayOf( - Manifest.permission.ACCESS_FINE_LOCATION, - Manifest.permission.ACCESS_COARSE_LOCATION, - Manifest.permission.RECORD_AUDIO - ) - private const val REQUEST_PERMISSION_CODE = 1000 - } -} diff --git a/app/src/main/java/com/squirtles/musicroad/main/MainViewModel.kt b/app/src/main/java/com/squirtles/musicroad/main/MainViewModel.kt deleted file mode 100644 index 233dbf79..00000000 --- a/app/src/main/java/com/squirtles/musicroad/main/MainViewModel.kt +++ /dev/null @@ -1,61 +0,0 @@ -package com.squirtles.musicroad.main - -import android.util.Log -import androidx.lifecycle.ViewModel -import androidx.lifecycle.viewModelScope -import com.squirtles.domain.firebase.FirebaseException -import com.squirtles.user.usecase.FetchUserByIdUseCase -import com.squirtles.user.usecase.GetCurrentUidUseCase -import dagger.hilt.android.lifecycle.HiltViewModel -import kotlinx.coroutines.flow.MutableStateFlow -import kotlinx.coroutines.flow.asStateFlow -import kotlinx.coroutines.launch -import javax.inject.Inject - -@HiltViewModel -class MainViewModel @Inject constructor( - private val fetchUserByIdUseCase: FetchUserByIdUseCase, - private val getCurrentUidUseCase: GetCurrentUidUseCase -) : ViewModel() { - - private val _loadingState = MutableStateFlow(LoadingState.Loading) - val loadingState = _loadingState.asStateFlow() - - private var _canRequestPermission = true - val canRequestPermission get() = _canRequestPermission - - init { - viewModelScope.launch { - getCurrentUidUseCase().let { uid -> - Log.d("AutoLogin", "현재 uid : $uid") - if (uid == null) { // 비로그인 상태 - _loadingState.emit(LoadingState.Success(null)) - } else { - fetchUser(uid) - } - } - } - } - - fun setCanRequestPermission(canRequest: Boolean) { - _canRequestPermission = canRequest - } - - private suspend fun fetchUser(uid: String) { - fetchUserByIdUseCase(uid) - .onSuccess { - _loadingState.emit(LoadingState.Success(it.uid)) - } - .onFailure { exception -> - when (exception) { - is FirebaseException.FetchDocumentFailedException -> { - _loadingState.emit(LoadingState.UserNotFoundError(exception.message)) - } - - else -> { - _loadingState.emit(LoadingState.NetworkError(exception.message.toString())) - } - } - } - } -} diff --git a/app/src/main/java/com/squirtles/musicroad/main/NeedPermissionDialog.kt b/app/src/main/java/com/squirtles/musicroad/main/NeedPermissionDialog.kt deleted file mode 100644 index 662ec410..00000000 --- a/app/src/main/java/com/squirtles/musicroad/main/NeedPermissionDialog.kt +++ /dev/null @@ -1,96 +0,0 @@ -package com.squirtles.musicroad.main - -import androidx.compose.foundation.layout.Arrangement -import androidx.compose.foundation.layout.Column -import androidx.compose.foundation.layout.fillMaxWidth -import androidx.compose.foundation.layout.padding -import androidx.compose.foundation.shape.RoundedCornerShape -import androidx.compose.material3.BasicAlertDialog -import androidx.compose.material3.ButtonDefaults -import androidx.compose.material3.ExperimentalMaterial3Api -import androidx.compose.material3.MaterialTheme -import androidx.compose.material3.Surface -import androidx.compose.material3.Text -import androidx.compose.material3.TextButton -import androidx.compose.runtime.Composable -import androidx.compose.ui.Alignment -import androidx.compose.ui.Modifier -import androidx.compose.ui.graphics.Color -import androidx.compose.ui.res.stringResource -import androidx.compose.ui.text.font.FontWeight -import androidx.compose.ui.text.style.TextAlign -import androidx.compose.ui.tooling.preview.Preview -import androidx.compose.ui.unit.dp -import com.squirtles.common.ui.HorizontalSpacer -import com.squirtles.common.ui.VerticalSpacer -import com.squirtles.common.ui.theme.Black -import com.squirtles.common.ui.theme.DarkGray -import com.squirtles.common.ui.theme.MusicRoadTheme -import com.squirtles.common.ui.theme.White -import com.squirtles.musicroad.R - -@OptIn(ExperimentalMaterial3Api::class) -@Composable -fun NeedPermissionDialog( - showDialog: Boolean, - onConfirmClick: () -> Unit, -) { - if (showDialog) { - BasicAlertDialog( - onDismissRequest = {}, - ) { - Surface( - modifier = Modifier.fillMaxWidth(), - shape = RoundedCornerShape(16.dp), - color = White - ) { - Column( - modifier = Modifier.padding(24.dp), - horizontalAlignment = Alignment.CenterHorizontally, - verticalArrangement = Arrangement.Center - ) { - Text( - text = stringResource(R.string.request_permissions_title), - color = Black, - fontWeight = FontWeight.Bold, - style = MaterialTheme.typography.bodyLarge - ) - - VerticalSpacer(8) - - Text( - text = stringResource(R.string.request_permissions_body), - color = Black, - textAlign = TextAlign.Center, - style = MaterialTheme.typography.bodyLarge - ) - - VerticalSpacer(24) - - TextButton( - onClick = onConfirmClick, - colors = ButtonDefaults.buttonColors().copy( - containerColor = Color.Transparent, - contentColor = DarkGray - ) - ) { - Text(stringResource(R.string.confirm_text)) - } - - HorizontalSpacer(8) - } - } - } - } -} - -@Preview(showBackground = true) -@Composable -fun NeedPermissionDialogPreview() { - MusicRoadTheme { - NeedPermissionDialog( - showDialog = true, - onConfirmClick = {}, - ) - } -} diff --git a/app/src/main/java/com/squirtles/musicroad/main/PermissionBar.kt b/app/src/main/java/com/squirtles/musicroad/main/PermissionBar.kt deleted file mode 100644 index ae7755c6..00000000 --- a/app/src/main/java/com/squirtles/musicroad/main/PermissionBar.kt +++ /dev/null @@ -1,81 +0,0 @@ -package com.squirtles.musicroad.main - -import androidx.compose.foundation.background -import androidx.compose.foundation.layout.Box -import androidx.compose.foundation.layout.Row -import androidx.compose.foundation.layout.WindowInsets -import androidx.compose.foundation.layout.fillMaxSize -import androidx.compose.foundation.layout.fillMaxWidth -import androidx.compose.foundation.layout.navigationBars -import androidx.compose.foundation.layout.padding -import androidx.compose.foundation.layout.wrapContentHeight -import androidx.compose.foundation.shape.RoundedCornerShape -import androidx.compose.material3.CardDefaults -import androidx.compose.material3.ElevatedCard -import androidx.compose.material3.MaterialTheme -import androidx.compose.material3.Scaffold -import androidx.compose.material3.Text -import androidx.compose.material3.TextButton -import androidx.compose.runtime.Composable -import androidx.compose.ui.Alignment -import androidx.compose.ui.Modifier -import androidx.compose.ui.platform.LocalContext -import androidx.compose.ui.unit.dp -import com.squirtles.common.ui.Constants.DEFAULT_PADDING -import com.squirtles.musicroad.R - -@Composable -fun PermissionBar( - onClick: () -> Unit, -) { - val context = LocalContext.current - - Scaffold( - contentWindowInsets = WindowInsets.navigationBars - ) { innerPadding -> - Box( - modifier = Modifier - .fillMaxSize() - .padding(innerPadding) - .background(MaterialTheme.colorScheme.surface), - contentAlignment = Alignment.BottomCenter - ) { - ElevatedCard( - modifier = Modifier - .fillMaxWidth() - .wrapContentHeight() - .padding(DEFAULT_PADDING), - shape = RoundedCornerShape(4.dp), - colors = CardDefaults.elevatedCardColors( - containerColor = MaterialTheme.colorScheme.primaryContainer - ), - elevation = CardDefaults.elevatedCardElevation( - defaultElevation = 4.dp - ) - ) { - Row( - verticalAlignment = Alignment.CenterVertically - ) { - Text( - text = context.getString(R.string.snackbar_text), - modifier = Modifier - .weight(1f) - .padding(start = DEFAULT_PADDING), - color = MaterialTheme.colorScheme.onPrimaryContainer, - style = MaterialTheme.typography.bodyMedium - ) - - TextButton( - onClick = onClick, - modifier = Modifier.padding(end = DEFAULT_PADDING / 2) - ) { - Text( - text = context.getString(R.string.action_to_setting), - style = MaterialTheme.typography.bodyMedium - ) - } - } - } - } - } -} diff --git a/app/src/main/java/com/squirtles/musicroad/main/navigation/MainNavHost.kt b/app/src/main/java/com/squirtles/musicroad/main/navigation/MainNavHost.kt deleted file mode 100644 index b7dfd2d3..00000000 --- a/app/src/main/java/com/squirtles/musicroad/main/navigation/MainNavHost.kt +++ /dev/null @@ -1,77 +0,0 @@ -package com.squirtles.musicroad.main.navigation - -import androidx.compose.runtime.Composable -import androidx.compose.ui.Modifier -import androidx.hilt.navigation.compose.hiltViewModel -import androidx.navigation.compose.NavHost -import com.squirtles.create.navigation.createNavGraph -import com.squirtles.detail.navigation.detailNavGraph -import com.squirtles.favorite.navigation.favoriteNavGraph -import com.squirtles.map.MapViewModel -import com.squirtles.map.navigation.mapNavGraph -import com.squirtles.musicplayer.PlayerServiceViewModel -import com.squirtles.mypick.navigation.myPickNavGraph -import com.squirtles.search.navigation.searchNavGraph -import com.squirtles.userinfo.navigation.userInfoNavGraph - -@Composable -internal fun MainNavHost( - modifier: Modifier = Modifier, - navigator: MainNavigator, - finishActivity: () -> Unit, - mapViewModel: MapViewModel = hiltViewModel(), - playerServiceViewModel: PlayerServiceViewModel = hiltViewModel(), -) { - NavHost( - navController = navigator.navController, - startDestination = navigator.startDestination, - ) { - mapNavGraph( - mapViewModel = mapViewModel, - playerServiceViewModel = playerServiceViewModel, - onFavoriteClick = navigator::navigateFavorite, - onCenterClick = navigator::navigateSearch, - onUserInfoClick = navigator::navigateUserInfo, - onPickSummaryClick = navigator::navigatePickDetail, - onLoadingDialogCloseClick = finishActivity - ) - - searchNavGraph( - onBackClick = navigator::popBackStackIfNotMap, - onItemClick = navigator::navigateCreate, - ) - - detailNavGraph( - playerServiceViewModel = playerServiceViewModel, - onUserInfoClick = navigator::navigateUserInfo, - onBackClick = navigator::popBackStackIfNotMap, - onDeleted = mapViewModel::resetClickedMarkerState - ) - - createNavGraph( - onBackClick = navigator::popBackStackIfNotMap, - onCreateClick = { pickId -> - navigator.navigatePickDetail(pickId, true) - }, - ) - - favoriteNavGraph( - onBackClick = navigator::popBackStackIfNotMap, - onItemClick = navigator::navigatePickDetail - ) - - userInfoNavGraph( - onBackClick = navigator::popBackStackIfNotMap, - onBackToMapClick = navigator::navigateMap, - onFavoritePicksClick = navigator::navigateFavorite, - onMyPicksClick = navigator::navigateMyPicks, - onEditProfileClick = navigator::navigateEditProfile, - onEditNotificationClick = navigator::navigateEditNotificationSetting, - ) - - myPickNavGraph( - onBackClick = navigator::popBackStackIfNotMap, - onItemClick = navigator::navigatePickDetail, - ) - } -} diff --git a/app/src/main/java/com/squirtles/musicroad/main/navigation/MainNavigator.kt b/app/src/main/java/com/squirtles/musicroad/main/navigation/MainNavigator.kt deleted file mode 100644 index 54e76206..00000000 --- a/app/src/main/java/com/squirtles/musicroad/main/navigation/MainNavigator.kt +++ /dev/null @@ -1,127 +0,0 @@ -package com.squirtles.musicroad.main.navigation - -import androidx.compose.runtime.Composable -import androidx.compose.runtime.remember -import androidx.navigation.NavDestination -import androidx.navigation.NavDestination.Companion.hasRoute -import androidx.navigation.NavHostController -import androidx.navigation.compose.currentBackStackEntryAsState -import androidx.navigation.compose.rememberNavController -import androidx.navigation.navOptions -import com.squirtles.create.navigation.navigateCreate -import com.squirtles.detail.navigation.navigatePickDetail -import com.squirtles.favorite.navigation.navigateFavorite -import com.squirtles.map.navigation.navigateMap -import com.squirtles.model.Song -import com.squirtles.mypick.navigation.navigateMyPicks -import com.squirtles.navigation.Route -import com.squirtles.search.navigation.navigateSearch -import com.squirtles.userinfo.navigation.navigateEditNotificationSetting -import com.squirtles.userinfo.navigation.navigateEditProfile -import com.squirtles.userinfo.navigation.navigateUserInfo - -internal class MainNavigator( - val navController: NavHostController -) { - private val currentDestination: NavDestination? - @Composable get() = navController - .currentBackStackEntryAsState().value?.destination - - val startDestination = Route.Map - - fun navigateMap() { - navController.navigateMap( - navOptions { - popUpTo(startDestination) { - inclusive = true - } - launchSingleTop = true - } - ) - } - - fun navigateFavorite(uid: String) { - navController.navigateFavorite( - uid = uid, - navOptions { - launchSingleTop = true - } - ) - } - - fun navigatePickDetail(pickId: String, navigateToMap: Boolean = false) { - navController.navigatePickDetail( - pickId = pickId, - navOptions = navOptions { - if (navigateToMap) { - popUpTo(startDestination) { - inclusive = false - } - } - launchSingleTop = true - } - ) - } - - fun navigateMyPicks(uid: String) { - navController.navigateMyPicks( - uid = uid, - navOptions = navOptions { launchSingleTop = true } - ) - } - - fun navigateUserInfo(uid: String) { - navController.navigateUserInfo( - uid = uid, - navOptions = navOptions { launchSingleTop = true } - ) - } - - fun navigateEditProfile(userName: String) { - navController.navigateEditProfile( - userName = userName, - navOptions = navOptions { launchSingleTop = true } - ) - } - - fun navigateEditNotificationSetting() { - navController.navigateEditNotificationSetting( - navOptions = navOptions { launchSingleTop = true } - ) - } - - fun navigateSearch() { - navController.navigateSearch( - navOptions = navOptions { launchSingleTop = true } - ) - } - - fun navigateCreate(song: Song) { - navController.navigateCreate( - song = song, - navOptions = navOptions { launchSingleTop = true } - ) - } - - private fun popBackStack() { - navController.popBackStack() - } - - fun popBackStackIfNotMap() { - if (!isSameCurrentDestination()) { - popBackStack() - } - } - - private inline fun isSameCurrentDestination(): Boolean { - return navController.currentDestination?.hasRoute() == true - } - -} - -@Composable -internal fun rememberMainNavigator( - navController: NavHostController = rememberNavController(), -): MainNavigator = remember(navController) { - MainNavigator(navController) -} diff --git a/build-logic/convention/src/main/java/com/squirtles/convention/AndroidApplicationPlugin.kt b/build-logic/convention/src/main/java/com/squirtles/convention/AndroidApplicationPlugin.kt index b0218b13..f4ec51b9 100644 --- a/build-logic/convention/src/main/java/com/squirtles/convention/AndroidApplicationPlugin.kt +++ b/build-logic/convention/src/main/java/com/squirtles/convention/AndroidApplicationPlugin.kt @@ -18,9 +18,13 @@ class AndroidApplicationPlugin: Plugin { extensions.configure { defaultConfig { + applicationId = "com.squirtles.musicroad" + targetSdk = libs.findVersion("targetSdk").get().toString().toInt() versionCode = libs.findVersion("versionCode").get().toString().toInt() versionName = libs.findVersion("versionName").get().toString() + + testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner" } configureKotlinAndroid(this) From 530e475447bdbc1fff3150ca8f74bb980f9b2d4a Mon Sep 17 00:00:00 2001 From: miller198 Date: Wed, 23 Apr 2025 18:33:30 +0900 Subject: [PATCH 56/62] =?UTF-8?q?[refactor]=20mediaservice=20=EB=AA=A8?= =?UTF-8?q?=EB=93=88=20=EC=82=AD=EC=A0=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../audiovisualizer/configs/Configs.kt | 2 +- core/account/src/main/res/values/strings.xml | 2 + .../musicplayer/PlayerServiceViewModel.kt | 2 +- .../domain/pick/usecase/DeletePickUseCase.kt | 4 +- .../picklist/RemovePickUseCaseInterface.kt | 2 +- .../com/squirtles/detail/PickDetailScreen.kt | 4 +- .../java/com/squirtles/main/MainActivity.kt | 2 - .../map/marker/LeafMarkerIconView.kt | 2 +- mediaservice/.gitignore | 1 - mediaservice/build.gradle.kts | 17 --- mediaservice/consumer-rules.pro | 0 mediaservice/proguard-rules.pro | 21 --- .../mediaservice/ExampleInstrumentedTest.kt | 24 ---- mediaservice/src/main/AndroidManifest.xml | 4 - .../CustomMediaSessionCallback.kt | 96 -------------- .../mediaservice/MediaControllerProvider.kt | 58 --------- .../mediaservice/MediaNotificationProvider.kt | 120 ------------------ .../mediaservice/MediaPlayerService.kt | 62 --------- .../squirtles/mediaservice/PlayerCommands.kt | 40 ------ .../mediaservice/di/MediaDiModule.kt | 101 --------------- .../res/drawable/ic_musicroad_foreground.xml | 56 -------- .../squirtles/mediaservice/ExampleUnitTest.kt | 17 --- settings.gradle.kts | 1 - 23 files changed, 10 insertions(+), 628 deletions(-) delete mode 100644 mediaservice/.gitignore delete mode 100644 mediaservice/build.gradle.kts delete mode 100644 mediaservice/consumer-rules.pro delete mode 100644 mediaservice/proguard-rules.pro delete mode 100644 mediaservice/src/androidTest/java/com/squirtles/mediaservice/ExampleInstrumentedTest.kt delete mode 100644 mediaservice/src/main/AndroidManifest.xml delete mode 100644 mediaservice/src/main/java/com/squirtles/mediaservice/CustomMediaSessionCallback.kt delete mode 100644 mediaservice/src/main/java/com/squirtles/mediaservice/MediaControllerProvider.kt delete mode 100644 mediaservice/src/main/java/com/squirtles/mediaservice/MediaNotificationProvider.kt delete mode 100644 mediaservice/src/main/java/com/squirtles/mediaservice/MediaPlayerService.kt delete mode 100644 mediaservice/src/main/java/com/squirtles/mediaservice/PlayerCommands.kt delete mode 100644 mediaservice/src/main/java/com/squirtles/mediaservice/di/MediaDiModule.kt delete mode 100644 mediaservice/src/main/res/drawable/ic_musicroad_foreground.xml delete mode 100644 mediaservice/src/test/java/com/squirtles/mediaservice/ExampleUnitTest.kt diff --git a/audio_visualizer/src/main/java/com/miller198/audiovisualizer/configs/Configs.kt b/audio_visualizer/src/main/java/com/miller198/audiovisualizer/configs/Configs.kt index 44adaaeb..a0328598 100644 --- a/audio_visualizer/src/main/java/com/miller198/audiovisualizer/configs/Configs.kt +++ b/audio_visualizer/src/main/java/com/miller198/audiovisualizer/configs/Configs.kt @@ -89,7 +89,7 @@ sealed interface VisualizerConfig { /** Default waveform configuration. */ data object Default : WaveCaptureConfig( captureSize = 1024, - processWaveData = { _, byteArray, _ -> + processWaveData = { _, _, _ -> // TODO: Implement default waveform preprocessing logic emptyList() } diff --git a/core/account/src/main/res/values/strings.xml b/core/account/src/main/res/values/strings.xml index 711c2358..9d28e755 100644 --- a/core/account/src/main/res/values/strings.xml +++ b/core/account/src/main/res/values/strings.xml @@ -2,4 +2,6 @@ 기기에 로그인된 구글 계정이 없습니다 + Google 로그인을 사용할 수 없는 기기입니다 + 로그인에 실패했습니다 diff --git a/core/musicplayer/src/main/java/com/squirtles/musicplayer/PlayerServiceViewModel.kt b/core/musicplayer/src/main/java/com/squirtles/musicplayer/PlayerServiceViewModel.kt index 3fa6fbb9..ecbb3c15 100644 --- a/core/musicplayer/src/main/java/com/squirtles/musicplayer/PlayerServiceViewModel.kt +++ b/core/musicplayer/src/main/java/com/squirtles/musicplayer/PlayerServiceViewModel.kt @@ -87,7 +87,7 @@ class PlayerServiceViewModel @Inject constructor( } } - fun togglePlayPause(song: Song) { + fun togglePlayPause() { if (playerState.value.isPlaying) { onPause() } else { diff --git a/domain/pick/src/main/java/com/squirtles/domain/pick/usecase/DeletePickUseCase.kt b/domain/pick/src/main/java/com/squirtles/domain/pick/usecase/DeletePickUseCase.kt index 4600a725..6c553669 100644 --- a/domain/pick/src/main/java/com/squirtles/domain/pick/usecase/DeletePickUseCase.kt +++ b/domain/pick/src/main/java/com/squirtles/domain/pick/usecase/DeletePickUseCase.kt @@ -7,6 +7,6 @@ import javax.inject.Inject class DeletePickUseCase @Inject constructor( private val pickRepository: FirebasePickRepository ) : RemovePickUseCaseInterface { - override suspend operator fun invoke(pickId: String, userId: String): Result = - pickRepository.deletePick(pickId, userId) + override suspend operator fun invoke(pickId: String, uid: String): Result = + pickRepository.deletePick(pickId, uid) } diff --git a/domain/picklist/src/main/java/com/squirtles/domain/picklist/RemovePickUseCaseInterface.kt b/domain/picklist/src/main/java/com/squirtles/domain/picklist/RemovePickUseCaseInterface.kt index 9c47a89a..fc6cbc06 100644 --- a/domain/picklist/src/main/java/com/squirtles/domain/picklist/RemovePickUseCaseInterface.kt +++ b/domain/picklist/src/main/java/com/squirtles/domain/picklist/RemovePickUseCaseInterface.kt @@ -1,5 +1,5 @@ package com.squirtles.domain.picklist interface RemovePickUseCaseInterface { - suspend operator fun invoke(pickId: String, userId: String): Result + suspend operator fun invoke(pickId: String, uid: String): Result } diff --git a/feature/detail/src/main/java/com/squirtles/detail/PickDetailScreen.kt b/feature/detail/src/main/java/com/squirtles/detail/PickDetailScreen.kt index 9f20a77e..7126497c 100644 --- a/feature/detail/src/main/java/com/squirtles/detail/PickDetailScreen.kt +++ b/feature/detail/src/main/java/com/squirtles/detail/PickDetailScreen.kt @@ -508,8 +508,8 @@ private fun PickDetailContents( playerServiceViewModel.onRewindBy() } }, - onPauseToggle = { song -> - playerServiceViewModel.togglePlayPause(song) + onPauseToggle = { _ -> + playerServiceViewModel.togglePlayPause() }, ) } diff --git a/feature/main/src/main/java/com/squirtles/main/MainActivity.kt b/feature/main/src/main/java/com/squirtles/main/MainActivity.kt index dcfb7b69..a6d4426b 100644 --- a/feature/main/src/main/java/com/squirtles/main/MainActivity.kt +++ b/feature/main/src/main/java/com/squirtles/main/MainActivity.kt @@ -140,8 +140,6 @@ class MainActivity : AppCompatActivity() { val navigator: MainNavigator = rememberMainNavigator() MusicRoadTheme { - val navController = rememberNavController() - MainNavHost( navigator = navigator, finishActivity = { this.finish() }, diff --git a/feature/map/src/main/java/com/squirtles/map/marker/LeafMarkerIconView.kt b/feature/map/src/main/java/com/squirtles/map/marker/LeafMarkerIconView.kt index 26496465..76e3b906 100644 --- a/feature/map/src/main/java/com/squirtles/map/marker/LeafMarkerIconView.kt +++ b/feature/map/src/main/java/com/squirtles/map/marker/LeafMarkerIconView.kt @@ -86,7 +86,7 @@ class LeafMarkerIconView( bitmap = result.image.toBitmap() onImageLoaded() }, - onError = { _, error -> + onError = { _, _ -> onImageLoaded() } ) diff --git a/mediaservice/.gitignore b/mediaservice/.gitignore deleted file mode 100644 index 42afabfd..00000000 --- a/mediaservice/.gitignore +++ /dev/null @@ -1 +0,0 @@ -/build \ No newline at end of file diff --git a/mediaservice/build.gradle.kts b/mediaservice/build.gradle.kts deleted file mode 100644 index 8c0622cf..00000000 --- a/mediaservice/build.gradle.kts +++ /dev/null @@ -1,17 +0,0 @@ -plugins { - alias(libs.plugins.musicroad.android.library) - alias(libs.plugins.musicroad.hilt) -} - -android { - namespace = "com.squirtles.mediaservice" -} - -dependencies { - testImplementation(libs.junit) - androidTestImplementation(libs.bundles.test) - - //media3 - implementation(libs.bundles.media3) - implementation(libs.bundles.exoplayer) -} diff --git a/mediaservice/consumer-rules.pro b/mediaservice/consumer-rules.pro deleted file mode 100644 index e69de29b..00000000 diff --git a/mediaservice/proguard-rules.pro b/mediaservice/proguard-rules.pro deleted file mode 100644 index 481bb434..00000000 --- a/mediaservice/proguard-rules.pro +++ /dev/null @@ -1,21 +0,0 @@ -# Add project specific ProGuard rules here. -# You can control the set of applied configuration files using the -# proguardFiles setting in build.gradle. -# -# For more details, see -# http://developer.android.com/guide/developing/tools/proguard.html - -# If your project uses WebView with JS, uncomment the following -# and specify the fully qualified class name to the JavaScript interface -# class: -#-keepclassmembers class fqcn.of.javascript.interface.for.webview { -# public *; -#} - -# Uncomment this to preserve the line number information for -# debugging stack traces. -#-keepattributes SourceFile,LineNumberTable - -# If you keep the line number information, uncomment this to -# hide the original source file name. -#-renamesourcefileattribute SourceFile \ No newline at end of file diff --git a/mediaservice/src/androidTest/java/com/squirtles/mediaservice/ExampleInstrumentedTest.kt b/mediaservice/src/androidTest/java/com/squirtles/mediaservice/ExampleInstrumentedTest.kt deleted file mode 100644 index 75004631..00000000 --- a/mediaservice/src/androidTest/java/com/squirtles/mediaservice/ExampleInstrumentedTest.kt +++ /dev/null @@ -1,24 +0,0 @@ -package com.squirtles.mediaservice - -import androidx.test.platform.app.InstrumentationRegistry -import androidx.test.ext.junit.runners.AndroidJUnit4 - -import org.junit.Test -import org.junit.runner.RunWith - -import org.junit.Assert.* - -/** - * Instrumented test, which will execute on an Android device. - * - * See [testing documentation](http://d.android.com/tools/testing). - */ -@RunWith(AndroidJUnit4::class) -class ExampleInstrumentedTest { - @Test - fun useAppContext() { - // Context of the app under test. - val appContext = InstrumentationRegistry.getInstrumentation().targetContext - assertEquals("com.squirtles.mediaservice.test", appContext.packageName) - } -} diff --git a/mediaservice/src/main/AndroidManifest.xml b/mediaservice/src/main/AndroidManifest.xml deleted file mode 100644 index 8bdb7e14..00000000 --- a/mediaservice/src/main/AndroidManifest.xml +++ /dev/null @@ -1,4 +0,0 @@ - - - - diff --git a/mediaservice/src/main/java/com/squirtles/mediaservice/CustomMediaSessionCallback.kt b/mediaservice/src/main/java/com/squirtles/mediaservice/CustomMediaSessionCallback.kt deleted file mode 100644 index a6601231..00000000 --- a/mediaservice/src/main/java/com/squirtles/mediaservice/CustomMediaSessionCallback.kt +++ /dev/null @@ -1,96 +0,0 @@ -//package com.squirtles.mediaservice -// -//import android.os.Build -//import android.os.Bundle -//import androidx.annotation.OptIn -//import androidx.media3.common.util.UnstableApi -//import androidx.media3.session.CommandButton -//import androidx.media3.session.MediaSession -//import androidx.media3.session.SessionCommand -//import androidx.media3.session.SessionResult -//import com.google.common.util.concurrent.Futures -//import com.google.common.util.concurrent.ListenableFuture -// -//const val SEEK_TO_DURATION = 5_000L -// -//@OptIn(UnstableApi::class) -//internal class CustomMediaSessionCallback : MediaSession.Callback { -// -// /* MediaSession이 MediaController의 연결 요청을 수락할 때 사용할 수 있는 명령어 집합을 구성하고 반환 */ -// override fun onConnect( -// session: MediaSession, -// controller: MediaSession.ControllerInfo -// ): MediaSession.ConnectionResult = -// -// // 컨트롤러가 미디어 알림과 연관된 컨트롤러인지 확인 -// if (session.isMediaNotificationController(controller)) { -// val connectionResult = super.onConnect(session, controller) -// val availableSessionCommands = connectionResult.availableSessionCommands.buildUpon() -// var customCommands = PlayerCommands.entries.toList() -// -// if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE) { -// // 안드로이드 14 (API 34) 이상 -// customCommands = customCommands.reversed() -// } -// -// customCommands.forEach { commandButton -> -// commandButton.sessionCommand.let { -// it -// availableSessionCommands.add(it) -// } -// } -// -// MediaSession.ConnectionResult.AcceptedResultBuilder(session) -// .setAvailableSessionCommands(availableSessionCommands.build()) -// .setCustomLayout( -// customCommands -// .filter { -// it.customAction == PlayerCommands.SEEK_REWIND.customAction -// || it.customAction == PlayerCommands.SEEK_FORWARD.customAction -// }.map { -// CommandButton.Builder() -// .setDisplayName(it.displayName) -// .setIconResId(it.iconResId(session.player.isPlaying)) -// .setSessionCommand(it.sessionCommand) -// .build() -// } -// ) -// .build() -// } else { -// MediaSession.ConnectionResult.AcceptedResultBuilder(session).build() -// } -// -// // 사용자 정의 명령이 수신되었을 때 호출되는 콜백 -// override fun onCustomCommand( -// session: MediaSession, -// controller: MediaSession.ControllerInfo, -// customCommand: SessionCommand, -// args: Bundle -// ): ListenableFuture { -// -// when (customCommand.customAction) { -// PlayerCommands.SEEK_REWIND.customAction -> { -// session.player.run { -// seekTo(currentPosition - SEEK_TO_DURATION) -// } -// } -// -// PlayerCommands.PLAY_AND_PAUSE.customAction -> { -// if (!session.player.isPlaying) session.player.play() -// else session.player.pause() -// } -// -// PlayerCommands.SEEK_FORWARD.customAction -> { -// session.player.run { -// seekTo(currentPosition + SEEK_TO_DURATION) -// } -// } -// -// else -> { -// // Do nothing -// } -// } -// -// return Futures.immediateFuture(SessionResult(SessionResult.RESULT_SUCCESS)) -// } -//} diff --git a/mediaservice/src/main/java/com/squirtles/mediaservice/MediaControllerProvider.kt b/mediaservice/src/main/java/com/squirtles/mediaservice/MediaControllerProvider.kt deleted file mode 100644 index 8f8e89d4..00000000 --- a/mediaservice/src/main/java/com/squirtles/mediaservice/MediaControllerProvider.kt +++ /dev/null @@ -1,58 +0,0 @@ -//package com.squirtles.mediaservice -// -//import androidx.media3.common.util.UnstableApi -//import androidx.media3.session.MediaController -//import com.google.common.util.concurrent.FutureCallback -//import com.google.common.util.concurrent.Futures -//import com.google.common.util.concurrent.ListenableFuture -//import com.google.common.util.concurrent.MoreExecutors -//import kotlinx.coroutines.cancel -//import kotlinx.coroutines.channels.awaitClose -//import kotlinx.coroutines.flow.Flow -//import kotlinx.coroutines.flow.callbackFlow -//import javax.inject.Inject -//import javax.inject.Singleton -//import kotlin.coroutines.cancellation.CancellationException -// -//interface MediaControllerProvider { -// val mediaControllerFlow: Flow -// val audioSessionFlow: Flow -//} -// -///* mediaControllerFuture: MediaController를 비동기적으로 제공하는 ListenableFuture */ -//@UnstableApi -//@Singleton -//class MediaControllerProviderImpl @Inject constructor( -// private val audioSessionId: Int, -// mediaControllerFuture: ListenableFuture -//) : MediaControllerProvider { -// -// /* mediaControllerFlow가 처음 구독될 때 callbackFlow가 실행 */ -// override val mediaControllerFlow: Flow = callbackFlow { -// /* Futures.addCallback을 사용하여 mediaControllerFuture의 결과를 기다림 */ -// Futures.addCallback( -// mediaControllerFuture, -// object : FutureCallback { -// -// /* MediaController 객체가 준비되면 trySend(result)를 통해 Flow로 결과를 전송 -// 즉, mediaControllerFlow를 구독하고 있는 곳에 MediaController 객체가 전달 */ -// override fun onSuccess(result: MediaController) { -// result.setPlaybackSpeed(1.0f) -// trySend(result) -// } -// -// override fun onFailure(t: Throwable) { -// cancel(CancellationException(t.message)) -// } -// }, -// MoreExecutors.directExecutor() -// ) -// -// awaitClose { } -// } -// -// override val audioSessionFlow = callbackFlow { -// trySend(audioSessionId) -// awaitClose { } -// } -//} diff --git a/mediaservice/src/main/java/com/squirtles/mediaservice/MediaNotificationProvider.kt b/mediaservice/src/main/java/com/squirtles/mediaservice/MediaNotificationProvider.kt deleted file mode 100644 index 93ca80d4..00000000 --- a/mediaservice/src/main/java/com/squirtles/mediaservice/MediaNotificationProvider.kt +++ /dev/null @@ -1,120 +0,0 @@ -//package com.squirtles.mediaservice -// -//import android.annotation.SuppressLint -//import android.app.NotificationChannel -//import android.app.NotificationManager -//import android.app.PendingIntent -//import android.content.ComponentName -//import android.content.Context -//import android.content.Intent -//import android.os.Build -//import androidx.annotation.OptIn -//import androidx.core.app.NotificationCompat -//import androidx.core.graphics.drawable.IconCompat -//import androidx.media3.common.Player -//import androidx.media3.common.util.UnstableApi -//import androidx.media3.session.MediaNotification -//import androidx.media3.session.MediaSession -//import androidx.media3.session.MediaStyleNotificationHelper -//import javax.inject.Inject -// -//interface MediaNotificationProvider { -// -// @OptIn(UnstableApi::class) -// fun createMediaNotification(actionFactory: MediaNotification.ActionFactory): MediaNotification -//} -// -//class MediaNotificationProviderImpl @Inject constructor( -// private val context: Context, -// private val mediaSession: MediaSession -//) : MediaNotificationProvider { -// -// @OptIn(UnstableApi::class) -// override fun createMediaNotification(actionFactory: MediaNotification.ActionFactory): MediaNotification { -// val notificationManager: NotificationManager = -// context.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager -// -// makeNotificationChannel(notificationManager) -// -// val mediaItem = mediaSession.player.currentMediaItem -// -// val notificationBuilder = NotificationCompat.Builder( -// context, -// NOTIFICATION_CHANNEL_ID.toString() -// ).apply { -// priority = NotificationCompat.PRIORITY_DEFAULT -// setSilent(true) -// setSmallIcon(R.drawable.ic_musicroad_foreground) -// setContentTitle(mediaItem?.mediaMetadata?.title) -// setContentIntent(createNotifyPendingIntent()) -// setDeleteIntent( -// actionFactory.createMediaActionPendingIntent( -// mediaSession, -// Player.COMMAND_STOP.toLong() -// ) -// ) -// setStyle( -// MediaStyleNotificationHelper -// .MediaStyle(mediaSession) -// ) -// setOngoing(true) -// -// PlayerCommands.entries.forEach { commandButton -> -// addAction( -// actionFactory.createCustomAction( -// mediaSession, -// IconCompat.createWithResource( -// context, -// commandButton.iconResId(mediaSession.player.isPlaying) -// ), -// commandButton.displayName, -// commandButton.customAction, -// commandButton.sessionCommand.customExtras -// ) -// ) -// } -// } -// -// return MediaNotification( -// NOTIFICATION_CHANNEL_ID, -// notificationBuilder.build() -// ) -// } -// -// /* 알림 누르면 이동할 intent 설정 */ -// private fun createNotifyPendingIntent(): PendingIntent = -// PendingIntent.getActivity( -// context, -// 0, -// Intent().apply { -// action = Intent.ACTION_VIEW -// component = ComponentName(context, TARGET_ACTIVITY) -// flags = Intent.FLAG_ACTIVITY_CLEAR_TOP or Intent.FLAG_ACTIVITY_SINGLE_TOP -// }, -// PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE -// ) -// -// /* Notification Channel 생성 */ -// @SuppressLint("ObsoleteSdkInt") -// private fun makeNotificationChannel(notificationManager: NotificationManager) { -// // Android 8.0 미만 버전에서는 Notification Channel을 생성할 필요가 없음 -> 앱 단일 Channel 가짐 -// if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O || -// notificationManager.getNotificationChannel(NOTIFICATION_CHANNEL_ID.toString()) != null -// ) { -// return -// } -// -// val channel = NotificationChannel( -// NOTIFICATION_CHANNEL_ID.toString(), -// "MediaPlayer", -// NotificationManager.IMPORTANCE_DEFAULT -// ) -// -// notificationManager.createNotificationChannel(channel) -// } -// -// companion object { -// const val NOTIFICATION_CHANNEL_ID = 100 -// const val TARGET_ACTIVITY = "com.squirtles.musicroad.main.MainActivity" -// } -//} diff --git a/mediaservice/src/main/java/com/squirtles/mediaservice/MediaPlayerService.kt b/mediaservice/src/main/java/com/squirtles/mediaservice/MediaPlayerService.kt deleted file mode 100644 index f3735b88..00000000 --- a/mediaservice/src/main/java/com/squirtles/mediaservice/MediaPlayerService.kt +++ /dev/null @@ -1,62 +0,0 @@ -//package com.squirtles.mediaservice -// -//import android.content.Intent -//import android.os.Bundle -//import androidx.annotation.OptIn -//import androidx.media3.common.Player -//import androidx.media3.common.util.UnstableApi -//import androidx.media3.session.CommandButton -//import androidx.media3.session.MediaNotification -//import androidx.media3.session.MediaSession -//import androidx.media3.session.MediaSessionService -//import com.google.common.collect.ImmutableList -//import dagger.hilt.android.AndroidEntryPoint -//import javax.inject.Inject -// -//@OptIn(UnstableApi::class) -//@AndroidEntryPoint -//class MediaPlayerService : MediaSessionService() { -// @Inject -// lateinit var mediaNotificationProvider: MediaNotificationProvider -// -// @Inject -// lateinit var mediaSession: MediaSession -// -// override fun onCreate() { -// super.onCreate() -// -// setMediaNotificationProvider(object : MediaNotification.Provider { -// override fun createNotification( -// mediaSession: MediaSession, -// customLayout: ImmutableList, -// actionFactory: MediaNotification.ActionFactory, -// onNotificationChangedCallback: MediaNotification.Provider.Callback -// ): MediaNotification = -// mediaNotificationProvider.createMediaNotification(actionFactory) -// -// override fun handleCustomCommand(session: MediaSession, action: String, extras: Bundle): Boolean = false -// }) -// } -// -// // The user dismissed the app from the recent tasks -// override fun onTaskRemoved(rootIntent: Intent?) { -// val player = mediaSession.player -// if (!player.playWhenReady -// || player.mediaItemCount == 0 -// || player.playbackState == Player.STATE_ENDED -// ) { -// // stopSelf() -// } -// } -// -// override fun onGetSession(controllerInfo: MediaSession.ControllerInfo): MediaSession = mediaSession -// -// // Remember to release the player and media session in onDestroy -// override fun onDestroy() { -// mediaSession.run { -// player.release() -// release() -// } -// super.onDestroy() -// } -//} diff --git a/mediaservice/src/main/java/com/squirtles/mediaservice/PlayerCommands.kt b/mediaservice/src/main/java/com/squirtles/mediaservice/PlayerCommands.kt deleted file mode 100644 index 2c4fcb9b..00000000 --- a/mediaservice/src/main/java/com/squirtles/mediaservice/PlayerCommands.kt +++ /dev/null @@ -1,40 +0,0 @@ -//package com.squirtles.mediaservice -// -//import android.os.Bundle -//import androidx.media3.session.SessionCommand -// -//private const val ACTION_SEEK_FORWARD = "action_seek_forward" -//private const val ACTION_SEEK_REWIND = "action_seek_rewind" -//private const val ACTION_PLAY_AND_PAUSE = "action_play_and_pause" -// -//enum class PlayerCommands( -// val customAction: String, -// val displayName: String, -// val iconResId: (Boolean) -> Int, -// val sessionCommand: SessionCommand, -//) { -// SEEK_REWIND( -// customAction = ACTION_SEEK_REWIND, -// displayName = "SeekRewind", -// iconResId = { androidx.media3.session.R.drawable.media3_icon_skip_back_5 }, -// sessionCommand = SessionCommand(ACTION_SEEK_REWIND, Bundle.EMPTY) -// ), -// PLAY_AND_PAUSE( -// customAction = ACTION_PLAY_AND_PAUSE, -// displayName = "PlayPause", -// iconResId = { isPlaying -> -// if (isPlaying) { -// androidx.media3.session.R.drawable.media3_icon_pause -// } else { -// androidx.media3.session.R.drawable.media3_icon_play -// } -// }, -// sessionCommand = SessionCommand(ACTION_PLAY_AND_PAUSE, Bundle.EMPTY) -// ), -// SEEK_FORWARD( -// customAction = ACTION_SEEK_FORWARD, -// displayName = "SeekForward", -// iconResId = { androidx.media3.session.R.drawable.media3_icon_skip_forward_5 }, -// sessionCommand = SessionCommand(ACTION_SEEK_FORWARD, Bundle.EMPTY) -// ), -//} diff --git a/mediaservice/src/main/java/com/squirtles/mediaservice/di/MediaDiModule.kt b/mediaservice/src/main/java/com/squirtles/mediaservice/di/MediaDiModule.kt deleted file mode 100644 index 161bce3b..00000000 --- a/mediaservice/src/main/java/com/squirtles/mediaservice/di/MediaDiModule.kt +++ /dev/null @@ -1,101 +0,0 @@ -//package com.squirtles.mediaservice.di -// -//import android.content.ComponentName -//import android.content.Context -//import androidx.annotation.OptIn -//import androidx.media3.common.AudioAttributes -//import androidx.media3.common.util.UnstableApi -//import androidx.media3.exoplayer.ExoPlayer -//import androidx.media3.session.MediaController -//import androidx.media3.session.MediaSession -//import androidx.media3.session.SessionToken -//import com.google.common.util.concurrent.ListenableFuture -//import com.squirtles.mediaservice.CustomMediaSessionCallback -//import com.squirtles.mediaservice.MediaControllerProvider -//import com.squirtles.mediaservice.MediaControllerProviderImpl -//import com.squirtles.mediaservice.MediaNotificationProvider -//import com.squirtles.mediaservice.MediaNotificationProviderImpl -//import com.squirtles.mediaservice.MediaPlayerService -//import dagger.Binds -//import dagger.Module -//import dagger.Provides -//import dagger.hilt.InstallIn -//import dagger.hilt.android.qualifiers.ApplicationContext -//import dagger.hilt.components.SingletonComponent -//import javax.inject.Singleton -// -// -//@Module -//@InstallIn(SingletonComponent::class) -//abstract class MediaServiceBinds { -// -// @Binds -// abstract fun bindsMediaNotificationProvider( -// mediaNotification: MediaNotificationProviderImpl -// ): MediaNotificationProvider -// -// @OptIn(UnstableApi::class) -// @Binds -// abstract fun bindsMediaControllerProvider( -// mediaControllerProvider: MediaControllerProviderImpl -// ): MediaControllerProvider -//} -// -//@Module -//@InstallIn(SingletonComponent::class) -//object MediaServiceModule { -// -// @Singleton -// @Provides -// fun providesExoPlayer( -// @ApplicationContext context: Context, -// ): ExoPlayer = -// ExoPlayer.Builder(context) -// .setAudioAttributes(AudioAttributes.DEFAULT, true) -// .build() -// -// @OptIn(UnstableApi::class) -// @Singleton -// @Provides -// fun provideAudioSessionId( -// exoPlayer: ExoPlayer -// ): Int = exoPlayer.audioSessionId -// -// @Singleton -// @Provides -// fun providesMediaSession( -// @ApplicationContext context: Context, -// player: ExoPlayer, -// ): MediaSession = -// MediaSession.Builder(context, player) -// .setCallback(CustomMediaSessionCallback()) -// .build() -// -// @Singleton -// @Provides -// fun providesMediaNotificationManager( -// @ApplicationContext context: Context, -// mediaSession: MediaSession, -// ): MediaNotificationProviderImpl = -// MediaNotificationProviderImpl(context, mediaSession) -// -// @Singleton -// @Provides -// fun providesSessionToken( -// @ApplicationContext context: Context -// ): SessionToken { -// val sessionToken = SessionToken(context, ComponentName(context, MediaPlayerService::class.java)) -// return sessionToken -// } -// -// -// @Singleton -// @Provides -// fun providesListenableFutureMediaController( -// @ApplicationContext context: Context, -// sessionToken: SessionToken -// ): ListenableFuture = -// MediaController -// .Builder(context, sessionToken) -// .buildAsync() -//} diff --git a/mediaservice/src/main/res/drawable/ic_musicroad_foreground.xml b/mediaservice/src/main/res/drawable/ic_musicroad_foreground.xml deleted file mode 100644 index ba25c130..00000000 --- a/mediaservice/src/main/res/drawable/ic_musicroad_foreground.xml +++ /dev/null @@ -1,56 +0,0 @@ - - - - - - - - - - - - - - - - - diff --git a/mediaservice/src/test/java/com/squirtles/mediaservice/ExampleUnitTest.kt b/mediaservice/src/test/java/com/squirtles/mediaservice/ExampleUnitTest.kt deleted file mode 100644 index fb1aeef9..00000000 --- a/mediaservice/src/test/java/com/squirtles/mediaservice/ExampleUnitTest.kt +++ /dev/null @@ -1,17 +0,0 @@ -package com.squirtles.mediaservice - -import org.junit.Test - -import org.junit.Assert.* - -/** - * Example local unit test, which will execute on the development machine (host). - * - * See [testing documentation](http://d.android.com/tools/testing). - */ -class ExampleUnitTest { - @Test - fun addition_isCorrect() { - assertEquals(4, 2 + 2) - } -} diff --git a/settings.gradle.kts b/settings.gradle.kts index 6763983e..887c4984 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -31,7 +31,6 @@ rootProject.name = "MusicRoad" include(":app") include(":domain") include(":data") -include(":mediaservice") enableFeaturePreview("TYPESAFE_PROJECT_ACCESSORS") include(":core:model") include(":core:navigation") From 4e8c6442a2b07ced66a0f3df49eadf37d89336e9 Mon Sep 17 00:00:00 2001 From: miller198 Date: Wed, 23 Apr 2025 20:13:04 +0900 Subject: [PATCH 57/62] =?UTF-8?q?[chore]=20=ED=8C=A8=ED=82=A4=EC=A7=80=20?= =?UTF-8?q?=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/src/main/AndroidManifest.xml | 2 +- .../{ => core}/account/AccountViewModel.kt | 10 ++--- .../squirtles/{ => core}/account/GoogleId.kt | 7 +-- .../buildconfig}/LocalPropertyProvider.kt | 4 +- .../{ => core}/common/ui/AlbumImage.kt | 4 +- .../{ => core}/common/ui/Constants.kt | 6 +-- .../{ => core}/common/ui/CreatedByPickText.kt | 2 +- .../{ => core}/common/ui/DefaultTopAppBar.kt | 4 +- .../common/ui/MessageAlertDialog.kt | 10 ++--- .../{ => core}/common/ui/PickInfoText.kt | 4 +- .../{ => core}/common/ui/SignInAlertDialog.kt | 16 +++---- .../squirtles/{ => core}/common/ui/Spacer.kt | 2 +- .../{ => core}/common/ui/theme/Color.kt | 2 +- .../{ => core}/common/ui/theme/Theme.kt | 2 +- .../{ => core}/common/ui/theme/Type.kt | 2 +- .../mediaservice/ExampleInstrumentedTest.kt | 0 .../CustomMediaSessionCallback.kt | 2 +- .../mediaservice/MediaControllerProvider.kt | 2 +- .../mediaservice/MediaNotificationProvider.kt | 3 +- .../mediaservice/MediaPlayerService.kt | 2 +- .../{ => core}/mediaservice/PlayerCommands.kt | 2 +- .../mediaservice/di/MediaDiModule.kt | 14 +++--- .../mediaservice/ExampleUnitTest.kt | 0 .../squirtles/{ => core}/model/MusicVideo.kt | 2 +- .../com/squirtles/{ => core}/model/Order.kt | 2 +- .../com/squirtles/{ => core}/model/Pick.kt | 2 +- .../squirtles/{ => core}/model/PlayerState.kt | 2 +- .../com/squirtles/{ => core}/model/Song.kt | 2 +- .../com/squirtles/{ => core}/model/User.kt | 2 +- .../musicplayer/PlayerServiceViewModel.kt | 11 +++-- .../{ => core}/musicplayer/PlayerUiState.kt | 2 +- .../{ => core}/musicplayer/PlayerViewModel.kt | 6 +-- .../{ => core}/navigation/MainRoute.kt | 2 +- .../{ => core}/navigation/MapRoute.kt | 2 +- .../squirtles/{ => core}/navigation/Route.kt | 2 +- .../{ => core}/navigation/SearchRoute.kt | 4 +- .../{ => core}/navigation/UserInfoRoute.kt | 2 +- .../picklist/ExampleInstrumentedTest.kt | 0 .../picklist/PickListScreenContents.kt | 31 ++++++------- .../{ => core}/picklist/PickListType.kt | 2 +- .../{ => core}/picklist/PickListUiState.kt | 6 +-- .../{ => core}/picklist/PickListViewModel.kt | 8 ++-- .../components/DeleteSelectedPickDialog.kt | 12 ++--- .../picklist/components/EditModeAction.kt | 6 +-- .../components/EditModeBottomButton.kt | 12 ++--- .../picklist/components/OrderBottomSheet.kt | 12 ++--- .../picklist/components/PickItem.kt | 24 +++++----- .../{ => core}/picklist/ExampleUnitTest.kt | 0 .../{ => core}/util/SerializableType.kt | 2 +- .../{ => core}/util/ThrottleFirst.kt | 2 +- .../applemusic/AppleMusicDataSource.kt | 6 +-- .../applemusic/AppleMusicDataSourceImpl.kt | 14 +++--- .../applemusic/AppleMusicRepositoryImpl.kt | 8 ++-- .../applemusic/SearchSongsPagingSource.kt | 8 ++-- .../applemusic/api/AppleMusicApi.kt | 4 +- .../applemusic/api/NetworkModule.kt | 4 +- .../applemusic/di/AppleMusicDiModule.kt | 12 ++--- .../applemusic/model/AppleMusicMapper.kt | 6 +-- .../{ => data}/applemusic/model/Artwork.kt | 2 +- .../{ => data}/applemusic/model/Attributes.kt | 2 +- .../{ => data}/applemusic/model/Data.kt | 2 +- .../applemusic/model/MusicVideoResponse.kt | 2 +- .../{ => data}/applemusic/model/Preview.kt | 2 +- .../{ => data}/applemusic/model/Results.kt | 2 +- .../applemusic/model/SearchResponse.kt | 2 +- .../{ => data}/applemusic/model/Songs.kt | 2 +- .../favorite/CloudFunctionHelper.kt | 4 +- .../favorite/FirebaseFavoriteDataSource.kt | 2 +- .../FirebaseFavoriteDataSourceImpl.kt | 10 ++--- .../FirebaseFavoriteRepositoryImpl.kt | 3 +- .../favorite/di/FavoriteDiModule.kt | 12 ++--- .../firebase/BaseFirebaseDataSource.kt | 2 +- .../firebase/FirebaseDataSourceConstants.kt | 2 +- .../{ => data}/firebase/FirebaseModule.kt | 4 +- .../firebase/FirebaseRepositoryUtils.kt | 2 +- .../firebase/model/FirebaseFavorite.kt | 2 +- .../{ => data}/firebase/model/FirebasePick.kt | 2 +- .../{ => data}/firebase/model/FirebaseUser.kt | 2 +- .../{ => data}/firebase/model/Mapper.kt | 12 ++--- .../location/LocalLocationRepositoryImpl.kt | 3 +- .../location/di/LocationDiModule.kt | 6 +-- .../order/LocalPickListOrderRepositoryImpl.kt | 5 ++- .../{ => data}/order/di/OrderDiModule.kt | 6 +-- .../{ => data}/pick/FirebasePickDataSource.kt | 5 +-- .../pick/FirebasePickDataSourceImpl.kt | 14 +++--- .../pick/FirebasePickRepositoryImpl.kt | 8 ++-- .../{ => data}/pick/di/PickDiModule.kt | 8 ++-- .../{ => data}/user/FirebaseUserDataSource.kt | 5 +-- .../user/FirebaseUserDataSourceImpl.kt | 10 ++--- .../user/FirebaseUserRepositoryImpl.kt | 9 ++-- .../{ => data}/user/LocalUserDataSource.kt | 4 +- .../user/LocalUserDataSourceImpl.kt | 4 +- .../user/LocalUserRepositoryImpl.kt | 5 ++- .../{ => data}/user/di/UserDiModule.kt | 18 ++++---- .../applemusic/AppleMusicException.kt | 2 +- .../applemusic/AppleMusicRepository.kt | 6 +-- .../usecase/FetchMusicVideoUseCase.kt | 8 ++-- .../applemusic/usecase/FetchSongsUseCase.kt | 4 +- .../favorite/FirebaseFavoriteRepository.kt | 2 +- .../favorite/usecase/CreateFavoriteUseCase.kt | 4 +- .../favorite/usecase/DeleteFavoriteUseCase.kt | 4 +- .../usecase/FetchIsFavoriteUseCase.kt | 4 +- .../location/LocalLocationRepository.kt | 2 +- .../usecase/GetLastLocationUseCase.kt | 4 +- .../usecase/SaveLastLocationUseCase.kt | 4 +- .../order/LocalPickListOrderRepository.kt | 4 +- .../usecase/GetFavoriteListOrderUseCase.kt | 4 +- .../usecase/GetMyPickListOrderUseCase.kt | 4 +- .../usecase/SaveFavoriteListOrderUseCase.kt | 6 +-- .../usecase/SaveMyPickListOrderUseCase.kt | 6 +-- .../domain/pick/FirebasePickRepository.kt | 2 +- .../domain/pick/usecase/CreatePickUseCase.kt | 2 +- .../pick/usecase/FetchFavoritePicksUseCase.kt | 2 +- .../pick/usecase/FetchMyPicksUseCase.kt | 2 +- .../domain/pick/usecase/FetchPickUseCase.kt | 2 +- .../picklist/FetchPickListUseCaseInterface.kt | 2 +- .../GetPickListOrderUseCaseInterface.kt | 2 +- .../SavePickListOrderUseCaseInterface.kt | 2 +- .../player/MediaPlayerListenerUseCase.kt | 6 +-- .../{ => domain}/player/MediaPlayerUseCase.kt | 8 ++-- .../user/FirebaseUserRepository.kt | 4 +- .../{ => domain}/user/LocalUserRepository.kt | 4 +- .../user/usecase/CreateGoogleIdUserUseCase.kt | 7 ++- .../user/usecase/DeleteAccountUseCase.kt | 6 +-- .../user/usecase/FetchUserByIdUseCase.kt | 4 +- .../user/usecase/GetCurrentUidUseCase.kt | 4 +- .../user/usecase/SignOutUseCase.kt | 4 +- .../user/usecase/UpdateUserNameUseCase.kt | 4 +- .../create/ExampleInstrumentedTest.kt | 0 feature/create/src/main/AndroidManifest.xml | 4 -- .../{ => feature}/create/CreatePickScreen.kt | 17 +++---- .../{ => feature}/create/CreatePickUiState.kt | 2 +- .../create/CreatePickViewModel.kt | 24 +++++----- .../create/navigation/CreateNavigation.kt | 10 ++--- .../{ => feature}/create/ExampleUnitTest.kt | 0 .../detail/ExampleInstrumentedTest.kt | 0 .../{ => feature}/detail/DetailViewModel.kt | 21 +++++---- .../{ => feature}/detail/FavoriteAction.kt | 2 +- .../{ => feature}/detail/PickDetailScreen.kt | 45 ++++++++++--------- .../{ => feature}/detail/PickDetailUiState.kt | 4 +- .../detail/components/CircleAlbumCover.kt | 4 +- .../detail/components/DetailPickTopAppBar.kt | 6 +-- .../detail/components/MusicVideoKnob.kt | 6 +-- .../detail/components/PickCommentText.kt | 8 ++-- .../detail/components/PickInformation.kt | 4 +- .../PlayCircularProgressIndicator.kt | 4 +- .../detail/components/SongInfo.kt | 4 +- .../detail/components/SwipeUpIcon.kt | 4 +- .../detail/components/music/MusicPlayer.kt | 10 ++--- .../detail/components/music/PlayBar.kt | 6 +-- .../components/music/PlayProgressIndicator.kt | 6 +-- .../detail/components/music/PlayerControls.kt | 6 +-- .../detail/navigation/PickDetailNavigation.kt | 8 ++-- .../detail/videoplayer/MusicVideoPlayer.kt | 3 +- .../detail/videoplayer/MusicVideoScreen.kt | 4 +- .../detail/videoplayer/VideoPlayerOverlay.kt | 18 ++++---- .../detail/videoplayer/VideoPlayerState.kt | 2 +- .../videoplayer/VideoPlayerViewModel.kt | 2 +- .../{ => feature}/detail/ExampleUnitTest.kt | 0 .../favorite/ExampleInstrumentedTest.kt | 2 +- .../favorite/FavoriteListViewModel.kt | 12 ++--- .../{ => feature}/favorite/FavoriteScreen.kt | 6 +-- .../favorite/navigation/FavoriteNavigation.kt | 6 +-- .../{ => feature}/favorite/ExampleUnitTest.kt | 0 .../main/ExampleInstrumentedTest.kt | 0 feature/main/src/main/AndroidManifest.xml | 2 +- .../{ => feature}/main/LoadingState.kt | 2 +- .../{ => feature}/main/MainActivity.kt | 12 ++--- .../{ => feature}/main/MainViewModel.kt | 6 +-- .../main/NeedPermissionDialog.kt | 15 ++++--- .../{ => feature}/main/PermissionBar.kt | 5 ++- .../main/navigation/MainNavHost.kt | 21 +++++---- .../main/navigation/MainNavigator.kt | 24 +++++----- .../{ => feature}/main/ExampleUnitTest.kt | 0 .../map/ExampleInstrumentedTest.kt | 0 .../squirtles/{ => feature}/map/Constants.kt | 2 +- .../squirtles/{ => feature}/map/MapScreen.kt | 25 ++++++----- .../{ => feature}/map/MapViewModel.kt | 12 ++--- .../squirtles/{ => feature}/map/NaverMap.kt | 12 ++--- .../map/components/ClusterBottomSheet.kt | 30 ++++++------- .../map/components/InfoWindowCard.kt | 26 +++++------ .../map/components/LoadingDialog.kt | 14 +++--- .../map/components/MapBottomNavBar.kt | 12 ++--- .../map/components/PickNotificationBanner.kt | 19 ++++---- .../map/marker/ClusterMarkerIconView.kt | 2 +- .../{ => feature}/map/marker/Clusterer.kt | 18 ++++---- .../{ => feature}/map/marker/DensityType.kt | 8 ++-- .../map/marker/LeafMarkerIconView.kt | 4 +- .../{ => feature}/map/marker/MarkerKey.kt | 4 +- .../map/navigation/MapNavigation.kt | 10 ++--- .../{ => feature}/map/navigation/NavTab.kt | 4 +- .../{ => feature}/map/ExampleUnitTest.kt | 0 .../mypick/ExampleInstrumentedTest.kt | 0 .../mypick/MyPickListViewModel.kt | 10 ++--- .../{ => feature}/mypick/MyPickScreen.kt | 6 +-- .../mypick/navigation/MyPickNavigation.kt | 6 +-- .../{ => feature}/mypick/ExampleUnitTest.kt | 0 .../search/ExampleInstrumentedTest.kt | 0 .../{ => feature}/search/SearchMusicScreen.kt | 27 +++++------ .../{ => feature}/search/SearchUiConstants.kt | 2 +- .../{ => feature}/search/SearchUiState.kt | 2 +- .../{ => feature}/search/SearchViewModel.kt | 6 +-- .../search/navigation/SearchNavigation.kt | 8 ++-- .../{ => feature}/search/ExampleUnitTest.kt | 0 .../userinfo/ExampleInstrumentedTest.kt | 0 .../userinfo/UserInfoConstants.kt | 4 +- .../userinfo/UserInfoViewModel.kt | 11 +++-- .../userinfo/components/MenuItem.kt | 4 +- .../userinfo/components/UserInfoMenus.kt | 14 +++--- .../userinfo/navigation/UserInfoNavigation.kt | 12 ++--- .../screen/EditNotificationSettingScreen.kt | 10 ++--- .../userinfo/screen/EditProfileScreen.kt | 30 ++++++------- .../userinfo/screen/UserInfoScreen.kt | 30 ++++++------- .../{ => feature}/userinfo/ExampleUnitTest.kt | 0 214 files changed, 690 insertions(+), 685 deletions(-) rename core/account/src/main/java/com/squirtles/{ => core}/account/AccountViewModel.kt (89%) rename core/account/src/main/java/com/squirtles/{ => core}/account/GoogleId.kt (95%) rename core/buildconfig/src/main/java/com/squirtles/{localproperties => core/buildconfig}/LocalPropertyProvider.kt (79%) rename core/common/src/main/java/com/squirtles/{ => core}/common/ui/AlbumImage.kt (93%) rename core/common/src/main/java/com/squirtles/{ => core}/common/ui/Constants.kt (65%) rename core/common/src/main/java/com/squirtles/{ => core}/common/ui/CreatedByPickText.kt (98%) rename core/common/src/main/java/com/squirtles/{ => core}/common/ui/DefaultTopAppBar.kt (95%) rename core/common/src/main/java/com/squirtles/{ => core}/common/ui/MessageAlertDialog.kt (93%) rename core/common/src/main/java/com/squirtles/{ => core}/common/ui/PickInfoText.kt (96%) rename core/common/src/main/java/com/squirtles/{ => core}/common/ui/SignInAlertDialog.kt (91%) rename core/common/src/main/java/com/squirtles/{ => core}/common/ui/Spacer.kt (91%) rename core/common/src/main/java/com/squirtles/{ => core}/common/ui/theme/Color.kt (93%) rename core/common/src/main/java/com/squirtles/{ => core}/common/ui/theme/Theme.kt (96%) rename core/common/src/main/java/com/squirtles/{ => core}/common/ui/theme/Type.kt (95%) rename core/mediaservice/src/androidTest/java/com/squirtles/{ => core}/mediaservice/ExampleInstrumentedTest.kt (100%) rename core/mediaservice/src/main/java/com/squirtles/{ => core}/mediaservice/CustomMediaSessionCallback.kt (98%) rename core/mediaservice/src/main/java/com/squirtles/{ => core}/mediaservice/MediaControllerProvider.kt (98%) rename core/mediaservice/src/main/java/com/squirtles/{ => core}/mediaservice/MediaNotificationProvider.kt (98%) rename core/mediaservice/src/main/java/com/squirtles/{ => core}/mediaservice/MediaPlayerService.kt (98%) rename core/mediaservice/src/main/java/com/squirtles/{ => core}/mediaservice/PlayerCommands.kt (97%) rename core/mediaservice/src/main/java/com/squirtles/{ => core}/mediaservice/di/MediaDiModule.kt (86%) rename core/mediaservice/src/test/java/com/squirtles/{ => core}/mediaservice/ExampleUnitTest.kt (100%) rename core/model/src/main/java/com/squirtles/{ => core}/model/MusicVideo.kt (88%) rename core/model/src/main/java/com/squirtles/{ => core}/model/Order.kt (66%) rename core/model/src/main/java/com/squirtles/{ => core}/model/Pick.kt (94%) rename core/model/src/main/java/com/squirtles/{ => core}/model/PlayerState.kt (88%) rename core/model/src/main/java/com/squirtles/{ => core}/model/Song.kt (95%) rename core/model/src/main/java/com/squirtles/{ => core}/model/User.kt (82%) rename core/musicplayer/src/main/java/com/squirtles/{ => core}/musicplayer/PlayerServiceViewModel.kt (91%) rename core/musicplayer/src/main/java/com/squirtles/{ => core}/musicplayer/PlayerUiState.kt (88%) rename core/musicplayer/src/main/java/com/squirtles/{ => core}/musicplayer/PlayerViewModel.kt (96%) rename core/navigation/src/main/java/com/squirtles/{ => core}/navigation/MainRoute.kt (88%) rename core/navigation/src/main/java/com/squirtles/{ => core}/navigation/MapRoute.kt (81%) rename core/navigation/src/main/java/com/squirtles/{ => core}/navigation/Route.kt (77%) rename core/navigation/src/main/java/com/squirtles/{ => core}/navigation/SearchRoute.kt (69%) rename core/navigation/src/main/java/com/squirtles/{ => core}/navigation/UserInfoRoute.kt (89%) rename core/picklist/src/androidTest/java/com/squirtles/{ => core}/picklist/ExampleInstrumentedTest.kt (100%) rename core/picklist/src/main/java/com/squirtles/{ => core}/picklist/PickListScreenContents.kt (92%) rename core/picklist/src/main/java/com/squirtles/{ => core}/picklist/PickListType.kt (58%) rename core/picklist/src/main/java/com/squirtles/{ => core}/picklist/PickListUiState.kt (65%) rename core/picklist/src/main/java/com/squirtles/{ => core}/picklist/PickListViewModel.kt (95%) rename core/picklist/src/main/java/com/squirtles/{ => core}/picklist/components/DeleteSelectedPickDialog.kt (80%) rename core/picklist/src/main/java/com/squirtles/{ => core}/picklist/components/EditModeAction.kt (92%) rename core/picklist/src/main/java/com/squirtles/{ => core}/picklist/components/EditModeBottomButton.kt (89%) rename core/picklist/src/main/java/com/squirtles/{ => core}/picklist/components/OrderBottomSheet.kt (94%) rename core/picklist/src/main/java/com/squirtles/{ => core}/picklist/components/PickItem.kt (87%) rename core/picklist/src/test/java/com/squirtles/{ => core}/picklist/ExampleUnitTest.kt (100%) rename core/util/src/main/java/com/squirtles/{ => core}/util/SerializableType.kt (95%) rename core/util/src/main/java/com/squirtles/{ => core}/util/ThrottleFirst.kt (93%) rename data/applemusic/src/main/java/com/squirtles/{ => data}/applemusic/AppleMusicDataSource.kt (67%) rename data/applemusic/src/main/java/com/squirtles/{ => data}/applemusic/AppleMusicDataSourceImpl.kt (81%) rename data/applemusic/src/main/java/com/squirtles/{ => data}/applemusic/AppleMusicRepositoryImpl.kt (78%) rename data/applemusic/src/main/java/com/squirtles/{ => data}/applemusic/SearchSongsPagingSource.kt (91%) rename data/applemusic/src/main/java/com/squirtles/{ => data}/applemusic/api/AppleMusicApi.kt (81%) rename data/applemusic/src/main/java/com/squirtles/{ => data}/applemusic/api/NetworkModule.kt (93%) rename data/applemusic/src/main/java/com/squirtles/{ => data}/applemusic/di/AppleMusicDiModule.kt (77%) rename data/applemusic/src/main/java/com/squirtles/{ => data}/applemusic/model/AppleMusicMapper.kt (90%) rename data/applemusic/src/main/java/com/squirtles/{ => data}/applemusic/model/Artwork.kt (79%) rename data/applemusic/src/main/java/com/squirtles/{ => data}/applemusic/model/Attributes.kt (92%) rename data/applemusic/src/main/java/com/squirtles/{ => data}/applemusic/model/Data.kt (74%) rename data/applemusic/src/main/java/com/squirtles/{ => data}/applemusic/model/MusicVideoResponse.kt (72%) rename data/applemusic/src/main/java/com/squirtles/{ => data}/applemusic/model/Preview.kt (79%) rename data/applemusic/src/main/java/com/squirtles/{ => data}/applemusic/model/Results.kt (84%) rename data/applemusic/src/main/java/com/squirtles/{ => data}/applemusic/model/SearchResponse.kt (71%) rename data/applemusic/src/main/java/com/squirtles/{ => data}/applemusic/model/Songs.kt (75%) rename data/favorite/src/main/java/com/squirtles/{ => data}/favorite/CloudFunctionHelper.kt (92%) rename data/favorite/src/main/java/com/squirtles/{ => data}/favorite/FirebaseFavoriteDataSource.kt (88%) rename data/favorite/src/main/java/com/squirtles/{ => data}/favorite/FirebaseFavoriteDataSourceImpl.kt (90%) rename data/favorite/src/main/java/com/squirtles/{ => data}/favorite/FirebaseFavoriteRepositoryImpl.kt (87%) rename data/favorite/src/main/java/com/squirtles/{ => data}/favorite/di/FavoriteDiModule.kt (71%) rename data/firebase/src/main/java/com/squirtles/{ => data}/firebase/BaseFirebaseDataSource.kt (99%) rename data/firebase/src/main/java/com/squirtles/{ => data}/firebase/FirebaseDataSourceConstants.kt (95%) rename data/firebase/src/main/java/com/squirtles/{ => data}/firebase/FirebaseModule.kt (83%) rename data/firebase/src/main/java/com/squirtles/{ => data}/firebase/FirebaseRepositoryUtils.kt (91%) rename data/firebase/src/main/java/com/squirtles/{ => data}/firebase/model/FirebaseFavorite.kt (84%) rename data/firebase/src/main/java/com/squirtles/{ => data}/firebase/model/FirebasePick.kt (95%) rename data/firebase/src/main/java/com/squirtles/{ => data}/firebase/model/FirebaseUser.kt (80%) rename data/firebase/src/main/java/com/squirtles/{ => data}/firebase/model/Mapper.kt (91%) rename data/location/src/main/java/com/squirtles/{ => data}/location/LocalLocationRepositoryImpl.kt (85%) rename data/location/src/main/java/com/squirtles/{ => data}/location/di/LocationDiModule.kt (69%) rename data/order/src/main/java/com/squirtles/{ => data}/order/LocalPickListOrderRepositoryImpl.kt (79%) rename data/order/src/main/java/com/squirtles/{ => data}/order/di/OrderDiModule.kt (69%) rename data/pick/src/main/java/com/squirtles/{ => data}/pick/FirebasePickDataSource.kt (82%) rename data/pick/src/main/java/com/squirtles/{ => data}/pick/FirebasePickDataSourceImpl.kt (94%) rename data/pick/src/main/java/com/squirtles/{ => data}/pick/FirebasePickRepositoryImpl.kt (91%) rename data/pick/src/main/java/com/squirtles/{ => data}/pick/di/PickDiModule.kt (77%) rename data/user/src/main/java/com/squirtles/{ => data}/user/FirebaseUserDataSource.kt (75%) rename data/user/src/main/java/com/squirtles/{ => data}/user/FirebaseUserDataSourceImpl.kt (90%) rename data/user/src/main/java/com/squirtles/{ => data}/user/FirebaseUserRepositoryImpl.kt (86%) rename data/user/src/main/java/com/squirtles/{ => data}/user/LocalUserDataSource.kt (79%) rename data/user/src/main/java/com/squirtles/{ => data}/user/LocalUserDataSourceImpl.kt (95%) rename data/user/src/main/java/com/squirtles/{ => data}/user/LocalUserRepositoryImpl.kt (87%) rename data/user/src/main/java/com/squirtles/{ => data}/user/di/UserDiModule.kt (69%) rename domain/applemusic/src/main/java/com/squirtles/{ => domain}/applemusic/AppleMusicException.kt (91%) rename domain/applemusic/src/main/java/com/squirtles/{ => domain}/applemusic/AppleMusicRepository.kt (67%) rename domain/applemusic/src/main/java/com/squirtles/{ => domain}/applemusic/usecase/FetchMusicVideoUseCase.kt (73%) rename domain/applemusic/src/main/java/com/squirtles/{ => domain}/applemusic/usecase/FetchSongsUseCase.kt (68%) rename domain/favorite/src/main/java/com/squirtles/{ => domain}/favorite/FirebaseFavoriteRepository.kt (87%) rename domain/favorite/src/main/java/com/squirtles/{ => domain}/favorite/usecase/CreateFavoriteUseCase.kt (70%) rename domain/favorite/src/main/java/com/squirtles/{ => domain}/favorite/usecase/DeleteFavoriteUseCase.kt (77%) rename domain/favorite/src/main/java/com/squirtles/{ => domain}/favorite/usecase/FetchIsFavoriteUseCase.kt (71%) rename domain/location/src/main/java/com/squirtles/{ => domain}/location/LocalLocationRepository.kt (85%) rename domain/location/src/main/java/com/squirtles/{ => domain}/location/usecase/GetLastLocationUseCase.kt (66%) rename domain/location/src/main/java/com/squirtles/{ => domain}/location/usecase/SaveLastLocationUseCase.kt (73%) rename domain/order/src/main/java/com/squirtles/{ => domain}/order/LocalPickListOrderRepository.kt (78%) rename domain/order/src/main/java/com/squirtles/{ => domain}/order/usecase/GetFavoriteListOrderUseCase.kt (77%) rename domain/order/src/main/java/com/squirtles/{ => domain}/order/usecase/GetMyPickListOrderUseCase.kt (76%) rename domain/order/src/main/java/com/squirtles/{ => domain}/order/usecase/SaveFavoriteListOrderUseCase.kt (72%) rename domain/order/src/main/java/com/squirtles/{ => domain}/order/usecase/SaveMyPickListOrderUseCase.kt (72%) rename domain/player/src/main/java/com/squirtles/{ => domain}/player/MediaPlayerListenerUseCase.kt (97%) rename domain/player/src/main/java/com/squirtles/{ => domain}/player/MediaPlayerUseCase.kt (94%) rename domain/user/src/main/java/com/squirtles/{ => domain}/user/FirebaseUserRepository.kt (85%) rename domain/user/src/main/java/com/squirtles/{ => domain}/user/LocalUserRepository.kt (79%) rename domain/user/src/main/java/com/squirtles/{ => domain}/user/usecase/CreateGoogleIdUserUseCase.kt (73%) rename domain/user/src/main/java/com/squirtles/{ => domain}/user/usecase/DeleteAccountUseCase.kt (91%) rename domain/user/src/main/java/com/squirtles/{ => domain}/user/usecase/FetchUserByIdUseCase.kt (71%) rename domain/user/src/main/java/com/squirtles/{ => domain}/user/usecase/GetCurrentUidUseCase.kt (68%) rename domain/user/src/main/java/com/squirtles/{ => domain}/user/usecase/SignOutUseCase.kt (67%) rename domain/user/src/main/java/com/squirtles/{ => domain}/user/usecase/UpdateUserNameUseCase.kt (74%) rename feature/create/src/androidTest/java/com/squirtles/{ => feature}/create/ExampleInstrumentedTest.kt (100%) delete mode 100644 feature/create/src/main/AndroidManifest.xml rename feature/create/src/main/java/com/squirtles/{ => feature}/create/CreatePickScreen.kt (96%) rename feature/create/src/main/java/com/squirtles/{ => feature}/create/CreatePickUiState.kt (84%) rename feature/create/src/main/java/com/squirtles/{ => feature}/create/CreatePickViewModel.kt (87%) rename feature/create/src/main/java/com/squirtles/{ => feature}/create/navigation/CreateNavigation.kt (84%) rename feature/create/src/test/java/com/squirtles/{ => feature}/create/ExampleUnitTest.kt (100%) rename feature/detail/src/androidTest/java/com/squirtles/{ => feature}/detail/ExampleInstrumentedTest.kt (100%) rename feature/detail/src/main/java/com/squirtles/{ => feature}/detail/DetailViewModel.kt (91%) rename feature/detail/src/main/java/com/squirtles/{ => feature}/detail/FavoriteAction.kt (57%) rename feature/detail/src/main/java/com/squirtles/{ => feature}/detail/PickDetailScreen.kt (93%) rename feature/detail/src/main/java/com/squirtles/{ => feature}/detail/PickDetailUiState.kt (77%) rename feature/detail/src/main/java/com/squirtles/{ => feature}/detail/components/CircleAlbumCover.kt (97%) rename feature/detail/src/main/java/com/squirtles/{ => feature}/detail/components/DetailPickTopAppBar.kt (96%) rename feature/detail/src/main/java/com/squirtles/{ => feature}/detail/components/MusicVideoKnob.kt (94%) rename feature/detail/src/main/java/com/squirtles/{ => feature}/detail/components/PickCommentText.kt (91%) rename feature/detail/src/main/java/com/squirtles/{ => feature}/detail/components/PickInformation.kt (93%) rename feature/detail/src/main/java/com/squirtles/{ => feature}/detail/components/PlayCircularProgressIndicator.kt (96%) rename feature/detail/src/main/java/com/squirtles/{ => feature}/detail/components/SongInfo.kt (93%) rename feature/detail/src/main/java/com/squirtles/{ => feature}/detail/components/SwipeUpIcon.kt (90%) rename feature/detail/src/main/java/com/squirtles/{ => feature}/detail/components/music/MusicPlayer.kt (86%) rename feature/detail/src/main/java/com/squirtles/{ => feature}/detail/components/music/PlayBar.kt (94%) rename feature/detail/src/main/java/com/squirtles/{ => feature}/detail/components/music/PlayProgressIndicator.kt (91%) rename feature/detail/src/main/java/com/squirtles/{ => feature}/detail/components/music/PlayerControls.kt (94%) rename feature/detail/src/main/java/com/squirtles/{ => feature}/detail/navigation/PickDetailNavigation.kt (82%) rename feature/detail/src/main/java/com/squirtles/{ => feature}/detail/videoplayer/MusicVideoPlayer.kt (98%) rename feature/detail/src/main/java/com/squirtles/{ => feature}/detail/videoplayer/MusicVideoScreen.kt (92%) rename feature/detail/src/main/java/com/squirtles/{ => feature}/detail/videoplayer/VideoPlayerOverlay.kt (95%) rename feature/detail/src/main/java/com/squirtles/{ => feature}/detail/videoplayer/VideoPlayerState.kt (55%) rename feature/detail/src/main/java/com/squirtles/{ => feature}/detail/videoplayer/VideoPlayerViewModel.kt (98%) rename feature/detail/src/test/java/com/squirtles/{ => feature}/detail/ExampleUnitTest.kt (100%) rename feature/favorite/src/androidTest/java/com/squirtles/{ => feature}/favorite/ExampleInstrumentedTest.kt (94%) rename feature/favorite/src/main/java/com/squirtles/{ => feature}/favorite/FavoriteListViewModel.kt (68%) rename feature/favorite/src/main/java/com/squirtles/{ => feature}/favorite/FavoriteScreen.kt (92%) rename feature/favorite/src/main/java/com/squirtles/{ => feature}/favorite/navigation/FavoriteNavigation.kt (82%) rename feature/favorite/src/test/java/com/squirtles/{ => feature}/favorite/ExampleUnitTest.kt (100%) rename feature/main/src/androidTest/java/com/squirtles/{ => feature}/main/ExampleInstrumentedTest.kt (100%) rename feature/main/src/main/java/com/squirtles/{ => feature}/main/LoadingState.kt (90%) rename feature/main/src/main/java/com/squirtles/{ => feature}/main/MainActivity.kt (95%) rename feature/main/src/main/java/com/squirtles/{ => feature}/main/MainViewModel.kt (92%) rename feature/main/src/main/java/com/squirtles/{ => feature}/main/NeedPermissionDialog.kt (88%) rename feature/main/src/main/java/com/squirtles/{ => feature}/main/PermissionBar.kt (95%) rename feature/main/src/main/java/com/squirtles/{ => feature}/main/navigation/MainNavHost.kt (79%) rename feature/main/src/main/java/com/squirtles/{ => feature}/main/navigation/MainNavigator.kt (81%) rename feature/main/src/test/java/com/squirtles/{ => feature}/main/ExampleUnitTest.kt (100%) rename feature/map/src/androidTest/java/com/squirtles/{ => feature}/map/ExampleInstrumentedTest.kt (100%) rename feature/map/src/main/java/com/squirtles/{ => feature}/map/Constants.kt (87%) rename feature/map/src/main/java/com/squirtles/{ => feature}/map/MapScreen.kt (94%) rename feature/map/src/main/java/com/squirtles/{ => feature}/map/MapViewModel.kt (95%) rename feature/map/src/main/java/com/squirtles/{ => feature}/map/NaverMap.kt (97%) rename feature/map/src/main/java/com/squirtles/{ => feature}/map/components/ClusterBottomSheet.kt (86%) rename feature/map/src/main/java/com/squirtles/{ => feature}/map/components/InfoWindowCard.kt (89%) rename feature/map/src/main/java/com/squirtles/{ => feature}/map/components/LoadingDialog.kt (87%) rename feature/map/src/main/java/com/squirtles/{ => feature}/map/components/MapBottomNavBar.kt (93%) rename feature/map/src/main/java/com/squirtles/{ => feature}/map/components/PickNotificationBanner.kt (89%) rename feature/map/src/main/java/com/squirtles/{ => feature}/map/marker/ClusterMarkerIconView.kt (97%) rename feature/map/src/main/java/com/squirtles/{ => feature}/map/marker/Clusterer.kt (92%) rename feature/map/src/main/java/com/squirtles/{ => feature}/map/marker/DensityType.kt (50%) rename feature/map/src/main/java/com/squirtles/{ => feature}/map/marker/LeafMarkerIconView.kt (95%) rename feature/map/src/main/java/com/squirtles/{ => feature}/map/marker/MarkerKey.kt (75%) rename feature/map/src/main/java/com/squirtles/{ => feature}/map/navigation/MapNavigation.kt (80%) rename feature/map/src/main/java/com/squirtles/{ => feature}/map/navigation/NavTab.kt (90%) rename feature/map/src/test/java/com/squirtles/{ => feature}/map/ExampleUnitTest.kt (100%) rename feature/mypick/src/androidTest/java/com/squirtles/{ => feature}/mypick/ExampleInstrumentedTest.kt (100%) rename feature/mypick/src/main/java/com/squirtles/{ => feature}/mypick/MyPickListViewModel.kt (73%) rename feature/mypick/src/main/java/com/squirtles/{ => feature}/mypick/MyPickScreen.kt (92%) rename feature/mypick/src/main/java/com/squirtles/{ => feature}/mypick/navigation/MyPickNavigation.kt (82%) rename feature/mypick/src/test/java/com/squirtles/{ => feature}/mypick/ExampleUnitTest.kt (100%) rename feature/search/src/androidTest/java/com/squirtles/{ => feature}/search/ExampleInstrumentedTest.kt (100%) rename feature/search/src/main/java/com/squirtles/{ => feature}/search/SearchMusicScreen.kt (93%) rename feature/search/src/main/java/com/squirtles/{ => feature}/search/SearchUiConstants.kt (83%) rename feature/search/src/main/java/com/squirtles/{ => feature}/search/SearchUiState.kt (76%) rename feature/search/src/main/java/com/squirtles/{ => feature}/search/SearchViewModel.kt (93%) rename feature/search/src/main/java/com/squirtles/{ => feature}/search/navigation/SearchNavigation.kt (75%) rename feature/search/src/test/java/com/squirtles/{ => feature}/search/ExampleUnitTest.kt (100%) rename feature/userinfo/src/androidTest/java/com/squirtles/{ => feature}/userinfo/ExampleInstrumentedTest.kt (100%) rename feature/userinfo/src/main/java/com/squirtles/{ => feature}/userinfo/UserInfoConstants.kt (79%) rename feature/userinfo/src/main/java/com/squirtles/{ => feature}/userinfo/UserInfoViewModel.kt (83%) rename feature/userinfo/src/main/java/com/squirtles/{ => feature}/userinfo/components/MenuItem.kt (74%) rename feature/userinfo/src/main/java/com/squirtles/{ => feature}/userinfo/components/UserInfoMenus.kt (89%) rename feature/userinfo/src/main/java/com/squirtles/{ => feature}/userinfo/navigation/UserInfoNavigation.kt (84%) rename feature/userinfo/src/main/java/com/squirtles/{ => feature}/userinfo/screen/EditNotificationSettingScreen.kt (84%) rename feature/userinfo/src/main/java/com/squirtles/{ => feature}/userinfo/screen/EditProfileScreen.kt (93%) rename feature/userinfo/src/main/java/com/squirtles/{ => feature}/userinfo/screen/UserInfoScreen.kt (93%) rename feature/userinfo/src/test/java/com/squirtles/{ => feature}/userinfo/ExampleUnitTest.kt (100%) diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index d1f03844..89aba6a1 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -23,7 +23,7 @@ tools:targetApi="31"> diff --git a/core/account/src/main/java/com/squirtles/account/AccountViewModel.kt b/core/account/src/main/java/com/squirtles/core/account/AccountViewModel.kt similarity index 89% rename from core/account/src/main/java/com/squirtles/account/AccountViewModel.kt rename to core/account/src/main/java/com/squirtles/core/account/AccountViewModel.kt index 37e7d3f5..c4e5fa23 100644 --- a/core/account/src/main/java/com/squirtles/account/AccountViewModel.kt +++ b/core/account/src/main/java/com/squirtles/core/account/AccountViewModel.kt @@ -1,13 +1,13 @@ -package com.squirtles.account +package com.squirtles.core.account import android.util.Log import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope import com.google.android.libraries.identity.googleid.GoogleIdTokenCredential -import com.squirtles.user.usecase.SignOutUseCase -import com.squirtles.user.usecase.CreateGoogleIdUserUseCase -import com.squirtles.user.usecase.DeleteAccountUseCase -import com.squirtles.user.usecase.FetchUserByIdUseCase +import com.squirtles.domain.user.usecase.SignOutUseCase +import com.squirtles.domain.user.usecase.CreateGoogleIdUserUseCase +import com.squirtles.domain.user.usecase.DeleteAccountUseCase +import com.squirtles.domain.user.usecase.FetchUserByIdUseCase import dagger.hilt.android.lifecycle.HiltViewModel import kotlinx.coroutines.flow.MutableSharedFlow import kotlinx.coroutines.flow.asSharedFlow diff --git a/core/account/src/main/java/com/squirtles/account/GoogleId.kt b/core/account/src/main/java/com/squirtles/core/account/GoogleId.kt similarity index 95% rename from core/account/src/main/java/com/squirtles/account/GoogleId.kt rename to core/account/src/main/java/com/squirtles/core/account/GoogleId.kt index ba9231fe..3f3a54b6 100644 --- a/core/account/src/main/java/com/squirtles/account/GoogleId.kt +++ b/core/account/src/main/java/com/squirtles/core/account/GoogleId.kt @@ -1,4 +1,4 @@ -package com.squirtles.account +package com.squirtles.core.account import android.content.Context import android.util.Log @@ -14,7 +14,8 @@ import com.google.android.libraries.identity.googleid.GetGoogleIdOption import com.google.android.libraries.identity.googleid.GoogleIdTokenCredential import com.google.firebase.auth.FirebaseAuth import com.google.firebase.auth.GoogleAuthProvider -import com.squirtles.localproperties.BuildConfig +import com.squirtles.account.R +import com.squirtles.core.buildconfig.LocalPropertyProvider import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.launch @@ -27,7 +28,7 @@ class GoogleId(private val context: Context) { private val googleIdOption: GetGoogleIdOption = GetGoogleIdOption.Builder() .setFilterByAuthorizedAccounts(false) - .setServerClientId(BuildConfig.GOOGLE_CLIENT_ID) + .setServerClientId(LocalPropertyProvider.googleClientId) .setAutoSelectEnabled(true) .build() diff --git a/core/buildconfig/src/main/java/com/squirtles/localproperties/LocalPropertyProvider.kt b/core/buildconfig/src/main/java/com/squirtles/core/buildconfig/LocalPropertyProvider.kt similarity index 79% rename from core/buildconfig/src/main/java/com/squirtles/localproperties/LocalPropertyProvider.kt rename to core/buildconfig/src/main/java/com/squirtles/core/buildconfig/LocalPropertyProvider.kt index 148ca373..eed5fe7a 100644 --- a/core/buildconfig/src/main/java/com/squirtles/localproperties/LocalPropertyProvider.kt +++ b/core/buildconfig/src/main/java/com/squirtles/core/buildconfig/LocalPropertyProvider.kt @@ -1,4 +1,6 @@ -package com.squirtles.localproperties +package com.squirtles.core.buildconfig + +import com.squirtles.localproperties.BuildConfig object LocalPropertyProvider { val googleClientId: String diff --git a/core/common/src/main/java/com/squirtles/common/ui/AlbumImage.kt b/core/common/src/main/java/com/squirtles/core/common/ui/AlbumImage.kt similarity index 93% rename from core/common/src/main/java/com/squirtles/common/ui/AlbumImage.kt rename to core/common/src/main/java/com/squirtles/core/common/ui/AlbumImage.kt index 104690b9..e53ce8e6 100644 --- a/core/common/src/main/java/com/squirtles/common/ui/AlbumImage.kt +++ b/core/common/src/main/java/com/squirtles/core/common/ui/AlbumImage.kt @@ -1,4 +1,4 @@ -package com.squirtles.common.ui +package com.squirtles.core.common.ui import android.util.Size import androidx.compose.runtime.Composable @@ -11,7 +11,7 @@ import coil3.compose.AsyncImage import coil3.request.ImageRequest import coil3.request.crossfade import com.squirtles.common.R -import com.squirtles.common.ui.theme.Gray +import com.squirtles.core.common.ui.theme.Gray @Composable fun AlbumImage( diff --git a/core/common/src/main/java/com/squirtles/common/ui/Constants.kt b/core/common/src/main/java/com/squirtles/core/common/ui/Constants.kt similarity index 65% rename from core/common/src/main/java/com/squirtles/common/ui/Constants.kt rename to core/common/src/main/java/com/squirtles/core/common/ui/Constants.kt index 702acc09..9d8fe320 100644 --- a/core/common/src/main/java/com/squirtles/common/ui/Constants.kt +++ b/core/common/src/main/java/com/squirtles/core/common/ui/Constants.kt @@ -1,9 +1,9 @@ -package com.squirtles.common.ui +package com.squirtles.core.common.ui import android.util.Size import androidx.compose.ui.unit.dp -import com.squirtles.common.ui.theme.Black -import com.squirtles.common.ui.theme.Primary +import com.squirtles.core.common.ui.theme.Black +import com.squirtles.core.common.ui.theme.Primary object Constants { val DEFAULT_PADDING = 16.dp diff --git a/core/common/src/main/java/com/squirtles/common/ui/CreatedByPickText.kt b/core/common/src/main/java/com/squirtles/core/common/ui/CreatedByPickText.kt similarity index 98% rename from core/common/src/main/java/com/squirtles/common/ui/CreatedByPickText.kt rename to core/common/src/main/java/com/squirtles/core/common/ui/CreatedByPickText.kt index 5cfc8e0c..b56c7eec 100644 --- a/core/common/src/main/java/com/squirtles/common/ui/CreatedByPickText.kt +++ b/core/common/src/main/java/com/squirtles/core/common/ui/CreatedByPickText.kt @@ -1,4 +1,4 @@ -package com.squirtles.common.ui +package com.squirtles.core.common.ui import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Text diff --git a/core/common/src/main/java/com/squirtles/common/ui/DefaultTopAppBar.kt b/core/common/src/main/java/com/squirtles/core/common/ui/DefaultTopAppBar.kt similarity index 95% rename from core/common/src/main/java/com/squirtles/common/ui/DefaultTopAppBar.kt rename to core/common/src/main/java/com/squirtles/core/common/ui/DefaultTopAppBar.kt index 9172674e..051c0e5d 100644 --- a/core/common/src/main/java/com/squirtles/common/ui/DefaultTopAppBar.kt +++ b/core/common/src/main/java/com/squirtles/core/common/ui/DefaultTopAppBar.kt @@ -1,4 +1,4 @@ -package com.squirtles.common.ui +package com.squirtles.core.common.ui import androidx.compose.foundation.layout.RowScope import androidx.compose.foundation.layout.displayCutoutPadding @@ -18,7 +18,7 @@ import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.TextStyle import androidx.compose.ui.text.font.FontWeight import com.squirtles.common.R -import com.squirtles.common.ui.theme.White +import com.squirtles.core.common.ui.theme.White @OptIn(ExperimentalMaterial3Api::class) @Composable diff --git a/core/common/src/main/java/com/squirtles/common/ui/MessageAlertDialog.kt b/core/common/src/main/java/com/squirtles/core/common/ui/MessageAlertDialog.kt similarity index 93% rename from core/common/src/main/java/com/squirtles/common/ui/MessageAlertDialog.kt rename to core/common/src/main/java/com/squirtles/core/common/ui/MessageAlertDialog.kt index e36bc386..d0f4eeff 100644 --- a/core/common/src/main/java/com/squirtles/common/ui/MessageAlertDialog.kt +++ b/core/common/src/main/java/com/squirtles/core/common/ui/MessageAlertDialog.kt @@ -1,4 +1,4 @@ -package com.squirtles.common.ui +package com.squirtles.core.common.ui import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Column @@ -23,10 +23,10 @@ import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp import com.squirtles.common.R -import com.squirtles.common.ui.theme.Black -import com.squirtles.common.ui.theme.MusicRoadTheme -import com.squirtles.common.ui.theme.Primary -import com.squirtles.common.ui.theme.White +import com.squirtles.core.common.ui.theme.Black +import com.squirtles.core.common.ui.theme.MusicRoadTheme +import com.squirtles.core.common.ui.theme.Primary +import com.squirtles.core.common.ui.theme.White @OptIn(ExperimentalMaterial3Api::class) @Composable diff --git a/core/common/src/main/java/com/squirtles/common/ui/PickInfoText.kt b/core/common/src/main/java/com/squirtles/core/common/ui/PickInfoText.kt similarity index 96% rename from core/common/src/main/java/com/squirtles/common/ui/PickInfoText.kt rename to core/common/src/main/java/com/squirtles/core/common/ui/PickInfoText.kt index 38c6617b..d89f12c0 100644 --- a/core/common/src/main/java/com/squirtles/common/ui/PickInfoText.kt +++ b/core/common/src/main/java/com/squirtles/core/common/ui/PickInfoText.kt @@ -1,4 +1,4 @@ -package com.squirtles.common.ui +package com.squirtles.core.common.ui import androidx.compose.material3.Icon import androidx.compose.material3.MaterialTheme @@ -15,7 +15,7 @@ import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.text.withStyle import com.squirtles.common.R -import com.squirtles.common.ui.theme.Primary +import com.squirtles.core.common.ui.theme.Primary @Composable fun SongInfoText( diff --git a/core/common/src/main/java/com/squirtles/common/ui/SignInAlertDialog.kt b/core/common/src/main/java/com/squirtles/core/common/ui/SignInAlertDialog.kt similarity index 91% rename from core/common/src/main/java/com/squirtles/common/ui/SignInAlertDialog.kt rename to core/common/src/main/java/com/squirtles/core/common/ui/SignInAlertDialog.kt index c0deec96..8dc529dc 100644 --- a/core/common/src/main/java/com/squirtles/common/ui/SignInAlertDialog.kt +++ b/core/common/src/main/java/com/squirtles/core/common/ui/SignInAlertDialog.kt @@ -1,4 +1,4 @@ -package com.squirtles.common.ui +package com.squirtles.core.common.ui import android.content.res.Configuration.UI_MODE_NIGHT_YES import androidx.compose.foundation.BorderStroke @@ -31,13 +31,13 @@ import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp import com.squirtles.common.R -import com.squirtles.common.ui.theme.Black -import com.squirtles.common.ui.theme.DarkGray -import com.squirtles.common.ui.theme.MusicRoadTheme -import com.squirtles.common.ui.theme.SignInButtonDarkBackground -import com.squirtles.common.ui.theme.SignInButtonDarkStroke -import com.squirtles.common.ui.theme.SignInButtonLightStroke -import com.squirtles.common.ui.theme.White +import com.squirtles.core.common.ui.theme.Black +import com.squirtles.core.common.ui.theme.DarkGray +import com.squirtles.core.common.ui.theme.MusicRoadTheme +import com.squirtles.core.common.ui.theme.SignInButtonDarkBackground +import com.squirtles.core.common.ui.theme.SignInButtonDarkStroke +import com.squirtles.core.common.ui.theme.SignInButtonLightStroke +import com.squirtles.core.common.ui.theme.White @OptIn(ExperimentalMaterial3Api::class) diff --git a/core/common/src/main/java/com/squirtles/common/ui/Spacer.kt b/core/common/src/main/java/com/squirtles/core/common/ui/Spacer.kt similarity index 91% rename from core/common/src/main/java/com/squirtles/common/ui/Spacer.kt rename to core/common/src/main/java/com/squirtles/core/common/ui/Spacer.kt index 2c2e9750..c88c8080 100644 --- a/core/common/src/main/java/com/squirtles/common/ui/Spacer.kt +++ b/core/common/src/main/java/com/squirtles/core/common/ui/Spacer.kt @@ -1,4 +1,4 @@ -package com.squirtles.common.ui +package com.squirtles.core.common.ui import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.height diff --git a/core/common/src/main/java/com/squirtles/common/ui/theme/Color.kt b/core/common/src/main/java/com/squirtles/core/common/ui/theme/Color.kt similarity index 93% rename from core/common/src/main/java/com/squirtles/common/ui/theme/Color.kt rename to core/common/src/main/java/com/squirtles/core/common/ui/theme/Color.kt index ae0135f6..86f4a3bd 100644 --- a/core/common/src/main/java/com/squirtles/common/ui/theme/Color.kt +++ b/core/common/src/main/java/com/squirtles/core/common/ui/theme/Color.kt @@ -1,4 +1,4 @@ -package com.squirtles.common.ui.theme +package com.squirtles.core.common.ui.theme import androidx.compose.ui.graphics.Color diff --git a/core/common/src/main/java/com/squirtles/common/ui/theme/Theme.kt b/core/common/src/main/java/com/squirtles/core/common/ui/theme/Theme.kt similarity index 96% rename from core/common/src/main/java/com/squirtles/common/ui/theme/Theme.kt rename to core/common/src/main/java/com/squirtles/core/common/ui/theme/Theme.kt index a27ee6e2..b091b27a 100644 --- a/core/common/src/main/java/com/squirtles/common/ui/theme/Theme.kt +++ b/core/common/src/main/java/com/squirtles/core/common/ui/theme/Theme.kt @@ -1,4 +1,4 @@ -package com.squirtles.common.ui.theme +package com.squirtles.core.common.ui.theme import androidx.compose.foundation.isSystemInDarkTheme import androidx.compose.material3.MaterialTheme diff --git a/core/common/src/main/java/com/squirtles/common/ui/theme/Type.kt b/core/common/src/main/java/com/squirtles/core/common/ui/theme/Type.kt similarity index 95% rename from core/common/src/main/java/com/squirtles/common/ui/theme/Type.kt rename to core/common/src/main/java/com/squirtles/core/common/ui/theme/Type.kt index 17a898e2..bcbff2be 100644 --- a/core/common/src/main/java/com/squirtles/common/ui/theme/Type.kt +++ b/core/common/src/main/java/com/squirtles/core/common/ui/theme/Type.kt @@ -1,4 +1,4 @@ -package com.squirtles.common.ui.theme +package com.squirtles.core.common.ui.theme import androidx.compose.material3.Typography import androidx.compose.ui.text.TextStyle diff --git a/core/mediaservice/src/androidTest/java/com/squirtles/mediaservice/ExampleInstrumentedTest.kt b/core/mediaservice/src/androidTest/java/com/squirtles/core/mediaservice/ExampleInstrumentedTest.kt similarity index 100% rename from core/mediaservice/src/androidTest/java/com/squirtles/mediaservice/ExampleInstrumentedTest.kt rename to core/mediaservice/src/androidTest/java/com/squirtles/core/mediaservice/ExampleInstrumentedTest.kt diff --git a/core/mediaservice/src/main/java/com/squirtles/mediaservice/CustomMediaSessionCallback.kt b/core/mediaservice/src/main/java/com/squirtles/core/mediaservice/CustomMediaSessionCallback.kt similarity index 98% rename from core/mediaservice/src/main/java/com/squirtles/mediaservice/CustomMediaSessionCallback.kt rename to core/mediaservice/src/main/java/com/squirtles/core/mediaservice/CustomMediaSessionCallback.kt index ac9ae731..b4f3f59c 100644 --- a/core/mediaservice/src/main/java/com/squirtles/mediaservice/CustomMediaSessionCallback.kt +++ b/core/mediaservice/src/main/java/com/squirtles/core/mediaservice/CustomMediaSessionCallback.kt @@ -1,4 +1,4 @@ -package com.squirtles.mediaservice +package com.squirtles.core.mediaservice import android.os.Build import android.os.Bundle diff --git a/core/mediaservice/src/main/java/com/squirtles/mediaservice/MediaControllerProvider.kt b/core/mediaservice/src/main/java/com/squirtles/core/mediaservice/MediaControllerProvider.kt similarity index 98% rename from core/mediaservice/src/main/java/com/squirtles/mediaservice/MediaControllerProvider.kt rename to core/mediaservice/src/main/java/com/squirtles/core/mediaservice/MediaControllerProvider.kt index b17d9b54..6d0be76e 100644 --- a/core/mediaservice/src/main/java/com/squirtles/mediaservice/MediaControllerProvider.kt +++ b/core/mediaservice/src/main/java/com/squirtles/core/mediaservice/MediaControllerProvider.kt @@ -1,4 +1,4 @@ -package com.squirtles.mediaservice +package com.squirtles.core.mediaservice import androidx.media3.common.util.UnstableApi import androidx.media3.session.MediaController diff --git a/core/mediaservice/src/main/java/com/squirtles/mediaservice/MediaNotificationProvider.kt b/core/mediaservice/src/main/java/com/squirtles/core/mediaservice/MediaNotificationProvider.kt similarity index 98% rename from core/mediaservice/src/main/java/com/squirtles/mediaservice/MediaNotificationProvider.kt rename to core/mediaservice/src/main/java/com/squirtles/core/mediaservice/MediaNotificationProvider.kt index ada84fe7..0f675f0e 100644 --- a/core/mediaservice/src/main/java/com/squirtles/mediaservice/MediaNotificationProvider.kt +++ b/core/mediaservice/src/main/java/com/squirtles/core/mediaservice/MediaNotificationProvider.kt @@ -1,4 +1,4 @@ -package com.squirtles.mediaservice +package com.squirtles.core.mediaservice import android.annotation.SuppressLint import android.app.NotificationChannel @@ -16,6 +16,7 @@ import androidx.media3.common.util.UnstableApi import androidx.media3.session.MediaNotification import androidx.media3.session.MediaSession import androidx.media3.session.MediaStyleNotificationHelper +import com.squirtles.mediaservice.R import javax.inject.Inject interface MediaNotificationProvider { diff --git a/core/mediaservice/src/main/java/com/squirtles/mediaservice/MediaPlayerService.kt b/core/mediaservice/src/main/java/com/squirtles/core/mediaservice/MediaPlayerService.kt similarity index 98% rename from core/mediaservice/src/main/java/com/squirtles/mediaservice/MediaPlayerService.kt rename to core/mediaservice/src/main/java/com/squirtles/core/mediaservice/MediaPlayerService.kt index b5a028a4..3be6a7d7 100644 --- a/core/mediaservice/src/main/java/com/squirtles/mediaservice/MediaPlayerService.kt +++ b/core/mediaservice/src/main/java/com/squirtles/core/mediaservice/MediaPlayerService.kt @@ -1,4 +1,4 @@ -package com.squirtles.mediaservice +package com.squirtles.core.mediaservice import android.content.Intent import android.os.Bundle diff --git a/core/mediaservice/src/main/java/com/squirtles/mediaservice/PlayerCommands.kt b/core/mediaservice/src/main/java/com/squirtles/core/mediaservice/PlayerCommands.kt similarity index 97% rename from core/mediaservice/src/main/java/com/squirtles/mediaservice/PlayerCommands.kt rename to core/mediaservice/src/main/java/com/squirtles/core/mediaservice/PlayerCommands.kt index a26936d0..f8d8d91b 100644 --- a/core/mediaservice/src/main/java/com/squirtles/mediaservice/PlayerCommands.kt +++ b/core/mediaservice/src/main/java/com/squirtles/core/mediaservice/PlayerCommands.kt @@ -1,4 +1,4 @@ -package com.squirtles.mediaservice +package com.squirtles.core.mediaservice import android.os.Bundle import androidx.media3.session.SessionCommand diff --git a/core/mediaservice/src/main/java/com/squirtles/mediaservice/di/MediaDiModule.kt b/core/mediaservice/src/main/java/com/squirtles/core/mediaservice/di/MediaDiModule.kt similarity index 86% rename from core/mediaservice/src/main/java/com/squirtles/mediaservice/di/MediaDiModule.kt rename to core/mediaservice/src/main/java/com/squirtles/core/mediaservice/di/MediaDiModule.kt index e056396a..935e4deb 100644 --- a/core/mediaservice/src/main/java/com/squirtles/mediaservice/di/MediaDiModule.kt +++ b/core/mediaservice/src/main/java/com/squirtles/core/mediaservice/di/MediaDiModule.kt @@ -1,4 +1,4 @@ -package com.squirtles.mediaservice.di +package com.squirtles.core.mediaservice.di import android.content.ComponentName import android.content.Context @@ -9,12 +9,12 @@ import androidx.media3.exoplayer.ExoPlayer import androidx.media3.session.MediaController import androidx.media3.session.MediaSession import androidx.media3.session.SessionToken -import com.squirtles.mediaservice.CustomMediaSessionCallback -import com.squirtles.mediaservice.MediaControllerProvider -import com.squirtles.mediaservice.MediaControllerProviderImpl -import com.squirtles.mediaservice.MediaNotificationProvider -import com.squirtles.mediaservice.MediaNotificationProviderImpl -import com.squirtles.mediaservice.MediaPlayerService +import com.squirtles.core.mediaservice.CustomMediaSessionCallback +import com.squirtles.core.mediaservice.MediaControllerProvider +import com.squirtles.core.mediaservice.MediaControllerProviderImpl +import com.squirtles.core.mediaservice.MediaNotificationProvider +import com.squirtles.core.mediaservice.MediaNotificationProviderImpl +import com.squirtles.core.mediaservice.MediaPlayerService import com.google.common.util.concurrent.ListenableFuture import dagger.Binds import dagger.Module diff --git a/core/mediaservice/src/test/java/com/squirtles/mediaservice/ExampleUnitTest.kt b/core/mediaservice/src/test/java/com/squirtles/core/mediaservice/ExampleUnitTest.kt similarity index 100% rename from core/mediaservice/src/test/java/com/squirtles/mediaservice/ExampleUnitTest.kt rename to core/mediaservice/src/test/java/com/squirtles/core/mediaservice/ExampleUnitTest.kt diff --git a/core/model/src/main/java/com/squirtles/model/MusicVideo.kt b/core/model/src/main/java/com/squirtles/core/model/MusicVideo.kt similarity index 88% rename from core/model/src/main/java/com/squirtles/model/MusicVideo.kt rename to core/model/src/main/java/com/squirtles/core/model/MusicVideo.kt index 9516977f..37e259bf 100644 --- a/core/model/src/main/java/com/squirtles/model/MusicVideo.kt +++ b/core/model/src/main/java/com/squirtles/core/model/MusicVideo.kt @@ -1,4 +1,4 @@ -package com.squirtles.model +package com.squirtles.core.model import java.time.LocalDate diff --git a/core/model/src/main/java/com/squirtles/model/Order.kt b/core/model/src/main/java/com/squirtles/core/model/Order.kt similarity index 66% rename from core/model/src/main/java/com/squirtles/model/Order.kt rename to core/model/src/main/java/com/squirtles/core/model/Order.kt index 267e8d99..4b91e795 100644 --- a/core/model/src/main/java/com/squirtles/model/Order.kt +++ b/core/model/src/main/java/com/squirtles/core/model/Order.kt @@ -1,4 +1,4 @@ -package com.squirtles.model +package com.squirtles.core.model enum class Order { LATEST, diff --git a/core/model/src/main/java/com/squirtles/model/Pick.kt b/core/model/src/main/java/com/squirtles/core/model/Pick.kt similarity index 94% rename from core/model/src/main/java/com/squirtles/model/Pick.kt rename to core/model/src/main/java/com/squirtles/core/model/Pick.kt index 391caca0..12d6a1c1 100644 --- a/core/model/src/main/java/com/squirtles/model/Pick.kt +++ b/core/model/src/main/java/com/squirtles/core/model/Pick.kt @@ -1,4 +1,4 @@ -package com.squirtles.model +package com.squirtles.core.model /** * 앱에서 사용하기 위한 Pick 정보 데이터클래스 diff --git a/core/model/src/main/java/com/squirtles/model/PlayerState.kt b/core/model/src/main/java/com/squirtles/core/model/PlayerState.kt similarity index 88% rename from core/model/src/main/java/com/squirtles/model/PlayerState.kt rename to core/model/src/main/java/com/squirtles/core/model/PlayerState.kt index 451b5179..12a6bbee 100644 --- a/core/model/src/main/java/com/squirtles/model/PlayerState.kt +++ b/core/model/src/main/java/com/squirtles/core/model/PlayerState.kt @@ -1,4 +1,4 @@ -package com.squirtles.model +package com.squirtles.core.model data class PlayerState( val id: String = "", diff --git a/core/model/src/main/java/com/squirtles/model/Song.kt b/core/model/src/main/java/com/squirtles/core/model/Song.kt similarity index 95% rename from core/model/src/main/java/com/squirtles/model/Song.kt rename to core/model/src/main/java/com/squirtles/core/model/Song.kt index 0b2ae786..4d85cf66 100644 --- a/core/model/src/main/java/com/squirtles/model/Song.kt +++ b/core/model/src/main/java/com/squirtles/core/model/Song.kt @@ -1,4 +1,4 @@ -package com.squirtles.model +package com.squirtles.core.model import kotlinx.serialization.Serializable diff --git a/core/model/src/main/java/com/squirtles/model/User.kt b/core/model/src/main/java/com/squirtles/core/model/User.kt similarity index 82% rename from core/model/src/main/java/com/squirtles/model/User.kt rename to core/model/src/main/java/com/squirtles/core/model/User.kt index 0198552c..f493e263 100644 --- a/core/model/src/main/java/com/squirtles/model/User.kt +++ b/core/model/src/main/java/com/squirtles/core/model/User.kt @@ -1,4 +1,4 @@ -package com.squirtles.model +package com.squirtles.core.model data class User( val uid: String, diff --git a/core/musicplayer/src/main/java/com/squirtles/musicplayer/PlayerServiceViewModel.kt b/core/musicplayer/src/main/java/com/squirtles/core/musicplayer/PlayerServiceViewModel.kt similarity index 91% rename from core/musicplayer/src/main/java/com/squirtles/musicplayer/PlayerServiceViewModel.kt rename to core/musicplayer/src/main/java/com/squirtles/core/musicplayer/PlayerServiceViewModel.kt index ecbb3c15..93f6b4be 100644 --- a/core/musicplayer/src/main/java/com/squirtles/musicplayer/PlayerServiceViewModel.kt +++ b/core/musicplayer/src/main/java/com/squirtles/core/musicplayer/PlayerServiceViewModel.kt @@ -1,12 +1,11 @@ -package com.squirtles.musicplayer +package com.squirtles.core.musicplayer import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope -import com.squirtles.model.Pick -import com.squirtles.model.PlayerState -import com.squirtles.model.Song -import com.squirtles.player.MediaPlayerListenerUseCase -import com.squirtles.player.MediaPlayerUseCase +import com.squirtles.core.model.Pick +import com.squirtles.core.model.PlayerState +import com.squirtles.domain.player.MediaPlayerListenerUseCase +import com.squirtles.domain.player.MediaPlayerUseCase import dagger.hilt.android.lifecycle.HiltViewModel import kotlinx.coroutines.async import kotlinx.coroutines.flow.SharingStarted diff --git a/core/musicplayer/src/main/java/com/squirtles/musicplayer/PlayerUiState.kt b/core/musicplayer/src/main/java/com/squirtles/core/musicplayer/PlayerUiState.kt similarity index 88% rename from core/musicplayer/src/main/java/com/squirtles/musicplayer/PlayerUiState.kt rename to core/musicplayer/src/main/java/com/squirtles/core/musicplayer/PlayerUiState.kt index 5a5e06fa..826121bf 100644 --- a/core/musicplayer/src/main/java/com/squirtles/musicplayer/PlayerUiState.kt +++ b/core/musicplayer/src/main/java/com/squirtles/core/musicplayer/PlayerUiState.kt @@ -1,4 +1,4 @@ -package com.squirtles.musicplayer +package com.squirtles.core.musicplayer data class PlayerUiState( val isReady: Boolean = true, diff --git a/core/musicplayer/src/main/java/com/squirtles/musicplayer/PlayerViewModel.kt b/core/musicplayer/src/main/java/com/squirtles/core/musicplayer/PlayerViewModel.kt similarity index 96% rename from core/musicplayer/src/main/java/com/squirtles/musicplayer/PlayerViewModel.kt rename to core/musicplayer/src/main/java/com/squirtles/core/musicplayer/PlayerViewModel.kt index 927fc552..c7b83890 100644 --- a/core/musicplayer/src/main/java/com/squirtles/musicplayer/PlayerViewModel.kt +++ b/core/musicplayer/src/main/java/com/squirtles/core/musicplayer/PlayerViewModel.kt @@ -1,4 +1,4 @@ -package com.squirtles.musicplayer +package com.squirtles.core.musicplayer import android.content.Context import android.util.Log @@ -11,8 +11,8 @@ import androidx.media3.common.PlaybackException import androidx.media3.common.Player import androidx.media3.common.util.UnstableApi import androidx.media3.exoplayer.ExoPlayer -import com.squirtles.musicplayer.PlayerUiState.Companion.PLAYER_STATE_INITIAL -import com.squirtles.musicplayer.PlayerUiState.Companion.PLAYER_STATE_STOP +import com.squirtles.core.musicplayer.PlayerUiState.Companion.PLAYER_STATE_INITIAL +import com.squirtles.core.musicplayer.PlayerUiState.Companion.PLAYER_STATE_STOP import dagger.hilt.android.lifecycle.HiltViewModel import kotlinx.coroutines.delay import kotlinx.coroutines.flow.MutableStateFlow diff --git a/core/navigation/src/main/java/com/squirtles/navigation/MainRoute.kt b/core/navigation/src/main/java/com/squirtles/core/navigation/MainRoute.kt similarity index 88% rename from core/navigation/src/main/java/com/squirtles/navigation/MainRoute.kt rename to core/navigation/src/main/java/com/squirtles/core/navigation/MainRoute.kt index afcd3908..b285e60f 100644 --- a/core/navigation/src/main/java/com/squirtles/navigation/MainRoute.kt +++ b/core/navigation/src/main/java/com/squirtles/core/navigation/MainRoute.kt @@ -1,4 +1,4 @@ -package com.squirtles.navigation +package com.squirtles.core.navigation import kotlinx.serialization.Serializable diff --git a/core/navigation/src/main/java/com/squirtles/navigation/MapRoute.kt b/core/navigation/src/main/java/com/squirtles/core/navigation/MapRoute.kt similarity index 81% rename from core/navigation/src/main/java/com/squirtles/navigation/MapRoute.kt rename to core/navigation/src/main/java/com/squirtles/core/navigation/MapRoute.kt index 816e5ef5..ec99b12b 100644 --- a/core/navigation/src/main/java/com/squirtles/navigation/MapRoute.kt +++ b/core/navigation/src/main/java/com/squirtles/core/navigation/MapRoute.kt @@ -1,4 +1,4 @@ -package com.squirtles.navigation +package com.squirtles.core.navigation import kotlinx.serialization.Serializable diff --git a/core/navigation/src/main/java/com/squirtles/navigation/Route.kt b/core/navigation/src/main/java/com/squirtles/core/navigation/Route.kt similarity index 77% rename from core/navigation/src/main/java/com/squirtles/navigation/Route.kt rename to core/navigation/src/main/java/com/squirtles/core/navigation/Route.kt index faa1a6fb..77ae775c 100644 --- a/core/navigation/src/main/java/com/squirtles/navigation/Route.kt +++ b/core/navigation/src/main/java/com/squirtles/core/navigation/Route.kt @@ -1,4 +1,4 @@ -package com.squirtles.navigation +package com.squirtles.core.navigation import kotlinx.serialization.Serializable diff --git a/core/navigation/src/main/java/com/squirtles/navigation/SearchRoute.kt b/core/navigation/src/main/java/com/squirtles/core/navigation/SearchRoute.kt similarity index 69% rename from core/navigation/src/main/java/com/squirtles/navigation/SearchRoute.kt rename to core/navigation/src/main/java/com/squirtles/core/navigation/SearchRoute.kt index f4f77b17..8ec47bc3 100644 --- a/core/navigation/src/main/java/com/squirtles/navigation/SearchRoute.kt +++ b/core/navigation/src/main/java/com/squirtles/core/navigation/SearchRoute.kt @@ -1,6 +1,6 @@ -package com.squirtles.navigation +package com.squirtles.core.navigation -import com.squirtles.model.Song +import com.squirtles.core.model.Song import kotlinx.serialization.Serializable @Serializable diff --git a/core/navigation/src/main/java/com/squirtles/navigation/UserInfoRoute.kt b/core/navigation/src/main/java/com/squirtles/core/navigation/UserInfoRoute.kt similarity index 89% rename from core/navigation/src/main/java/com/squirtles/navigation/UserInfoRoute.kt rename to core/navigation/src/main/java/com/squirtles/core/navigation/UserInfoRoute.kt index 92ccdc66..d73dd1e7 100644 --- a/core/navigation/src/main/java/com/squirtles/navigation/UserInfoRoute.kt +++ b/core/navigation/src/main/java/com/squirtles/core/navigation/UserInfoRoute.kt @@ -1,4 +1,4 @@ -package com.squirtles.navigation +package com.squirtles.core.navigation import kotlinx.serialization.Serializable diff --git a/core/picklist/src/androidTest/java/com/squirtles/picklist/ExampleInstrumentedTest.kt b/core/picklist/src/androidTest/java/com/squirtles/core/picklist/ExampleInstrumentedTest.kt similarity index 100% rename from core/picklist/src/androidTest/java/com/squirtles/picklist/ExampleInstrumentedTest.kt rename to core/picklist/src/androidTest/java/com/squirtles/core/picklist/ExampleInstrumentedTest.kt diff --git a/core/picklist/src/main/java/com/squirtles/picklist/PickListScreenContents.kt b/core/picklist/src/main/java/com/squirtles/core/picklist/PickListScreenContents.kt similarity index 92% rename from core/picklist/src/main/java/com/squirtles/picklist/PickListScreenContents.kt rename to core/picklist/src/main/java/com/squirtles/core/picklist/PickListScreenContents.kt index 82a4b2f6..8796e66c 100644 --- a/core/picklist/src/main/java/com/squirtles/picklist/PickListScreenContents.kt +++ b/core/picklist/src/main/java/com/squirtles/core/picklist/PickListScreenContents.kt @@ -1,4 +1,4 @@ -package com.squirtles.picklist +package com.squirtles.core.picklist import android.content.res.Configuration import androidx.compose.foundation.background @@ -33,20 +33,21 @@ import androidx.compose.ui.platform.LocalConfiguration import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.dp import androidx.wear.compose.material.CircularProgressIndicator -import com.squirtles.common.ui.Constants.COLOR_STOPS -import com.squirtles.common.ui.Constants.DEFAULT_PADDING -import com.squirtles.common.ui.CountText -import com.squirtles.common.ui.DefaultTopAppBar -import com.squirtles.common.ui.VerticalSpacer -import com.squirtles.common.ui.theme.Primary -import com.squirtles.common.ui.theme.White -import com.squirtles.model.Order -import com.squirtles.model.Pick -import com.squirtles.picklist.components.DeleteSelectedPickDialog -import com.squirtles.picklist.components.EditModeAction -import com.squirtles.picklist.components.EditModeBottomButton -import com.squirtles.picklist.components.OrderBottomSheet -import com.squirtles.picklist.components.PickItem +import com.squirtles.core.common.ui.Constants.COLOR_STOPS +import com.squirtles.core.common.ui.Constants.DEFAULT_PADDING +import com.squirtles.core.common.ui.CountText +import com.squirtles.core.common.ui.DefaultTopAppBar +import com.squirtles.core.common.ui.VerticalSpacer +import com.squirtles.core.common.ui.theme.Primary +import com.squirtles.core.common.ui.theme.White +import com.squirtles.core.model.Order +import com.squirtles.core.model.Pick +import com.squirtles.picklist.R +import com.squirtles.core.picklist.components.DeleteSelectedPickDialog +import com.squirtles.core.picklist.components.EditModeAction +import com.squirtles.core.picklist.components.EditModeBottomButton +import com.squirtles.core.picklist.components.OrderBottomSheet +import com.squirtles.core.picklist.components.PickItem @Composable fun PickListScreenContents( diff --git a/core/picklist/src/main/java/com/squirtles/picklist/PickListType.kt b/core/picklist/src/main/java/com/squirtles/core/picklist/PickListType.kt similarity index 58% rename from core/picklist/src/main/java/com/squirtles/picklist/PickListType.kt rename to core/picklist/src/main/java/com/squirtles/core/picklist/PickListType.kt index 3f6c0764..dbdfe65c 100644 --- a/core/picklist/src/main/java/com/squirtles/picklist/PickListType.kt +++ b/core/picklist/src/main/java/com/squirtles/core/picklist/PickListType.kt @@ -1,4 +1,4 @@ -package com.squirtles.picklist +package com.squirtles.core.picklist enum class PickListType { FAVORITE, CREATED diff --git a/core/picklist/src/main/java/com/squirtles/picklist/PickListUiState.kt b/core/picklist/src/main/java/com/squirtles/core/picklist/PickListUiState.kt similarity index 65% rename from core/picklist/src/main/java/com/squirtles/picklist/PickListUiState.kt rename to core/picklist/src/main/java/com/squirtles/core/picklist/PickListUiState.kt index 1f6278e8..4237c81c 100644 --- a/core/picklist/src/main/java/com/squirtles/picklist/PickListUiState.kt +++ b/core/picklist/src/main/java/com/squirtles/core/picklist/PickListUiState.kt @@ -1,7 +1,7 @@ -package com.squirtles.picklist +package com.squirtles.core.picklist -import com.squirtles.model.Order -import com.squirtles.model.Pick +import com.squirtles.core.model.Order +import com.squirtles.core.model.Pick sealed class PickListUiState { data object Loading : PickListUiState() diff --git a/core/picklist/src/main/java/com/squirtles/picklist/PickListViewModel.kt b/core/picklist/src/main/java/com/squirtles/core/picklist/PickListViewModel.kt similarity index 95% rename from core/picklist/src/main/java/com/squirtles/picklist/PickListViewModel.kt rename to core/picklist/src/main/java/com/squirtles/core/picklist/PickListViewModel.kt index dca3b8f3..a34c910c 100644 --- a/core/picklist/src/main/java/com/squirtles/picklist/PickListViewModel.kt +++ b/core/picklist/src/main/java/com/squirtles/core/picklist/PickListViewModel.kt @@ -1,4 +1,4 @@ -package com.squirtles.picklist +package com.squirtles.core.picklist import android.util.Log import androidx.lifecycle.ViewModel @@ -7,9 +7,9 @@ import com.squirtles.domain.picklist.FetchPickListUseCaseInterface import com.squirtles.domain.picklist.GetPickListOrderUseCaseInterface import com.squirtles.domain.picklist.RemovePickUseCaseInterface import com.squirtles.domain.picklist.SavePickListOrderUseCaseInterface -import com.squirtles.model.Order -import com.squirtles.model.Pick -import com.squirtles.user.usecase.GetCurrentUidUseCase +import com.squirtles.core.model.Order +import com.squirtles.core.model.Pick +import com.squirtles.domain.user.usecase.GetCurrentUidUseCase import kotlinx.coroutines.async import kotlinx.coroutines.awaitAll import kotlinx.coroutines.flow.MutableStateFlow diff --git a/core/picklist/src/main/java/com/squirtles/picklist/components/DeleteSelectedPickDialog.kt b/core/picklist/src/main/java/com/squirtles/core/picklist/components/DeleteSelectedPickDialog.kt similarity index 80% rename from core/picklist/src/main/java/com/squirtles/picklist/components/DeleteSelectedPickDialog.kt rename to core/picklist/src/main/java/com/squirtles/core/picklist/components/DeleteSelectedPickDialog.kt index 7fba1a31..50c4b362 100644 --- a/core/picklist/src/main/java/com/squirtles/picklist/components/DeleteSelectedPickDialog.kt +++ b/core/picklist/src/main/java/com/squirtles/core/picklist/components/DeleteSelectedPickDialog.kt @@ -1,13 +1,13 @@ -package com.squirtles.picklist.components +package com.squirtles.core.picklist.components import androidx.compose.runtime.Composable import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.font.FontWeight -import com.squirtles.common.ui.DialogTextButton -import com.squirtles.common.ui.HorizontalSpacer -import com.squirtles.common.ui.MessageAlertDialog -import com.squirtles.common.ui.theme.Primary -import com.squirtles.picklist.PickListType +import com.squirtles.core.common.ui.DialogTextButton +import com.squirtles.core.common.ui.HorizontalSpacer +import com.squirtles.core.common.ui.MessageAlertDialog +import com.squirtles.core.common.ui.theme.Primary +import com.squirtles.core.picklist.PickListType import com.squirtles.picklist.R @Composable diff --git a/core/picklist/src/main/java/com/squirtles/picklist/components/EditModeAction.kt b/core/picklist/src/main/java/com/squirtles/core/picklist/components/EditModeAction.kt similarity index 92% rename from core/picklist/src/main/java/com/squirtles/picklist/components/EditModeAction.kt rename to core/picklist/src/main/java/com/squirtles/core/picklist/components/EditModeAction.kt index 53a0703c..b8bc685a 100644 --- a/core/picklist/src/main/java/com/squirtles/picklist/components/EditModeAction.kt +++ b/core/picklist/src/main/java/com/squirtles/core/picklist/components/EditModeAction.kt @@ -1,4 +1,4 @@ -package com.squirtles.picklist.components +package com.squirtles.core.picklist.components import androidx.compose.material.icons.Icons import androidx.compose.material.icons.filled.Edit @@ -9,8 +9,8 @@ import androidx.compose.material3.Text import androidx.compose.material3.TextButton import androidx.compose.runtime.Composable import androidx.compose.ui.res.stringResource -import com.squirtles.common.ui.theme.Gray -import com.squirtles.common.ui.theme.White +import com.squirtles.core.common.ui.theme.Gray +import com.squirtles.core.common.ui.theme.White import com.squirtles.picklist.R diff --git a/core/picklist/src/main/java/com/squirtles/picklist/components/EditModeBottomButton.kt b/core/picklist/src/main/java/com/squirtles/core/picklist/components/EditModeBottomButton.kt similarity index 89% rename from core/picklist/src/main/java/com/squirtles/picklist/components/EditModeBottomButton.kt rename to core/picklist/src/main/java/com/squirtles/core/picklist/components/EditModeBottomButton.kt index 8def40cb..7c1f4cfe 100644 --- a/core/picklist/src/main/java/com/squirtles/picklist/components/EditModeBottomButton.kt +++ b/core/picklist/src/main/java/com/squirtles/core/picklist/components/EditModeBottomButton.kt @@ -1,4 +1,4 @@ -package com.squirtles.picklist.components +package com.squirtles.core.picklist.components import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.fillMaxHeight @@ -15,11 +15,11 @@ import androidx.compose.ui.graphics.RectangleShape import androidx.compose.ui.res.stringResource import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp -import com.squirtles.common.ui.theme.DarkGray -import com.squirtles.common.ui.theme.Gray -import com.squirtles.common.ui.theme.MusicRoadTheme -import com.squirtles.common.ui.theme.Primary -import com.squirtles.common.ui.theme.White +import com.squirtles.core.common.ui.theme.DarkGray +import com.squirtles.core.common.ui.theme.Gray +import com.squirtles.core.common.ui.theme.MusicRoadTheme +import com.squirtles.core.common.ui.theme.Primary +import com.squirtles.core.common.ui.theme.White import com.squirtles.picklist.R @Composable diff --git a/core/picklist/src/main/java/com/squirtles/picklist/components/OrderBottomSheet.kt b/core/picklist/src/main/java/com/squirtles/core/picklist/components/OrderBottomSheet.kt similarity index 94% rename from core/picklist/src/main/java/com/squirtles/picklist/components/OrderBottomSheet.kt rename to core/picklist/src/main/java/com/squirtles/core/picklist/components/OrderBottomSheet.kt index 35969015..9550c776 100644 --- a/core/picklist/src/main/java/com/squirtles/picklist/components/OrderBottomSheet.kt +++ b/core/picklist/src/main/java/com/squirtles/core/picklist/components/OrderBottomSheet.kt @@ -1,4 +1,4 @@ -package com.squirtles.picklist.components +package com.squirtles.core.picklist.components import androidx.compose.foundation.clickable import androidx.compose.foundation.layout.Arrangement @@ -25,11 +25,11 @@ import androidx.compose.ui.Modifier import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.text.style.TextAlign -import com.squirtles.common.ui.Constants.DEFAULT_PADDING -import com.squirtles.common.ui.theme.Dark -import com.squirtles.common.ui.theme.Primary -import com.squirtles.common.ui.theme.White -import com.squirtles.model.Order +import com.squirtles.core.common.ui.Constants.DEFAULT_PADDING +import com.squirtles.core.common.ui.theme.Dark +import com.squirtles.core.common.ui.theme.Primary +import com.squirtles.core.common.ui.theme.White +import com.squirtles.core.model.Order import com.squirtles.picklist.R import kotlinx.coroutines.launch diff --git a/core/picklist/src/main/java/com/squirtles/picklist/components/PickItem.kt b/core/picklist/src/main/java/com/squirtles/core/picklist/components/PickItem.kt similarity index 87% rename from core/picklist/src/main/java/com/squirtles/picklist/components/PickItem.kt rename to core/picklist/src/main/java/com/squirtles/core/picklist/components/PickItem.kt index ec7f23a8..7301e614 100644 --- a/core/picklist/src/main/java/com/squirtles/picklist/components/PickItem.kt +++ b/core/picklist/src/main/java/com/squirtles/core/picklist/components/PickItem.kt @@ -1,4 +1,4 @@ -package com.squirtles.picklist.components +package com.squirtles.core.picklist.components import androidx.compose.foundation.background import androidx.compose.foundation.clickable @@ -25,17 +25,17 @@ import androidx.compose.ui.graphics.Color import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.unit.dp -import com.squirtles.common.ui.AlbumImage -import com.squirtles.common.ui.CommentText -import com.squirtles.common.ui.Constants -import com.squirtles.common.ui.CreatedByOtherUserText -import com.squirtles.common.ui.FavoriteCountText -import com.squirtles.common.ui.HorizontalSpacer -import com.squirtles.common.ui.SongInfoText -import com.squirtles.common.ui.theme.Gray -import com.squirtles.common.ui.theme.Primary -import com.squirtles.common.ui.theme.White -import com.squirtles.model.Song +import com.squirtles.core.common.ui.AlbumImage +import com.squirtles.core.common.ui.CommentText +import com.squirtles.core.common.ui.Constants +import com.squirtles.core.common.ui.CreatedByOtherUserText +import com.squirtles.core.common.ui.FavoriteCountText +import com.squirtles.core.common.ui.HorizontalSpacer +import com.squirtles.core.common.ui.SongInfoText +import com.squirtles.core.common.ui.theme.Gray +import com.squirtles.core.common.ui.theme.Primary +import com.squirtles.core.common.ui.theme.White +import com.squirtles.core.model.Song import com.squirtles.picklist.R @Composable diff --git a/core/picklist/src/test/java/com/squirtles/picklist/ExampleUnitTest.kt b/core/picklist/src/test/java/com/squirtles/core/picklist/ExampleUnitTest.kt similarity index 100% rename from core/picklist/src/test/java/com/squirtles/picklist/ExampleUnitTest.kt rename to core/picklist/src/test/java/com/squirtles/core/picklist/ExampleUnitTest.kt diff --git a/core/util/src/main/java/com/squirtles/util/SerializableType.kt b/core/util/src/main/java/com/squirtles/core/util/SerializableType.kt similarity index 95% rename from core/util/src/main/java/com/squirtles/util/SerializableType.kt rename to core/util/src/main/java/com/squirtles/core/util/SerializableType.kt index 6775ea5d..a3d376b5 100644 --- a/core/util/src/main/java/com/squirtles/util/SerializableType.kt +++ b/core/util/src/main/java/com/squirtles/core/util/SerializableType.kt @@ -1,4 +1,4 @@ -package com.squirtles.util +package com.squirtles.core.util import android.os.Bundle import androidx.navigation.NavType diff --git a/core/util/src/main/java/com/squirtles/util/ThrottleFirst.kt b/core/util/src/main/java/com/squirtles/core/util/ThrottleFirst.kt similarity index 93% rename from core/util/src/main/java/com/squirtles/util/ThrottleFirst.kt rename to core/util/src/main/java/com/squirtles/core/util/ThrottleFirst.kt index 5ede902c..269ed3bb 100644 --- a/core/util/src/main/java/com/squirtles/util/ThrottleFirst.kt +++ b/core/util/src/main/java/com/squirtles/core/util/ThrottleFirst.kt @@ -1,4 +1,4 @@ -package com.squirtles.util +package com.squirtles.core.util import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.flow diff --git a/data/applemusic/src/main/java/com/squirtles/applemusic/AppleMusicDataSource.kt b/data/applemusic/src/main/java/com/squirtles/data/applemusic/AppleMusicDataSource.kt similarity index 67% rename from data/applemusic/src/main/java/com/squirtles/applemusic/AppleMusicDataSource.kt rename to data/applemusic/src/main/java/com/squirtles/data/applemusic/AppleMusicDataSource.kt index 099ea0db..375727f8 100644 --- a/data/applemusic/src/main/java/com/squirtles/applemusic/AppleMusicDataSource.kt +++ b/data/applemusic/src/main/java/com/squirtles/data/applemusic/AppleMusicDataSource.kt @@ -1,8 +1,8 @@ -package com.squirtles.applemusic +package com.squirtles.data.applemusic import androidx.paging.PagingData -import com.squirtles.model.MusicVideo -import com.squirtles.model.Song +import com.squirtles.core.model.MusicVideo +import com.squirtles.core.model.Song import kotlinx.coroutines.flow.Flow interface AppleMusicDataSource { diff --git a/data/applemusic/src/main/java/com/squirtles/applemusic/AppleMusicDataSourceImpl.kt b/data/applemusic/src/main/java/com/squirtles/data/applemusic/AppleMusicDataSourceImpl.kt similarity index 81% rename from data/applemusic/src/main/java/com/squirtles/applemusic/AppleMusicDataSourceImpl.kt rename to data/applemusic/src/main/java/com/squirtles/data/applemusic/AppleMusicDataSourceImpl.kt index fdcf8002..f388b8a8 100644 --- a/data/applemusic/src/main/java/com/squirtles/applemusic/AppleMusicDataSourceImpl.kt +++ b/data/applemusic/src/main/java/com/squirtles/data/applemusic/AppleMusicDataSourceImpl.kt @@ -1,14 +1,14 @@ -package com.squirtles.applemusic +package com.squirtles.data.applemusic import androidx.paging.Pager import androidx.paging.PagingConfig import androidx.paging.PagingData -import com.squirtles.applemusic.SearchSongsPagingSource.Companion.SEARCH_PAGE_SIZE -import com.squirtles.applemusic.api.AppleMusicApi -import com.squirtles.applemusic.model.SearchResponse -import com.squirtles.applemusic.model.toMusicVideo -import com.squirtles.model.MusicVideo -import com.squirtles.model.Song +import com.squirtles.data.applemusic.SearchSongsPagingSource.Companion.SEARCH_PAGE_SIZE +import com.squirtles.data.applemusic.api.AppleMusicApi +import com.squirtles.data.applemusic.model.SearchResponse +import com.squirtles.data.applemusic.model.toMusicVideo +import com.squirtles.core.model.MusicVideo +import com.squirtles.core.model.Song import kotlinx.coroutines.flow.Flow import retrofit2.Response import javax.inject.Inject diff --git a/data/applemusic/src/main/java/com/squirtles/applemusic/AppleMusicRepositoryImpl.kt b/data/applemusic/src/main/java/com/squirtles/data/applemusic/AppleMusicRepositoryImpl.kt similarity index 78% rename from data/applemusic/src/main/java/com/squirtles/applemusic/AppleMusicRepositoryImpl.kt rename to data/applemusic/src/main/java/com/squirtles/data/applemusic/AppleMusicRepositoryImpl.kt index ace82f57..2fe6ba1b 100644 --- a/data/applemusic/src/main/java/com/squirtles/applemusic/AppleMusicRepositoryImpl.kt +++ b/data/applemusic/src/main/java/com/squirtles/data/applemusic/AppleMusicRepositoryImpl.kt @@ -1,8 +1,10 @@ -package com.squirtles.applemusic +package com.squirtles.data.applemusic import androidx.paging.PagingData -import com.squirtles.model.MusicVideo -import com.squirtles.model.Song +import com.squirtles.domain.applemusic.AppleMusicException +import com.squirtles.domain.applemusic.AppleMusicRepository +import com.squirtles.core.model.MusicVideo +import com.squirtles.core.model.Song import kotlinx.coroutines.flow.Flow import javax.inject.Inject diff --git a/data/applemusic/src/main/java/com/squirtles/applemusic/SearchSongsPagingSource.kt b/data/applemusic/src/main/java/com/squirtles/data/applemusic/SearchSongsPagingSource.kt similarity index 91% rename from data/applemusic/src/main/java/com/squirtles/applemusic/SearchSongsPagingSource.kt rename to data/applemusic/src/main/java/com/squirtles/data/applemusic/SearchSongsPagingSource.kt index 98699149..ade133a1 100644 --- a/data/applemusic/src/main/java/com/squirtles/applemusic/SearchSongsPagingSource.kt +++ b/data/applemusic/src/main/java/com/squirtles/data/applemusic/SearchSongsPagingSource.kt @@ -1,10 +1,10 @@ -package com.squirtles.applemusic +package com.squirtles.data.applemusic import androidx.paging.PagingSource import androidx.paging.PagingState -import com.squirtles.applemusic.api.AppleMusicApi -import com.squirtles.applemusic.model.toSong -import com.squirtles.model.Song +import com.squirtles.data.applemusic.api.AppleMusicApi +import com.squirtles.data.applemusic.model.toSong +import com.squirtles.core.model.Song import retrofit2.HttpException import java.io.IOException diff --git a/data/applemusic/src/main/java/com/squirtles/applemusic/api/AppleMusicApi.kt b/data/applemusic/src/main/java/com/squirtles/data/applemusic/api/AppleMusicApi.kt similarity index 81% rename from data/applemusic/src/main/java/com/squirtles/applemusic/api/AppleMusicApi.kt rename to data/applemusic/src/main/java/com/squirtles/data/applemusic/api/AppleMusicApi.kt index c3a1212c..03eb3dff 100644 --- a/data/applemusic/src/main/java/com/squirtles/applemusic/api/AppleMusicApi.kt +++ b/data/applemusic/src/main/java/com/squirtles/data/applemusic/api/AppleMusicApi.kt @@ -1,6 +1,6 @@ -package com.squirtles.applemusic.api +package com.squirtles.data.applemusic.api -import com.squirtles.applemusic.model.SearchResponse +import com.squirtles.data.applemusic.model.SearchResponse import retrofit2.Response import retrofit2.http.GET import retrofit2.http.Path diff --git a/data/applemusic/src/main/java/com/squirtles/applemusic/api/NetworkModule.kt b/data/applemusic/src/main/java/com/squirtles/data/applemusic/api/NetworkModule.kt similarity index 93% rename from data/applemusic/src/main/java/com/squirtles/applemusic/api/NetworkModule.kt rename to data/applemusic/src/main/java/com/squirtles/data/applemusic/api/NetworkModule.kt index 1ece320e..178dfa50 100644 --- a/data/applemusic/src/main/java/com/squirtles/applemusic/api/NetworkModule.kt +++ b/data/applemusic/src/main/java/com/squirtles/data/applemusic/api/NetworkModule.kt @@ -1,6 +1,6 @@ -package com.squirtles.applemusic.api +package com.squirtles.data.applemusic.api -import com.squirtles.localproperties.LocalPropertyProvider +import com.squirtles.core.buildconfig.LocalPropertyProvider import dagger.Module import dagger.Provides import dagger.hilt.InstallIn diff --git a/data/applemusic/src/main/java/com/squirtles/applemusic/di/AppleMusicDiModule.kt b/data/applemusic/src/main/java/com/squirtles/data/applemusic/di/AppleMusicDiModule.kt similarity index 77% rename from data/applemusic/src/main/java/com/squirtles/applemusic/di/AppleMusicDiModule.kt rename to data/applemusic/src/main/java/com/squirtles/data/applemusic/di/AppleMusicDiModule.kt index a6dac3fc..cfb8f0ab 100644 --- a/data/applemusic/src/main/java/com/squirtles/applemusic/di/AppleMusicDiModule.kt +++ b/data/applemusic/src/main/java/com/squirtles/data/applemusic/di/AppleMusicDiModule.kt @@ -1,10 +1,10 @@ -package com.squirtles.applemusic.di +package com.squirtles.data.applemusic.di -import com.squirtles.applemusic.AppleMusicDataSource -import com.squirtles.applemusic.AppleMusicDataSourceImpl -import com.squirtles.applemusic.AppleMusicRepository -import com.squirtles.applemusic.AppleMusicRepositoryImpl -import com.squirtles.applemusic.api.AppleMusicApi +import com.squirtles.data.applemusic.AppleMusicDataSource +import com.squirtles.data.applemusic.AppleMusicDataSourceImpl +import com.squirtles.domain.applemusic.AppleMusicRepository +import com.squirtles.data.applemusic.AppleMusicRepositoryImpl +import com.squirtles.data.applemusic.api.AppleMusicApi import dagger.Module import dagger.Provides import dagger.hilt.InstallIn diff --git a/data/applemusic/src/main/java/com/squirtles/applemusic/model/AppleMusicMapper.kt b/data/applemusic/src/main/java/com/squirtles/data/applemusic/model/AppleMusicMapper.kt similarity index 90% rename from data/applemusic/src/main/java/com/squirtles/applemusic/model/AppleMusicMapper.kt rename to data/applemusic/src/main/java/com/squirtles/data/applemusic/model/AppleMusicMapper.kt index af346217..ba111fe7 100644 --- a/data/applemusic/src/main/java/com/squirtles/applemusic/model/AppleMusicMapper.kt +++ b/data/applemusic/src/main/java/com/squirtles/data/applemusic/model/AppleMusicMapper.kt @@ -1,8 +1,8 @@ -package com.squirtles.applemusic.model +package com.squirtles.data.applemusic.model import androidx.core.graphics.toColorInt -import com.squirtles.model.MusicVideo -import com.squirtles.model.Song +import com.squirtles.core.model.MusicVideo +import com.squirtles.core.model.Song import java.time.LocalDate import java.time.format.DateTimeFormatter diff --git a/data/applemusic/src/main/java/com/squirtles/applemusic/model/Artwork.kt b/data/applemusic/src/main/java/com/squirtles/data/applemusic/model/Artwork.kt similarity index 79% rename from data/applemusic/src/main/java/com/squirtles/applemusic/model/Artwork.kt rename to data/applemusic/src/main/java/com/squirtles/data/applemusic/model/Artwork.kt index 8764ab55..ccdcc5e2 100644 --- a/data/applemusic/src/main/java/com/squirtles/applemusic/model/Artwork.kt +++ b/data/applemusic/src/main/java/com/squirtles/data/applemusic/model/Artwork.kt @@ -1,4 +1,4 @@ -package com.squirtles.applemusic.model +package com.squirtles.data.applemusic.model import kotlinx.serialization.Serializable diff --git a/data/applemusic/src/main/java/com/squirtles/applemusic/model/Attributes.kt b/data/applemusic/src/main/java/com/squirtles/data/applemusic/model/Attributes.kt similarity index 92% rename from data/applemusic/src/main/java/com/squirtles/applemusic/model/Attributes.kt rename to data/applemusic/src/main/java/com/squirtles/data/applemusic/model/Attributes.kt index 7cc5a8c0..ecd71865 100644 --- a/data/applemusic/src/main/java/com/squirtles/applemusic/model/Attributes.kt +++ b/data/applemusic/src/main/java/com/squirtles/data/applemusic/model/Attributes.kt @@ -1,4 +1,4 @@ -package com.squirtles.applemusic.model +package com.squirtles.data.applemusic.model import kotlinx.serialization.SerialName import kotlinx.serialization.Serializable diff --git a/data/applemusic/src/main/java/com/squirtles/applemusic/model/Data.kt b/data/applemusic/src/main/java/com/squirtles/data/applemusic/model/Data.kt similarity index 74% rename from data/applemusic/src/main/java/com/squirtles/applemusic/model/Data.kt rename to data/applemusic/src/main/java/com/squirtles/data/applemusic/model/Data.kt index 49322912..5d8dcf82 100644 --- a/data/applemusic/src/main/java/com/squirtles/applemusic/model/Data.kt +++ b/data/applemusic/src/main/java/com/squirtles/data/applemusic/model/Data.kt @@ -1,4 +1,4 @@ -package com.squirtles.applemusic.model +package com.squirtles.data.applemusic.model import kotlinx.serialization.Serializable diff --git a/data/applemusic/src/main/java/com/squirtles/applemusic/model/MusicVideoResponse.kt b/data/applemusic/src/main/java/com/squirtles/data/applemusic/model/MusicVideoResponse.kt similarity index 72% rename from data/applemusic/src/main/java/com/squirtles/applemusic/model/MusicVideoResponse.kt rename to data/applemusic/src/main/java/com/squirtles/data/applemusic/model/MusicVideoResponse.kt index 4a9fc053..1dc83d54 100644 --- a/data/applemusic/src/main/java/com/squirtles/applemusic/model/MusicVideoResponse.kt +++ b/data/applemusic/src/main/java/com/squirtles/data/applemusic/model/MusicVideoResponse.kt @@ -1,4 +1,4 @@ -package com.squirtles.applemusic.model +package com.squirtles.data.applemusic.model import kotlinx.serialization.Serializable diff --git a/data/applemusic/src/main/java/com/squirtles/applemusic/model/Preview.kt b/data/applemusic/src/main/java/com/squirtles/data/applemusic/model/Preview.kt similarity index 79% rename from data/applemusic/src/main/java/com/squirtles/applemusic/model/Preview.kt rename to data/applemusic/src/main/java/com/squirtles/data/applemusic/model/Preview.kt index 03d2a301..2253cbc9 100644 --- a/data/applemusic/src/main/java/com/squirtles/applemusic/model/Preview.kt +++ b/data/applemusic/src/main/java/com/squirtles/data/applemusic/model/Preview.kt @@ -1,4 +1,4 @@ -package com.squirtles.applemusic.model +package com.squirtles.data.applemusic.model import kotlinx.serialization.Serializable diff --git a/data/applemusic/src/main/java/com/squirtles/applemusic/model/Results.kt b/data/applemusic/src/main/java/com/squirtles/data/applemusic/model/Results.kt similarity index 84% rename from data/applemusic/src/main/java/com/squirtles/applemusic/model/Results.kt rename to data/applemusic/src/main/java/com/squirtles/data/applemusic/model/Results.kt index 85a903eb..5183ae97 100644 --- a/data/applemusic/src/main/java/com/squirtles/applemusic/model/Results.kt +++ b/data/applemusic/src/main/java/com/squirtles/data/applemusic/model/Results.kt @@ -1,4 +1,4 @@ -package com.squirtles.applemusic.model +package com.squirtles.data.applemusic.model import kotlinx.serialization.SerialName import kotlinx.serialization.Serializable diff --git a/data/applemusic/src/main/java/com/squirtles/applemusic/model/SearchResponse.kt b/data/applemusic/src/main/java/com/squirtles/data/applemusic/model/SearchResponse.kt similarity index 71% rename from data/applemusic/src/main/java/com/squirtles/applemusic/model/SearchResponse.kt rename to data/applemusic/src/main/java/com/squirtles/data/applemusic/model/SearchResponse.kt index 59e66b69..e184087e 100644 --- a/data/applemusic/src/main/java/com/squirtles/applemusic/model/SearchResponse.kt +++ b/data/applemusic/src/main/java/com/squirtles/data/applemusic/model/SearchResponse.kt @@ -1,4 +1,4 @@ -package com.squirtles.applemusic.model +package com.squirtles.data.applemusic.model import kotlinx.serialization.Serializable diff --git a/data/applemusic/src/main/java/com/squirtles/applemusic/model/Songs.kt b/data/applemusic/src/main/java/com/squirtles/data/applemusic/model/Songs.kt similarity index 75% rename from data/applemusic/src/main/java/com/squirtles/applemusic/model/Songs.kt rename to data/applemusic/src/main/java/com/squirtles/data/applemusic/model/Songs.kt index aa82869e..f4fba366 100644 --- a/data/applemusic/src/main/java/com/squirtles/applemusic/model/Songs.kt +++ b/data/applemusic/src/main/java/com/squirtles/data/applemusic/model/Songs.kt @@ -1,4 +1,4 @@ -package com.squirtles.applemusic.model +package com.squirtles.data.applemusic.model import kotlinx.serialization.Serializable diff --git a/data/favorite/src/main/java/com/squirtles/favorite/CloudFunctionHelper.kt b/data/favorite/src/main/java/com/squirtles/data/favorite/CloudFunctionHelper.kt similarity index 92% rename from data/favorite/src/main/java/com/squirtles/favorite/CloudFunctionHelper.kt rename to data/favorite/src/main/java/com/squirtles/data/favorite/CloudFunctionHelper.kt index 15e3431d..78e4cba5 100644 --- a/data/favorite/src/main/java/com/squirtles/favorite/CloudFunctionHelper.kt +++ b/data/favorite/src/main/java/com/squirtles/data/favorite/CloudFunctionHelper.kt @@ -1,11 +1,11 @@ -package com.squirtles.favorite +package com.squirtles.data.favorite import android.util.Log import com.google.firebase.functions.FirebaseFunctions import com.google.firebase.functions.ktx.functions import com.google.firebase.ktx.Firebase import com.squirtles.domain.firebase.FirebaseException -import com.squirtles.localproperties.LocalPropertyProvider +import com.squirtles.core.buildconfig.LocalPropertyProvider import kotlinx.coroutines.tasks.await import javax.inject.Singleton diff --git a/data/favorite/src/main/java/com/squirtles/favorite/FirebaseFavoriteDataSource.kt b/data/favorite/src/main/java/com/squirtles/data/favorite/FirebaseFavoriteDataSource.kt similarity index 88% rename from data/favorite/src/main/java/com/squirtles/favorite/FirebaseFavoriteDataSource.kt rename to data/favorite/src/main/java/com/squirtles/data/favorite/FirebaseFavoriteDataSource.kt index 82a19dbc..5af7dd81 100644 --- a/data/favorite/src/main/java/com/squirtles/favorite/FirebaseFavoriteDataSource.kt +++ b/data/favorite/src/main/java/com/squirtles/data/favorite/FirebaseFavoriteDataSource.kt @@ -1,4 +1,4 @@ -package com.squirtles.favorite +package com.squirtles.data.favorite interface FirebaseFavoriteDataSource { suspend fun fetchIsFavorite(pickId: String, userId: String): Result diff --git a/data/favorite/src/main/java/com/squirtles/favorite/FirebaseFavoriteDataSourceImpl.kt b/data/favorite/src/main/java/com/squirtles/data/favorite/FirebaseFavoriteDataSourceImpl.kt similarity index 90% rename from data/favorite/src/main/java/com/squirtles/favorite/FirebaseFavoriteDataSourceImpl.kt rename to data/favorite/src/main/java/com/squirtles/data/favorite/FirebaseFavoriteDataSourceImpl.kt index e1675995..7a629742 100644 --- a/data/favorite/src/main/java/com/squirtles/favorite/FirebaseFavoriteDataSourceImpl.kt +++ b/data/favorite/src/main/java/com/squirtles/data/favorite/FirebaseFavoriteDataSourceImpl.kt @@ -1,12 +1,12 @@ -package com.squirtles.favorite +package com.squirtles.data.favorite import android.util.Log import com.google.firebase.firestore.FirebaseFirestore import com.google.firebase.firestore.QuerySnapshot -import com.squirtles.firebase.BaseFirebaseDataSource -import com.squirtles.firebase.FirebaseCollections -import com.squirtles.firebase.FirebaseDocumentFields -import com.squirtles.firebase.model.FirebaseFavorite +import com.squirtles.data.firebase.BaseFirebaseDataSource +import com.squirtles.data.firebase.FirebaseCollections +import com.squirtles.data.firebase.FirebaseDocumentFields +import com.squirtles.data.firebase.model.FirebaseFavorite import javax.inject.Inject import javax.inject.Singleton diff --git a/data/favorite/src/main/java/com/squirtles/favorite/FirebaseFavoriteRepositoryImpl.kt b/data/favorite/src/main/java/com/squirtles/data/favorite/FirebaseFavoriteRepositoryImpl.kt similarity index 87% rename from data/favorite/src/main/java/com/squirtles/favorite/FirebaseFavoriteRepositoryImpl.kt rename to data/favorite/src/main/java/com/squirtles/data/favorite/FirebaseFavoriteRepositoryImpl.kt index 9e9e523e..8632ec5a 100644 --- a/data/favorite/src/main/java/com/squirtles/favorite/FirebaseFavoriteRepositoryImpl.kt +++ b/data/favorite/src/main/java/com/squirtles/data/favorite/FirebaseFavoriteRepositoryImpl.kt @@ -1,5 +1,6 @@ -package com.squirtles.favorite +package com.squirtles.data.favorite +import com.squirtles.domain.favorite.FirebaseFavoriteRepository import javax.inject.Inject import javax.inject.Singleton diff --git a/data/favorite/src/main/java/com/squirtles/favorite/di/FavoriteDiModule.kt b/data/favorite/src/main/java/com/squirtles/data/favorite/di/FavoriteDiModule.kt similarity index 71% rename from data/favorite/src/main/java/com/squirtles/favorite/di/FavoriteDiModule.kt rename to data/favorite/src/main/java/com/squirtles/data/favorite/di/FavoriteDiModule.kt index 08841af4..733544cb 100644 --- a/data/favorite/src/main/java/com/squirtles/favorite/di/FavoriteDiModule.kt +++ b/data/favorite/src/main/java/com/squirtles/data/favorite/di/FavoriteDiModule.kt @@ -1,10 +1,10 @@ -package com.squirtles.favorite.di +package com.squirtles.data.favorite.di -import com.squirtles.favorite.CloudFunctionHelper -import com.squirtles.favorite.FirebaseFavoriteDataSource -import com.squirtles.favorite.FirebaseFavoriteDataSourceImpl -import com.squirtles.favorite.FirebaseFavoriteRepository -import com.squirtles.favorite.FirebaseFavoriteRepositoryImpl +import com.squirtles.data.favorite.CloudFunctionHelper +import com.squirtles.data.favorite.FirebaseFavoriteDataSource +import com.squirtles.data.favorite.FirebaseFavoriteDataSourceImpl +import com.squirtles.domain.favorite.FirebaseFavoriteRepository +import com.squirtles.data.favorite.FirebaseFavoriteRepositoryImpl import com.google.firebase.firestore.FirebaseFirestore import dagger.Module import dagger.Provides diff --git a/data/firebase/src/main/java/com/squirtles/firebase/BaseFirebaseDataSource.kt b/data/firebase/src/main/java/com/squirtles/data/firebase/BaseFirebaseDataSource.kt similarity index 99% rename from data/firebase/src/main/java/com/squirtles/firebase/BaseFirebaseDataSource.kt rename to data/firebase/src/main/java/com/squirtles/data/firebase/BaseFirebaseDataSource.kt index cd03947e..c8b7ea12 100644 --- a/data/firebase/src/main/java/com/squirtles/firebase/BaseFirebaseDataSource.kt +++ b/data/firebase/src/main/java/com/squirtles/data/firebase/BaseFirebaseDataSource.kt @@ -1,4 +1,4 @@ -package com.squirtles.firebase +package com.squirtles.data.firebase import android.util.Log import com.google.firebase.firestore.CollectionReference diff --git a/data/firebase/src/main/java/com/squirtles/firebase/FirebaseDataSourceConstants.kt b/data/firebase/src/main/java/com/squirtles/data/firebase/FirebaseDataSourceConstants.kt similarity index 95% rename from data/firebase/src/main/java/com/squirtles/firebase/FirebaseDataSourceConstants.kt rename to data/firebase/src/main/java/com/squirtles/data/firebase/FirebaseDataSourceConstants.kt index 51036de1..f827cd61 100644 --- a/data/firebase/src/main/java/com/squirtles/firebase/FirebaseDataSourceConstants.kt +++ b/data/firebase/src/main/java/com/squirtles/data/firebase/FirebaseDataSourceConstants.kt @@ -1,4 +1,4 @@ -package com.squirtles.firebase +package com.squirtles.data.firebase sealed class FirebaseCollections(val name: String) { data object Favorites: FirebaseCollections("favorites") diff --git a/data/firebase/src/main/java/com/squirtles/firebase/FirebaseModule.kt b/data/firebase/src/main/java/com/squirtles/data/firebase/FirebaseModule.kt similarity index 83% rename from data/firebase/src/main/java/com/squirtles/firebase/FirebaseModule.kt rename to data/firebase/src/main/java/com/squirtles/data/firebase/FirebaseModule.kt index 705ac17d..7a466c5e 100644 --- a/data/firebase/src/main/java/com/squirtles/firebase/FirebaseModule.kt +++ b/data/firebase/src/main/java/com/squirtles/data/firebase/FirebaseModule.kt @@ -1,7 +1,7 @@ -package com.squirtles.firebase +package com.squirtles.data.firebase import com.google.firebase.firestore.FirebaseFirestore -import com.squirtles.localproperties.LocalPropertyProvider +import com.squirtles.core.buildconfig.LocalPropertyProvider import dagger.Module import dagger.Provides import dagger.hilt.InstallIn diff --git a/data/firebase/src/main/java/com/squirtles/firebase/FirebaseRepositoryUtils.kt b/data/firebase/src/main/java/com/squirtles/data/firebase/FirebaseRepositoryUtils.kt similarity index 91% rename from data/firebase/src/main/java/com/squirtles/firebase/FirebaseRepositoryUtils.kt rename to data/firebase/src/main/java/com/squirtles/data/firebase/FirebaseRepositoryUtils.kt index 1b562502..3646c8db 100644 --- a/data/firebase/src/main/java/com/squirtles/firebase/FirebaseRepositoryUtils.kt +++ b/data/firebase/src/main/java/com/squirtles/data/firebase/FirebaseRepositoryUtils.kt @@ -1,4 +1,4 @@ -package com.squirtles.firebase +package com.squirtles.data.firebase import com.squirtles.domain.firebase.FirebaseException diff --git a/data/firebase/src/main/java/com/squirtles/firebase/model/FirebaseFavorite.kt b/data/firebase/src/main/java/com/squirtles/data/firebase/model/FirebaseFavorite.kt similarity index 84% rename from data/firebase/src/main/java/com/squirtles/firebase/model/FirebaseFavorite.kt rename to data/firebase/src/main/java/com/squirtles/data/firebase/model/FirebaseFavorite.kt index d039c341..0eff57af 100644 --- a/data/firebase/src/main/java/com/squirtles/firebase/model/FirebaseFavorite.kt +++ b/data/firebase/src/main/java/com/squirtles/data/firebase/model/FirebaseFavorite.kt @@ -1,4 +1,4 @@ -package com.squirtles.firebase.model +package com.squirtles.data.firebase.model import com.google.firebase.Timestamp import com.google.firebase.firestore.ServerTimestamp diff --git a/data/firebase/src/main/java/com/squirtles/firebase/model/FirebasePick.kt b/data/firebase/src/main/java/com/squirtles/data/firebase/model/FirebasePick.kt similarity index 95% rename from data/firebase/src/main/java/com/squirtles/firebase/model/FirebasePick.kt rename to data/firebase/src/main/java/com/squirtles/data/firebase/model/FirebasePick.kt index c2de0b59..d3dbfd21 100644 --- a/data/firebase/src/main/java/com/squirtles/firebase/model/FirebasePick.kt +++ b/data/firebase/src/main/java/com/squirtles/data/firebase/model/FirebasePick.kt @@ -1,4 +1,4 @@ -package com.squirtles.firebase.model +package com.squirtles.data.firebase.model import com.google.firebase.Timestamp import com.google.firebase.firestore.GeoPoint diff --git a/data/firebase/src/main/java/com/squirtles/firebase/model/FirebaseUser.kt b/data/firebase/src/main/java/com/squirtles/data/firebase/model/FirebaseUser.kt similarity index 80% rename from data/firebase/src/main/java/com/squirtles/firebase/model/FirebaseUser.kt rename to data/firebase/src/main/java/com/squirtles/data/firebase/model/FirebaseUser.kt index b131ad46..6ab6fe4e 100644 --- a/data/firebase/src/main/java/com/squirtles/firebase/model/FirebaseUser.kt +++ b/data/firebase/src/main/java/com/squirtles/data/firebase/model/FirebaseUser.kt @@ -1,4 +1,4 @@ -package com.squirtles.firebase.model +package com.squirtles.data.firebase.model data class FirebaseUser( val email: String? = null, diff --git a/data/firebase/src/main/java/com/squirtles/firebase/model/Mapper.kt b/data/firebase/src/main/java/com/squirtles/data/firebase/model/Mapper.kt similarity index 91% rename from data/firebase/src/main/java/com/squirtles/firebase/model/Mapper.kt rename to data/firebase/src/main/java/com/squirtles/data/firebase/model/Mapper.kt index a7dd4cc2..331cd6c6 100644 --- a/data/firebase/src/main/java/com/squirtles/firebase/model/Mapper.kt +++ b/data/firebase/src/main/java/com/squirtles/data/firebase/model/Mapper.kt @@ -1,11 +1,11 @@ -package com.squirtles.firebase.model +package com.squirtles.data.firebase.model import androidx.core.graphics.toColorInt -import com.squirtles.model.Creator -import com.squirtles.model.LocationPoint -import com.squirtles.model.Pick -import com.squirtles.model.Song -import com.squirtles.model.User +import com.squirtles.core.model.Creator +import com.squirtles.core.model.LocationPoint +import com.squirtles.core.model.Pick +import com.squirtles.core.model.Song +import com.squirtles.core.model.User import com.firebase.geofire.GeoFireUtils import com.firebase.geofire.GeoLocation import com.google.firebase.firestore.GeoPoint diff --git a/data/location/src/main/java/com/squirtles/location/LocalLocationRepositoryImpl.kt b/data/location/src/main/java/com/squirtles/data/location/LocalLocationRepositoryImpl.kt similarity index 85% rename from data/location/src/main/java/com/squirtles/location/LocalLocationRepositoryImpl.kt rename to data/location/src/main/java/com/squirtles/data/location/LocalLocationRepositoryImpl.kt index 54cdad61..e48fc04d 100644 --- a/data/location/src/main/java/com/squirtles/location/LocalLocationRepositoryImpl.kt +++ b/data/location/src/main/java/com/squirtles/data/location/LocalLocationRepositoryImpl.kt @@ -1,6 +1,7 @@ -package com.squirtles.location +package com.squirtles.data.location import android.location.Location +import com.squirtles.domain.location.LocalLocationRepository import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.asStateFlow diff --git a/data/location/src/main/java/com/squirtles/location/di/LocationDiModule.kt b/data/location/src/main/java/com/squirtles/data/location/di/LocationDiModule.kt similarity index 69% rename from data/location/src/main/java/com/squirtles/location/di/LocationDiModule.kt rename to data/location/src/main/java/com/squirtles/data/location/di/LocationDiModule.kt index 06931790..5ec893b3 100644 --- a/data/location/src/main/java/com/squirtles/location/di/LocationDiModule.kt +++ b/data/location/src/main/java/com/squirtles/data/location/di/LocationDiModule.kt @@ -1,7 +1,7 @@ -package com.squirtles.location.di +package com.squirtles.data.location.di -import com.squirtles.location.LocalLocationRepository -import com.squirtles.location.LocalLocationRepositoryImpl +import com.squirtles.domain.location.LocalLocationRepository +import com.squirtles.data.location.LocalLocationRepositoryImpl import dagger.Module import dagger.Provides import dagger.hilt.InstallIn diff --git a/data/order/src/main/java/com/squirtles/order/LocalPickListOrderRepositoryImpl.kt b/data/order/src/main/java/com/squirtles/data/order/LocalPickListOrderRepositoryImpl.kt similarity index 79% rename from data/order/src/main/java/com/squirtles/order/LocalPickListOrderRepositoryImpl.kt rename to data/order/src/main/java/com/squirtles/data/order/LocalPickListOrderRepositoryImpl.kt index 27543d48..a355993a 100644 --- a/data/order/src/main/java/com/squirtles/order/LocalPickListOrderRepositoryImpl.kt +++ b/data/order/src/main/java/com/squirtles/data/order/LocalPickListOrderRepositoryImpl.kt @@ -1,6 +1,7 @@ -package com.squirtles.order +package com.squirtles.data.order -import com.squirtles.model.Order +import com.squirtles.core.model.Order +import com.squirtles.domain.order.LocalPickListOrderRepository import javax.inject.Singleton @Singleton diff --git a/data/order/src/main/java/com/squirtles/order/di/OrderDiModule.kt b/data/order/src/main/java/com/squirtles/data/order/di/OrderDiModule.kt similarity index 69% rename from data/order/src/main/java/com/squirtles/order/di/OrderDiModule.kt rename to data/order/src/main/java/com/squirtles/data/order/di/OrderDiModule.kt index 6d2fb0dd..564d1320 100644 --- a/data/order/src/main/java/com/squirtles/order/di/OrderDiModule.kt +++ b/data/order/src/main/java/com/squirtles/data/order/di/OrderDiModule.kt @@ -1,7 +1,7 @@ -package com.squirtles.order.di +package com.squirtles.data.order.di -import com.squirtles.order.LocalPickListOrderRepository -import com.squirtles.order.LocalPickListOrderRepositoryImpl +import com.squirtles.domain.order.LocalPickListOrderRepository +import com.squirtles.data.order.LocalPickListOrderRepositoryImpl import dagger.Module import dagger.Provides import dagger.hilt.InstallIn diff --git a/data/pick/src/main/java/com/squirtles/pick/FirebasePickDataSource.kt b/data/pick/src/main/java/com/squirtles/data/pick/FirebasePickDataSource.kt similarity index 82% rename from data/pick/src/main/java/com/squirtles/pick/FirebasePickDataSource.kt rename to data/pick/src/main/java/com/squirtles/data/pick/FirebasePickDataSource.kt index 5d94b34e..68f15477 100644 --- a/data/pick/src/main/java/com/squirtles/pick/FirebasePickDataSource.kt +++ b/data/pick/src/main/java/com/squirtles/data/pick/FirebasePickDataSource.kt @@ -1,7 +1,6 @@ -package com.squirtles.pick +package com.squirtles.data.pick -import com.squirtles.firebase.model.FirebasePick -import com.squirtles.model.Pick +import com.squirtles.data.firebase.model.FirebasePick interface FirebasePickDataSource { suspend fun fetchPick(pickId: String): Result diff --git a/data/pick/src/main/java/com/squirtles/pick/FirebasePickDataSourceImpl.kt b/data/pick/src/main/java/com/squirtles/data/pick/FirebasePickDataSourceImpl.kt similarity index 94% rename from data/pick/src/main/java/com/squirtles/pick/FirebasePickDataSourceImpl.kt rename to data/pick/src/main/java/com/squirtles/data/pick/FirebasePickDataSourceImpl.kt index 07af1b92..fd16f386 100644 --- a/data/pick/src/main/java/com/squirtles/pick/FirebasePickDataSourceImpl.kt +++ b/data/pick/src/main/java/com/squirtles/data/pick/FirebasePickDataSourceImpl.kt @@ -1,4 +1,4 @@ -package com.squirtles.pick +package com.squirtles.data.pick import android.util.Log import com.firebase.geofire.GeoFireUtils @@ -10,12 +10,12 @@ import com.google.firebase.firestore.FirebaseFirestore import com.google.firebase.firestore.Query import com.google.firebase.firestore.QuerySnapshot import com.google.firebase.firestore.toObject -import com.squirtles.firebase.BaseFirebaseDataSource -import com.squirtles.firebase.FirebaseCollections -import com.squirtles.firebase.FirebaseDocumentFields -import com.squirtles.firebase.model.FirebaseFavorite -import com.squirtles.firebase.model.FirebasePick -import com.squirtles.firebase.model.FirebaseUser +import com.squirtles.data.firebase.BaseFirebaseDataSource +import com.squirtles.data.firebase.FirebaseCollections +import com.squirtles.data.firebase.FirebaseDocumentFields +import com.squirtles.data.firebase.model.FirebaseFavorite +import com.squirtles.data.firebase.model.FirebasePick +import com.squirtles.data.firebase.model.FirebaseUser import kotlinx.coroutines.suspendCancellableCoroutine import kotlinx.coroutines.tasks.await import javax.inject.Inject diff --git a/data/pick/src/main/java/com/squirtles/pick/FirebasePickRepositoryImpl.kt b/data/pick/src/main/java/com/squirtles/data/pick/FirebasePickRepositoryImpl.kt similarity index 91% rename from data/pick/src/main/java/com/squirtles/pick/FirebasePickRepositoryImpl.kt rename to data/pick/src/main/java/com/squirtles/data/pick/FirebasePickRepositoryImpl.kt index 885b7f47..e5c42d1a 100644 --- a/data/pick/src/main/java/com/squirtles/pick/FirebasePickRepositoryImpl.kt +++ b/data/pick/src/main/java/com/squirtles/data/pick/FirebasePickRepositoryImpl.kt @@ -1,9 +1,9 @@ -package com.squirtles.pick +package com.squirtles.data.pick import com.squirtles.domain.pick.FirebasePickRepository -import com.squirtles.firebase.model.toFirebasePick -import com.squirtles.firebase.model.toPick -import com.squirtles.model.Pick +import com.squirtles.data.firebase.model.toFirebasePick +import com.squirtles.data.firebase.model.toPick +import com.squirtles.core.model.Pick import javax.inject.Inject import javax.inject.Singleton diff --git a/data/pick/src/main/java/com/squirtles/pick/di/PickDiModule.kt b/data/pick/src/main/java/com/squirtles/data/pick/di/PickDiModule.kt similarity index 77% rename from data/pick/src/main/java/com/squirtles/pick/di/PickDiModule.kt rename to data/pick/src/main/java/com/squirtles/data/pick/di/PickDiModule.kt index 7a1a9d69..8bc04f8d 100644 --- a/data/pick/src/main/java/com/squirtles/pick/di/PickDiModule.kt +++ b/data/pick/src/main/java/com/squirtles/data/pick/di/PickDiModule.kt @@ -1,9 +1,9 @@ -package com.squirtles.pick.di +package com.squirtles.data.pick.di -import com.squirtles.pick.FirebasePickDataSource -import com.squirtles.pick.FirebasePickDataSourceImpl +import com.squirtles.data.pick.FirebasePickDataSource +import com.squirtles.data.pick.FirebasePickDataSourceImpl import com.squirtles.domain.pick.FirebasePickRepository -import com.squirtles.pick.FirebasePickRepositoryImpl +import com.squirtles.data.pick.FirebasePickRepositoryImpl import com.google.firebase.firestore.FirebaseFirestore import dagger.Module import dagger.Provides diff --git a/data/user/src/main/java/com/squirtles/user/FirebaseUserDataSource.kt b/data/user/src/main/java/com/squirtles/data/user/FirebaseUserDataSource.kt similarity index 75% rename from data/user/src/main/java/com/squirtles/user/FirebaseUserDataSource.kt rename to data/user/src/main/java/com/squirtles/data/user/FirebaseUserDataSource.kt index 43ed496a..38a43967 100644 --- a/data/user/src/main/java/com/squirtles/user/FirebaseUserDataSource.kt +++ b/data/user/src/main/java/com/squirtles/data/user/FirebaseUserDataSource.kt @@ -1,7 +1,6 @@ -package com.squirtles.user +package com.squirtles.data.user -import com.squirtles.firebase.model.FirebaseUser -import com.squirtles.model.User +import com.squirtles.data.firebase.model.FirebaseUser interface FirebaseUserDataSource { suspend fun fetchUser(uid: String): Result diff --git a/data/user/src/main/java/com/squirtles/user/FirebaseUserDataSourceImpl.kt b/data/user/src/main/java/com/squirtles/data/user/FirebaseUserDataSourceImpl.kt similarity index 90% rename from data/user/src/main/java/com/squirtles/user/FirebaseUserDataSourceImpl.kt rename to data/user/src/main/java/com/squirtles/data/user/FirebaseUserDataSourceImpl.kt index 67ce2165..3857798c 100644 --- a/data/user/src/main/java/com/squirtles/user/FirebaseUserDataSourceImpl.kt +++ b/data/user/src/main/java/com/squirtles/data/user/FirebaseUserDataSourceImpl.kt @@ -1,13 +1,13 @@ -package com.squirtles.user +package com.squirtles.data.user import android.util.Log import com.google.firebase.auth.FirebaseAuth import com.google.firebase.firestore.FirebaseFirestore import com.google.firebase.firestore.toObject -import com.squirtles.firebase.BaseFirebaseDataSource -import com.squirtles.firebase.FirebaseCollections -import com.squirtles.firebase.FirebaseDocumentFields -import com.squirtles.firebase.model.FirebaseUser +import com.squirtles.data.firebase.BaseFirebaseDataSource +import com.squirtles.data.firebase.FirebaseCollections +import com.squirtles.data.firebase.FirebaseDocumentFields +import com.squirtles.data.firebase.model.FirebaseUser import kotlinx.coroutines.tasks.await import javax.inject.Inject import javax.inject.Singleton diff --git a/data/user/src/main/java/com/squirtles/user/FirebaseUserRepositoryImpl.kt b/data/user/src/main/java/com/squirtles/data/user/FirebaseUserRepositoryImpl.kt similarity index 86% rename from data/user/src/main/java/com/squirtles/user/FirebaseUserRepositoryImpl.kt rename to data/user/src/main/java/com/squirtles/data/user/FirebaseUserRepositoryImpl.kt index 0ecf4cc7..99ed2bf9 100644 --- a/data/user/src/main/java/com/squirtles/user/FirebaseUserRepositoryImpl.kt +++ b/data/user/src/main/java/com/squirtles/data/user/FirebaseUserRepositoryImpl.kt @@ -1,9 +1,10 @@ -package com.squirtles.user +package com.squirtles.data.user import com.google.firebase.auth.FirebaseAuth -import com.squirtles.firebase.model.FirebaseUser -import com.squirtles.firebase.model.toUser -import com.squirtles.model.User +import com.squirtles.data.firebase.model.FirebaseUser +import com.squirtles.data.firebase.model.toUser +import com.squirtles.core.model.User +import com.squirtles.domain.user.FirebaseUserRepository import javax.inject.Inject import javax.inject.Singleton diff --git a/data/user/src/main/java/com/squirtles/user/LocalUserDataSource.kt b/data/user/src/main/java/com/squirtles/data/user/LocalUserDataSource.kt similarity index 79% rename from data/user/src/main/java/com/squirtles/user/LocalUserDataSource.kt rename to data/user/src/main/java/com/squirtles/data/user/LocalUserDataSource.kt index 322cce1a..771a9bfe 100644 --- a/data/user/src/main/java/com/squirtles/user/LocalUserDataSource.kt +++ b/data/user/src/main/java/com/squirtles/data/user/LocalUserDataSource.kt @@ -1,6 +1,6 @@ -package com.squirtles.user +package com.squirtles.data.user -import com.squirtles.model.User +import com.squirtles.core.model.User import kotlinx.coroutines.flow.Flow interface LocalUserDataSource { diff --git a/data/user/src/main/java/com/squirtles/user/LocalUserDataSourceImpl.kt b/data/user/src/main/java/com/squirtles/data/user/LocalUserDataSourceImpl.kt similarity index 95% rename from data/user/src/main/java/com/squirtles/user/LocalUserDataSourceImpl.kt rename to data/user/src/main/java/com/squirtles/data/user/LocalUserDataSourceImpl.kt index a04d9e43..bd9cfe57 100644 --- a/data/user/src/main/java/com/squirtles/user/LocalUserDataSourceImpl.kt +++ b/data/user/src/main/java/com/squirtles/data/user/LocalUserDataSourceImpl.kt @@ -1,10 +1,10 @@ -package com.squirtles.user +package com.squirtles.data.user import android.content.Context import androidx.datastore.preferences.core.edit import androidx.datastore.preferences.core.stringPreferencesKey import androidx.datastore.preferences.preferencesDataStore -import com.squirtles.model.User +import com.squirtles.core.model.User import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.map import javax.inject.Inject diff --git a/data/user/src/main/java/com/squirtles/user/LocalUserRepositoryImpl.kt b/data/user/src/main/java/com/squirtles/data/user/LocalUserRepositoryImpl.kt similarity index 87% rename from data/user/src/main/java/com/squirtles/user/LocalUserRepositoryImpl.kt rename to data/user/src/main/java/com/squirtles/data/user/LocalUserRepositoryImpl.kt index 62aa586a..8432097d 100644 --- a/data/user/src/main/java/com/squirtles/user/LocalUserRepositoryImpl.kt +++ b/data/user/src/main/java/com/squirtles/data/user/LocalUserRepositoryImpl.kt @@ -1,6 +1,7 @@ -package com.squirtles.user +package com.squirtles.data.user -import com.squirtles.model.User +import com.squirtles.core.model.User +import com.squirtles.domain.user.LocalUserRepository import kotlinx.coroutines.flow.Flow import javax.inject.Inject import javax.inject.Singleton diff --git a/data/user/src/main/java/com/squirtles/user/di/UserDiModule.kt b/data/user/src/main/java/com/squirtles/data/user/di/UserDiModule.kt similarity index 69% rename from data/user/src/main/java/com/squirtles/user/di/UserDiModule.kt rename to data/user/src/main/java/com/squirtles/data/user/di/UserDiModule.kt index 30237ead..f6a8b4e8 100644 --- a/data/user/src/main/java/com/squirtles/user/di/UserDiModule.kt +++ b/data/user/src/main/java/com/squirtles/data/user/di/UserDiModule.kt @@ -1,15 +1,15 @@ -package com.squirtles.user.di +package com.squirtles.data.user.di import android.content.Context import com.google.firebase.firestore.FirebaseFirestore -import com.squirtles.user.FirebaseUserDataSource -import com.squirtles.user.FirebaseUserDataSourceImpl -import com.squirtles.user.FirebaseUserRepository -import com.squirtles.user.FirebaseUserRepositoryImpl -import com.squirtles.user.LocalUserDataSource -import com.squirtles.user.LocalUserDataSourceImpl -import com.squirtles.user.LocalUserRepository -import com.squirtles.user.LocalUserRepositoryImpl +import com.squirtles.data.user.FirebaseUserDataSource +import com.squirtles.data.user.FirebaseUserDataSourceImpl +import com.squirtles.domain.user.FirebaseUserRepository +import com.squirtles.data.user.FirebaseUserRepositoryImpl +import com.squirtles.data.user.LocalUserDataSource +import com.squirtles.data.user.LocalUserDataSourceImpl +import com.squirtles.domain.user.LocalUserRepository +import com.squirtles.data.user.LocalUserRepositoryImpl import dagger.Module import dagger.Provides import dagger.hilt.InstallIn diff --git a/domain/applemusic/src/main/java/com/squirtles/applemusic/AppleMusicException.kt b/domain/applemusic/src/main/java/com/squirtles/domain/applemusic/AppleMusicException.kt similarity index 91% rename from domain/applemusic/src/main/java/com/squirtles/applemusic/AppleMusicException.kt rename to domain/applemusic/src/main/java/com/squirtles/domain/applemusic/AppleMusicException.kt index 81c066a1..779ea832 100644 --- a/domain/applemusic/src/main/java/com/squirtles/applemusic/AppleMusicException.kt +++ b/domain/applemusic/src/main/java/com/squirtles/domain/applemusic/AppleMusicException.kt @@ -1,4 +1,4 @@ -package com.squirtles.applemusic +package com.squirtles.domain.applemusic /** * 400 에러가 여러 종류가 있는데 이를 구분할 용도로 만든 예외 클래스 diff --git a/domain/applemusic/src/main/java/com/squirtles/applemusic/AppleMusicRepository.kt b/domain/applemusic/src/main/java/com/squirtles/domain/applemusic/AppleMusicRepository.kt similarity index 67% rename from domain/applemusic/src/main/java/com/squirtles/applemusic/AppleMusicRepository.kt rename to domain/applemusic/src/main/java/com/squirtles/domain/applemusic/AppleMusicRepository.kt index 64b2b031..485cc364 100644 --- a/domain/applemusic/src/main/java/com/squirtles/applemusic/AppleMusicRepository.kt +++ b/domain/applemusic/src/main/java/com/squirtles/domain/applemusic/AppleMusicRepository.kt @@ -1,8 +1,8 @@ -package com.squirtles.applemusic +package com.squirtles.domain.applemusic import androidx.paging.PagingData -import com.squirtles.model.MusicVideo -import com.squirtles.model.Song +import com.squirtles.core.model.MusicVideo +import com.squirtles.core.model.Song import kotlinx.coroutines.flow.Flow interface AppleMusicRepository { diff --git a/domain/applemusic/src/main/java/com/squirtles/applemusic/usecase/FetchMusicVideoUseCase.kt b/domain/applemusic/src/main/java/com/squirtles/domain/applemusic/usecase/FetchMusicVideoUseCase.kt similarity index 73% rename from domain/applemusic/src/main/java/com/squirtles/applemusic/usecase/FetchMusicVideoUseCase.kt rename to domain/applemusic/src/main/java/com/squirtles/domain/applemusic/usecase/FetchMusicVideoUseCase.kt index 3b14deaf..e30954b4 100644 --- a/domain/applemusic/src/main/java/com/squirtles/applemusic/usecase/FetchMusicVideoUseCase.kt +++ b/domain/applemusic/src/main/java/com/squirtles/domain/applemusic/usecase/FetchMusicVideoUseCase.kt @@ -1,8 +1,8 @@ -package com.squirtles.applemusic.usecase +package com.squirtles.domain.applemusic.usecase -import com.squirtles.applemusic.AppleMusicRepository -import com.squirtles.model.MusicVideo -import com.squirtles.model.Song +import com.squirtles.domain.applemusic.AppleMusicRepository +import com.squirtles.core.model.MusicVideo +import com.squirtles.core.model.Song import javax.inject.Inject diff --git a/domain/applemusic/src/main/java/com/squirtles/applemusic/usecase/FetchSongsUseCase.kt b/domain/applemusic/src/main/java/com/squirtles/domain/applemusic/usecase/FetchSongsUseCase.kt similarity index 68% rename from domain/applemusic/src/main/java/com/squirtles/applemusic/usecase/FetchSongsUseCase.kt rename to domain/applemusic/src/main/java/com/squirtles/domain/applemusic/usecase/FetchSongsUseCase.kt index cbf29445..3f7e87fa 100644 --- a/domain/applemusic/src/main/java/com/squirtles/applemusic/usecase/FetchSongsUseCase.kt +++ b/domain/applemusic/src/main/java/com/squirtles/domain/applemusic/usecase/FetchSongsUseCase.kt @@ -1,6 +1,6 @@ -package com.squirtles.applemusic.usecase +package com.squirtles.domain.applemusic.usecase -import com.squirtles.applemusic.AppleMusicRepository +import com.squirtles.domain.applemusic.AppleMusicRepository import javax.inject.Inject class FetchSongsUseCase @Inject constructor( diff --git a/domain/favorite/src/main/java/com/squirtles/favorite/FirebaseFavoriteRepository.kt b/domain/favorite/src/main/java/com/squirtles/domain/favorite/FirebaseFavoriteRepository.kt similarity index 87% rename from domain/favorite/src/main/java/com/squirtles/favorite/FirebaseFavoriteRepository.kt rename to domain/favorite/src/main/java/com/squirtles/domain/favorite/FirebaseFavoriteRepository.kt index dbea95c8..b83b4b8a 100644 --- a/domain/favorite/src/main/java/com/squirtles/favorite/FirebaseFavoriteRepository.kt +++ b/domain/favorite/src/main/java/com/squirtles/domain/favorite/FirebaseFavoriteRepository.kt @@ -1,4 +1,4 @@ -package com.squirtles.favorite +package com.squirtles.domain.favorite interface FirebaseFavoriteRepository { suspend fun fetchIsFavorite(pickId: String, uid: String): Result diff --git a/domain/favorite/src/main/java/com/squirtles/favorite/usecase/CreateFavoriteUseCase.kt b/domain/favorite/src/main/java/com/squirtles/domain/favorite/usecase/CreateFavoriteUseCase.kt similarity index 70% rename from domain/favorite/src/main/java/com/squirtles/favorite/usecase/CreateFavoriteUseCase.kt rename to domain/favorite/src/main/java/com/squirtles/domain/favorite/usecase/CreateFavoriteUseCase.kt index 5556c97b..b31deb39 100644 --- a/domain/favorite/src/main/java/com/squirtles/favorite/usecase/CreateFavoriteUseCase.kt +++ b/domain/favorite/src/main/java/com/squirtles/domain/favorite/usecase/CreateFavoriteUseCase.kt @@ -1,6 +1,6 @@ -package com.squirtles.favorite.usecase +package com.squirtles.domain.favorite.usecase -import com.squirtles.favorite.FirebaseFavoriteRepository +import com.squirtles.domain.favorite.FirebaseFavoriteRepository import javax.inject.Inject class CreateFavoriteUseCase @Inject constructor( diff --git a/domain/favorite/src/main/java/com/squirtles/favorite/usecase/DeleteFavoriteUseCase.kt b/domain/favorite/src/main/java/com/squirtles/domain/favorite/usecase/DeleteFavoriteUseCase.kt similarity index 77% rename from domain/favorite/src/main/java/com/squirtles/favorite/usecase/DeleteFavoriteUseCase.kt rename to domain/favorite/src/main/java/com/squirtles/domain/favorite/usecase/DeleteFavoriteUseCase.kt index afa66532..358450ab 100644 --- a/domain/favorite/src/main/java/com/squirtles/favorite/usecase/DeleteFavoriteUseCase.kt +++ b/domain/favorite/src/main/java/com/squirtles/domain/favorite/usecase/DeleteFavoriteUseCase.kt @@ -1,6 +1,6 @@ -package com.squirtles.favorite.usecase +package com.squirtles.domain.favorite.usecase -import com.squirtles.favorite.FirebaseFavoriteRepository +import com.squirtles.domain.favorite.FirebaseFavoriteRepository import com.squirtles.domain.picklist.RemovePickUseCaseInterface import javax.inject.Inject diff --git a/domain/favorite/src/main/java/com/squirtles/favorite/usecase/FetchIsFavoriteUseCase.kt b/domain/favorite/src/main/java/com/squirtles/domain/favorite/usecase/FetchIsFavoriteUseCase.kt similarity index 71% rename from domain/favorite/src/main/java/com/squirtles/favorite/usecase/FetchIsFavoriteUseCase.kt rename to domain/favorite/src/main/java/com/squirtles/domain/favorite/usecase/FetchIsFavoriteUseCase.kt index 18044962..9a30d0cc 100644 --- a/domain/favorite/src/main/java/com/squirtles/favorite/usecase/FetchIsFavoriteUseCase.kt +++ b/domain/favorite/src/main/java/com/squirtles/domain/favorite/usecase/FetchIsFavoriteUseCase.kt @@ -1,6 +1,6 @@ -package com.squirtles.favorite.usecase +package com.squirtles.domain.favorite.usecase -import com.squirtles.favorite.FirebaseFavoriteRepository +import com.squirtles.domain.favorite.FirebaseFavoriteRepository import javax.inject.Inject class FetchIsFavoriteUseCase @Inject constructor( diff --git a/domain/location/src/main/java/com/squirtles/location/LocalLocationRepository.kt b/domain/location/src/main/java/com/squirtles/domain/location/LocalLocationRepository.kt similarity index 85% rename from domain/location/src/main/java/com/squirtles/location/LocalLocationRepository.kt rename to domain/location/src/main/java/com/squirtles/domain/location/LocalLocationRepository.kt index a0fd1ab1..943b5b00 100644 --- a/domain/location/src/main/java/com/squirtles/location/LocalLocationRepository.kt +++ b/domain/location/src/main/java/com/squirtles/domain/location/LocalLocationRepository.kt @@ -1,4 +1,4 @@ -package com.squirtles.location +package com.squirtles.domain.location import android.location.Location import kotlinx.coroutines.flow.StateFlow diff --git a/domain/location/src/main/java/com/squirtles/location/usecase/GetLastLocationUseCase.kt b/domain/location/src/main/java/com/squirtles/domain/location/usecase/GetLastLocationUseCase.kt similarity index 66% rename from domain/location/src/main/java/com/squirtles/location/usecase/GetLastLocationUseCase.kt rename to domain/location/src/main/java/com/squirtles/domain/location/usecase/GetLastLocationUseCase.kt index 22ec39b5..d4a29cfc 100644 --- a/domain/location/src/main/java/com/squirtles/location/usecase/GetLastLocationUseCase.kt +++ b/domain/location/src/main/java/com/squirtles/domain/location/usecase/GetLastLocationUseCase.kt @@ -1,6 +1,6 @@ -package com.squirtles.location.usecase +package com.squirtles.domain.location.usecase -import com.squirtles.location.LocalLocationRepository +import com.squirtles.domain.location.LocalLocationRepository import javax.inject.Inject class GetLastLocationUseCase @Inject constructor( diff --git a/domain/location/src/main/java/com/squirtles/location/usecase/SaveLastLocationUseCase.kt b/domain/location/src/main/java/com/squirtles/domain/location/usecase/SaveLastLocationUseCase.kt similarity index 73% rename from domain/location/src/main/java/com/squirtles/location/usecase/SaveLastLocationUseCase.kt rename to domain/location/src/main/java/com/squirtles/domain/location/usecase/SaveLastLocationUseCase.kt index e250f0ba..62e0226b 100644 --- a/domain/location/src/main/java/com/squirtles/location/usecase/SaveLastLocationUseCase.kt +++ b/domain/location/src/main/java/com/squirtles/domain/location/usecase/SaveLastLocationUseCase.kt @@ -1,7 +1,7 @@ -package com.squirtles.location.usecase +package com.squirtles.domain.location.usecase import android.location.Location -import com.squirtles.location.LocalLocationRepository +import com.squirtles.domain.location.LocalLocationRepository import javax.inject.Inject class SaveLastLocationUseCase @Inject constructor( diff --git a/domain/order/src/main/java/com/squirtles/order/LocalPickListOrderRepository.kt b/domain/order/src/main/java/com/squirtles/domain/order/LocalPickListOrderRepository.kt similarity index 78% rename from domain/order/src/main/java/com/squirtles/order/LocalPickListOrderRepository.kt rename to domain/order/src/main/java/com/squirtles/domain/order/LocalPickListOrderRepository.kt index 6c0234c8..04c3bfe5 100644 --- a/domain/order/src/main/java/com/squirtles/order/LocalPickListOrderRepository.kt +++ b/domain/order/src/main/java/com/squirtles/domain/order/LocalPickListOrderRepository.kt @@ -1,6 +1,6 @@ -package com.squirtles.order +package com.squirtles.domain.order -import com.squirtles.model.Order +import com.squirtles.core.model.Order interface LocalPickListOrderRepository { val favoriteListOrder: Order // 픽 보관함 정렬 순서 diff --git a/domain/order/src/main/java/com/squirtles/order/usecase/GetFavoriteListOrderUseCase.kt b/domain/order/src/main/java/com/squirtles/domain/order/usecase/GetFavoriteListOrderUseCase.kt similarity index 77% rename from domain/order/src/main/java/com/squirtles/order/usecase/GetFavoriteListOrderUseCase.kt rename to domain/order/src/main/java/com/squirtles/domain/order/usecase/GetFavoriteListOrderUseCase.kt index 8b747a0d..b7d9b0db 100644 --- a/domain/order/src/main/java/com/squirtles/order/usecase/GetFavoriteListOrderUseCase.kt +++ b/domain/order/src/main/java/com/squirtles/domain/order/usecase/GetFavoriteListOrderUseCase.kt @@ -1,6 +1,6 @@ -package com.squirtles.order.usecase +package com.squirtles.domain.order.usecase -import com.squirtles.order.LocalPickListOrderRepository +import com.squirtles.domain.order.LocalPickListOrderRepository import com.squirtles.domain.picklist.GetPickListOrderUseCaseInterface import javax.inject.Inject diff --git a/domain/order/src/main/java/com/squirtles/order/usecase/GetMyPickListOrderUseCase.kt b/domain/order/src/main/java/com/squirtles/domain/order/usecase/GetMyPickListOrderUseCase.kt similarity index 76% rename from domain/order/src/main/java/com/squirtles/order/usecase/GetMyPickListOrderUseCase.kt rename to domain/order/src/main/java/com/squirtles/domain/order/usecase/GetMyPickListOrderUseCase.kt index f64b92f2..ab8b72e6 100644 --- a/domain/order/src/main/java/com/squirtles/order/usecase/GetMyPickListOrderUseCase.kt +++ b/domain/order/src/main/java/com/squirtles/domain/order/usecase/GetMyPickListOrderUseCase.kt @@ -1,6 +1,6 @@ -package com.squirtles.order.usecase +package com.squirtles.domain.order.usecase -import com.squirtles.order.LocalPickListOrderRepository +import com.squirtles.domain.order.LocalPickListOrderRepository import com.squirtles.domain.picklist.GetPickListOrderUseCaseInterface import javax.inject.Inject diff --git a/domain/order/src/main/java/com/squirtles/order/usecase/SaveFavoriteListOrderUseCase.kt b/domain/order/src/main/java/com/squirtles/domain/order/usecase/SaveFavoriteListOrderUseCase.kt similarity index 72% rename from domain/order/src/main/java/com/squirtles/order/usecase/SaveFavoriteListOrderUseCase.kt rename to domain/order/src/main/java/com/squirtles/domain/order/usecase/SaveFavoriteListOrderUseCase.kt index c92d84c5..4115a7c0 100644 --- a/domain/order/src/main/java/com/squirtles/order/usecase/SaveFavoriteListOrderUseCase.kt +++ b/domain/order/src/main/java/com/squirtles/domain/order/usecase/SaveFavoriteListOrderUseCase.kt @@ -1,7 +1,7 @@ -package com.squirtles.order.usecase +package com.squirtles.domain.order.usecase -import com.squirtles.model.Order -import com.squirtles.order.LocalPickListOrderRepository +import com.squirtles.core.model.Order +import com.squirtles.domain.order.LocalPickListOrderRepository import com.squirtles.domain.picklist.SavePickListOrderUseCaseInterface import javax.inject.Inject diff --git a/domain/order/src/main/java/com/squirtles/order/usecase/SaveMyPickListOrderUseCase.kt b/domain/order/src/main/java/com/squirtles/domain/order/usecase/SaveMyPickListOrderUseCase.kt similarity index 72% rename from domain/order/src/main/java/com/squirtles/order/usecase/SaveMyPickListOrderUseCase.kt rename to domain/order/src/main/java/com/squirtles/domain/order/usecase/SaveMyPickListOrderUseCase.kt index 8ddc15a6..2aea91f0 100644 --- a/domain/order/src/main/java/com/squirtles/order/usecase/SaveMyPickListOrderUseCase.kt +++ b/domain/order/src/main/java/com/squirtles/domain/order/usecase/SaveMyPickListOrderUseCase.kt @@ -1,7 +1,7 @@ -package com.squirtles.order.usecase +package com.squirtles.domain.order.usecase -import com.squirtles.model.Order -import com.squirtles.order.LocalPickListOrderRepository +import com.squirtles.core.model.Order +import com.squirtles.domain.order.LocalPickListOrderRepository import com.squirtles.domain.picklist.SavePickListOrderUseCaseInterface import javax.inject.Inject diff --git a/domain/pick/src/main/java/com/squirtles/domain/pick/FirebasePickRepository.kt b/domain/pick/src/main/java/com/squirtles/domain/pick/FirebasePickRepository.kt index 2e7fef62..ad8cbaf4 100644 --- a/domain/pick/src/main/java/com/squirtles/domain/pick/FirebasePickRepository.kt +++ b/domain/pick/src/main/java/com/squirtles/domain/pick/FirebasePickRepository.kt @@ -1,6 +1,6 @@ package com.squirtles.domain.pick -import com.squirtles.model.Pick +import com.squirtles.core.model.Pick interface FirebasePickRepository { suspend fun createPick(pick: Pick): Result diff --git a/domain/pick/src/main/java/com/squirtles/domain/pick/usecase/CreatePickUseCase.kt b/domain/pick/src/main/java/com/squirtles/domain/pick/usecase/CreatePickUseCase.kt index 9576037b..48df71441 100644 --- a/domain/pick/src/main/java/com/squirtles/domain/pick/usecase/CreatePickUseCase.kt +++ b/domain/pick/src/main/java/com/squirtles/domain/pick/usecase/CreatePickUseCase.kt @@ -1,6 +1,6 @@ package com.squirtles.domain.pick.usecase -import com.squirtles.model.Pick +import com.squirtles.core.model.Pick import com.squirtles.domain.pick.FirebasePickRepository import javax.inject.Inject diff --git a/domain/pick/src/main/java/com/squirtles/domain/pick/usecase/FetchFavoritePicksUseCase.kt b/domain/pick/src/main/java/com/squirtles/domain/pick/usecase/FetchFavoritePicksUseCase.kt index 3a766465..1eb8c16b 100644 --- a/domain/pick/src/main/java/com/squirtles/domain/pick/usecase/FetchFavoritePicksUseCase.kt +++ b/domain/pick/src/main/java/com/squirtles/domain/pick/usecase/FetchFavoritePicksUseCase.kt @@ -1,6 +1,6 @@ package com.squirtles.domain.pick.usecase -import com.squirtles.model.Pick +import com.squirtles.core.model.Pick import com.squirtles.domain.pick.FirebasePickRepository import com.squirtles.domain.picklist.FetchPickListUseCaseInterface import javax.inject.Inject diff --git a/domain/pick/src/main/java/com/squirtles/domain/pick/usecase/FetchMyPicksUseCase.kt b/domain/pick/src/main/java/com/squirtles/domain/pick/usecase/FetchMyPicksUseCase.kt index 76d30778..cd410c9f 100644 --- a/domain/pick/src/main/java/com/squirtles/domain/pick/usecase/FetchMyPicksUseCase.kt +++ b/domain/pick/src/main/java/com/squirtles/domain/pick/usecase/FetchMyPicksUseCase.kt @@ -1,6 +1,6 @@ package com.squirtles.domain.pick.usecase -import com.squirtles.model.Pick +import com.squirtles.core.model.Pick import com.squirtles.domain.pick.FirebasePickRepository import com.squirtles.domain.picklist.FetchPickListUseCaseInterface import javax.inject.Inject diff --git a/domain/pick/src/main/java/com/squirtles/domain/pick/usecase/FetchPickUseCase.kt b/domain/pick/src/main/java/com/squirtles/domain/pick/usecase/FetchPickUseCase.kt index 415fd085..ce08b33e 100644 --- a/domain/pick/src/main/java/com/squirtles/domain/pick/usecase/FetchPickUseCase.kt +++ b/domain/pick/src/main/java/com/squirtles/domain/pick/usecase/FetchPickUseCase.kt @@ -1,6 +1,6 @@ package com.squirtles.domain.pick.usecase -import com.squirtles.model.Pick +import com.squirtles.core.model.Pick import com.squirtles.domain.pick.FirebasePickRepository import javax.inject.Inject diff --git a/domain/picklist/src/main/java/com/squirtles/domain/picklist/FetchPickListUseCaseInterface.kt b/domain/picklist/src/main/java/com/squirtles/domain/picklist/FetchPickListUseCaseInterface.kt index 3ad5ccbf..bfa4beae 100644 --- a/domain/picklist/src/main/java/com/squirtles/domain/picklist/FetchPickListUseCaseInterface.kt +++ b/domain/picklist/src/main/java/com/squirtles/domain/picklist/FetchPickListUseCaseInterface.kt @@ -1,6 +1,6 @@ package com.squirtles.domain.picklist -import com.squirtles.model.Pick +import com.squirtles.core.model.Pick interface FetchPickListUseCaseInterface { suspend operator fun invoke(userId: String): Result> diff --git a/domain/picklist/src/main/java/com/squirtles/domain/picklist/GetPickListOrderUseCaseInterface.kt b/domain/picklist/src/main/java/com/squirtles/domain/picklist/GetPickListOrderUseCaseInterface.kt index e7b97951..40833983 100644 --- a/domain/picklist/src/main/java/com/squirtles/domain/picklist/GetPickListOrderUseCaseInterface.kt +++ b/domain/picklist/src/main/java/com/squirtles/domain/picklist/GetPickListOrderUseCaseInterface.kt @@ -1,6 +1,6 @@ package com.squirtles.domain.picklist -import com.squirtles.model.Order +import com.squirtles.core.model.Order interface GetPickListOrderUseCaseInterface { suspend operator fun invoke(): Order diff --git a/domain/picklist/src/main/java/com/squirtles/domain/picklist/SavePickListOrderUseCaseInterface.kt b/domain/picklist/src/main/java/com/squirtles/domain/picklist/SavePickListOrderUseCaseInterface.kt index f83993d3..350aebf0 100644 --- a/domain/picklist/src/main/java/com/squirtles/domain/picklist/SavePickListOrderUseCaseInterface.kt +++ b/domain/picklist/src/main/java/com/squirtles/domain/picklist/SavePickListOrderUseCaseInterface.kt @@ -1,6 +1,6 @@ package com.squirtles.domain.picklist -import com.squirtles.model.Order +import com.squirtles.core.model.Order interface SavePickListOrderUseCaseInterface { suspend operator fun invoke(order: Order) diff --git a/domain/player/src/main/java/com/squirtles/player/MediaPlayerListenerUseCase.kt b/domain/player/src/main/java/com/squirtles/domain/player/MediaPlayerListenerUseCase.kt similarity index 97% rename from domain/player/src/main/java/com/squirtles/player/MediaPlayerListenerUseCase.kt rename to domain/player/src/main/java/com/squirtles/domain/player/MediaPlayerListenerUseCase.kt index e02dcd40..380020ab 100644 --- a/domain/player/src/main/java/com/squirtles/player/MediaPlayerListenerUseCase.kt +++ b/domain/player/src/main/java/com/squirtles/domain/player/MediaPlayerListenerUseCase.kt @@ -1,9 +1,9 @@ -package com.squirtles.player +package com.squirtles.domain.player import androidx.media3.common.Player import androidx.media3.common.Tracks -import com.squirtles.mediaservice.MediaControllerProvider -import com.squirtles.model.PlayerState +import com.squirtles.core.mediaservice.MediaControllerProvider +import com.squirtles.core.model.PlayerState import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Job diff --git a/domain/player/src/main/java/com/squirtles/player/MediaPlayerUseCase.kt b/domain/player/src/main/java/com/squirtles/domain/player/MediaPlayerUseCase.kt similarity index 94% rename from domain/player/src/main/java/com/squirtles/player/MediaPlayerUseCase.kt rename to domain/player/src/main/java/com/squirtles/domain/player/MediaPlayerUseCase.kt index 8ff3d68e..74fe53dd 100644 --- a/domain/player/src/main/java/com/squirtles/player/MediaPlayerUseCase.kt +++ b/domain/player/src/main/java/com/squirtles/domain/player/MediaPlayerUseCase.kt @@ -1,12 +1,12 @@ -package com.squirtles.player +package com.squirtles.domain.player import androidx.media3.common.MediaItem import androidx.media3.common.MediaMetadata import androidx.media3.common.Player import androidx.media3.session.MediaController -import com.squirtles.mediaservice.MediaControllerProvider -import com.squirtles.mediaservice.SEEK_TO_DURATION -import com.squirtles.model.Pick +import com.squirtles.core.mediaservice.MediaControllerProvider +import com.squirtles.core.mediaservice.SEEK_TO_DURATION +import com.squirtles.core.model.Pick import kotlinx.coroutines.flow.first import kotlinx.coroutines.flow.flow import javax.inject.Inject diff --git a/domain/user/src/main/java/com/squirtles/user/FirebaseUserRepository.kt b/domain/user/src/main/java/com/squirtles/domain/user/FirebaseUserRepository.kt similarity index 85% rename from domain/user/src/main/java/com/squirtles/user/FirebaseUserRepository.kt rename to domain/user/src/main/java/com/squirtles/domain/user/FirebaseUserRepository.kt index 30c66e32..874dc995 100644 --- a/domain/user/src/main/java/com/squirtles/user/FirebaseUserRepository.kt +++ b/domain/user/src/main/java/com/squirtles/domain/user/FirebaseUserRepository.kt @@ -1,6 +1,6 @@ -package com.squirtles.user +package com.squirtles.domain.user -import com.squirtles.model.User +import com.squirtles.core.model.User interface FirebaseUserRepository { val currentUser: String? diff --git a/domain/user/src/main/java/com/squirtles/user/LocalUserRepository.kt b/domain/user/src/main/java/com/squirtles/domain/user/LocalUserRepository.kt similarity index 79% rename from domain/user/src/main/java/com/squirtles/user/LocalUserRepository.kt rename to domain/user/src/main/java/com/squirtles/domain/user/LocalUserRepository.kt index 0a8e5029..a26457e3 100644 --- a/domain/user/src/main/java/com/squirtles/user/LocalUserRepository.kt +++ b/domain/user/src/main/java/com/squirtles/domain/user/LocalUserRepository.kt @@ -1,6 +1,6 @@ -package com.squirtles.user +package com.squirtles.domain.user -import com.squirtles.model.User +import com.squirtles.core.model.User import kotlinx.coroutines.flow.Flow interface LocalUserRepository { diff --git a/domain/user/src/main/java/com/squirtles/user/usecase/CreateGoogleIdUserUseCase.kt b/domain/user/src/main/java/com/squirtles/domain/user/usecase/CreateGoogleIdUserUseCase.kt similarity index 73% rename from domain/user/src/main/java/com/squirtles/user/usecase/CreateGoogleIdUserUseCase.kt rename to domain/user/src/main/java/com/squirtles/domain/user/usecase/CreateGoogleIdUserUseCase.kt index 2c47f3d7..5a1b3b53 100644 --- a/domain/user/src/main/java/com/squirtles/user/usecase/CreateGoogleIdUserUseCase.kt +++ b/domain/user/src/main/java/com/squirtles/domain/user/usecase/CreateGoogleIdUserUseCase.kt @@ -1,8 +1,7 @@ -package com.squirtles.user.usecase +package com.squirtles.domain.user.usecase -import com.squirtles.model.User -import com.squirtles.user.FirebaseUserRepository -import com.squirtles.user.LocalUserRepository +import com.squirtles.core.model.User +import com.squirtles.domain.user.FirebaseUserRepository import javax.inject.Inject class CreateGoogleIdUserUseCase @Inject constructor( diff --git a/domain/user/src/main/java/com/squirtles/user/usecase/DeleteAccountUseCase.kt b/domain/user/src/main/java/com/squirtles/domain/user/usecase/DeleteAccountUseCase.kt similarity index 91% rename from domain/user/src/main/java/com/squirtles/user/usecase/DeleteAccountUseCase.kt rename to domain/user/src/main/java/com/squirtles/domain/user/usecase/DeleteAccountUseCase.kt index f36bb95c..0cdf572b 100644 --- a/domain/user/src/main/java/com/squirtles/user/usecase/DeleteAccountUseCase.kt +++ b/domain/user/src/main/java/com/squirtles/domain/user/usecase/DeleteAccountUseCase.kt @@ -1,10 +1,10 @@ -package com.squirtles.user.usecase +package com.squirtles.domain.user.usecase -import com.squirtles.favorite.usecase.DeleteFavoriteUseCase +import com.squirtles.domain.favorite.usecase.DeleteFavoriteUseCase import com.squirtles.domain.pick.usecase.DeletePickUseCase import com.squirtles.domain.pick.usecase.FetchFavoritePicksUseCase import com.squirtles.domain.pick.usecase.FetchMyPicksUseCase -import com.squirtles.user.FirebaseUserRepository +import com.squirtles.domain.user.FirebaseUserRepository import kotlinx.coroutines.async import kotlinx.coroutines.awaitAll import kotlinx.coroutines.coroutineScope diff --git a/domain/user/src/main/java/com/squirtles/user/usecase/FetchUserByIdUseCase.kt b/domain/user/src/main/java/com/squirtles/domain/user/usecase/FetchUserByIdUseCase.kt similarity index 71% rename from domain/user/src/main/java/com/squirtles/user/usecase/FetchUserByIdUseCase.kt rename to domain/user/src/main/java/com/squirtles/domain/user/usecase/FetchUserByIdUseCase.kt index 51513e84..f5295d0b 100644 --- a/domain/user/src/main/java/com/squirtles/user/usecase/FetchUserByIdUseCase.kt +++ b/domain/user/src/main/java/com/squirtles/domain/user/usecase/FetchUserByIdUseCase.kt @@ -1,6 +1,6 @@ -package com.squirtles.user.usecase +package com.squirtles.domain.user.usecase -import com.squirtles.user.FirebaseUserRepository +import com.squirtles.domain.user.FirebaseUserRepository import javax.inject.Inject class FetchUserByIdUseCase @Inject constructor( diff --git a/domain/user/src/main/java/com/squirtles/user/usecase/GetCurrentUidUseCase.kt b/domain/user/src/main/java/com/squirtles/domain/user/usecase/GetCurrentUidUseCase.kt similarity index 68% rename from domain/user/src/main/java/com/squirtles/user/usecase/GetCurrentUidUseCase.kt rename to domain/user/src/main/java/com/squirtles/domain/user/usecase/GetCurrentUidUseCase.kt index fea1ffbb..c0c2d927 100644 --- a/domain/user/src/main/java/com/squirtles/user/usecase/GetCurrentUidUseCase.kt +++ b/domain/user/src/main/java/com/squirtles/domain/user/usecase/GetCurrentUidUseCase.kt @@ -1,6 +1,6 @@ -package com.squirtles.user.usecase +package com.squirtles.domain.user.usecase -import com.squirtles.user.FirebaseUserRepository +import com.squirtles.domain.user.FirebaseUserRepository import javax.inject.Inject class GetCurrentUidUseCase @Inject constructor( diff --git a/domain/user/src/main/java/com/squirtles/user/usecase/SignOutUseCase.kt b/domain/user/src/main/java/com/squirtles/domain/user/usecase/SignOutUseCase.kt similarity index 67% rename from domain/user/src/main/java/com/squirtles/user/usecase/SignOutUseCase.kt rename to domain/user/src/main/java/com/squirtles/domain/user/usecase/SignOutUseCase.kt index 073a0426..a3dc11f9 100644 --- a/domain/user/src/main/java/com/squirtles/user/usecase/SignOutUseCase.kt +++ b/domain/user/src/main/java/com/squirtles/domain/user/usecase/SignOutUseCase.kt @@ -1,6 +1,6 @@ -package com.squirtles.user.usecase +package com.squirtles.domain.user.usecase -import com.squirtles.user.FirebaseUserRepository +import com.squirtles.domain.user.FirebaseUserRepository import javax.inject.Inject class SignOutUseCase @Inject constructor( diff --git a/domain/user/src/main/java/com/squirtles/user/usecase/UpdateUserNameUseCase.kt b/domain/user/src/main/java/com/squirtles/domain/user/usecase/UpdateUserNameUseCase.kt similarity index 74% rename from domain/user/src/main/java/com/squirtles/user/usecase/UpdateUserNameUseCase.kt rename to domain/user/src/main/java/com/squirtles/domain/user/usecase/UpdateUserNameUseCase.kt index 44f554da..b79f4b99 100644 --- a/domain/user/src/main/java/com/squirtles/user/usecase/UpdateUserNameUseCase.kt +++ b/domain/user/src/main/java/com/squirtles/domain/user/usecase/UpdateUserNameUseCase.kt @@ -1,6 +1,6 @@ -package com.squirtles.user.usecase +package com.squirtles.domain.user.usecase -import com.squirtles.user.FirebaseUserRepository +import com.squirtles.domain.user.FirebaseUserRepository import javax.inject.Inject class UpdateUserNameUseCase @Inject constructor( diff --git a/feature/create/src/androidTest/java/com/squirtles/create/ExampleInstrumentedTest.kt b/feature/create/src/androidTest/java/com/squirtles/feature/create/ExampleInstrumentedTest.kt similarity index 100% rename from feature/create/src/androidTest/java/com/squirtles/create/ExampleInstrumentedTest.kt rename to feature/create/src/androidTest/java/com/squirtles/feature/create/ExampleInstrumentedTest.kt diff --git a/feature/create/src/main/AndroidManifest.xml b/feature/create/src/main/AndroidManifest.xml deleted file mode 100644 index 8bdb7e14..00000000 --- a/feature/create/src/main/AndroidManifest.xml +++ /dev/null @@ -1,4 +0,0 @@ - - - - diff --git a/feature/create/src/main/java/com/squirtles/create/CreatePickScreen.kt b/feature/create/src/main/java/com/squirtles/feature/create/CreatePickScreen.kt similarity index 96% rename from feature/create/src/main/java/com/squirtles/create/CreatePickScreen.kt rename to feature/create/src/main/java/com/squirtles/feature/create/CreatePickScreen.kt index 2346ccf4..0c50240d 100644 --- a/feature/create/src/main/java/com/squirtles/create/CreatePickScreen.kt +++ b/feature/create/src/main/java/com/squirtles/feature/create/CreatePickScreen.kt @@ -1,4 +1,4 @@ -package com.squirtles.create +package com.squirtles.feature.create import android.app.Activity import android.util.Size @@ -56,13 +56,14 @@ import androidx.core.graphics.toColorInt import androidx.core.view.WindowInsetsControllerCompat import androidx.hilt.navigation.compose.hiltViewModel import androidx.lifecycle.compose.collectAsStateWithLifecycle -import com.squirtles.common.ui.AlbumImage -import com.squirtles.common.ui.VerticalSpacer -import com.squirtles.common.ui.theme.Black -import com.squirtles.common.ui.theme.Dark -import com.squirtles.common.ui.theme.Gray -import com.squirtles.common.ui.theme.White -import com.squirtles.model.Song +import com.squirtles.core.common.ui.AlbumImage +import com.squirtles.core.common.ui.VerticalSpacer +import com.squirtles.core.common.ui.theme.Black +import com.squirtles.core.common.ui.theme.Dark +import com.squirtles.core.common.ui.theme.Gray +import com.squirtles.core.common.ui.theme.White +import com.squirtles.core.model.Song +import com.squirtles.create.R @Composable fun CreatePickScreen( diff --git a/feature/create/src/main/java/com/squirtles/create/CreatePickUiState.kt b/feature/create/src/main/java/com/squirtles/feature/create/CreatePickUiState.kt similarity index 84% rename from feature/create/src/main/java/com/squirtles/create/CreatePickUiState.kt rename to feature/create/src/main/java/com/squirtles/feature/create/CreatePickUiState.kt index ac42389e..ca1e61b7 100644 --- a/feature/create/src/main/java/com/squirtles/create/CreatePickUiState.kt +++ b/feature/create/src/main/java/com/squirtles/feature/create/CreatePickUiState.kt @@ -1,4 +1,4 @@ -package com.squirtles.create +package com.squirtles.feature.create sealed class CreateUiState { data object Default : CreateUiState() diff --git a/feature/create/src/main/java/com/squirtles/create/CreatePickViewModel.kt b/feature/create/src/main/java/com/squirtles/feature/create/CreatePickViewModel.kt similarity index 87% rename from feature/create/src/main/java/com/squirtles/create/CreatePickViewModel.kt rename to feature/create/src/main/java/com/squirtles/feature/create/CreatePickViewModel.kt index 5d326848..3f115235 100644 --- a/feature/create/src/main/java/com/squirtles/create/CreatePickViewModel.kt +++ b/feature/create/src/main/java/com/squirtles/feature/create/CreatePickViewModel.kt @@ -1,4 +1,4 @@ -package com.squirtles.create +package com.squirtles.feature.create import android.location.Location import android.util.Log @@ -6,18 +6,18 @@ import androidx.lifecycle.SavedStateHandle import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope import androidx.navigation.toRoute -import com.squirtles.applemusic.usecase.FetchMusicVideoUseCase -import com.squirtles.location.usecase.GetLastLocationUseCase -import com.squirtles.model.Creator -import com.squirtles.model.LocationPoint -import com.squirtles.model.Pick -import com.squirtles.model.Song -import com.squirtles.navigation.SearchRoute +import com.squirtles.domain.applemusic.usecase.FetchMusicVideoUseCase +import com.squirtles.domain.location.usecase.GetLastLocationUseCase +import com.squirtles.core.model.Creator +import com.squirtles.core.model.LocationPoint +import com.squirtles.core.model.Pick +import com.squirtles.core.model.Song +import com.squirtles.core.navigation.SearchRoute import com.squirtles.domain.pick.usecase.CreatePickUseCase -import com.squirtles.user.usecase.FetchUserByIdUseCase -import com.squirtles.user.usecase.GetCurrentUidUseCase -import com.squirtles.util.serializableType -import com.squirtles.util.throttleFirst +import com.squirtles.domain.user.usecase.FetchUserByIdUseCase +import com.squirtles.domain.user.usecase.GetCurrentUidUseCase +import com.squirtles.core.util.serializableType +import com.squirtles.core.util.throttleFirst import dagger.hilt.android.lifecycle.HiltViewModel import kotlinx.coroutines.flow.MutableSharedFlow import kotlinx.coroutines.flow.MutableStateFlow diff --git a/feature/create/src/main/java/com/squirtles/create/navigation/CreateNavigation.kt b/feature/create/src/main/java/com/squirtles/feature/create/navigation/CreateNavigation.kt similarity index 84% rename from feature/create/src/main/java/com/squirtles/create/navigation/CreateNavigation.kt rename to feature/create/src/main/java/com/squirtles/feature/create/navigation/CreateNavigation.kt index 113e275d..7dc565bf 100644 --- a/feature/create/src/main/java/com/squirtles/create/navigation/CreateNavigation.kt +++ b/feature/create/src/main/java/com/squirtles/feature/create/navigation/CreateNavigation.kt @@ -1,14 +1,14 @@ -package com.squirtles.create.navigation +package com.squirtles.feature.create.navigation import androidx.navigation.NavController import androidx.navigation.NavGraphBuilder import androidx.navigation.NavOptions import androidx.navigation.compose.composable import androidx.navigation.toRoute -import com.squirtles.create.CreatePickScreen -import com.squirtles.model.Song -import com.squirtles.navigation.SearchRoute -import com.squirtles.util.serializableType +import com.squirtles.feature.create.CreatePickScreen +import com.squirtles.core.model.Song +import com.squirtles.core.navigation.SearchRoute +import com.squirtles.core.util.serializableType import java.net.URLEncoder import java.nio.charset.StandardCharsets import kotlin.reflect.typeOf diff --git a/feature/create/src/test/java/com/squirtles/create/ExampleUnitTest.kt b/feature/create/src/test/java/com/squirtles/feature/create/ExampleUnitTest.kt similarity index 100% rename from feature/create/src/test/java/com/squirtles/create/ExampleUnitTest.kt rename to feature/create/src/test/java/com/squirtles/feature/create/ExampleUnitTest.kt diff --git a/feature/detail/src/androidTest/java/com/squirtles/detail/ExampleInstrumentedTest.kt b/feature/detail/src/androidTest/java/com/squirtles/feature/detail/ExampleInstrumentedTest.kt similarity index 100% rename from feature/detail/src/androidTest/java/com/squirtles/detail/ExampleInstrumentedTest.kt rename to feature/detail/src/androidTest/java/com/squirtles/feature/detail/ExampleInstrumentedTest.kt diff --git a/feature/detail/src/main/java/com/squirtles/detail/DetailViewModel.kt b/feature/detail/src/main/java/com/squirtles/feature/detail/DetailViewModel.kt similarity index 91% rename from feature/detail/src/main/java/com/squirtles/detail/DetailViewModel.kt rename to feature/detail/src/main/java/com/squirtles/feature/detail/DetailViewModel.kt index c7df6bc4..6637e5f7 100644 --- a/feature/detail/src/main/java/com/squirtles/detail/DetailViewModel.kt +++ b/feature/detail/src/main/java/com/squirtles/feature/detail/DetailViewModel.kt @@ -1,20 +1,19 @@ -package com.squirtles.detail +package com.squirtles.feature.detail import androidx.core.graphics.toColorInt -import androidx.lifecycle.SavedStateHandle import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope -import com.squirtles.favorite.usecase.CreateFavoriteUseCase -import com.squirtles.favorite.usecase.DeleteFavoriteUseCase -import com.squirtles.favorite.usecase.FetchIsFavoriteUseCase -import com.squirtles.model.Creator -import com.squirtles.model.LocationPoint -import com.squirtles.model.Pick -import com.squirtles.model.Song +import com.squirtles.domain.favorite.usecase.CreateFavoriteUseCase +import com.squirtles.domain.favorite.usecase.DeleteFavoriteUseCase +import com.squirtles.domain.favorite.usecase.FetchIsFavoriteUseCase +import com.squirtles.core.model.Creator +import com.squirtles.core.model.LocationPoint +import com.squirtles.core.model.Pick +import com.squirtles.core.model.Song import com.squirtles.domain.pick.usecase.DeletePickUseCase import com.squirtles.domain.pick.usecase.FetchPickUseCase -import com.squirtles.user.usecase.GetCurrentUidUseCase -import com.squirtles.util.throttleFirst +import com.squirtles.domain.user.usecase.GetCurrentUidUseCase +import com.squirtles.core.util.throttleFirst import dagger.hilt.android.lifecycle.HiltViewModel import kotlinx.coroutines.async import kotlinx.coroutines.flow.MutableSharedFlow diff --git a/feature/detail/src/main/java/com/squirtles/detail/FavoriteAction.kt b/feature/detail/src/main/java/com/squirtles/feature/detail/FavoriteAction.kt similarity index 57% rename from feature/detail/src/main/java/com/squirtles/detail/FavoriteAction.kt rename to feature/detail/src/main/java/com/squirtles/feature/detail/FavoriteAction.kt index b4066409..4b484f73 100644 --- a/feature/detail/src/main/java/com/squirtles/detail/FavoriteAction.kt +++ b/feature/detail/src/main/java/com/squirtles/feature/detail/FavoriteAction.kt @@ -1,4 +1,4 @@ -package com.squirtles.detail +package com.squirtles.feature.detail enum class FavoriteAction { ADDED, DELETED diff --git a/feature/detail/src/main/java/com/squirtles/detail/PickDetailScreen.kt b/feature/detail/src/main/java/com/squirtles/feature/detail/PickDetailScreen.kt similarity index 93% rename from feature/detail/src/main/java/com/squirtles/detail/PickDetailScreen.kt rename to feature/detail/src/main/java/com/squirtles/feature/detail/PickDetailScreen.kt index 7126497c..abfc1d16 100644 --- a/feature/detail/src/main/java/com/squirtles/detail/PickDetailScreen.kt +++ b/feature/detail/src/main/java/com/squirtles/feature/detail/PickDetailScreen.kt @@ -1,4 +1,4 @@ -package com.squirtles.detail +package com.squirtles.feature.detail import android.app.Activity import android.content.Context @@ -54,27 +54,28 @@ import androidx.lifecycle.Lifecycle import androidx.lifecycle.compose.LocalLifecycleOwner import androidx.lifecycle.compose.collectAsStateWithLifecycle import androidx.lifecycle.flowWithLifecycle -import com.squirtles.account.AccountViewModel -import com.squirtles.account.GoogleId -import com.squirtles.common.ui.DialogTextButton -import com.squirtles.common.ui.HorizontalSpacer -import com.squirtles.common.ui.MessageAlertDialog -import com.squirtles.common.ui.SignInAlertDialog -import com.squirtles.common.ui.VerticalSpacer -import com.squirtles.common.ui.theme.Black -import com.squirtles.common.ui.theme.Primary -import com.squirtles.common.ui.theme.White -import com.squirtles.detail.DetailViewModel.Companion.DEFAULT_PICK -import com.squirtles.detail.components.CircleAlbumCover -import com.squirtles.detail.components.PickCommentText -import com.squirtles.detail.components.DetailPickTopAppBar -import com.squirtles.detail.components.MusicVideoKnob -import com.squirtles.detail.components.PickInformation -import com.squirtles.detail.components.SongInfo -import com.squirtles.detail.components.music.MusicPlayer -import com.squirtles.detail.videoplayer.MusicVideoScreen -import com.squirtles.model.Pick -import com.squirtles.musicplayer.PlayerServiceViewModel +import com.squirtles.core.account.AccountViewModel +import com.squirtles.core.account.GoogleId +import com.squirtles.core.common.ui.DialogTextButton +import com.squirtles.core.common.ui.HorizontalSpacer +import com.squirtles.core.common.ui.MessageAlertDialog +import com.squirtles.core.common.ui.SignInAlertDialog +import com.squirtles.core.common.ui.VerticalSpacer +import com.squirtles.core.common.ui.theme.Black +import com.squirtles.core.common.ui.theme.Primary +import com.squirtles.core.common.ui.theme.White +import com.squirtles.feature.detail.DetailViewModel.Companion.DEFAULT_PICK +import com.squirtles.feature.detail.components.CircleAlbumCover +import com.squirtles.feature.detail.components.PickCommentText +import com.squirtles.feature.detail.components.DetailPickTopAppBar +import com.squirtles.feature.detail.components.MusicVideoKnob +import com.squirtles.feature.detail.components.PickInformation +import com.squirtles.feature.detail.components.SongInfo +import com.squirtles.feature.detail.components.music.MusicPlayer +import com.squirtles.feature.detail.videoplayer.MusicVideoScreen +import com.squirtles.core.model.Pick +import com.squirtles.core.musicplayer.PlayerServiceViewModel +import com.squirtles.detail.R import kotlinx.coroutines.launch import kotlin.math.absoluteValue diff --git a/feature/detail/src/main/java/com/squirtles/detail/PickDetailUiState.kt b/feature/detail/src/main/java/com/squirtles/feature/detail/PickDetailUiState.kt similarity index 77% rename from feature/detail/src/main/java/com/squirtles/detail/PickDetailUiState.kt rename to feature/detail/src/main/java/com/squirtles/feature/detail/PickDetailUiState.kt index 39134689..6ef49820 100644 --- a/feature/detail/src/main/java/com/squirtles/detail/PickDetailUiState.kt +++ b/feature/detail/src/main/java/com/squirtles/feature/detail/PickDetailUiState.kt @@ -1,6 +1,6 @@ -package com.squirtles.detail +package com.squirtles.feature.detail -import com.squirtles.model.Pick +import com.squirtles.core.model.Pick sealed class PickDetailUiState { data object Loading : PickDetailUiState() diff --git a/feature/detail/src/main/java/com/squirtles/detail/components/CircleAlbumCover.kt b/feature/detail/src/main/java/com/squirtles/feature/detail/components/CircleAlbumCover.kt similarity index 97% rename from feature/detail/src/main/java/com/squirtles/detail/components/CircleAlbumCover.kt rename to feature/detail/src/main/java/com/squirtles/feature/detail/components/CircleAlbumCover.kt index e781c45d..6f8a7e89 100644 --- a/feature/detail/src/main/java/com/squirtles/detail/components/CircleAlbumCover.kt +++ b/feature/detail/src/main/java/com/squirtles/feature/detail/components/CircleAlbumCover.kt @@ -1,4 +1,4 @@ -package com.squirtles.detail.components +package com.squirtles.feature.detail.components import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.aspectRatio @@ -20,7 +20,7 @@ import com.miller198.audiovisualizer.configs.VisualizerConfig import com.miller198.audiovisualizer.soundeffect.SoundEffects import com.miller198.audiovisualizer.ui.CircleVisualizer import com.squirtles.detail.R -import com.squirtles.model.Song +import com.squirtles.core.model.Song @Composable internal fun CircleAlbumCover( diff --git a/feature/detail/src/main/java/com/squirtles/detail/components/DetailPickTopAppBar.kt b/feature/detail/src/main/java/com/squirtles/feature/detail/components/DetailPickTopAppBar.kt similarity index 96% rename from feature/detail/src/main/java/com/squirtles/detail/components/DetailPickTopAppBar.kt rename to feature/detail/src/main/java/com/squirtles/feature/detail/components/DetailPickTopAppBar.kt index 2feb5130..b0f5ee77 100644 --- a/feature/detail/src/main/java/com/squirtles/detail/components/DetailPickTopAppBar.kt +++ b/feature/detail/src/main/java/com/squirtles/feature/detail/components/DetailPickTopAppBar.kt @@ -1,4 +1,4 @@ -package com.squirtles.detail.components +package com.squirtles.feature.detail.components import androidx.compose.foundation.clickable import androidx.compose.foundation.interaction.MutableInteractionSource @@ -19,8 +19,8 @@ import androidx.compose.ui.graphics.Color import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.font.FontWeight -import com.squirtles.common.ui.CreatedByOtherUserText -import com.squirtles.common.ui.CreatedBySelfText +import com.squirtles.core.common.ui.CreatedByOtherUserText +import com.squirtles.core.common.ui.CreatedBySelfText import com.squirtles.detail.R @OptIn(ExperimentalMaterial3Api::class) diff --git a/feature/detail/src/main/java/com/squirtles/detail/components/MusicVideoKnob.kt b/feature/detail/src/main/java/com/squirtles/feature/detail/components/MusicVideoKnob.kt similarity index 94% rename from feature/detail/src/main/java/com/squirtles/detail/components/MusicVideoKnob.kt rename to feature/detail/src/main/java/com/squirtles/feature/detail/components/MusicVideoKnob.kt index b865acc2..8103c163 100644 --- a/feature/detail/src/main/java/com/squirtles/detail/components/MusicVideoKnob.kt +++ b/feature/detail/src/main/java/com/squirtles/feature/detail/components/MusicVideoKnob.kt @@ -1,4 +1,4 @@ -package com.squirtles.detail.components +package com.squirtles.feature.detail.components import android.util.Size import androidx.compose.animation.core.FastOutSlowInEasing @@ -26,8 +26,8 @@ import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp import coil3.compose.AsyncImage import coil3.request.ImageRequest -import com.squirtles.common.ui.theme.White -import com.squirtles.common.ui.toImageUrlWithSize +import com.squirtles.core.common.ui.theme.White +import com.squirtles.core.common.ui.toImageUrlWithSize import com.squirtles.detail.R @Composable diff --git a/feature/detail/src/main/java/com/squirtles/detail/components/PickCommentText.kt b/feature/detail/src/main/java/com/squirtles/feature/detail/components/PickCommentText.kt similarity index 91% rename from feature/detail/src/main/java/com/squirtles/detail/components/PickCommentText.kt rename to feature/detail/src/main/java/com/squirtles/feature/detail/components/PickCommentText.kt index 1b3b9575..74cadbd4 100644 --- a/feature/detail/src/main/java/com/squirtles/detail/components/PickCommentText.kt +++ b/feature/detail/src/main/java/com/squirtles/feature/detail/components/PickCommentText.kt @@ -1,4 +1,4 @@ -package com.squirtles.detail.components +package com.squirtles.feature.detail.components import androidx.compose.foundation.background import androidx.compose.foundation.layout.Arrangement @@ -16,9 +16,9 @@ import androidx.compose.ui.draw.clip import androidx.compose.ui.res.stringResource import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp -import com.squirtles.common.ui.theme.Dark -import com.squirtles.common.ui.theme.Gray -import com.squirtles.common.ui.theme.White +import com.squirtles.core.common.ui.theme.Dark +import com.squirtles.core.common.ui.theme.Gray +import com.squirtles.core.common.ui.theme.White import com.squirtles.detail.R @Composable diff --git a/feature/detail/src/main/java/com/squirtles/detail/components/PickInformation.kt b/feature/detail/src/main/java/com/squirtles/feature/detail/components/PickInformation.kt similarity index 93% rename from feature/detail/src/main/java/com/squirtles/detail/components/PickInformation.kt rename to feature/detail/src/main/java/com/squirtles/feature/detail/components/PickInformation.kt index 3e960705..c46b5bcb 100644 --- a/feature/detail/src/main/java/com/squirtles/detail/components/PickInformation.kt +++ b/feature/detail/src/main/java/com/squirtles/feature/detail/components/PickInformation.kt @@ -1,4 +1,4 @@ -package com.squirtles.detail.components +package com.squirtles.feature.detail.components import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Row @@ -13,7 +13,7 @@ import androidx.compose.ui.Modifier import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.dp -import com.squirtles.common.ui.theme.Gray +import com.squirtles.core.common.ui.theme.Gray import com.squirtles.detail.R @Composable diff --git a/feature/detail/src/main/java/com/squirtles/detail/components/PlayCircularProgressIndicator.kt b/feature/detail/src/main/java/com/squirtles/feature/detail/components/PlayCircularProgressIndicator.kt similarity index 96% rename from feature/detail/src/main/java/com/squirtles/detail/components/PlayCircularProgressIndicator.kt rename to feature/detail/src/main/java/com/squirtles/feature/detail/components/PlayCircularProgressIndicator.kt index ccf20264..b8f42b54 100644 --- a/feature/detail/src/main/java/com/squirtles/detail/components/PlayCircularProgressIndicator.kt +++ b/feature/detail/src/main/java/com/squirtles/feature/detail/components/PlayCircularProgressIndicator.kt @@ -1,4 +1,4 @@ -package com.squirtles.detail.components +package com.squirtles.feature.detail.components import androidx.compose.foundation.gestures.detectTapGestures import androidx.compose.foundation.layout.Box @@ -12,7 +12,7 @@ import androidx.compose.ui.graphics.StrokeCap import androidx.compose.ui.input.pointer.pointerInput import androidx.compose.ui.unit.Dp import androidx.compose.ui.unit.dp -import com.squirtles.common.ui.theme.White +import com.squirtles.core.common.ui.theme.White import kotlin.math.atan2 import kotlin.math.hypot diff --git a/feature/detail/src/main/java/com/squirtles/detail/components/SongInfo.kt b/feature/detail/src/main/java/com/squirtles/feature/detail/components/SongInfo.kt similarity index 93% rename from feature/detail/src/main/java/com/squirtles/detail/components/SongInfo.kt rename to feature/detail/src/main/java/com/squirtles/feature/detail/components/SongInfo.kt index df68de19..1569ce13 100644 --- a/feature/detail/src/main/java/com/squirtles/detail/components/SongInfo.kt +++ b/feature/detail/src/main/java/com/squirtles/feature/detail/components/SongInfo.kt @@ -1,4 +1,4 @@ -package com.squirtles.detail.components +package com.squirtles.feature.detail.components import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.fillMaxWidth @@ -10,7 +10,7 @@ import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.text.style.TextAlign -import com.squirtles.model.Song +import com.squirtles.core.model.Song @Composable internal fun SongInfo( diff --git a/feature/detail/src/main/java/com/squirtles/detail/components/SwipeUpIcon.kt b/feature/detail/src/main/java/com/squirtles/feature/detail/components/SwipeUpIcon.kt similarity index 90% rename from feature/detail/src/main/java/com/squirtles/detail/components/SwipeUpIcon.kt rename to feature/detail/src/main/java/com/squirtles/feature/detail/components/SwipeUpIcon.kt index bb74fba0..18e573fe 100644 --- a/feature/detail/src/main/java/com/squirtles/detail/components/SwipeUpIcon.kt +++ b/feature/detail/src/main/java/com/squirtles/feature/detail/components/SwipeUpIcon.kt @@ -1,4 +1,4 @@ -package com.squirtles.detail.components +package com.squirtles.feature.detail.components import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.fillMaxWidth @@ -10,7 +10,7 @@ import androidx.compose.ui.Modifier import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.dp -import com.squirtles.common.ui.theme.White +import com.squirtles.core.common.ui.theme.White import com.squirtles.detail.R @Composable diff --git a/feature/detail/src/main/java/com/squirtles/detail/components/music/MusicPlayer.kt b/feature/detail/src/main/java/com/squirtles/feature/detail/components/music/MusicPlayer.kt similarity index 86% rename from feature/detail/src/main/java/com/squirtles/detail/components/music/MusicPlayer.kt rename to feature/detail/src/main/java/com/squirtles/feature/detail/components/music/MusicPlayer.kt index 689a6745..f1a03cf3 100644 --- a/feature/detail/src/main/java/com/squirtles/detail/components/music/MusicPlayer.kt +++ b/feature/detail/src/main/java/com/squirtles/feature/detail/components/music/MusicPlayer.kt @@ -1,4 +1,4 @@ -package com.squirtles.detail.components.music +package com.squirtles.feature.detail.components.music import androidx.compose.foundation.background import androidx.compose.foundation.layout.Column @@ -9,10 +9,10 @@ import androidx.compose.runtime.Composable import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.unit.dp -import com.squirtles.common.ui.Constants.DEFAULT_PADDING -import com.squirtles.common.ui.theme.PlayerBackground -import com.squirtles.model.PlayerState -import com.squirtles.model.Song +import com.squirtles.core.common.ui.Constants.DEFAULT_PADDING +import com.squirtles.core.common.ui.theme.PlayerBackground +import com.squirtles.core.model.PlayerState +import com.squirtles.core.model.Song @Composable fun MusicPlayer( diff --git a/feature/detail/src/main/java/com/squirtles/detail/components/music/PlayBar.kt b/feature/detail/src/main/java/com/squirtles/feature/detail/components/music/PlayBar.kt similarity index 94% rename from feature/detail/src/main/java/com/squirtles/detail/components/music/PlayBar.kt rename to feature/detail/src/main/java/com/squirtles/feature/detail/components/music/PlayBar.kt index 525c7cd5..1f4bc0a9 100644 --- a/feature/detail/src/main/java/com/squirtles/detail/components/music/PlayBar.kt +++ b/feature/detail/src/main/java/com/squirtles/feature/detail/components/music/PlayBar.kt @@ -1,4 +1,4 @@ -package com.squirtles.detail.components.music +package com.squirtles.feature.detail.components.music import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Box @@ -11,8 +11,8 @@ import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp -import com.squirtles.common.ui.theme.Gray -import com.squirtles.common.ui.theme.MusicRoadTheme +import com.squirtles.core.common.ui.theme.Gray +import com.squirtles.core.common.ui.theme.MusicRoadTheme import java.util.concurrent.TimeUnit @Composable diff --git a/feature/detail/src/main/java/com/squirtles/detail/components/music/PlayProgressIndicator.kt b/feature/detail/src/main/java/com/squirtles/feature/detail/components/music/PlayProgressIndicator.kt similarity index 91% rename from feature/detail/src/main/java/com/squirtles/detail/components/music/PlayProgressIndicator.kt rename to feature/detail/src/main/java/com/squirtles/feature/detail/components/music/PlayProgressIndicator.kt index 11a587cf..acc1f498 100644 --- a/feature/detail/src/main/java/com/squirtles/detail/components/music/PlayProgressIndicator.kt +++ b/feature/detail/src/main/java/com/squirtles/feature/detail/components/music/PlayProgressIndicator.kt @@ -1,4 +1,4 @@ -package com.squirtles.detail.components.music +package com.squirtles.feature.detail.components.music import androidx.compose.foundation.gestures.detectTapGestures import androidx.compose.foundation.layout.Box @@ -10,8 +10,8 @@ import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color import androidx.compose.ui.input.pointer.pointerInput import androidx.compose.ui.unit.dp -import com.squirtles.common.ui.theme.DarkGray -import com.squirtles.common.ui.theme.White +import com.squirtles.core.common.ui.theme.DarkGray +import com.squirtles.core.common.ui.theme.White @Composable internal fun PlayProgressIndicator( diff --git a/feature/detail/src/main/java/com/squirtles/detail/components/music/PlayerControls.kt b/feature/detail/src/main/java/com/squirtles/feature/detail/components/music/PlayerControls.kt similarity index 94% rename from feature/detail/src/main/java/com/squirtles/detail/components/music/PlayerControls.kt rename to feature/detail/src/main/java/com/squirtles/feature/detail/components/music/PlayerControls.kt index 487de399..e142ff8d 100644 --- a/feature/detail/src/main/java/com/squirtles/detail/components/music/PlayerControls.kt +++ b/feature/detail/src/main/java/com/squirtles/feature/detail/components/music/PlayerControls.kt @@ -1,4 +1,4 @@ -package com.squirtles.detail.components.music +package com.squirtles.feature.detail.components.music import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Row @@ -16,8 +16,8 @@ import androidx.compose.ui.Modifier import androidx.compose.ui.res.stringResource import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp -import com.squirtles.common.ui.theme.MusicRoadTheme -import com.squirtles.common.ui.theme.White +import com.squirtles.core.common.ui.theme.MusicRoadTheme +import com.squirtles.core.common.ui.theme.White import com.squirtles.detail.R @Composable diff --git a/feature/detail/src/main/java/com/squirtles/detail/navigation/PickDetailNavigation.kt b/feature/detail/src/main/java/com/squirtles/feature/detail/navigation/PickDetailNavigation.kt similarity index 82% rename from feature/detail/src/main/java/com/squirtles/detail/navigation/PickDetailNavigation.kt rename to feature/detail/src/main/java/com/squirtles/feature/detail/navigation/PickDetailNavigation.kt index c9f33788..6bbe06d0 100644 --- a/feature/detail/src/main/java/com/squirtles/detail/navigation/PickDetailNavigation.kt +++ b/feature/detail/src/main/java/com/squirtles/feature/detail/navigation/PickDetailNavigation.kt @@ -1,4 +1,4 @@ -package com.squirtles.detail.navigation +package com.squirtles.feature.detail.navigation import android.content.Context import androidx.navigation.NavController @@ -6,9 +6,9 @@ import androidx.navigation.NavGraphBuilder import androidx.navigation.NavOptions import androidx.navigation.compose.composable import androidx.navigation.toRoute -import com.squirtles.detail.PickDetailScreen -import com.squirtles.musicplayer.PlayerServiceViewModel -import com.squirtles.navigation.MapRoute +import com.squirtles.feature.detail.PickDetailScreen +import com.squirtles.core.musicplayer.PlayerServiceViewModel +import com.squirtles.core.navigation.MapRoute fun NavController.navigatePickDetail(pickId: String, navOptions: NavOptions? = null) { navigate(MapRoute.PickDetail(pickId), navOptions) diff --git a/feature/detail/src/main/java/com/squirtles/detail/videoplayer/MusicVideoPlayer.kt b/feature/detail/src/main/java/com/squirtles/feature/detail/videoplayer/MusicVideoPlayer.kt similarity index 98% rename from feature/detail/src/main/java/com/squirtles/detail/videoplayer/MusicVideoPlayer.kt rename to feature/detail/src/main/java/com/squirtles/feature/detail/videoplayer/MusicVideoPlayer.kt index b3873e1e..7bb636ad 100644 --- a/feature/detail/src/main/java/com/squirtles/detail/videoplayer/MusicVideoPlayer.kt +++ b/feature/detail/src/main/java/com/squirtles/feature/detail/videoplayer/MusicVideoPlayer.kt @@ -1,4 +1,4 @@ -package com.squirtles.detail.videoplayer +package com.squirtles.feature.detail.videoplayer import android.graphics.Matrix import android.graphics.SurfaceTexture @@ -20,7 +20,6 @@ import androidx.lifecycle.Lifecycle import androidx.lifecycle.compose.LocalLifecycleOwner import androidx.lifecycle.compose.collectAsStateWithLifecycle import androidx.lifecycle.flowWithLifecycle -import com.squirtles.model.Pick import dagger.hilt.android.UnstableApi import kotlinx.coroutines.launch diff --git a/feature/detail/src/main/java/com/squirtles/detail/videoplayer/MusicVideoScreen.kt b/feature/detail/src/main/java/com/squirtles/feature/detail/videoplayer/MusicVideoScreen.kt similarity index 92% rename from feature/detail/src/main/java/com/squirtles/detail/videoplayer/MusicVideoScreen.kt rename to feature/detail/src/main/java/com/squirtles/feature/detail/videoplayer/MusicVideoScreen.kt index dee1c942..38068022 100644 --- a/feature/detail/src/main/java/com/squirtles/detail/videoplayer/MusicVideoScreen.kt +++ b/feature/detail/src/main/java/com/squirtles/feature/detail/videoplayer/MusicVideoScreen.kt @@ -1,4 +1,4 @@ -package com.squirtles.detail.videoplayer +package com.squirtles.feature.detail.videoplayer import androidx.activity.compose.BackHandler import androidx.annotation.OptIn @@ -11,7 +11,7 @@ import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.hilt.navigation.compose.hiltViewModel import androidx.lifecycle.compose.collectAsStateWithLifecycle -import com.squirtles.model.Pick +import com.squirtles.core.model.Pick import dagger.hilt.android.UnstableApi @OptIn(UnstableApi::class) diff --git a/feature/detail/src/main/java/com/squirtles/detail/videoplayer/VideoPlayerOverlay.kt b/feature/detail/src/main/java/com/squirtles/feature/detail/videoplayer/VideoPlayerOverlay.kt similarity index 95% rename from feature/detail/src/main/java/com/squirtles/detail/videoplayer/VideoPlayerOverlay.kt rename to feature/detail/src/main/java/com/squirtles/feature/detail/videoplayer/VideoPlayerOverlay.kt index e43bf43b..937d04fc 100644 --- a/feature/detail/src/main/java/com/squirtles/detail/videoplayer/VideoPlayerOverlay.kt +++ b/feature/detail/src/main/java/com/squirtles/feature/detail/videoplayer/VideoPlayerOverlay.kt @@ -1,4 +1,4 @@ -package com.squirtles.detail.videoplayer +package com.squirtles.feature.detail.videoplayer import androidx.compose.animation.core.Animatable import androidx.compose.animation.core.tween @@ -47,15 +47,15 @@ import androidx.compose.ui.unit.sp import androidx.core.graphics.toColorInt import androidx.hilt.navigation.compose.hiltViewModel import androidx.lifecycle.compose.collectAsStateWithLifecycle -import com.squirtles.common.ui.VerticalSpacer -import com.squirtles.common.ui.theme.Black -import com.squirtles.common.ui.theme.Gray -import com.squirtles.common.ui.theme.White +import com.squirtles.core.common.ui.VerticalSpacer +import com.squirtles.core.common.ui.theme.Black +import com.squirtles.core.common.ui.theme.Gray +import com.squirtles.core.common.ui.theme.White import com.squirtles.detail.R -import com.squirtles.model.Creator -import com.squirtles.model.LocationPoint -import com.squirtles.model.Pick -import com.squirtles.model.Song +import com.squirtles.core.model.Creator +import com.squirtles.core.model.LocationPoint +import com.squirtles.core.model.Pick +import com.squirtles.core.model.Song @OptIn(ExperimentalMaterial3Api::class) @Composable diff --git a/feature/detail/src/main/java/com/squirtles/detail/videoplayer/VideoPlayerState.kt b/feature/detail/src/main/java/com/squirtles/feature/detail/videoplayer/VideoPlayerState.kt similarity index 55% rename from feature/detail/src/main/java/com/squirtles/detail/videoplayer/VideoPlayerState.kt rename to feature/detail/src/main/java/com/squirtles/feature/detail/videoplayer/VideoPlayerState.kt index 0a6277fc..35680f07 100644 --- a/feature/detail/src/main/java/com/squirtles/detail/videoplayer/VideoPlayerState.kt +++ b/feature/detail/src/main/java/com/squirtles/feature/detail/videoplayer/VideoPlayerState.kt @@ -1,4 +1,4 @@ -package com.squirtles.detail.videoplayer +package com.squirtles.feature.detail.videoplayer enum class VideoPlayerState { Playing, Pause, Replay diff --git a/feature/detail/src/main/java/com/squirtles/detail/videoplayer/VideoPlayerViewModel.kt b/feature/detail/src/main/java/com/squirtles/feature/detail/videoplayer/VideoPlayerViewModel.kt similarity index 98% rename from feature/detail/src/main/java/com/squirtles/detail/videoplayer/VideoPlayerViewModel.kt rename to feature/detail/src/main/java/com/squirtles/feature/detail/videoplayer/VideoPlayerViewModel.kt index 0683a0d9..cd3e6342 100644 --- a/feature/detail/src/main/java/com/squirtles/detail/videoplayer/VideoPlayerViewModel.kt +++ b/feature/detail/src/main/java/com/squirtles/feature/detail/videoplayer/VideoPlayerViewModel.kt @@ -1,4 +1,4 @@ -package com.squirtles.detail.videoplayer +package com.squirtles.feature.detail.videoplayer import android.content.Context import android.util.Size diff --git a/feature/detail/src/test/java/com/squirtles/detail/ExampleUnitTest.kt b/feature/detail/src/test/java/com/squirtles/feature/detail/ExampleUnitTest.kt similarity index 100% rename from feature/detail/src/test/java/com/squirtles/detail/ExampleUnitTest.kt rename to feature/detail/src/test/java/com/squirtles/feature/detail/ExampleUnitTest.kt diff --git a/feature/favorite/src/androidTest/java/com/squirtles/favorite/ExampleInstrumentedTest.kt b/feature/favorite/src/androidTest/java/com/squirtles/feature/favorite/ExampleInstrumentedTest.kt similarity index 94% rename from feature/favorite/src/androidTest/java/com/squirtles/favorite/ExampleInstrumentedTest.kt rename to feature/favorite/src/androidTest/java/com/squirtles/feature/favorite/ExampleInstrumentedTest.kt index e488b426..ee9b49aa 100644 --- a/feature/favorite/src/androidTest/java/com/squirtles/favorite/ExampleInstrumentedTest.kt +++ b/feature/favorite/src/androidTest/java/com/squirtles/feature/favorite/ExampleInstrumentedTest.kt @@ -1,4 +1,4 @@ -package com.squirtles.favorite +package com.squirtles.feature.favorite import androidx.test.platform.app.InstrumentationRegistry import androidx.test.ext.junit.runners.AndroidJUnit4 diff --git a/feature/favorite/src/main/java/com/squirtles/favorite/FavoriteListViewModel.kt b/feature/favorite/src/main/java/com/squirtles/feature/favorite/FavoriteListViewModel.kt similarity index 68% rename from feature/favorite/src/main/java/com/squirtles/favorite/FavoriteListViewModel.kt rename to feature/favorite/src/main/java/com/squirtles/feature/favorite/FavoriteListViewModel.kt index 9dbcfb59..9e32fb82 100644 --- a/feature/favorite/src/main/java/com/squirtles/favorite/FavoriteListViewModel.kt +++ b/feature/favorite/src/main/java/com/squirtles/feature/favorite/FavoriteListViewModel.kt @@ -1,11 +1,11 @@ -package com.squirtles.favorite +package com.squirtles.feature.favorite -import com.squirtles.favorite.usecase.DeleteFavoriteUseCase -import com.squirtles.order.usecase.GetFavoriteListOrderUseCase -import com.squirtles.order.usecase.SaveFavoriteListOrderUseCase +import com.squirtles.domain.favorite.usecase.DeleteFavoriteUseCase +import com.squirtles.domain.order.usecase.GetFavoriteListOrderUseCase +import com.squirtles.domain.order.usecase.SaveFavoriteListOrderUseCase import com.squirtles.domain.pick.usecase.FetchFavoritePicksUseCase -import com.squirtles.picklist.PickListViewModel -import com.squirtles.user.usecase.GetCurrentUidUseCase +import com.squirtles.core.picklist.PickListViewModel +import com.squirtles.domain.user.usecase.GetCurrentUidUseCase import dagger.hilt.android.lifecycle.HiltViewModel import javax.inject.Inject diff --git a/feature/favorite/src/main/java/com/squirtles/favorite/FavoriteScreen.kt b/feature/favorite/src/main/java/com/squirtles/feature/favorite/FavoriteScreen.kt similarity index 92% rename from feature/favorite/src/main/java/com/squirtles/favorite/FavoriteScreen.kt rename to feature/favorite/src/main/java/com/squirtles/feature/favorite/FavoriteScreen.kt index 430e6ac4..a177efb7 100644 --- a/feature/favorite/src/main/java/com/squirtles/favorite/FavoriteScreen.kt +++ b/feature/favorite/src/main/java/com/squirtles/feature/favorite/FavoriteScreen.kt @@ -1,4 +1,4 @@ -package com.squirtles.favorite +package com.squirtles.feature.favorite import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect @@ -8,8 +8,8 @@ import androidx.compose.runtime.saveable.rememberSaveable import androidx.compose.runtime.setValue import androidx.hilt.navigation.compose.hiltViewModel import androidx.lifecycle.compose.collectAsStateWithLifecycle -import com.squirtles.picklist.PickListScreenContents -import com.squirtles.picklist.PickListType +import com.squirtles.core.picklist.PickListScreenContents +import com.squirtles.core.picklist.PickListType @Composable fun FavoriteScreen( diff --git a/feature/favorite/src/main/java/com/squirtles/favorite/navigation/FavoriteNavigation.kt b/feature/favorite/src/main/java/com/squirtles/feature/favorite/navigation/FavoriteNavigation.kt similarity index 82% rename from feature/favorite/src/main/java/com/squirtles/favorite/navigation/FavoriteNavigation.kt rename to feature/favorite/src/main/java/com/squirtles/feature/favorite/navigation/FavoriteNavigation.kt index 6b2e7df3..63b638cd 100644 --- a/feature/favorite/src/main/java/com/squirtles/favorite/navigation/FavoriteNavigation.kt +++ b/feature/favorite/src/main/java/com/squirtles/feature/favorite/navigation/FavoriteNavigation.kt @@ -1,12 +1,12 @@ -package com.squirtles.favorite.navigation +package com.squirtles.feature.favorite.navigation import androidx.navigation.NavController import androidx.navigation.NavGraphBuilder import androidx.navigation.NavOptions import androidx.navigation.compose.composable import androidx.navigation.toRoute -import com.squirtles.favorite.FavoriteScreen -import com.squirtles.navigation.MainRoute +import com.squirtles.feature.favorite.FavoriteScreen +import com.squirtles.core.navigation.MainRoute fun NavController.navigateFavorite(uid: String, navOptions: NavOptions? = null) { navigate(MainRoute.Favorite(uid), navOptions) diff --git a/feature/favorite/src/test/java/com/squirtles/favorite/ExampleUnitTest.kt b/feature/favorite/src/test/java/com/squirtles/feature/favorite/ExampleUnitTest.kt similarity index 100% rename from feature/favorite/src/test/java/com/squirtles/favorite/ExampleUnitTest.kt rename to feature/favorite/src/test/java/com/squirtles/feature/favorite/ExampleUnitTest.kt diff --git a/feature/main/src/androidTest/java/com/squirtles/main/ExampleInstrumentedTest.kt b/feature/main/src/androidTest/java/com/squirtles/feature/main/ExampleInstrumentedTest.kt similarity index 100% rename from feature/main/src/androidTest/java/com/squirtles/main/ExampleInstrumentedTest.kt rename to feature/main/src/androidTest/java/com/squirtles/feature/main/ExampleInstrumentedTest.kt diff --git a/feature/main/src/main/AndroidManifest.xml b/feature/main/src/main/AndroidManifest.xml index 22e07b82..d95426b4 100644 --- a/feature/main/src/main/AndroidManifest.xml +++ b/feature/main/src/main/AndroidManifest.xml @@ -10,7 +10,7 @@ diff --git a/feature/main/src/main/java/com/squirtles/main/LoadingState.kt b/feature/main/src/main/java/com/squirtles/feature/main/LoadingState.kt similarity index 90% rename from feature/main/src/main/java/com/squirtles/main/LoadingState.kt rename to feature/main/src/main/java/com/squirtles/feature/main/LoadingState.kt index f5ebf39a..9b2a32fe 100644 --- a/feature/main/src/main/java/com/squirtles/main/LoadingState.kt +++ b/feature/main/src/main/java/com/squirtles/feature/main/LoadingState.kt @@ -1,4 +1,4 @@ -package com.squirtles.main +package com.squirtles.feature.main sealed class LoadingState { data object Loading : LoadingState() diff --git a/feature/main/src/main/java/com/squirtles/main/MainActivity.kt b/feature/main/src/main/java/com/squirtles/feature/main/MainActivity.kt similarity index 95% rename from feature/main/src/main/java/com/squirtles/main/MainActivity.kt rename to feature/main/src/main/java/com/squirtles/feature/main/MainActivity.kt index a6d4426b..c7107069 100644 --- a/feature/main/src/main/java/com/squirtles/main/MainActivity.kt +++ b/feature/main/src/main/java/com/squirtles/feature/main/MainActivity.kt @@ -1,4 +1,4 @@ -package com.squirtles.main +package com.squirtles.feature.main import android.Manifest import android.content.Context @@ -22,12 +22,12 @@ import androidx.core.splashscreen.SplashScreen.Companion.installSplashScreen import androidx.lifecycle.Lifecycle import androidx.lifecycle.lifecycleScope import androidx.lifecycle.repeatOnLifecycle -import androidx.navigation.compose.rememberNavController import com.google.firebase.auth.FirebaseAuth -import com.squirtles.common.ui.theme.MusicRoadTheme -import com.squirtles.main.navigation.MainNavHost -import com.squirtles.main.navigation.MainNavigator -import com.squirtles.main.navigation.rememberMainNavigator +import com.squirtles.core.common.ui.theme.MusicRoadTheme +import com.squirtles.main.R +import com.squirtles.feature.main.navigation.MainNavHost +import com.squirtles.feature.main.navigation.MainNavigator +import com.squirtles.feature.main.navigation.rememberMainNavigator import dagger.hilt.android.AndroidEntryPoint import kotlinx.coroutines.cancel import kotlinx.coroutines.launch diff --git a/feature/main/src/main/java/com/squirtles/main/MainViewModel.kt b/feature/main/src/main/java/com/squirtles/feature/main/MainViewModel.kt similarity index 92% rename from feature/main/src/main/java/com/squirtles/main/MainViewModel.kt rename to feature/main/src/main/java/com/squirtles/feature/main/MainViewModel.kt index 60d4889c..650319bc 100644 --- a/feature/main/src/main/java/com/squirtles/main/MainViewModel.kt +++ b/feature/main/src/main/java/com/squirtles/feature/main/MainViewModel.kt @@ -1,11 +1,11 @@ -package com.squirtles.main +package com.squirtles.feature.main import android.util.Log import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope import com.squirtles.domain.firebase.FirebaseException -import com.squirtles.user.usecase.FetchUserByIdUseCase -import com.squirtles.user.usecase.GetCurrentUidUseCase +import com.squirtles.domain.user.usecase.FetchUserByIdUseCase +import com.squirtles.domain.user.usecase.GetCurrentUidUseCase import dagger.hilt.android.lifecycle.HiltViewModel import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.asStateFlow diff --git a/feature/main/src/main/java/com/squirtles/main/NeedPermissionDialog.kt b/feature/main/src/main/java/com/squirtles/feature/main/NeedPermissionDialog.kt similarity index 88% rename from feature/main/src/main/java/com/squirtles/main/NeedPermissionDialog.kt rename to feature/main/src/main/java/com/squirtles/feature/main/NeedPermissionDialog.kt index f2db36ee..f9229d7f 100644 --- a/feature/main/src/main/java/com/squirtles/main/NeedPermissionDialog.kt +++ b/feature/main/src/main/java/com/squirtles/feature/main/NeedPermissionDialog.kt @@ -1,4 +1,4 @@ -package com.squirtles.main +package com.squirtles.feature.main import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Column @@ -21,12 +21,13 @@ import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp -import com.squirtles.common.ui.HorizontalSpacer -import com.squirtles.common.ui.VerticalSpacer -import com.squirtles.common.ui.theme.Black -import com.squirtles.common.ui.theme.DarkGray -import com.squirtles.common.ui.theme.MusicRoadTheme -import com.squirtles.common.ui.theme.White +import com.squirtles.core.common.ui.HorizontalSpacer +import com.squirtles.core.common.ui.VerticalSpacer +import com.squirtles.core.common.ui.theme.Black +import com.squirtles.core.common.ui.theme.DarkGray +import com.squirtles.core.common.ui.theme.MusicRoadTheme +import com.squirtles.core.common.ui.theme.White +import com.squirtles.main.R @OptIn(ExperimentalMaterial3Api::class) @Composable diff --git a/feature/main/src/main/java/com/squirtles/main/PermissionBar.kt b/feature/main/src/main/java/com/squirtles/feature/main/PermissionBar.kt similarity index 95% rename from feature/main/src/main/java/com/squirtles/main/PermissionBar.kt rename to feature/main/src/main/java/com/squirtles/feature/main/PermissionBar.kt index ea2e50bc..e1ce5f0f 100644 --- a/feature/main/src/main/java/com/squirtles/main/PermissionBar.kt +++ b/feature/main/src/main/java/com/squirtles/feature/main/PermissionBar.kt @@ -1,4 +1,4 @@ -package com.squirtles.main +package com.squirtles.feature.main import androidx.compose.foundation.background import androidx.compose.foundation.layout.Box @@ -21,7 +21,8 @@ import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.unit.dp -import com.squirtles.common.ui.Constants.DEFAULT_PADDING +import com.squirtles.core.common.ui.Constants.DEFAULT_PADDING +import com.squirtles.main.R @Composable fun PermissionBar( diff --git a/feature/main/src/main/java/com/squirtles/main/navigation/MainNavHost.kt b/feature/main/src/main/java/com/squirtles/feature/main/navigation/MainNavHost.kt similarity index 79% rename from feature/main/src/main/java/com/squirtles/main/navigation/MainNavHost.kt rename to feature/main/src/main/java/com/squirtles/feature/main/navigation/MainNavHost.kt index 876dc8fb..e25633b2 100644 --- a/feature/main/src/main/java/com/squirtles/main/navigation/MainNavHost.kt +++ b/feature/main/src/main/java/com/squirtles/feature/main/navigation/MainNavHost.kt @@ -1,19 +1,18 @@ -package com.squirtles.main.navigation +package com.squirtles.feature.main.navigation import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier import androidx.hilt.navigation.compose.hiltViewModel import androidx.navigation.compose.NavHost -import com.squirtles.create.navigation.createNavGraph -import com.squirtles.detail.navigation.detailNavGraph -import com.squirtles.favorite.navigation.favoriteNavGraph -import com.squirtles.main.MainActivity -import com.squirtles.map.MapViewModel -import com.squirtles.map.navigation.mapNavGraph -import com.squirtles.musicplayer.PlayerServiceViewModel -import com.squirtles.mypick.navigation.myPickNavGraph -import com.squirtles.search.navigation.searchNavGraph -import com.squirtles.userinfo.navigation.userInfoNavGraph +import com.squirtles.feature.create.navigation.createNavGraph +import com.squirtles.feature.detail.navigation.detailNavGraph +import com.squirtles.feature.favorite.navigation.favoriteNavGraph +import com.squirtles.feature.map.MapViewModel +import com.squirtles.feature.map.navigation.mapNavGraph +import com.squirtles.core.musicplayer.PlayerServiceViewModel +import com.squirtles.feature.mypick.navigation.myPickNavGraph +import com.squirtles.feature.search.navigation.searchNavGraph +import com.squirtles.feature.userinfo.navigation.userInfoNavGraph @Composable internal fun MainNavHost( diff --git a/feature/main/src/main/java/com/squirtles/main/navigation/MainNavigator.kt b/feature/main/src/main/java/com/squirtles/feature/main/navigation/MainNavigator.kt similarity index 81% rename from feature/main/src/main/java/com/squirtles/main/navigation/MainNavigator.kt rename to feature/main/src/main/java/com/squirtles/feature/main/navigation/MainNavigator.kt index 37cf17d0..88f605d1 100644 --- a/feature/main/src/main/java/com/squirtles/main/navigation/MainNavigator.kt +++ b/feature/main/src/main/java/com/squirtles/feature/main/navigation/MainNavigator.kt @@ -1,4 +1,4 @@ -package com.squirtles.main.navigation +package com.squirtles.feature.main.navigation import androidx.compose.runtime.Composable import androidx.compose.runtime.remember @@ -8,17 +8,17 @@ import androidx.navigation.NavHostController import androidx.navigation.compose.currentBackStackEntryAsState import androidx.navigation.compose.rememberNavController import androidx.navigation.navOptions -import com.squirtles.create.navigation.navigateCreate -import com.squirtles.detail.navigation.navigatePickDetail -import com.squirtles.favorite.navigation.navigateFavorite -import com.squirtles.map.navigation.navigateMap -import com.squirtles.model.Song -import com.squirtles.mypick.navigation.navigateMyPicks -import com.squirtles.navigation.Route -import com.squirtles.search.navigation.navigateSearch -import com.squirtles.userinfo.navigation.navigateEditNotificationSetting -import com.squirtles.userinfo.navigation.navigateEditProfile -import com.squirtles.userinfo.navigation.navigateUserInfo +import com.squirtles.feature.create.navigation.navigateCreate +import com.squirtles.feature.detail.navigation.navigatePickDetail +import com.squirtles.feature.favorite.navigation.navigateFavorite +import com.squirtles.feature.map.navigation.navigateMap +import com.squirtles.core.model.Song +import com.squirtles.feature.mypick.navigation.navigateMyPicks +import com.squirtles.core.navigation.Route +import com.squirtles.feature.search.navigation.navigateSearch +import com.squirtles.feature.userinfo.navigation.navigateEditNotificationSetting +import com.squirtles.feature.userinfo.navigation.navigateEditProfile +import com.squirtles.feature.userinfo.navigation.navigateUserInfo internal class MainNavigator( val navController: NavHostController diff --git a/feature/main/src/test/java/com/squirtles/main/ExampleUnitTest.kt b/feature/main/src/test/java/com/squirtles/feature/main/ExampleUnitTest.kt similarity index 100% rename from feature/main/src/test/java/com/squirtles/main/ExampleUnitTest.kt rename to feature/main/src/test/java/com/squirtles/feature/main/ExampleUnitTest.kt diff --git a/feature/map/src/androidTest/java/com/squirtles/map/ExampleInstrumentedTest.kt b/feature/map/src/androidTest/java/com/squirtles/feature/map/ExampleInstrumentedTest.kt similarity index 100% rename from feature/map/src/androidTest/java/com/squirtles/map/ExampleInstrumentedTest.kt rename to feature/map/src/androidTest/java/com/squirtles/feature/map/ExampleInstrumentedTest.kt diff --git a/feature/map/src/main/java/com/squirtles/map/Constants.kt b/feature/map/src/main/java/com/squirtles/feature/map/Constants.kt similarity index 87% rename from feature/map/src/main/java/com/squirtles/map/Constants.kt rename to feature/map/src/main/java/com/squirtles/feature/map/Constants.kt index 3fbe632b..3e1a202c 100644 --- a/feature/map/src/main/java/com/squirtles/map/Constants.kt +++ b/feature/map/src/main/java/com/squirtles/feature/map/Constants.kt @@ -1,4 +1,4 @@ -package com.squirtles.map +package com.squirtles.feature.map internal enum class BottomNavigationSize( val size: Int diff --git a/feature/map/src/main/java/com/squirtles/map/MapScreen.kt b/feature/map/src/main/java/com/squirtles/feature/map/MapScreen.kt similarity index 94% rename from feature/map/src/main/java/com/squirtles/map/MapScreen.kt rename to feature/map/src/main/java/com/squirtles/feature/map/MapScreen.kt index 48a0b346..e63adf7a 100644 --- a/feature/map/src/main/java/com/squirtles/map/MapScreen.kt +++ b/feature/map/src/main/java/com/squirtles/feature/map/MapScreen.kt @@ -1,4 +1,4 @@ -package com.squirtles.map +package com.squirtles.feature.map import androidx.activity.compose.BackHandler import androidx.compose.foundation.background @@ -33,17 +33,18 @@ import androidx.lifecycle.Lifecycle import androidx.lifecycle.compose.LocalLifecycleOwner import androidx.lifecycle.compose.collectAsStateWithLifecycle import androidx.lifecycle.flowWithLifecycle -import com.squirtles.account.AccountViewModel -import com.squirtles.account.GoogleId -import com.squirtles.common.ui.SignInAlertDialog -import com.squirtles.common.ui.VerticalSpacer -import com.squirtles.common.ui.theme.Black -import com.squirtles.map.components.ClusterBottomSheet -import com.squirtles.map.components.InfoWindow -import com.squirtles.map.components.LoadingDialog -import com.squirtles.map.components.MapBottomNavBar -import com.squirtles.map.components.PickNotificationBanner -import com.squirtles.musicplayer.PlayerServiceViewModel +import com.squirtles.core.account.AccountViewModel +import com.squirtles.core.account.GoogleId +import com.squirtles.core.common.ui.SignInAlertDialog +import com.squirtles.core.common.ui.VerticalSpacer +import com.squirtles.core.common.ui.theme.Black +import com.squirtles.feature.map.components.ClusterBottomSheet +import com.squirtles.feature.map.components.InfoWindow +import com.squirtles.feature.map.components.LoadingDialog +import com.squirtles.feature.map.components.MapBottomNavBar +import com.squirtles.feature.map.components.PickNotificationBanner +import com.squirtles.core.musicplayer.PlayerServiceViewModel +import com.squirtles.map.R import kotlinx.coroutines.launch @Composable diff --git a/feature/map/src/main/java/com/squirtles/map/MapViewModel.kt b/feature/map/src/main/java/com/squirtles/feature/map/MapViewModel.kt similarity index 95% rename from feature/map/src/main/java/com/squirtles/map/MapViewModel.kt rename to feature/map/src/main/java/com/squirtles/feature/map/MapViewModel.kt index 13d948b7..0ef6faa7 100644 --- a/feature/map/src/main/java/com/squirtles/map/MapViewModel.kt +++ b/feature/map/src/main/java/com/squirtles/feature/map/MapViewModel.kt @@ -1,4 +1,4 @@ -package com.squirtles.map +package com.squirtles.feature.map import android.content.Context import android.location.Location @@ -9,12 +9,12 @@ import com.naver.maps.geometry.LatLng import com.naver.maps.map.CameraPosition import com.naver.maps.map.clustering.Clusterer import com.naver.maps.map.overlay.Marker -import com.squirtles.location.usecase.GetLastLocationUseCase -import com.squirtles.location.usecase.SaveLastLocationUseCase -import com.squirtles.map.marker.MarkerKey -import com.squirtles.model.Pick +import com.squirtles.domain.location.usecase.GetLastLocationUseCase +import com.squirtles.domain.location.usecase.SaveLastLocationUseCase +import com.squirtles.feature.map.marker.MarkerKey +import com.squirtles.core.model.Pick import com.squirtles.domain.pick.usecase.FetchPickUseCase -import com.squirtles.user.usecase.GetCurrentUidUseCase +import com.squirtles.domain.user.usecase.GetCurrentUidUseCase import dagger.hilt.android.lifecycle.HiltViewModel import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.StateFlow diff --git a/feature/map/src/main/java/com/squirtles/map/NaverMap.kt b/feature/map/src/main/java/com/squirtles/feature/map/NaverMap.kt similarity index 97% rename from feature/map/src/main/java/com/squirtles/map/NaverMap.kt rename to feature/map/src/main/java/com/squirtles/feature/map/NaverMap.kt index c9ec485b..552ec79c 100644 --- a/feature/map/src/main/java/com/squirtles/map/NaverMap.kt +++ b/feature/map/src/main/java/com/squirtles/feature/map/NaverMap.kt @@ -1,7 +1,6 @@ -package com.squirtles.map +package com.squirtles.feature.map import android.Manifest -import android.annotation.SuppressLint import android.app.Activity import android.content.Context import android.graphics.PointF @@ -42,10 +41,11 @@ import com.naver.maps.map.overlay.CircleOverlay import com.naver.maps.map.overlay.LocationOverlay import com.naver.maps.map.overlay.OverlayImage import com.naver.maps.map.util.FusedLocationSource -import com.squirtles.common.ui.theme.Primary -import com.squirtles.common.ui.theme.Purple15 -import com.squirtles.map.marker.MarkerKey -import com.squirtles.map.marker.buildClusterer +import com.squirtles.core.common.ui.theme.Primary +import com.squirtles.core.common.ui.theme.Purple15 +import com.squirtles.feature.map.marker.MarkerKey +import com.squirtles.feature.map.marker.buildClusterer +import com.squirtles.map.R import kotlinx.coroutines.launch import kotlin.coroutines.resume import kotlin.coroutines.suspendCoroutine diff --git a/feature/map/src/main/java/com/squirtles/map/components/ClusterBottomSheet.kt b/feature/map/src/main/java/com/squirtles/feature/map/components/ClusterBottomSheet.kt similarity index 86% rename from feature/map/src/main/java/com/squirtles/map/components/ClusterBottomSheet.kt rename to feature/map/src/main/java/com/squirtles/feature/map/components/ClusterBottomSheet.kt index 3dfa3af0..38abeffd 100644 --- a/feature/map/src/main/java/com/squirtles/map/components/ClusterBottomSheet.kt +++ b/feature/map/src/main/java/com/squirtles/feature/map/components/ClusterBottomSheet.kt @@ -1,4 +1,4 @@ -package com.squirtles.map.components +package com.squirtles.feature.map.components import androidx.compose.foundation.clickable import androidx.compose.foundation.layout.Arrangement @@ -21,20 +21,20 @@ import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clip import androidx.compose.ui.unit.dp -import com.squirtles.common.ui.AlbumImage -import com.squirtles.common.ui.CommentText -import com.squirtles.common.ui.Constants.DEFAULT_PADDING -import com.squirtles.common.ui.Constants.REQUEST_IMAGE_SIZE_DEFAULT -import com.squirtles.common.ui.CountText -import com.squirtles.common.ui.CreatedByOtherUserText -import com.squirtles.common.ui.CreatedBySelfText -import com.squirtles.common.ui.FavoriteCountText -import com.squirtles.common.ui.HorizontalSpacer -import com.squirtles.common.ui.SongInfoText -import com.squirtles.common.ui.VerticalSpacer -import com.squirtles.model.LocationPoint -import com.squirtles.model.Pick -import com.squirtles.model.Song +import com.squirtles.core.common.ui.AlbumImage +import com.squirtles.core.common.ui.CommentText +import com.squirtles.core.common.ui.Constants.DEFAULT_PADDING +import com.squirtles.core.common.ui.Constants.REQUEST_IMAGE_SIZE_DEFAULT +import com.squirtles.core.common.ui.CountText +import com.squirtles.core.common.ui.CreatedByOtherUserText +import com.squirtles.core.common.ui.CreatedBySelfText +import com.squirtles.core.common.ui.FavoriteCountText +import com.squirtles.core.common.ui.HorizontalSpacer +import com.squirtles.core.common.ui.SongInfoText +import com.squirtles.core.common.ui.VerticalSpacer +import com.squirtles.core.model.LocationPoint +import com.squirtles.core.model.Pick +import com.squirtles.core.model.Song import kotlinx.coroutines.launch @OptIn(ExperimentalMaterial3Api::class) diff --git a/feature/map/src/main/java/com/squirtles/map/components/InfoWindowCard.kt b/feature/map/src/main/java/com/squirtles/feature/map/components/InfoWindowCard.kt similarity index 89% rename from feature/map/src/main/java/com/squirtles/map/components/InfoWindowCard.kt rename to feature/map/src/main/java/com/squirtles/feature/map/components/InfoWindowCard.kt index 5e89330c..86796c1f 100644 --- a/feature/map/src/main/java/com/squirtles/map/components/InfoWindowCard.kt +++ b/feature/map/src/main/java/com/squirtles/feature/map/components/InfoWindowCard.kt @@ -1,4 +1,4 @@ -package com.squirtles.map.components +package com.squirtles.feature.map.components import android.content.res.Configuration.UI_MODE_NIGHT_YES import android.util.Size @@ -24,18 +24,18 @@ import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp import androidx.core.graphics.toColorInt -import com.squirtles.common.ui.AlbumImage -import com.squirtles.common.ui.CreatedByOtherUserText -import com.squirtles.common.ui.CreatedBySelfText -import com.squirtles.common.ui.FavoriteCountText -import com.squirtles.common.ui.HorizontalSpacer -import com.squirtles.common.ui.SongInfoText -import com.squirtles.common.ui.theme.Gray -import com.squirtles.common.ui.theme.MusicRoadTheme -import com.squirtles.model.Creator -import com.squirtles.model.LocationPoint -import com.squirtles.model.Pick -import com.squirtles.model.Song +import com.squirtles.core.common.ui.AlbumImage +import com.squirtles.core.common.ui.CreatedByOtherUserText +import com.squirtles.core.common.ui.CreatedBySelfText +import com.squirtles.core.common.ui.FavoriteCountText +import com.squirtles.core.common.ui.HorizontalSpacer +import com.squirtles.core.common.ui.SongInfoText +import com.squirtles.core.common.ui.theme.Gray +import com.squirtles.core.common.ui.theme.MusicRoadTheme +import com.squirtles.core.model.Creator +import com.squirtles.core.model.LocationPoint +import com.squirtles.core.model.Pick +import com.squirtles.core.model.Song @Composable fun InfoWindow( diff --git a/feature/map/src/main/java/com/squirtles/map/components/LoadingDialog.kt b/feature/map/src/main/java/com/squirtles/feature/map/components/LoadingDialog.kt similarity index 87% rename from feature/map/src/main/java/com/squirtles/map/components/LoadingDialog.kt rename to feature/map/src/main/java/com/squirtles/feature/map/components/LoadingDialog.kt index 027a83cf..47f342b3 100644 --- a/feature/map/src/main/java/com/squirtles/map/components/LoadingDialog.kt +++ b/feature/map/src/main/java/com/squirtles/feature/map/components/LoadingDialog.kt @@ -1,4 +1,4 @@ -package com.squirtles.map.components +package com.squirtles.feature.map.components import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Column @@ -20,12 +20,12 @@ import androidx.compose.ui.graphics.Color import androidx.compose.ui.res.stringResource import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp -import com.squirtles.common.ui.VerticalSpacer -import com.squirtles.common.ui.theme.Black -import com.squirtles.common.ui.theme.DarkGray -import com.squirtles.common.ui.theme.MusicRoadTheme -import com.squirtles.common.ui.theme.Primary -import com.squirtles.common.ui.theme.White +import com.squirtles.core.common.ui.VerticalSpacer +import com.squirtles.core.common.ui.theme.Black +import com.squirtles.core.common.ui.theme.DarkGray +import com.squirtles.core.common.ui.theme.MusicRoadTheme +import com.squirtles.core.common.ui.theme.Primary +import com.squirtles.core.common.ui.theme.White import com.squirtles.map.R @OptIn(ExperimentalMaterial3Api::class) diff --git a/feature/map/src/main/java/com/squirtles/map/components/MapBottomNavBar.kt b/feature/map/src/main/java/com/squirtles/feature/map/components/MapBottomNavBar.kt similarity index 93% rename from feature/map/src/main/java/com/squirtles/map/components/MapBottomNavBar.kt rename to feature/map/src/main/java/com/squirtles/feature/map/components/MapBottomNavBar.kt index f29104c6..3574f969 100644 --- a/feature/map/src/main/java/com/squirtles/map/components/MapBottomNavBar.kt +++ b/feature/map/src/main/java/com/squirtles/feature/map/components/MapBottomNavBar.kt @@ -1,4 +1,4 @@ -package com.squirtles.map.components +package com.squirtles.feature.map.components import android.content.res.Configuration import android.location.Location @@ -23,12 +23,12 @@ import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.stringResource import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp -import com.squirtles.common.ui.theme.MusicRoadTheme -import com.squirtles.common.ui.theme.Primary +import com.squirtles.core.common.ui.theme.MusicRoadTheme +import com.squirtles.core.common.ui.theme.Primary import com.squirtles.map.R -import com.squirtles.map.BottomNavigationSize -import com.squirtles.map.BottomNavigationIconSize -import com.squirtles.map.navigation.NavTab +import com.squirtles.feature.map.BottomNavigationSize +import com.squirtles.feature.map.BottomNavigationIconSize +import com.squirtles.feature.map.navigation.NavTab @Composable internal fun MapBottomNavBar( diff --git a/feature/map/src/main/java/com/squirtles/map/components/PickNotificationBanner.kt b/feature/map/src/main/java/com/squirtles/feature/map/components/PickNotificationBanner.kt similarity index 89% rename from feature/map/src/main/java/com/squirtles/map/components/PickNotificationBanner.kt rename to feature/map/src/main/java/com/squirtles/feature/map/components/PickNotificationBanner.kt index 136b8974..db08560d 100644 --- a/feature/map/src/main/java/com/squirtles/map/components/PickNotificationBanner.kt +++ b/feature/map/src/main/java/com/squirtles/feature/map/components/PickNotificationBanner.kt @@ -1,4 +1,4 @@ -package com.squirtles.map.components +package com.squirtles.feature.map.components import androidx.compose.animation.core.FastOutSlowInEasing import androidx.compose.animation.core.RepeatMode @@ -24,14 +24,14 @@ import androidx.compose.ui.res.stringResource import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp import androidx.core.graphics.toColorInt -import com.squirtles.common.ui.theme.Black -import com.squirtles.common.ui.theme.MusicRoadTheme -import com.squirtles.common.ui.theme.White +import com.squirtles.core.common.ui.theme.Black +import com.squirtles.core.common.ui.theme.MusicRoadTheme +import com.squirtles.core.common.ui.theme.White import com.squirtles.map.R -import com.squirtles.model.Creator -import com.squirtles.model.LocationPoint -import com.squirtles.model.Pick -import com.squirtles.model.Song +import com.squirtles.core.model.Creator +import com.squirtles.core.model.LocationPoint +import com.squirtles.core.model.Pick +import com.squirtles.core.model.Song @Composable fun PickNotificationBanner( @@ -99,7 +99,8 @@ private fun PickNotificationBannerPreview() { favoriteCount = 0, location = LocationPoint(1.0, 1.0), musicVideoUrl = "", - )), + ) + ), onClick = { } ) } diff --git a/feature/map/src/main/java/com/squirtles/map/marker/ClusterMarkerIconView.kt b/feature/map/src/main/java/com/squirtles/feature/map/marker/ClusterMarkerIconView.kt similarity index 97% rename from feature/map/src/main/java/com/squirtles/map/marker/ClusterMarkerIconView.kt rename to feature/map/src/main/java/com/squirtles/feature/map/marker/ClusterMarkerIconView.kt index 9dc4dfb6..0601ff39 100644 --- a/feature/map/src/main/java/com/squirtles/map/marker/ClusterMarkerIconView.kt +++ b/feature/map/src/main/java/com/squirtles/feature/map/marker/ClusterMarkerIconView.kt @@ -1,4 +1,4 @@ -package com.squirtles.map.marker +package com.squirtles.feature.map.marker import android.annotation.SuppressLint import android.content.Context diff --git a/feature/map/src/main/java/com/squirtles/map/marker/Clusterer.kt b/feature/map/src/main/java/com/squirtles/feature/map/marker/Clusterer.kt similarity index 92% rename from feature/map/src/main/java/com/squirtles/map/marker/Clusterer.kt rename to feature/map/src/main/java/com/squirtles/feature/map/marker/Clusterer.kt index 81aabc42..d613788b 100644 --- a/feature/map/src/main/java/com/squirtles/map/marker/Clusterer.kt +++ b/feature/map/src/main/java/com/squirtles/feature/map/marker/Clusterer.kt @@ -1,4 +1,4 @@ -package com.squirtles.map.marker +package com.squirtles.feature.map.marker import android.content.Context import android.graphics.PointF @@ -15,14 +15,14 @@ import com.naver.maps.map.overlay.Align import com.naver.maps.map.overlay.Marker import com.naver.maps.map.overlay.Overlay import com.naver.maps.map.overlay.OverlayImage -import com.squirtles.common.ui.Constants.REQUEST_IMAGE_SIZE_DEFAULT -import com.squirtles.common.ui.theme.Black -import com.squirtles.common.ui.theme.Blue -import com.squirtles.common.ui.theme.Primary -import com.squirtles.common.ui.theme.White -import com.squirtles.map.DEFAULT_MARKER_Z_INDEX -import com.squirtles.map.MapViewModel -import com.squirtles.map.setCameraToMarker +import com.squirtles.core.common.ui.Constants.REQUEST_IMAGE_SIZE_DEFAULT +import com.squirtles.core.common.ui.theme.Black +import com.squirtles.core.common.ui.theme.Blue +import com.squirtles.core.common.ui.theme.Primary +import com.squirtles.core.common.ui.theme.White +import com.squirtles.feature.map.DEFAULT_MARKER_Z_INDEX +import com.squirtles.feature.map.MapViewModel +import com.squirtles.feature.map.setCameraToMarker internal fun buildClusterer( context: Context, diff --git a/feature/map/src/main/java/com/squirtles/map/marker/DensityType.kt b/feature/map/src/main/java/com/squirtles/feature/map/marker/DensityType.kt similarity index 50% rename from feature/map/src/main/java/com/squirtles/map/marker/DensityType.kt rename to feature/map/src/main/java/com/squirtles/feature/map/marker/DensityType.kt index f9441c05..315902a8 100644 --- a/feature/map/src/main/java/com/squirtles/map/marker/DensityType.kt +++ b/feature/map/src/main/java/com/squirtles/feature/map/marker/DensityType.kt @@ -1,9 +1,9 @@ -package com.squirtles.map.marker +package com.squirtles.feature.map.marker import androidx.compose.ui.graphics.toArgb -import com.squirtles.common.ui.theme.Primary20 -import com.squirtles.common.ui.theme.Primary50 -import com.squirtles.common.ui.theme.Primary80 +import com.squirtles.core.common.ui.theme.Primary20 +import com.squirtles.core.common.ui.theme.Primary50 +import com.squirtles.core.common.ui.theme.Primary80 enum class DensityType(val offset: Int, val color: Int) { LOW(4, Primary80.toArgb()), diff --git a/feature/map/src/main/java/com/squirtles/map/marker/LeafMarkerIconView.kt b/feature/map/src/main/java/com/squirtles/feature/map/marker/LeafMarkerIconView.kt similarity index 95% rename from feature/map/src/main/java/com/squirtles/map/marker/LeafMarkerIconView.kt rename to feature/map/src/main/java/com/squirtles/feature/map/marker/LeafMarkerIconView.kt index 76e3b906..521aee18 100644 --- a/feature/map/src/main/java/com/squirtles/map/marker/LeafMarkerIconView.kt +++ b/feature/map/src/main/java/com/squirtles/feature/map/marker/LeafMarkerIconView.kt @@ -1,4 +1,4 @@ -package com.squirtles.map.marker +package com.squirtles.feature.map.marker import android.content.Context import android.graphics.Bitmap @@ -14,8 +14,6 @@ import coil3.request.allowHardware import coil3.request.transformations import coil3.toBitmap import coil3.transform.CircleCropTransformation -import com.squirtles.common.ui.Constants.REQUEST_IMAGE_SIZE_DEFAULT -import com.squirtles.model.Pick class LeafMarkerIconView( context: Context, diff --git a/feature/map/src/main/java/com/squirtles/map/marker/MarkerKey.kt b/feature/map/src/main/java/com/squirtles/feature/map/marker/MarkerKey.kt similarity index 75% rename from feature/map/src/main/java/com/squirtles/map/marker/MarkerKey.kt rename to feature/map/src/main/java/com/squirtles/feature/map/marker/MarkerKey.kt index 6a6c5ced..4c679880 100644 --- a/feature/map/src/main/java/com/squirtles/map/marker/MarkerKey.kt +++ b/feature/map/src/main/java/com/squirtles/feature/map/marker/MarkerKey.kt @@ -1,8 +1,8 @@ -package com.squirtles.map.marker +package com.squirtles.feature.map.marker import com.naver.maps.geometry.LatLng import com.naver.maps.map.clustering.ClusteringKey -import com.squirtles.model.Pick +import com.squirtles.core.model.Pick data class MarkerKey(val pick: Pick) : ClusteringKey { override fun getPosition() = LatLng(pick.location.latitude, pick.location.longitude) diff --git a/feature/map/src/main/java/com/squirtles/map/navigation/MapNavigation.kt b/feature/map/src/main/java/com/squirtles/feature/map/navigation/MapNavigation.kt similarity index 80% rename from feature/map/src/main/java/com/squirtles/map/navigation/MapNavigation.kt rename to feature/map/src/main/java/com/squirtles/feature/map/navigation/MapNavigation.kt index 476dd4d7..20fb13cd 100644 --- a/feature/map/src/main/java/com/squirtles/map/navigation/MapNavigation.kt +++ b/feature/map/src/main/java/com/squirtles/feature/map/navigation/MapNavigation.kt @@ -1,13 +1,13 @@ -package com.squirtles.map.navigation +package com.squirtles.feature.map.navigation import androidx.navigation.NavController import androidx.navigation.NavGraphBuilder import androidx.navigation.NavOptions import androidx.navigation.compose.composable -import com.squirtles.map.MapScreen -import com.squirtles.map.MapViewModel -import com.squirtles.musicplayer.PlayerServiceViewModel -import com.squirtles.navigation.Route +import com.squirtles.feature.map.MapScreen +import com.squirtles.feature.map.MapViewModel +import com.squirtles.core.musicplayer.PlayerServiceViewModel +import com.squirtles.core.navigation.Route fun NavController.navigateMap(navOptions: NavOptions? = null) { navigate(Route.Map, navOptions) diff --git a/feature/map/src/main/java/com/squirtles/map/navigation/NavTab.kt b/feature/map/src/main/java/com/squirtles/feature/map/navigation/NavTab.kt similarity index 90% rename from feature/map/src/main/java/com/squirtles/map/navigation/NavTab.kt rename to feature/map/src/main/java/com/squirtles/feature/map/navigation/NavTab.kt index 022106af..1a0888d5 100644 --- a/feature/map/src/main/java/com/squirtles/map/navigation/NavTab.kt +++ b/feature/map/src/main/java/com/squirtles/feature/map/navigation/NavTab.kt @@ -1,4 +1,4 @@ -package com.squirtles.map.navigation +package com.squirtles.feature.map.navigation import androidx.annotation.StringRes import androidx.compose.material.icons.Icons @@ -7,7 +7,7 @@ import androidx.compose.material.icons.outlined.AccountCircle import androidx.compose.material.icons.outlined.MusicNote import androidx.compose.ui.graphics.vector.ImageVector import com.squirtles.map.R -import com.squirtles.map.BottomNavigationIconSize +import com.squirtles.feature.map.BottomNavigationIconSize internal enum class NavTab( @StringRes val contentDescription: Int, diff --git a/feature/map/src/test/java/com/squirtles/map/ExampleUnitTest.kt b/feature/map/src/test/java/com/squirtles/feature/map/ExampleUnitTest.kt similarity index 100% rename from feature/map/src/test/java/com/squirtles/map/ExampleUnitTest.kt rename to feature/map/src/test/java/com/squirtles/feature/map/ExampleUnitTest.kt diff --git a/feature/mypick/src/androidTest/java/com/squirtles/mypick/ExampleInstrumentedTest.kt b/feature/mypick/src/androidTest/java/com/squirtles/feature/mypick/ExampleInstrumentedTest.kt similarity index 100% rename from feature/mypick/src/androidTest/java/com/squirtles/mypick/ExampleInstrumentedTest.kt rename to feature/mypick/src/androidTest/java/com/squirtles/feature/mypick/ExampleInstrumentedTest.kt diff --git a/feature/mypick/src/main/java/com/squirtles/mypick/MyPickListViewModel.kt b/feature/mypick/src/main/java/com/squirtles/feature/mypick/MyPickListViewModel.kt similarity index 73% rename from feature/mypick/src/main/java/com/squirtles/mypick/MyPickListViewModel.kt rename to feature/mypick/src/main/java/com/squirtles/feature/mypick/MyPickListViewModel.kt index fef28f3e..d9ccb756 100644 --- a/feature/mypick/src/main/java/com/squirtles/mypick/MyPickListViewModel.kt +++ b/feature/mypick/src/main/java/com/squirtles/feature/mypick/MyPickListViewModel.kt @@ -1,11 +1,11 @@ -package com.squirtles.mypick +package com.squirtles.feature.mypick -import com.squirtles.order.usecase.GetMyPickListOrderUseCase -import com.squirtles.order.usecase.SaveMyPickListOrderUseCase +import com.squirtles.domain.order.usecase.GetMyPickListOrderUseCase +import com.squirtles.domain.order.usecase.SaveMyPickListOrderUseCase import com.squirtles.domain.pick.usecase.DeletePickUseCase import com.squirtles.domain.pick.usecase.FetchMyPicksUseCase -import com.squirtles.picklist.PickListViewModel -import com.squirtles.user.usecase.GetCurrentUidUseCase +import com.squirtles.core.picklist.PickListViewModel +import com.squirtles.domain.user.usecase.GetCurrentUidUseCase import dagger.hilt.android.lifecycle.HiltViewModel import javax.inject.Inject diff --git a/feature/mypick/src/main/java/com/squirtles/mypick/MyPickScreen.kt b/feature/mypick/src/main/java/com/squirtles/feature/mypick/MyPickScreen.kt similarity index 92% rename from feature/mypick/src/main/java/com/squirtles/mypick/MyPickScreen.kt rename to feature/mypick/src/main/java/com/squirtles/feature/mypick/MyPickScreen.kt index 96e19d11..acdd6f3b 100644 --- a/feature/mypick/src/main/java/com/squirtles/mypick/MyPickScreen.kt +++ b/feature/mypick/src/main/java/com/squirtles/feature/mypick/MyPickScreen.kt @@ -1,4 +1,4 @@ -package com.squirtles.mypick +package com.squirtles.feature.mypick import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect @@ -8,8 +8,8 @@ import androidx.compose.runtime.saveable.rememberSaveable import androidx.compose.runtime.setValue import androidx.hilt.navigation.compose.hiltViewModel import androidx.lifecycle.compose.collectAsStateWithLifecycle -import com.squirtles.picklist.PickListScreenContents -import com.squirtles.picklist.PickListType +import com.squirtles.core.picklist.PickListScreenContents +import com.squirtles.core.picklist.PickListType @Composable fun MyPickScreen( diff --git a/feature/mypick/src/main/java/com/squirtles/mypick/navigation/MyPickNavigation.kt b/feature/mypick/src/main/java/com/squirtles/feature/mypick/navigation/MyPickNavigation.kt similarity index 82% rename from feature/mypick/src/main/java/com/squirtles/mypick/navigation/MyPickNavigation.kt rename to feature/mypick/src/main/java/com/squirtles/feature/mypick/navigation/MyPickNavigation.kt index a5cd36b2..7a9e0db3 100644 --- a/feature/mypick/src/main/java/com/squirtles/mypick/navigation/MyPickNavigation.kt +++ b/feature/mypick/src/main/java/com/squirtles/feature/mypick/navigation/MyPickNavigation.kt @@ -1,12 +1,12 @@ -package com.squirtles.mypick.navigation +package com.squirtles.feature.mypick.navigation import androidx.navigation.NavController import androidx.navigation.NavGraphBuilder import androidx.navigation.NavOptions import androidx.navigation.compose.composable import androidx.navigation.toRoute -import com.squirtles.mypick.MyPickScreen -import com.squirtles.navigation.UserInfoRoute +import com.squirtles.feature.mypick.MyPickScreen +import com.squirtles.core.navigation.UserInfoRoute fun NavController.navigateMyPicks(uid: String, navOptions: NavOptions) { navigate(UserInfoRoute.MyPicks(uid), navOptions) diff --git a/feature/mypick/src/test/java/com/squirtles/mypick/ExampleUnitTest.kt b/feature/mypick/src/test/java/com/squirtles/feature/mypick/ExampleUnitTest.kt similarity index 100% rename from feature/mypick/src/test/java/com/squirtles/mypick/ExampleUnitTest.kt rename to feature/mypick/src/test/java/com/squirtles/feature/mypick/ExampleUnitTest.kt diff --git a/feature/search/src/androidTest/java/com/squirtles/search/ExampleInstrumentedTest.kt b/feature/search/src/androidTest/java/com/squirtles/feature/search/ExampleInstrumentedTest.kt similarity index 100% rename from feature/search/src/androidTest/java/com/squirtles/search/ExampleInstrumentedTest.kt rename to feature/search/src/androidTest/java/com/squirtles/feature/search/ExampleInstrumentedTest.kt diff --git a/feature/search/src/main/java/com/squirtles/search/SearchMusicScreen.kt b/feature/search/src/main/java/com/squirtles/feature/search/SearchMusicScreen.kt similarity index 93% rename from feature/search/src/main/java/com/squirtles/search/SearchMusicScreen.kt rename to feature/search/src/main/java/com/squirtles/feature/search/SearchMusicScreen.kt index 275000d5..956254a1 100644 --- a/feature/search/src/main/java/com/squirtles/search/SearchMusicScreen.kt +++ b/feature/search/src/main/java/com/squirtles/feature/search/SearchMusicScreen.kt @@ -1,4 +1,4 @@ -package com.squirtles.search +package com.squirtles.feature.search import androidx.compose.foundation.background import androidx.compose.foundation.clickable @@ -55,18 +55,19 @@ import androidx.lifecycle.compose.collectAsStateWithLifecycle import androidx.paging.LoadState import androidx.paging.compose.LazyPagingItems import androidx.paging.compose.collectAsLazyPagingItems -import com.squirtles.common.ui.AlbumImage -import com.squirtles.common.ui.Constants.COLOR_STOPS -import com.squirtles.common.ui.Constants.REQUEST_IMAGE_SIZE_DEFAULT -import com.squirtles.common.ui.HorizontalSpacer -import com.squirtles.common.ui.VerticalSpacer -import com.squirtles.common.ui.theme.Gray -import com.squirtles.common.ui.theme.White -import com.squirtles.model.Song -import com.squirtles.search.SearchUiConstants.DefaultPadding -import com.squirtles.search.SearchUiConstants.ImageSize -import com.squirtles.search.SearchUiConstants.ItemSpacing -import com.squirtles.search.SearchUiConstants.SearchBarHeight +import com.squirtles.core.common.ui.AlbumImage +import com.squirtles.core.common.ui.Constants.COLOR_STOPS +import com.squirtles.core.common.ui.Constants.REQUEST_IMAGE_SIZE_DEFAULT +import com.squirtles.core.common.ui.HorizontalSpacer +import com.squirtles.core.common.ui.VerticalSpacer +import com.squirtles.core.common.ui.theme.Gray +import com.squirtles.core.common.ui.theme.White +import com.squirtles.core.model.Song +import com.squirtles.feature.search.SearchUiConstants.DefaultPadding +import com.squirtles.feature.search.SearchUiConstants.ImageSize +import com.squirtles.feature.search.SearchUiConstants.ItemSpacing +import com.squirtles.feature.search.SearchUiConstants.SearchBarHeight +import com.squirtles.search.R @Composable fun SearchMusicScreen( diff --git a/feature/search/src/main/java/com/squirtles/search/SearchUiConstants.kt b/feature/search/src/main/java/com/squirtles/feature/search/SearchUiConstants.kt similarity index 83% rename from feature/search/src/main/java/com/squirtles/search/SearchUiConstants.kt rename to feature/search/src/main/java/com/squirtles/feature/search/SearchUiConstants.kt index d093c226..2933270e 100644 --- a/feature/search/src/main/java/com/squirtles/search/SearchUiConstants.kt +++ b/feature/search/src/main/java/com/squirtles/feature/search/SearchUiConstants.kt @@ -1,4 +1,4 @@ -package com.squirtles.search +package com.squirtles.feature.search import androidx.compose.ui.unit.dp diff --git a/feature/search/src/main/java/com/squirtles/search/SearchUiState.kt b/feature/search/src/main/java/com/squirtles/feature/search/SearchUiState.kt similarity index 76% rename from feature/search/src/main/java/com/squirtles/search/SearchUiState.kt rename to feature/search/src/main/java/com/squirtles/feature/search/SearchUiState.kt index 6ef6effb..956bbb94 100644 --- a/feature/search/src/main/java/com/squirtles/search/SearchUiState.kt +++ b/feature/search/src/main/java/com/squirtles/feature/search/SearchUiState.kt @@ -1,4 +1,4 @@ -package com.squirtles.search +package com.squirtles.feature.search sealed class SearchUiState { data object HotResult : SearchUiState() diff --git a/feature/search/src/main/java/com/squirtles/search/SearchViewModel.kt b/feature/search/src/main/java/com/squirtles/feature/search/SearchViewModel.kt similarity index 93% rename from feature/search/src/main/java/com/squirtles/search/SearchViewModel.kt rename to feature/search/src/main/java/com/squirtles/feature/search/SearchViewModel.kt index d25dd4c5..77e11936 100644 --- a/feature/search/src/main/java/com/squirtles/search/SearchViewModel.kt +++ b/feature/search/src/main/java/com/squirtles/feature/search/SearchViewModel.kt @@ -1,11 +1,11 @@ -package com.squirtles.search +package com.squirtles.feature.search import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope import androidx.paging.PagingData import androidx.paging.cachedIn -import com.squirtles.applemusic.usecase.FetchSongsUseCase -import com.squirtles.model.Song +import com.squirtles.domain.applemusic.usecase.FetchSongsUseCase +import com.squirtles.core.model.Song import dagger.hilt.android.lifecycle.HiltViewModel import kotlinx.coroutines.FlowPreview import kotlinx.coroutines.Job diff --git a/feature/search/src/main/java/com/squirtles/search/navigation/SearchNavigation.kt b/feature/search/src/main/java/com/squirtles/feature/search/navigation/SearchNavigation.kt similarity index 75% rename from feature/search/src/main/java/com/squirtles/search/navigation/SearchNavigation.kt rename to feature/search/src/main/java/com/squirtles/feature/search/navigation/SearchNavigation.kt index 2f461ce2..f5deca88 100644 --- a/feature/search/src/main/java/com/squirtles/search/navigation/SearchNavigation.kt +++ b/feature/search/src/main/java/com/squirtles/feature/search/navigation/SearchNavigation.kt @@ -1,12 +1,12 @@ -package com.squirtles.search.navigation +package com.squirtles.feature.search.navigation import androidx.navigation.NavController import androidx.navigation.NavGraphBuilder import androidx.navigation.NavOptions import androidx.navigation.compose.composable -import com.squirtles.model.Song -import com.squirtles.navigation.MainRoute -import com.squirtles.search.SearchMusicScreen +import com.squirtles.core.model.Song +import com.squirtles.core.navigation.MainRoute +import com.squirtles.feature.search.SearchMusicScreen fun NavController.navigateSearch(navOptions: NavOptions? = null) { navigate(MainRoute.Search, navOptions) diff --git a/feature/search/src/test/java/com/squirtles/search/ExampleUnitTest.kt b/feature/search/src/test/java/com/squirtles/feature/search/ExampleUnitTest.kt similarity index 100% rename from feature/search/src/test/java/com/squirtles/search/ExampleUnitTest.kt rename to feature/search/src/test/java/com/squirtles/feature/search/ExampleUnitTest.kt diff --git a/feature/userinfo/src/androidTest/java/com/squirtles/userinfo/ExampleInstrumentedTest.kt b/feature/userinfo/src/androidTest/java/com/squirtles/feature/userinfo/ExampleInstrumentedTest.kt similarity index 100% rename from feature/userinfo/src/androidTest/java/com/squirtles/userinfo/ExampleInstrumentedTest.kt rename to feature/userinfo/src/androidTest/java/com/squirtles/feature/userinfo/ExampleInstrumentedTest.kt diff --git a/feature/userinfo/src/main/java/com/squirtles/userinfo/UserInfoConstants.kt b/feature/userinfo/src/main/java/com/squirtles/feature/userinfo/UserInfoConstants.kt similarity index 79% rename from feature/userinfo/src/main/java/com/squirtles/userinfo/UserInfoConstants.kt rename to feature/userinfo/src/main/java/com/squirtles/feature/userinfo/UserInfoConstants.kt index 137a25d3..c8535066 100644 --- a/feature/userinfo/src/main/java/com/squirtles/userinfo/UserInfoConstants.kt +++ b/feature/userinfo/src/main/java/com/squirtles/feature/userinfo/UserInfoConstants.kt @@ -1,7 +1,7 @@ -package com.squirtles.userinfo +package com.squirtles.feature.userinfo import androidx.compose.ui.unit.dp -import com.squirtles.model.User +import com.squirtles.core.model.User internal object UserInfoConstants { const val USERNAME_PATTERN = "^[ㄱ-ㅎ|ㅏ-ㅣ가-힣a-zA-Z0-9]+$" diff --git a/feature/userinfo/src/main/java/com/squirtles/userinfo/UserInfoViewModel.kt b/feature/userinfo/src/main/java/com/squirtles/feature/userinfo/UserInfoViewModel.kt similarity index 83% rename from feature/userinfo/src/main/java/com/squirtles/userinfo/UserInfoViewModel.kt rename to feature/userinfo/src/main/java/com/squirtles/feature/userinfo/UserInfoViewModel.kt index e237332e..1670387b 100644 --- a/feature/userinfo/src/main/java/com/squirtles/userinfo/UserInfoViewModel.kt +++ b/feature/userinfo/src/main/java/com/squirtles/feature/userinfo/UserInfoViewModel.kt @@ -1,12 +1,11 @@ -package com.squirtles.userinfo +package com.squirtles.feature.userinfo import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope -import com.squirtles.model.User -import com.squirtles.user.usecase.FetchUserByIdUseCase -import com.squirtles.user.usecase.GetCurrentUidUseCase -import com.squirtles.user.usecase.UpdateUserNameUseCase -import com.squirtles.userinfo.UserInfoConstants.DEFAULT_USER +import com.squirtles.domain.user.usecase.FetchUserByIdUseCase +import com.squirtles.domain.user.usecase.GetCurrentUidUseCase +import com.squirtles.domain.user.usecase.UpdateUserNameUseCase +import com.squirtles.feature.userinfo.UserInfoConstants.DEFAULT_USER import dagger.hilt.android.lifecycle.HiltViewModel import kotlinx.coroutines.flow.MutableSharedFlow import kotlinx.coroutines.flow.MutableStateFlow diff --git a/feature/userinfo/src/main/java/com/squirtles/userinfo/components/MenuItem.kt b/feature/userinfo/src/main/java/com/squirtles/feature/userinfo/components/MenuItem.kt similarity index 74% rename from feature/userinfo/src/main/java/com/squirtles/userinfo/components/MenuItem.kt rename to feature/userinfo/src/main/java/com/squirtles/feature/userinfo/components/MenuItem.kt index cc25460e..e8278f72 100644 --- a/feature/userinfo/src/main/java/com/squirtles/userinfo/components/MenuItem.kt +++ b/feature/userinfo/src/main/java/com/squirtles/feature/userinfo/components/MenuItem.kt @@ -1,8 +1,8 @@ -package com.squirtles.userinfo.components +package com.squirtles.feature.userinfo.components import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.vector.ImageVector -import com.squirtles.common.ui.theme.White +import com.squirtles.core.common.ui.theme.White data class MenuItem( val imageVector: ImageVector, diff --git a/feature/userinfo/src/main/java/com/squirtles/userinfo/components/UserInfoMenus.kt b/feature/userinfo/src/main/java/com/squirtles/feature/userinfo/components/UserInfoMenus.kt similarity index 89% rename from feature/userinfo/src/main/java/com/squirtles/userinfo/components/UserInfoMenus.kt rename to feature/userinfo/src/main/java/com/squirtles/feature/userinfo/components/UserInfoMenus.kt index 073432a5..026526dd 100644 --- a/feature/userinfo/src/main/java/com/squirtles/userinfo/components/UserInfoMenus.kt +++ b/feature/userinfo/src/main/java/com/squirtles/feature/userinfo/components/UserInfoMenus.kt @@ -1,4 +1,4 @@ -package com.squirtles.userinfo.components +package com.squirtles.feature.userinfo.components import androidx.compose.foundation.clickable import androidx.compose.foundation.interaction.MutableInteractionSource @@ -24,13 +24,13 @@ import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.TextStyle import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.unit.dp -import com.squirtles.common.ui.Constants.DEFAULT_PADDING -import com.squirtles.common.ui.VerticalSpacer -import com.squirtles.common.ui.theme.Gray -import com.squirtles.common.ui.theme.White +import com.squirtles.core.common.ui.Constants.DEFAULT_PADDING +import com.squirtles.core.common.ui.VerticalSpacer +import com.squirtles.core.common.ui.theme.Gray +import com.squirtles.core.common.ui.theme.White import com.squirtles.userinfo.R -import com.squirtles.userinfo.UserInfoConstants.MENU_PADDING_HORIZONTAL -import com.squirtles.userinfo.UserInfoConstants.MENU_PADDING_VERTICAL +import com.squirtles.feature.userinfo.UserInfoConstants.MENU_PADDING_HORIZONTAL +import com.squirtles.feature.userinfo.UserInfoConstants.MENU_PADDING_VERTICAL @Composable internal fun UserInfoMenus( diff --git a/feature/userinfo/src/main/java/com/squirtles/userinfo/navigation/UserInfoNavigation.kt b/feature/userinfo/src/main/java/com/squirtles/feature/userinfo/navigation/UserInfoNavigation.kt similarity index 84% rename from feature/userinfo/src/main/java/com/squirtles/userinfo/navigation/UserInfoNavigation.kt rename to feature/userinfo/src/main/java/com/squirtles/feature/userinfo/navigation/UserInfoNavigation.kt index 5d926763..53014e14 100644 --- a/feature/userinfo/src/main/java/com/squirtles/userinfo/navigation/UserInfoNavigation.kt +++ b/feature/userinfo/src/main/java/com/squirtles/feature/userinfo/navigation/UserInfoNavigation.kt @@ -1,15 +1,15 @@ -package com.squirtles.userinfo.navigation +package com.squirtles.feature.userinfo.navigation import androidx.navigation.NavController import androidx.navigation.NavGraphBuilder import androidx.navigation.NavOptions import androidx.navigation.compose.composable import androidx.navigation.toRoute -import com.squirtles.navigation.MainRoute -import com.squirtles.navigation.UserInfoRoute -import com.squirtles.userinfo.screen.EditNotificationSettingScreen -import com.squirtles.userinfo.screen.EditProfileScreen -import com.squirtles.userinfo.screen.UserInfoScreen +import com.squirtles.core.navigation.MainRoute +import com.squirtles.core.navigation.UserInfoRoute +import com.squirtles.feature.userinfo.screen.EditNotificationSettingScreen +import com.squirtles.feature.userinfo.screen.EditProfileScreen +import com.squirtles.feature.userinfo.screen.UserInfoScreen fun NavController.navigateUserInfo(uid: String, navOptions: NavOptions? = null) { navigate(MainRoute.UserInfo(uid), navOptions) diff --git a/feature/userinfo/src/main/java/com/squirtles/userinfo/screen/EditNotificationSettingScreen.kt b/feature/userinfo/src/main/java/com/squirtles/feature/userinfo/screen/EditNotificationSettingScreen.kt similarity index 84% rename from feature/userinfo/src/main/java/com/squirtles/userinfo/screen/EditNotificationSettingScreen.kt rename to feature/userinfo/src/main/java/com/squirtles/feature/userinfo/screen/EditNotificationSettingScreen.kt index b9e7fc28..a06d08ea 100644 --- a/feature/userinfo/src/main/java/com/squirtles/userinfo/screen/EditNotificationSettingScreen.kt +++ b/feature/userinfo/src/main/java/com/squirtles/feature/userinfo/screen/EditNotificationSettingScreen.kt @@ -1,4 +1,4 @@ -package com.squirtles.userinfo.screen +package com.squirtles.feature.userinfo.screen import androidx.compose.foundation.background import androidx.compose.foundation.layout.Box @@ -12,10 +12,10 @@ import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Brush import androidx.compose.ui.res.stringResource import androidx.wear.compose.material.Text -import com.squirtles.common.ui.Constants.COLOR_STOPS -import com.squirtles.common.ui.Constants.DEFAULT_PADDING -import com.squirtles.common.ui.DefaultTopAppBar -import com.squirtles.common.ui.theme.White +import com.squirtles.core.common.ui.Constants.COLOR_STOPS +import com.squirtles.core.common.ui.Constants.DEFAULT_PADDING +import com.squirtles.core.common.ui.DefaultTopAppBar +import com.squirtles.core.common.ui.theme.White import com.squirtles.userinfo.R @Composable diff --git a/feature/userinfo/src/main/java/com/squirtles/userinfo/screen/EditProfileScreen.kt b/feature/userinfo/src/main/java/com/squirtles/feature/userinfo/screen/EditProfileScreen.kt similarity index 93% rename from feature/userinfo/src/main/java/com/squirtles/userinfo/screen/EditProfileScreen.kt rename to feature/userinfo/src/main/java/com/squirtles/feature/userinfo/screen/EditProfileScreen.kt index 7f430f0b..f5c475df 100644 --- a/feature/userinfo/src/main/java/com/squirtles/userinfo/screen/EditProfileScreen.kt +++ b/feature/userinfo/src/main/java/com/squirtles/feature/userinfo/screen/EditProfileScreen.kt @@ -1,4 +1,4 @@ -package com.squirtles.userinfo.screen +package com.squirtles.feature.userinfo.screen import android.content.Context import android.widget.Toast @@ -51,21 +51,21 @@ import androidx.hilt.navigation.compose.hiltViewModel import androidx.lifecycle.Lifecycle import androidx.lifecycle.compose.LocalLifecycleOwner import androidx.lifecycle.flowWithLifecycle -import com.squirtles.account.AccountViewModel -import com.squirtles.account.GoogleId -import com.squirtles.common.ui.Constants.COLOR_STOPS -import com.squirtles.common.ui.DialogTextButton -import com.squirtles.common.ui.HorizontalSpacer -import com.squirtles.common.ui.MessageAlertDialog -import com.squirtles.common.ui.theme.Black -import com.squirtles.common.ui.theme.DarkGray -import com.squirtles.common.ui.theme.Gray -import com.squirtles.common.ui.theme.MusicRoadTheme -import com.squirtles.common.ui.theme.Primary -import com.squirtles.common.ui.theme.White +import com.squirtles.core.account.AccountViewModel +import com.squirtles.core.account.GoogleId +import com.squirtles.core.common.ui.Constants.COLOR_STOPS +import com.squirtles.core.common.ui.DialogTextButton +import com.squirtles.core.common.ui.HorizontalSpacer +import com.squirtles.core.common.ui.MessageAlertDialog +import com.squirtles.core.common.ui.theme.Black +import com.squirtles.core.common.ui.theme.DarkGray +import com.squirtles.core.common.ui.theme.Gray +import com.squirtles.core.common.ui.theme.MusicRoadTheme +import com.squirtles.core.common.ui.theme.Primary +import com.squirtles.core.common.ui.theme.White import com.squirtles.userinfo.R -import com.squirtles.userinfo.UserInfoConstants.USERNAME_PATTERN -import com.squirtles.userinfo.UserInfoViewModel +import com.squirtles.feature.userinfo.UserInfoConstants.USERNAME_PATTERN +import com.squirtles.feature.userinfo.UserInfoViewModel import kotlinx.coroutines.delay import kotlinx.coroutines.launch import java.util.regex.Pattern diff --git a/feature/userinfo/src/main/java/com/squirtles/userinfo/screen/UserInfoScreen.kt b/feature/userinfo/src/main/java/com/squirtles/feature/userinfo/screen/UserInfoScreen.kt similarity index 93% rename from feature/userinfo/src/main/java/com/squirtles/userinfo/screen/UserInfoScreen.kt rename to feature/userinfo/src/main/java/com/squirtles/feature/userinfo/screen/UserInfoScreen.kt index d8ed99cf..625aecd4 100644 --- a/feature/userinfo/src/main/java/com/squirtles/userinfo/screen/UserInfoScreen.kt +++ b/feature/userinfo/src/main/java/com/squirtles/feature/userinfo/screen/UserInfoScreen.kt @@ -1,4 +1,4 @@ -package com.squirtles.userinfo.screen +package com.squirtles.feature.userinfo.screen import androidx.activity.compose.BackHandler import androidx.compose.foundation.background @@ -51,21 +51,21 @@ import androidx.lifecycle.flowWithLifecycle import coil3.compose.AsyncImage import coil3.request.ImageRequest import coil3.request.crossfade -import com.squirtles.account.AccountViewModel -import com.squirtles.account.GoogleId -import com.squirtles.common.ui.Constants.COLOR_STOPS -import com.squirtles.common.ui.DefaultTopAppBar -import com.squirtles.common.ui.DialogTextButton -import com.squirtles.common.ui.HorizontalSpacer -import com.squirtles.common.ui.MessageAlertDialog -import com.squirtles.common.ui.VerticalSpacer -import com.squirtles.common.ui.theme.Black -import com.squirtles.common.ui.theme.Primary -import com.squirtles.common.ui.theme.White +import com.squirtles.core.account.AccountViewModel +import com.squirtles.core.account.GoogleId +import com.squirtles.core.common.ui.Constants.COLOR_STOPS +import com.squirtles.core.common.ui.DefaultTopAppBar +import com.squirtles.core.common.ui.DialogTextButton +import com.squirtles.core.common.ui.HorizontalSpacer +import com.squirtles.core.common.ui.MessageAlertDialog +import com.squirtles.core.common.ui.VerticalSpacer +import com.squirtles.core.common.ui.theme.Black +import com.squirtles.core.common.ui.theme.Primary +import com.squirtles.core.common.ui.theme.White import com.squirtles.userinfo.R -import com.squirtles.userinfo.UserInfoViewModel -import com.squirtles.userinfo.components.MenuItem -import com.squirtles.userinfo.components.UserInfoMenus +import com.squirtles.feature.userinfo.UserInfoViewModel +import com.squirtles.feature.userinfo.components.MenuItem +import com.squirtles.feature.userinfo.components.UserInfoMenus import kotlinx.coroutines.launch @Composable diff --git a/feature/userinfo/src/test/java/com/squirtles/userinfo/ExampleUnitTest.kt b/feature/userinfo/src/test/java/com/squirtles/feature/userinfo/ExampleUnitTest.kt similarity index 100% rename from feature/userinfo/src/test/java/com/squirtles/userinfo/ExampleUnitTest.kt rename to feature/userinfo/src/test/java/com/squirtles/feature/userinfo/ExampleUnitTest.kt From 3e69c9dd47698dc55d86183041134b8d3eaf57ba Mon Sep 17 00:00:00 2001 From: miller198 Date: Wed, 23 Apr 2025 20:45:16 +0900 Subject: [PATCH 58/62] =?UTF-8?q?[chore]=20android=20namespace=20=EB=B3=80?= =?UTF-8?q?=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- core/account/build.gradle.kts | 2 +- .../src/main/java/com/squirtles/core/account/GoogleId.kt | 1 - core/buildconfig/build.gradle.kts | 2 +- .../com/squirtles/core/buildconfig/LocalPropertyProvider.kt | 2 -- core/common/build.gradle.kts | 2 +- .../src/main/java/com/squirtles/core/common/ui/AlbumImage.kt | 2 +- .../java/com/squirtles/core/common/ui/CreatedByPickText.kt | 2 +- .../main/java/com/squirtles/core/common/ui/DefaultTopAppBar.kt | 2 +- .../java/com/squirtles/core/common/ui/MessageAlertDialog.kt | 2 +- .../src/main/java/com/squirtles/core/common/ui/PickInfoText.kt | 2 +- .../java/com/squirtles/core/common/ui/SignInAlertDialog.kt | 2 +- core/mediaservice/build.gradle.kts | 2 +- .../squirtles/core/mediaservice/MediaNotificationProvider.kt | 2 +- core/musicplayer/build.gradle.kts | 2 +- core/picklist/build.gradle.kts | 2 +- .../java/com/squirtles/core/picklist/PickListScreenContents.kt | 1 - .../core/picklist/components/DeleteSelectedPickDialog.kt | 2 +- .../com/squirtles/core/picklist/components/EditModeAction.kt | 2 +- .../squirtles/core/picklist/components/EditModeBottomButton.kt | 2 +- .../com/squirtles/core/picklist/components/OrderBottomSheet.kt | 2 +- .../java/com/squirtles/core/picklist/components/PickItem.kt | 2 +- core/util/build.gradle.kts | 2 +- data/applemusic/build.gradle.kts | 2 +- data/favorite/build.gradle.kts | 2 +- data/firebase/build.gradle.kts | 2 +- data/location/build.gradle.kts | 2 +- data/order/build.gradle.kts | 2 +- data/pick/build.gradle.kts | 2 +- data/user/build.gradle.kts | 2 +- domain/applemusic/build.gradle.kts | 2 +- .../domain/applemusic/usecase/FetchMusicVideoUseCase.kt | 1 - domain/location/build.gradle.kts | 2 +- domain/player/build.gradle.kts | 2 +- feature/create/build.gradle.kts | 2 +- .../main/java/com/squirtles/feature/create/CreatePickScreen.kt | 1 - feature/detail/build.gradle.kts | 2 +- .../main/java/com/squirtles/feature/detail/PickDetailScreen.kt | 1 - .../squirtles/feature/detail/components/CircleAlbumCover.kt | 2 +- .../squirtles/feature/detail/components/DetailPickTopAppBar.kt | 2 +- .../com/squirtles/feature/detail/components/MusicVideoKnob.kt | 2 +- .../com/squirtles/feature/detail/components/PickCommentText.kt | 2 +- .../com/squirtles/feature/detail/components/PickInformation.kt | 2 +- .../com/squirtles/feature/detail/components/SwipeUpIcon.kt | 2 +- .../feature/detail/components/music/PlayerControls.kt | 2 +- .../squirtles/feature/detail/videoplayer/VideoPlayerOverlay.kt | 2 +- .../feature/detail/videoplayer/VideoPlayerViewModel.kt | 2 +- feature/favorite/build.gradle.kts | 2 +- feature/main/build.gradle.kts | 2 +- .../src/main/java/com/squirtles/feature/main/MainActivity.kt | 1 - .../java/com/squirtles/feature/main/NeedPermissionDialog.kt | 1 - .../src/main/java/com/squirtles/feature/main/PermissionBar.kt | 1 - feature/map/build.gradle.kts | 2 +- .../map/src/main/java/com/squirtles/feature/map/MapScreen.kt | 3 +-- .../map/src/main/java/com/squirtles/feature/map/NaverMap.kt | 1 - .../java/com/squirtles/feature/map/components/LoadingDialog.kt | 2 +- .../com/squirtles/feature/map/components/MapBottomNavBar.kt | 2 +- .../squirtles/feature/map/components/PickNotificationBanner.kt | 2 +- .../main/java/com/squirtles/feature/map/navigation/NavTab.kt | 2 +- feature/mypick/build.gradle.kts | 2 +- feature/search/build.gradle.kts | 2 +- .../java/com/squirtles/feature/search/SearchMusicScreen.kt | 1 - feature/userinfo/build.gradle.kts | 2 +- .../com/squirtles/feature/userinfo/components/UserInfoMenus.kt | 2 +- .../feature/userinfo/screen/EditNotificationSettingScreen.kt | 2 +- .../com/squirtles/feature/userinfo/screen/EditProfileScreen.kt | 2 +- .../com/squirtles/feature/userinfo/screen/UserInfoScreen.kt | 2 +- 66 files changed, 55 insertions(+), 68 deletions(-) diff --git a/core/account/build.gradle.kts b/core/account/build.gradle.kts index f5244594..fb2d6ecc 100644 --- a/core/account/build.gradle.kts +++ b/core/account/build.gradle.kts @@ -4,7 +4,7 @@ plugins { } android { - namespace = "com.squirtles.account" + namespace = "com.squirtles.core.account" } dependencies { diff --git a/core/account/src/main/java/com/squirtles/core/account/GoogleId.kt b/core/account/src/main/java/com/squirtles/core/account/GoogleId.kt index 3f3a54b6..fc25b4d8 100644 --- a/core/account/src/main/java/com/squirtles/core/account/GoogleId.kt +++ b/core/account/src/main/java/com/squirtles/core/account/GoogleId.kt @@ -14,7 +14,6 @@ import com.google.android.libraries.identity.googleid.GetGoogleIdOption import com.google.android.libraries.identity.googleid.GoogleIdTokenCredential import com.google.firebase.auth.FirebaseAuth import com.google.firebase.auth.GoogleAuthProvider -import com.squirtles.account.R import com.squirtles.core.buildconfig.LocalPropertyProvider import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers diff --git a/core/buildconfig/build.gradle.kts b/core/buildconfig/build.gradle.kts index 72da1336..0402a087 100644 --- a/core/buildconfig/build.gradle.kts +++ b/core/buildconfig/build.gradle.kts @@ -10,7 +10,7 @@ plugins { } android { - namespace = "com.squirtles.localproperties" + namespace = "com.squirtles.core.buildconfig" buildFeatures { buildConfig = true diff --git a/core/buildconfig/src/main/java/com/squirtles/core/buildconfig/LocalPropertyProvider.kt b/core/buildconfig/src/main/java/com/squirtles/core/buildconfig/LocalPropertyProvider.kt index eed5fe7a..7a6386b4 100644 --- a/core/buildconfig/src/main/java/com/squirtles/core/buildconfig/LocalPropertyProvider.kt +++ b/core/buildconfig/src/main/java/com/squirtles/core/buildconfig/LocalPropertyProvider.kt @@ -1,7 +1,5 @@ package com.squirtles.core.buildconfig -import com.squirtles.localproperties.BuildConfig - object LocalPropertyProvider { val googleClientId: String get() = BuildConfig.GOOGLE_CLIENT_ID diff --git a/core/common/build.gradle.kts b/core/common/build.gradle.kts index 7c0b9516..5f530bcf 100644 --- a/core/common/build.gradle.kts +++ b/core/common/build.gradle.kts @@ -3,7 +3,7 @@ plugins { } android { - namespace = "com.squirtles.common" + namespace = "com.squirtles.core.common" } dependencies { diff --git a/core/common/src/main/java/com/squirtles/core/common/ui/AlbumImage.kt b/core/common/src/main/java/com/squirtles/core/common/ui/AlbumImage.kt index e53ce8e6..8bb61b3f 100644 --- a/core/common/src/main/java/com/squirtles/core/common/ui/AlbumImage.kt +++ b/core/common/src/main/java/com/squirtles/core/common/ui/AlbumImage.kt @@ -10,7 +10,7 @@ import androidx.compose.ui.res.stringResource import coil3.compose.AsyncImage import coil3.request.ImageRequest import coil3.request.crossfade -import com.squirtles.common.R +import com.squirtles.core.common.R import com.squirtles.core.common.ui.theme.Gray @Composable diff --git a/core/common/src/main/java/com/squirtles/core/common/ui/CreatedByPickText.kt b/core/common/src/main/java/com/squirtles/core/common/ui/CreatedByPickText.kt index b56c7eec..9fb60706 100644 --- a/core/common/src/main/java/com/squirtles/core/common/ui/CreatedByPickText.kt +++ b/core/common/src/main/java/com/squirtles/core/common/ui/CreatedByPickText.kt @@ -12,7 +12,7 @@ import androidx.compose.ui.text.buildAnnotatedString import androidx.compose.ui.text.style.TextDecoration import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.text.withStyle -import com.squirtles.common.R +import com.squirtles.core.common.R @Composable fun CreatedBySelfText( diff --git a/core/common/src/main/java/com/squirtles/core/common/ui/DefaultTopAppBar.kt b/core/common/src/main/java/com/squirtles/core/common/ui/DefaultTopAppBar.kt index 051c0e5d..c1ae501b 100644 --- a/core/common/src/main/java/com/squirtles/core/common/ui/DefaultTopAppBar.kt +++ b/core/common/src/main/java/com/squirtles/core/common/ui/DefaultTopAppBar.kt @@ -17,7 +17,7 @@ import androidx.compose.ui.graphics.Color import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.TextStyle import androidx.compose.ui.text.font.FontWeight -import com.squirtles.common.R +import com.squirtles.core.common.R import com.squirtles.core.common.ui.theme.White @OptIn(ExperimentalMaterial3Api::class) diff --git a/core/common/src/main/java/com/squirtles/core/common/ui/MessageAlertDialog.kt b/core/common/src/main/java/com/squirtles/core/common/ui/MessageAlertDialog.kt index d0f4eeff..38d2f86f 100644 --- a/core/common/src/main/java/com/squirtles/core/common/ui/MessageAlertDialog.kt +++ b/core/common/src/main/java/com/squirtles/core/common/ui/MessageAlertDialog.kt @@ -22,7 +22,7 @@ import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp -import com.squirtles.common.R +import com.squirtles.core.common.R import com.squirtles.core.common.ui.theme.Black import com.squirtles.core.common.ui.theme.MusicRoadTheme import com.squirtles.core.common.ui.theme.Primary diff --git a/core/common/src/main/java/com/squirtles/core/common/ui/PickInfoText.kt b/core/common/src/main/java/com/squirtles/core/common/ui/PickInfoText.kt index d89f12c0..82b8d2a5 100644 --- a/core/common/src/main/java/com/squirtles/core/common/ui/PickInfoText.kt +++ b/core/common/src/main/java/com/squirtles/core/common/ui/PickInfoText.kt @@ -14,7 +14,7 @@ import androidx.compose.ui.text.buildAnnotatedString import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.text.withStyle -import com.squirtles.common.R +import com.squirtles.core.common.R import com.squirtles.core.common.ui.theme.Primary @Composable diff --git a/core/common/src/main/java/com/squirtles/core/common/ui/SignInAlertDialog.kt b/core/common/src/main/java/com/squirtles/core/common/ui/SignInAlertDialog.kt index 8dc529dc..efe2ee68 100644 --- a/core/common/src/main/java/com/squirtles/core/common/ui/SignInAlertDialog.kt +++ b/core/common/src/main/java/com/squirtles/core/common/ui/SignInAlertDialog.kt @@ -30,7 +30,7 @@ import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp -import com.squirtles.common.R +import com.squirtles.core.common.R import com.squirtles.core.common.ui.theme.Black import com.squirtles.core.common.ui.theme.DarkGray import com.squirtles.core.common.ui.theme.MusicRoadTheme diff --git a/core/mediaservice/build.gradle.kts b/core/mediaservice/build.gradle.kts index a41d1331..d69508cf 100644 --- a/core/mediaservice/build.gradle.kts +++ b/core/mediaservice/build.gradle.kts @@ -4,7 +4,7 @@ plugins { } android { - namespace = "com.squirtles.mediaservice" + namespace = "com.squirtles.core.mediaservice" } dependencies { diff --git a/core/mediaservice/src/main/java/com/squirtles/core/mediaservice/MediaNotificationProvider.kt b/core/mediaservice/src/main/java/com/squirtles/core/mediaservice/MediaNotificationProvider.kt index 0f675f0e..1d3e53f4 100644 --- a/core/mediaservice/src/main/java/com/squirtles/core/mediaservice/MediaNotificationProvider.kt +++ b/core/mediaservice/src/main/java/com/squirtles/core/mediaservice/MediaNotificationProvider.kt @@ -16,7 +16,7 @@ import androidx.media3.common.util.UnstableApi import androidx.media3.session.MediaNotification import androidx.media3.session.MediaSession import androidx.media3.session.MediaStyleNotificationHelper -import com.squirtles.mediaservice.R +import com.squirtles.core.mediaservice.R import javax.inject.Inject interface MediaNotificationProvider { diff --git a/core/musicplayer/build.gradle.kts b/core/musicplayer/build.gradle.kts index 86ba2411..70ba497d 100644 --- a/core/musicplayer/build.gradle.kts +++ b/core/musicplayer/build.gradle.kts @@ -4,7 +4,7 @@ plugins { } android { - namespace = "com.squirtles.musicplayer" + namespace = "com.squirtles.core.musicplayer" } dependencies { diff --git a/core/picklist/build.gradle.kts b/core/picklist/build.gradle.kts index c38e44b8..03e0ac16 100644 --- a/core/picklist/build.gradle.kts +++ b/core/picklist/build.gradle.kts @@ -3,7 +3,7 @@ plugins { } android { - namespace = "com.squirtles.picklist" + namespace = "com.squirtles.core.picklist" } dependencies { diff --git a/core/picklist/src/main/java/com/squirtles/core/picklist/PickListScreenContents.kt b/core/picklist/src/main/java/com/squirtles/core/picklist/PickListScreenContents.kt index 8796e66c..3696b8e4 100644 --- a/core/picklist/src/main/java/com/squirtles/core/picklist/PickListScreenContents.kt +++ b/core/picklist/src/main/java/com/squirtles/core/picklist/PickListScreenContents.kt @@ -42,7 +42,6 @@ import com.squirtles.core.common.ui.theme.Primary import com.squirtles.core.common.ui.theme.White import com.squirtles.core.model.Order import com.squirtles.core.model.Pick -import com.squirtles.picklist.R import com.squirtles.core.picklist.components.DeleteSelectedPickDialog import com.squirtles.core.picklist.components.EditModeAction import com.squirtles.core.picklist.components.EditModeBottomButton diff --git a/core/picklist/src/main/java/com/squirtles/core/picklist/components/DeleteSelectedPickDialog.kt b/core/picklist/src/main/java/com/squirtles/core/picklist/components/DeleteSelectedPickDialog.kt index 50c4b362..7944c572 100644 --- a/core/picklist/src/main/java/com/squirtles/core/picklist/components/DeleteSelectedPickDialog.kt +++ b/core/picklist/src/main/java/com/squirtles/core/picklist/components/DeleteSelectedPickDialog.kt @@ -8,7 +8,7 @@ import com.squirtles.core.common.ui.HorizontalSpacer import com.squirtles.core.common.ui.MessageAlertDialog import com.squirtles.core.common.ui.theme.Primary import com.squirtles.core.picklist.PickListType -import com.squirtles.picklist.R +import com.squirtles.core.picklist.R @Composable internal fun DeleteSelectedPickDialog( diff --git a/core/picklist/src/main/java/com/squirtles/core/picklist/components/EditModeAction.kt b/core/picklist/src/main/java/com/squirtles/core/picklist/components/EditModeAction.kt index b8bc685a..f9a8cb42 100644 --- a/core/picklist/src/main/java/com/squirtles/core/picklist/components/EditModeAction.kt +++ b/core/picklist/src/main/java/com/squirtles/core/picklist/components/EditModeAction.kt @@ -11,7 +11,7 @@ import androidx.compose.runtime.Composable import androidx.compose.ui.res.stringResource import com.squirtles.core.common.ui.theme.Gray import com.squirtles.core.common.ui.theme.White -import com.squirtles.picklist.R +import com.squirtles.core.picklist.R @Composable diff --git a/core/picklist/src/main/java/com/squirtles/core/picklist/components/EditModeBottomButton.kt b/core/picklist/src/main/java/com/squirtles/core/picklist/components/EditModeBottomButton.kt index 7c1f4cfe..11c50c5b 100644 --- a/core/picklist/src/main/java/com/squirtles/core/picklist/components/EditModeBottomButton.kt +++ b/core/picklist/src/main/java/com/squirtles/core/picklist/components/EditModeBottomButton.kt @@ -20,7 +20,7 @@ import com.squirtles.core.common.ui.theme.Gray import com.squirtles.core.common.ui.theme.MusicRoadTheme import com.squirtles.core.common.ui.theme.Primary import com.squirtles.core.common.ui.theme.White -import com.squirtles.picklist.R +import com.squirtles.core.picklist.R @Composable internal fun EditModeBottomButton( diff --git a/core/picklist/src/main/java/com/squirtles/core/picklist/components/OrderBottomSheet.kt b/core/picklist/src/main/java/com/squirtles/core/picklist/components/OrderBottomSheet.kt index 9550c776..21bc37b1 100644 --- a/core/picklist/src/main/java/com/squirtles/core/picklist/components/OrderBottomSheet.kt +++ b/core/picklist/src/main/java/com/squirtles/core/picklist/components/OrderBottomSheet.kt @@ -30,7 +30,7 @@ import com.squirtles.core.common.ui.theme.Dark import com.squirtles.core.common.ui.theme.Primary import com.squirtles.core.common.ui.theme.White import com.squirtles.core.model.Order -import com.squirtles.picklist.R +import com.squirtles.core.picklist.R import kotlinx.coroutines.launch @OptIn(ExperimentalMaterial3Api::class) diff --git a/core/picklist/src/main/java/com/squirtles/core/picklist/components/PickItem.kt b/core/picklist/src/main/java/com/squirtles/core/picklist/components/PickItem.kt index 7301e614..d155cff3 100644 --- a/core/picklist/src/main/java/com/squirtles/core/picklist/components/PickItem.kt +++ b/core/picklist/src/main/java/com/squirtles/core/picklist/components/PickItem.kt @@ -36,7 +36,7 @@ import com.squirtles.core.common.ui.theme.Gray import com.squirtles.core.common.ui.theme.Primary import com.squirtles.core.common.ui.theme.White import com.squirtles.core.model.Song -import com.squirtles.picklist.R +import com.squirtles.core.picklist.R @Composable internal fun PickItem( diff --git a/core/util/build.gradle.kts b/core/util/build.gradle.kts index 34ac6616..000e3030 100644 --- a/core/util/build.gradle.kts +++ b/core/util/build.gradle.kts @@ -4,7 +4,7 @@ plugins { } android { - namespace = "com.squirtles.util" + namespace = "com.squirtles.core.util" } dependencies { diff --git a/data/applemusic/build.gradle.kts b/data/applemusic/build.gradle.kts index a76d6520..0a723a27 100644 --- a/data/applemusic/build.gradle.kts +++ b/data/applemusic/build.gradle.kts @@ -4,7 +4,7 @@ plugins { } android { - namespace = "com.squirtles.applemusic" + namespace = "com.squirtles.data.applemusic" } dependencies { diff --git a/data/favorite/build.gradle.kts b/data/favorite/build.gradle.kts index 75ba682e..10a501b5 100644 --- a/data/favorite/build.gradle.kts +++ b/data/favorite/build.gradle.kts @@ -4,7 +4,7 @@ plugins { } android { - namespace = "com.squirtles.favorite" + namespace = "com.squirtles.data.favorite" } dependencies { diff --git a/data/firebase/build.gradle.kts b/data/firebase/build.gradle.kts index 09697d6f..3b61ea8f 100644 --- a/data/firebase/build.gradle.kts +++ b/data/firebase/build.gradle.kts @@ -3,7 +3,7 @@ plugins { } android { - namespace = "com.squirtles.firebase" + namespace = "com.squirtles.data.firebase" } dependencies { diff --git a/data/location/build.gradle.kts b/data/location/build.gradle.kts index dd94d86d..28ae28ac 100644 --- a/data/location/build.gradle.kts +++ b/data/location/build.gradle.kts @@ -4,7 +4,7 @@ plugins { } android { - namespace = "com.squirtles.location" + namespace = "com.squirtles.data.location" } dependencies { diff --git a/data/order/build.gradle.kts b/data/order/build.gradle.kts index cd3ac213..f1e12278 100644 --- a/data/order/build.gradle.kts +++ b/data/order/build.gradle.kts @@ -3,7 +3,7 @@ plugins { } android { - namespace = "com.squirtles.order" + namespace = "com.squirtles.data.order" } dependencies { diff --git a/data/pick/build.gradle.kts b/data/pick/build.gradle.kts index 11f2531f..b1a7f20b 100644 --- a/data/pick/build.gradle.kts +++ b/data/pick/build.gradle.kts @@ -3,7 +3,7 @@ plugins { } android { - namespace = "com.squirtles.pick" + namespace = "com.squirtles.data.pick" } dependencies { diff --git a/data/user/build.gradle.kts b/data/user/build.gradle.kts index 778f8112..03b82a06 100644 --- a/data/user/build.gradle.kts +++ b/data/user/build.gradle.kts @@ -3,7 +3,7 @@ plugins { } android { - namespace = "com.squirtles.user" + namespace = "com.squirtles.data.user" } dependencies { diff --git a/domain/applemusic/build.gradle.kts b/domain/applemusic/build.gradle.kts index f150186b..566789f1 100644 --- a/domain/applemusic/build.gradle.kts +++ b/domain/applemusic/build.gradle.kts @@ -3,7 +3,7 @@ plugins { } android { - namespace = "com.squirtles.applemusic" + namespace = "com.squirtles.domain.applemusic" } dependencies { diff --git a/domain/applemusic/src/main/java/com/squirtles/domain/applemusic/usecase/FetchMusicVideoUseCase.kt b/domain/applemusic/src/main/java/com/squirtles/domain/applemusic/usecase/FetchMusicVideoUseCase.kt index e30954b4..3023cdda 100644 --- a/domain/applemusic/src/main/java/com/squirtles/domain/applemusic/usecase/FetchMusicVideoUseCase.kt +++ b/domain/applemusic/src/main/java/com/squirtles/domain/applemusic/usecase/FetchMusicVideoUseCase.kt @@ -5,7 +5,6 @@ import com.squirtles.core.model.MusicVideo import com.squirtles.core.model.Song import javax.inject.Inject - class FetchMusicVideoUseCase @Inject constructor( private val appleMusicRepository: AppleMusicRepository ) { diff --git a/domain/location/build.gradle.kts b/domain/location/build.gradle.kts index 53c9c9b1..5bbf924f 100644 --- a/domain/location/build.gradle.kts +++ b/domain/location/build.gradle.kts @@ -3,7 +3,7 @@ plugins { } android { - namespace = "com.squirtles.location" + namespace = "com.squirtles.domain.location" } dependencies { diff --git a/domain/player/build.gradle.kts b/domain/player/build.gradle.kts index ed215d51..88dea554 100644 --- a/domain/player/build.gradle.kts +++ b/domain/player/build.gradle.kts @@ -3,7 +3,7 @@ plugins { } android { - namespace = "com.squirtles.player" + namespace = "com.squirtles.domain.player" } dependencies { diff --git a/feature/create/build.gradle.kts b/feature/create/build.gradle.kts index 1a5bf244..498d8346 100644 --- a/feature/create/build.gradle.kts +++ b/feature/create/build.gradle.kts @@ -4,7 +4,7 @@ plugins { } android { - namespace = "com.squirtles.create" + namespace = "com.squirtles.feature.create" } dependencies { diff --git a/feature/create/src/main/java/com/squirtles/feature/create/CreatePickScreen.kt b/feature/create/src/main/java/com/squirtles/feature/create/CreatePickScreen.kt index 0c50240d..f2a8f1c3 100644 --- a/feature/create/src/main/java/com/squirtles/feature/create/CreatePickScreen.kt +++ b/feature/create/src/main/java/com/squirtles/feature/create/CreatePickScreen.kt @@ -63,7 +63,6 @@ import com.squirtles.core.common.ui.theme.Dark import com.squirtles.core.common.ui.theme.Gray import com.squirtles.core.common.ui.theme.White import com.squirtles.core.model.Song -import com.squirtles.create.R @Composable fun CreatePickScreen( diff --git a/feature/detail/build.gradle.kts b/feature/detail/build.gradle.kts index a5428afc..86e2db21 100644 --- a/feature/detail/build.gradle.kts +++ b/feature/detail/build.gradle.kts @@ -4,7 +4,7 @@ plugins { } android { - namespace = "com.squirtles.detail" + namespace = "com.squirtles.feature.detail" } dependencies { diff --git a/feature/detail/src/main/java/com/squirtles/feature/detail/PickDetailScreen.kt b/feature/detail/src/main/java/com/squirtles/feature/detail/PickDetailScreen.kt index abfc1d16..b52033fd 100644 --- a/feature/detail/src/main/java/com/squirtles/feature/detail/PickDetailScreen.kt +++ b/feature/detail/src/main/java/com/squirtles/feature/detail/PickDetailScreen.kt @@ -75,7 +75,6 @@ import com.squirtles.feature.detail.components.music.MusicPlayer import com.squirtles.feature.detail.videoplayer.MusicVideoScreen import com.squirtles.core.model.Pick import com.squirtles.core.musicplayer.PlayerServiceViewModel -import com.squirtles.detail.R import kotlinx.coroutines.launch import kotlin.math.absoluteValue diff --git a/feature/detail/src/main/java/com/squirtles/feature/detail/components/CircleAlbumCover.kt b/feature/detail/src/main/java/com/squirtles/feature/detail/components/CircleAlbumCover.kt index 6f8a7e89..29cfeac7 100644 --- a/feature/detail/src/main/java/com/squirtles/feature/detail/components/CircleAlbumCover.kt +++ b/feature/detail/src/main/java/com/squirtles/feature/detail/components/CircleAlbumCover.kt @@ -19,8 +19,8 @@ import com.miller198.audiovisualizer.configs.GradientConfig import com.miller198.audiovisualizer.configs.VisualizerConfig import com.miller198.audiovisualizer.soundeffect.SoundEffects import com.miller198.audiovisualizer.ui.CircleVisualizer -import com.squirtles.detail.R import com.squirtles.core.model.Song +import com.squirtles.feature.detail.R @Composable internal fun CircleAlbumCover( diff --git a/feature/detail/src/main/java/com/squirtles/feature/detail/components/DetailPickTopAppBar.kt b/feature/detail/src/main/java/com/squirtles/feature/detail/components/DetailPickTopAppBar.kt index b0f5ee77..9229b0c0 100644 --- a/feature/detail/src/main/java/com/squirtles/feature/detail/components/DetailPickTopAppBar.kt +++ b/feature/detail/src/main/java/com/squirtles/feature/detail/components/DetailPickTopAppBar.kt @@ -21,7 +21,7 @@ import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.font.FontWeight import com.squirtles.core.common.ui.CreatedByOtherUserText import com.squirtles.core.common.ui.CreatedBySelfText -import com.squirtles.detail.R +import com.squirtles.feature.detail.R @OptIn(ExperimentalMaterial3Api::class) @Composable diff --git a/feature/detail/src/main/java/com/squirtles/feature/detail/components/MusicVideoKnob.kt b/feature/detail/src/main/java/com/squirtles/feature/detail/components/MusicVideoKnob.kt index 8103c163..434dd522 100644 --- a/feature/detail/src/main/java/com/squirtles/feature/detail/components/MusicVideoKnob.kt +++ b/feature/detail/src/main/java/com/squirtles/feature/detail/components/MusicVideoKnob.kt @@ -28,7 +28,7 @@ import coil3.compose.AsyncImage import coil3.request.ImageRequest import com.squirtles.core.common.ui.theme.White import com.squirtles.core.common.ui.toImageUrlWithSize -import com.squirtles.detail.R +import com.squirtles.feature.detail.R @Composable internal fun MusicVideoKnob( diff --git a/feature/detail/src/main/java/com/squirtles/feature/detail/components/PickCommentText.kt b/feature/detail/src/main/java/com/squirtles/feature/detail/components/PickCommentText.kt index 74cadbd4..be9c08e6 100644 --- a/feature/detail/src/main/java/com/squirtles/feature/detail/components/PickCommentText.kt +++ b/feature/detail/src/main/java/com/squirtles/feature/detail/components/PickCommentText.kt @@ -19,7 +19,7 @@ import androidx.compose.ui.unit.dp import com.squirtles.core.common.ui.theme.Dark import com.squirtles.core.common.ui.theme.Gray import com.squirtles.core.common.ui.theme.White -import com.squirtles.detail.R +import com.squirtles.feature.detail.R @Composable internal fun PickCommentText( diff --git a/feature/detail/src/main/java/com/squirtles/feature/detail/components/PickInformation.kt b/feature/detail/src/main/java/com/squirtles/feature/detail/components/PickInformation.kt index c46b5bcb..f490bdcb 100644 --- a/feature/detail/src/main/java/com/squirtles/feature/detail/components/PickInformation.kt +++ b/feature/detail/src/main/java/com/squirtles/feature/detail/components/PickInformation.kt @@ -14,7 +14,7 @@ import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.dp import com.squirtles.core.common.ui.theme.Gray -import com.squirtles.detail.R +import com.squirtles.feature.detail.R @Composable internal fun PickInformation(formattedDate: String, favoriteCount: Int) { diff --git a/feature/detail/src/main/java/com/squirtles/feature/detail/components/SwipeUpIcon.kt b/feature/detail/src/main/java/com/squirtles/feature/detail/components/SwipeUpIcon.kt index 18e573fe..010ceff6 100644 --- a/feature/detail/src/main/java/com/squirtles/feature/detail/components/SwipeUpIcon.kt +++ b/feature/detail/src/main/java/com/squirtles/feature/detail/components/SwipeUpIcon.kt @@ -11,7 +11,7 @@ import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.dp import com.squirtles.core.common.ui.theme.White -import com.squirtles.detail.R +import com.squirtles.feature.detail.R @Composable internal fun SwipeUpIcon( diff --git a/feature/detail/src/main/java/com/squirtles/feature/detail/components/music/PlayerControls.kt b/feature/detail/src/main/java/com/squirtles/feature/detail/components/music/PlayerControls.kt index e142ff8d..076a0360 100644 --- a/feature/detail/src/main/java/com/squirtles/feature/detail/components/music/PlayerControls.kt +++ b/feature/detail/src/main/java/com/squirtles/feature/detail/components/music/PlayerControls.kt @@ -18,7 +18,7 @@ import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp import com.squirtles.core.common.ui.theme.MusicRoadTheme import com.squirtles.core.common.ui.theme.White -import com.squirtles.detail.R +import com.squirtles.feature.detail.R @Composable internal fun PlayerControls( diff --git a/feature/detail/src/main/java/com/squirtles/feature/detail/videoplayer/VideoPlayerOverlay.kt b/feature/detail/src/main/java/com/squirtles/feature/detail/videoplayer/VideoPlayerOverlay.kt index 937d04fc..1e35abe2 100644 --- a/feature/detail/src/main/java/com/squirtles/feature/detail/videoplayer/VideoPlayerOverlay.kt +++ b/feature/detail/src/main/java/com/squirtles/feature/detail/videoplayer/VideoPlayerOverlay.kt @@ -51,11 +51,11 @@ import com.squirtles.core.common.ui.VerticalSpacer import com.squirtles.core.common.ui.theme.Black import com.squirtles.core.common.ui.theme.Gray import com.squirtles.core.common.ui.theme.White -import com.squirtles.detail.R import com.squirtles.core.model.Creator import com.squirtles.core.model.LocationPoint import com.squirtles.core.model.Pick import com.squirtles.core.model.Song +import com.squirtles.feature.detail.R @OptIn(ExperimentalMaterial3Api::class) @Composable diff --git a/feature/detail/src/main/java/com/squirtles/feature/detail/videoplayer/VideoPlayerViewModel.kt b/feature/detail/src/main/java/com/squirtles/feature/detail/videoplayer/VideoPlayerViewModel.kt index cd3e6342..b91a39e8 100644 --- a/feature/detail/src/main/java/com/squirtles/feature/detail/videoplayer/VideoPlayerViewModel.kt +++ b/feature/detail/src/main/java/com/squirtles/feature/detail/videoplayer/VideoPlayerViewModel.kt @@ -11,7 +11,7 @@ import androidx.media3.common.MediaItem import androidx.media3.common.Player import androidx.media3.common.VideoSize import androidx.media3.exoplayer.ExoPlayer -import com.squirtles.detail.R +import com.squirtles.feature.detail.R import dagger.hilt.android.lifecycle.HiltViewModel import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.asStateFlow diff --git a/feature/favorite/build.gradle.kts b/feature/favorite/build.gradle.kts index 90c66643..864f46a7 100644 --- a/feature/favorite/build.gradle.kts +++ b/feature/favorite/build.gradle.kts @@ -3,7 +3,7 @@ plugins { } android { - namespace = "com.squirtles.favorite" + namespace = "com.squirtles.feature.favorite" } dependencies { diff --git a/feature/main/build.gradle.kts b/feature/main/build.gradle.kts index 216bee11..d51337f7 100644 --- a/feature/main/build.gradle.kts +++ b/feature/main/build.gradle.kts @@ -4,7 +4,7 @@ plugins { } android { - namespace = "com.squirtles.main" + namespace = "com.squirtles.feature.main" } dependencies { diff --git a/feature/main/src/main/java/com/squirtles/feature/main/MainActivity.kt b/feature/main/src/main/java/com/squirtles/feature/main/MainActivity.kt index c7107069..c1fe6bb6 100644 --- a/feature/main/src/main/java/com/squirtles/feature/main/MainActivity.kt +++ b/feature/main/src/main/java/com/squirtles/feature/main/MainActivity.kt @@ -24,7 +24,6 @@ import androidx.lifecycle.lifecycleScope import androidx.lifecycle.repeatOnLifecycle import com.google.firebase.auth.FirebaseAuth import com.squirtles.core.common.ui.theme.MusicRoadTheme -import com.squirtles.main.R import com.squirtles.feature.main.navigation.MainNavHost import com.squirtles.feature.main.navigation.MainNavigator import com.squirtles.feature.main.navigation.rememberMainNavigator diff --git a/feature/main/src/main/java/com/squirtles/feature/main/NeedPermissionDialog.kt b/feature/main/src/main/java/com/squirtles/feature/main/NeedPermissionDialog.kt index f9229d7f..0de66b78 100644 --- a/feature/main/src/main/java/com/squirtles/feature/main/NeedPermissionDialog.kt +++ b/feature/main/src/main/java/com/squirtles/feature/main/NeedPermissionDialog.kt @@ -27,7 +27,6 @@ import com.squirtles.core.common.ui.theme.Black import com.squirtles.core.common.ui.theme.DarkGray import com.squirtles.core.common.ui.theme.MusicRoadTheme import com.squirtles.core.common.ui.theme.White -import com.squirtles.main.R @OptIn(ExperimentalMaterial3Api::class) @Composable diff --git a/feature/main/src/main/java/com/squirtles/feature/main/PermissionBar.kt b/feature/main/src/main/java/com/squirtles/feature/main/PermissionBar.kt index e1ce5f0f..1dcf1c1d 100644 --- a/feature/main/src/main/java/com/squirtles/feature/main/PermissionBar.kt +++ b/feature/main/src/main/java/com/squirtles/feature/main/PermissionBar.kt @@ -22,7 +22,6 @@ import androidx.compose.ui.Modifier import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.unit.dp import com.squirtles.core.common.ui.Constants.DEFAULT_PADDING -import com.squirtles.main.R @Composable fun PermissionBar( diff --git a/feature/map/build.gradle.kts b/feature/map/build.gradle.kts index 1e104871..997c8d89 100644 --- a/feature/map/build.gradle.kts +++ b/feature/map/build.gradle.kts @@ -3,7 +3,7 @@ plugins { } android { - namespace = "com.squirtles.map" + namespace = "com.squirtles.feature.map" } dependencies { diff --git a/feature/map/src/main/java/com/squirtles/feature/map/MapScreen.kt b/feature/map/src/main/java/com/squirtles/feature/map/MapScreen.kt index e63adf7a..39995ac0 100644 --- a/feature/map/src/main/java/com/squirtles/feature/map/MapScreen.kt +++ b/feature/map/src/main/java/com/squirtles/feature/map/MapScreen.kt @@ -38,13 +38,12 @@ import com.squirtles.core.account.GoogleId import com.squirtles.core.common.ui.SignInAlertDialog import com.squirtles.core.common.ui.VerticalSpacer import com.squirtles.core.common.ui.theme.Black +import com.squirtles.core.musicplayer.PlayerServiceViewModel import com.squirtles.feature.map.components.ClusterBottomSheet import com.squirtles.feature.map.components.InfoWindow import com.squirtles.feature.map.components.LoadingDialog import com.squirtles.feature.map.components.MapBottomNavBar import com.squirtles.feature.map.components.PickNotificationBanner -import com.squirtles.core.musicplayer.PlayerServiceViewModel -import com.squirtles.map.R import kotlinx.coroutines.launch @Composable diff --git a/feature/map/src/main/java/com/squirtles/feature/map/NaverMap.kt b/feature/map/src/main/java/com/squirtles/feature/map/NaverMap.kt index 552ec79c..5f1097c9 100644 --- a/feature/map/src/main/java/com/squirtles/feature/map/NaverMap.kt +++ b/feature/map/src/main/java/com/squirtles/feature/map/NaverMap.kt @@ -45,7 +45,6 @@ import com.squirtles.core.common.ui.theme.Primary import com.squirtles.core.common.ui.theme.Purple15 import com.squirtles.feature.map.marker.MarkerKey import com.squirtles.feature.map.marker.buildClusterer -import com.squirtles.map.R import kotlinx.coroutines.launch import kotlin.coroutines.resume import kotlin.coroutines.suspendCoroutine diff --git a/feature/map/src/main/java/com/squirtles/feature/map/components/LoadingDialog.kt b/feature/map/src/main/java/com/squirtles/feature/map/components/LoadingDialog.kt index 47f342b3..314b7752 100644 --- a/feature/map/src/main/java/com/squirtles/feature/map/components/LoadingDialog.kt +++ b/feature/map/src/main/java/com/squirtles/feature/map/components/LoadingDialog.kt @@ -26,7 +26,7 @@ import com.squirtles.core.common.ui.theme.DarkGray import com.squirtles.core.common.ui.theme.MusicRoadTheme import com.squirtles.core.common.ui.theme.Primary import com.squirtles.core.common.ui.theme.White -import com.squirtles.map.R +import com.squirtles.feature.map.R @OptIn(ExperimentalMaterial3Api::class) @Composable diff --git a/feature/map/src/main/java/com/squirtles/feature/map/components/MapBottomNavBar.kt b/feature/map/src/main/java/com/squirtles/feature/map/components/MapBottomNavBar.kt index 3574f969..8de63566 100644 --- a/feature/map/src/main/java/com/squirtles/feature/map/components/MapBottomNavBar.kt +++ b/feature/map/src/main/java/com/squirtles/feature/map/components/MapBottomNavBar.kt @@ -25,9 +25,9 @@ import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp import com.squirtles.core.common.ui.theme.MusicRoadTheme import com.squirtles.core.common.ui.theme.Primary -import com.squirtles.map.R import com.squirtles.feature.map.BottomNavigationSize import com.squirtles.feature.map.BottomNavigationIconSize +import com.squirtles.feature.map.R import com.squirtles.feature.map.navigation.NavTab @Composable diff --git a/feature/map/src/main/java/com/squirtles/feature/map/components/PickNotificationBanner.kt b/feature/map/src/main/java/com/squirtles/feature/map/components/PickNotificationBanner.kt index db08560d..80c8ed0b 100644 --- a/feature/map/src/main/java/com/squirtles/feature/map/components/PickNotificationBanner.kt +++ b/feature/map/src/main/java/com/squirtles/feature/map/components/PickNotificationBanner.kt @@ -27,11 +27,11 @@ import androidx.core.graphics.toColorInt import com.squirtles.core.common.ui.theme.Black import com.squirtles.core.common.ui.theme.MusicRoadTheme import com.squirtles.core.common.ui.theme.White -import com.squirtles.map.R import com.squirtles.core.model.Creator import com.squirtles.core.model.LocationPoint import com.squirtles.core.model.Pick import com.squirtles.core.model.Song +import com.squirtles.feature.map.R @Composable fun PickNotificationBanner( diff --git a/feature/map/src/main/java/com/squirtles/feature/map/navigation/NavTab.kt b/feature/map/src/main/java/com/squirtles/feature/map/navigation/NavTab.kt index 1a0888d5..b1f51001 100644 --- a/feature/map/src/main/java/com/squirtles/feature/map/navigation/NavTab.kt +++ b/feature/map/src/main/java/com/squirtles/feature/map/navigation/NavTab.kt @@ -6,8 +6,8 @@ import androidx.compose.material.icons.filled.FavoriteBorder import androidx.compose.material.icons.outlined.AccountCircle import androidx.compose.material.icons.outlined.MusicNote import androidx.compose.ui.graphics.vector.ImageVector -import com.squirtles.map.R import com.squirtles.feature.map.BottomNavigationIconSize +import com.squirtles.feature.map.R internal enum class NavTab( @StringRes val contentDescription: Int, diff --git a/feature/mypick/build.gradle.kts b/feature/mypick/build.gradle.kts index d62b6b19..ef7b19f8 100644 --- a/feature/mypick/build.gradle.kts +++ b/feature/mypick/build.gradle.kts @@ -3,7 +3,7 @@ plugins { } android { - namespace = "com.squirtles.mypick" + namespace = "com.squirtles.feature.mypick" } dependencies { diff --git a/feature/search/build.gradle.kts b/feature/search/build.gradle.kts index 03ffdc8c..ce8478d0 100644 --- a/feature/search/build.gradle.kts +++ b/feature/search/build.gradle.kts @@ -3,7 +3,7 @@ plugins { } android { - namespace = "com.squirtles.search" + namespace = "com.squirtles.feature.search" } dependencies { diff --git a/feature/search/src/main/java/com/squirtles/feature/search/SearchMusicScreen.kt b/feature/search/src/main/java/com/squirtles/feature/search/SearchMusicScreen.kt index 956254a1..361234d0 100644 --- a/feature/search/src/main/java/com/squirtles/feature/search/SearchMusicScreen.kt +++ b/feature/search/src/main/java/com/squirtles/feature/search/SearchMusicScreen.kt @@ -67,7 +67,6 @@ import com.squirtles.feature.search.SearchUiConstants.DefaultPadding import com.squirtles.feature.search.SearchUiConstants.ImageSize import com.squirtles.feature.search.SearchUiConstants.ItemSpacing import com.squirtles.feature.search.SearchUiConstants.SearchBarHeight -import com.squirtles.search.R @Composable fun SearchMusicScreen( diff --git a/feature/userinfo/build.gradle.kts b/feature/userinfo/build.gradle.kts index f435468f..3cf86c4c 100644 --- a/feature/userinfo/build.gradle.kts +++ b/feature/userinfo/build.gradle.kts @@ -3,7 +3,7 @@ plugins { } android { - namespace = "com.squirtles.userinfo" + namespace = "com.squirtles.feature.userinfo" } dependencies { diff --git a/feature/userinfo/src/main/java/com/squirtles/feature/userinfo/components/UserInfoMenus.kt b/feature/userinfo/src/main/java/com/squirtles/feature/userinfo/components/UserInfoMenus.kt index 026526dd..e2ee38ca 100644 --- a/feature/userinfo/src/main/java/com/squirtles/feature/userinfo/components/UserInfoMenus.kt +++ b/feature/userinfo/src/main/java/com/squirtles/feature/userinfo/components/UserInfoMenus.kt @@ -28,7 +28,7 @@ import com.squirtles.core.common.ui.Constants.DEFAULT_PADDING import com.squirtles.core.common.ui.VerticalSpacer import com.squirtles.core.common.ui.theme.Gray import com.squirtles.core.common.ui.theme.White -import com.squirtles.userinfo.R +import com.squirtles.feature.userinfo.R import com.squirtles.feature.userinfo.UserInfoConstants.MENU_PADDING_HORIZONTAL import com.squirtles.feature.userinfo.UserInfoConstants.MENU_PADDING_VERTICAL diff --git a/feature/userinfo/src/main/java/com/squirtles/feature/userinfo/screen/EditNotificationSettingScreen.kt b/feature/userinfo/src/main/java/com/squirtles/feature/userinfo/screen/EditNotificationSettingScreen.kt index a06d08ea..45b26356 100644 --- a/feature/userinfo/src/main/java/com/squirtles/feature/userinfo/screen/EditNotificationSettingScreen.kt +++ b/feature/userinfo/src/main/java/com/squirtles/feature/userinfo/screen/EditNotificationSettingScreen.kt @@ -16,7 +16,7 @@ import com.squirtles.core.common.ui.Constants.COLOR_STOPS import com.squirtles.core.common.ui.Constants.DEFAULT_PADDING import com.squirtles.core.common.ui.DefaultTopAppBar import com.squirtles.core.common.ui.theme.White -import com.squirtles.userinfo.R +import com.squirtles.feature.userinfo.R @Composable internal fun EditNotificationSettingScreen( diff --git a/feature/userinfo/src/main/java/com/squirtles/feature/userinfo/screen/EditProfileScreen.kt b/feature/userinfo/src/main/java/com/squirtles/feature/userinfo/screen/EditProfileScreen.kt index f5c475df..d75d57e0 100644 --- a/feature/userinfo/src/main/java/com/squirtles/feature/userinfo/screen/EditProfileScreen.kt +++ b/feature/userinfo/src/main/java/com/squirtles/feature/userinfo/screen/EditProfileScreen.kt @@ -63,7 +63,7 @@ import com.squirtles.core.common.ui.theme.Gray import com.squirtles.core.common.ui.theme.MusicRoadTheme import com.squirtles.core.common.ui.theme.Primary import com.squirtles.core.common.ui.theme.White -import com.squirtles.userinfo.R +import com.squirtles.feature.userinfo.R import com.squirtles.feature.userinfo.UserInfoConstants.USERNAME_PATTERN import com.squirtles.feature.userinfo.UserInfoViewModel import kotlinx.coroutines.delay diff --git a/feature/userinfo/src/main/java/com/squirtles/feature/userinfo/screen/UserInfoScreen.kt b/feature/userinfo/src/main/java/com/squirtles/feature/userinfo/screen/UserInfoScreen.kt index 625aecd4..7ad62f67 100644 --- a/feature/userinfo/src/main/java/com/squirtles/feature/userinfo/screen/UserInfoScreen.kt +++ b/feature/userinfo/src/main/java/com/squirtles/feature/userinfo/screen/UserInfoScreen.kt @@ -62,7 +62,7 @@ import com.squirtles.core.common.ui.VerticalSpacer import com.squirtles.core.common.ui.theme.Black import com.squirtles.core.common.ui.theme.Primary import com.squirtles.core.common.ui.theme.White -import com.squirtles.userinfo.R +import com.squirtles.feature.userinfo.R import com.squirtles.feature.userinfo.UserInfoViewModel import com.squirtles.feature.userinfo.components.MenuItem import com.squirtles.feature.userinfo.components.UserInfoMenus From 6fd560bf178b3250e05653f22cdf9887630d8b84 Mon Sep 17 00:00:00 2001 From: miller198 Date: Wed, 23 Apr 2025 21:02:36 +0900 Subject: [PATCH 59/62] =?UTF-8?q?[chore]=20=ED=95=84=EC=9A=94=EC=97=86?= =?UTF-8?q?=EB=8A=94=20resource=20=EC=82=AD=EC=A0=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/build.gradle.kts | 2 +- app/src/main/res/drawable/ic_delete.xml | 10 -- app/src/main/res/drawable/ic_favorite.xml | 10 -- .../main/res/drawable/ic_favorite_false.xml | 10 -- .../main/res/drawable/ic_favorite_true.xml | 10 -- app/src/main/res/drawable/ic_location.xml | 12 -- .../main/res/drawable/ic_musical_note_64.png | Bin 1067 -> 0 bytes .../res/drawable/ic_musicroad_foreground.xml | 56 ------- app/src/main/res/drawable/ic_swipe.xml | 13 -- app/src/main/res/drawable/img_google_logo.png | Bin 16590 -> 0 bytes .../res/drawable/img_user_default_profile.jpg | Bin 15062 -> 0 bytes app/src/main/res/values/colors.xml | 11 -- app/src/main/res/values/strings.xml | 157 ------------------ app/src/main/res/values/themes.xml | 11 -- 14 files changed, 1 insertion(+), 301 deletions(-) delete mode 100644 app/src/main/res/drawable/ic_delete.xml delete mode 100644 app/src/main/res/drawable/ic_favorite.xml delete mode 100644 app/src/main/res/drawable/ic_favorite_false.xml delete mode 100644 app/src/main/res/drawable/ic_favorite_true.xml delete mode 100644 app/src/main/res/drawable/ic_location.xml delete mode 100644 app/src/main/res/drawable/ic_musical_note_64.png delete mode 100644 app/src/main/res/drawable/ic_musicroad_foreground.xml delete mode 100644 app/src/main/res/drawable/ic_swipe.xml delete mode 100644 app/src/main/res/drawable/img_google_logo.png delete mode 100644 app/src/main/res/drawable/img_user_default_profile.jpg delete mode 100644 app/src/main/res/values/colors.xml delete mode 100644 app/src/main/res/values/strings.xml delete mode 100644 app/src/main/res/values/themes.xml diff --git a/app/build.gradle.kts b/app/build.gradle.kts index f0818862..de974a34 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -92,8 +92,8 @@ dependencies { // Hilt implementation(libs.hilt.android) - ksp(libs.hilt.android.compiler) androidTestImplementation(libs.hilt.android.testing) + ksp(libs.hilt.android.compiler) kspAndroidTest(libs.hilt.android.compiler) // Firebase diff --git a/app/src/main/res/drawable/ic_delete.xml b/app/src/main/res/drawable/ic_delete.xml deleted file mode 100644 index 47dd0af5..00000000 --- a/app/src/main/res/drawable/ic_delete.xml +++ /dev/null @@ -1,10 +0,0 @@ - - - - diff --git a/app/src/main/res/drawable/ic_favorite.xml b/app/src/main/res/drawable/ic_favorite.xml deleted file mode 100644 index 67b2268e..00000000 --- a/app/src/main/res/drawable/ic_favorite.xml +++ /dev/null @@ -1,10 +0,0 @@ - - - - \ No newline at end of file diff --git a/app/src/main/res/drawable/ic_favorite_false.xml b/app/src/main/res/drawable/ic_favorite_false.xml deleted file mode 100644 index 1073fca7..00000000 --- a/app/src/main/res/drawable/ic_favorite_false.xml +++ /dev/null @@ -1,10 +0,0 @@ - - - - diff --git a/app/src/main/res/drawable/ic_favorite_true.xml b/app/src/main/res/drawable/ic_favorite_true.xml deleted file mode 100644 index 2acf3267..00000000 --- a/app/src/main/res/drawable/ic_favorite_true.xml +++ /dev/null @@ -1,10 +0,0 @@ - - - - diff --git a/app/src/main/res/drawable/ic_location.xml b/app/src/main/res/drawable/ic_location.xml deleted file mode 100644 index df94a6aa..00000000 --- a/app/src/main/res/drawable/ic_location.xml +++ /dev/null @@ -1,12 +0,0 @@ - - - - \ No newline at end of file diff --git a/app/src/main/res/drawable/ic_musical_note_64.png b/app/src/main/res/drawable/ic_musical_note_64.png deleted file mode 100644 index 6d2225db467258d3cd64e50b6bc320bb275c3139..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1067 zcmV+`1l0S9P)=dWm6c(@z3SVHAWa?{J(iIUQojeg9(jElr*VJ zNmAZWW8xJlQS*irCFO+@(ZWX#V&k{jV+F&6{$_;pH(@bqzodPvDqQ1?(CCI1O8H*eHT^ z5df^m(>Q$Cfw~F+uEchnILshj20%P8eSqm<1?Uohm+(izew@e&k6>BTLAwNCJFdXq z0qr;8U94>~XqN!M49>@A1KLl=8@L-wnh4l60PwMRF?w`Bdk>rOvIY`l7XiR8;wOJK z_6+#WI=qeZ789te01oI`oP`$#v`>lLac{ zi3Is+qWxGrkL6Vzmjd8`J`~sd>xs5g@laKVr2!xkt=p{f8@;1xAX(Op#4yTHT7X!2A8a0#v* z)nWd~YH7T~EG=28>Vf6^-u_&D_l2wxcYOUn7sde~S zGn3yu;;}cT)uR4ZcjCLmPVl1D6I0ebV_z@oumP!Bs002ovPDHLkV1j+s;OhVY diff --git a/app/src/main/res/drawable/ic_musicroad_foreground.xml b/app/src/main/res/drawable/ic_musicroad_foreground.xml deleted file mode 100644 index ba25c130..00000000 --- a/app/src/main/res/drawable/ic_musicroad_foreground.xml +++ /dev/null @@ -1,56 +0,0 @@ - - - - - - - - - - - - - - - - - diff --git a/app/src/main/res/drawable/ic_swipe.xml b/app/src/main/res/drawable/ic_swipe.xml deleted file mode 100644 index 20d5a921..00000000 --- a/app/src/main/res/drawable/ic_swipe.xml +++ /dev/null @@ -1,13 +0,0 @@ - - - - diff --git a/app/src/main/res/drawable/img_google_logo.png b/app/src/main/res/drawable/img_google_logo.png deleted file mode 100644 index a4a9918dad10832bc8aaa5574d71fe15f089c8cb..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 16590 zcmZvEc|6o#^!J^y?<5gfk|j&XzSAPco+XupgzU=N*aoFSVMf+$8Dv+4tRqRvI=1Z7 zrtHeT&U5Gcd!Eer_c`Z%&RtGV%uV%K7 zr-gs52M_H6AOuhs&shb(Sejx8ozy!Z{|#EkPYJwWD$rGvKKi-&i^OMIHe+ZX;m9e z$hp$ic-;w!;5%9-mn!R)j{n6M+H#eMM9Jz`zZ2+d1-U=z0TBEo{(W|8M|yAEIk_+Y zMg$85$C_BWCQ0C$X%_H97o$iu$MZoL!g&rbru)@kKAkCUqY# zbDU;C0*9!7Pqs&}tSSEev{Z+l;VqJdGqgxDHo3a86HYKUe+dyEYi~|ag%gzAGd+C8 zaV zQC7+6jW(hO7#7%%JBrRg-P9ruKWgOu^63kQTG43<36Gltp35|&eAM4A0r2}5=ih65 z%-cQK+JI)0RmSQG5-X~1;&{|sMW*);0O;)!|E{!|bzr*~=eqs%2oCYr8&!vDP|E1h zYJ6Bp7n%ToDTLKhG)rjiD9R_LhD$4fA@~MyMfBht@c`nK_VHprI}ob*_oTinJ1B#H zJt4_T-zT5ixbS_Z=+rGiF6Mzx^hhLl5IcqQp*|(s0rgF7 z4i&nYCKF<#2)|Tu%FTytY3Y&nhP!%-nIVr^BWGauiT|DiXN9FiJi~LU_$8kq(akbP z=Zra!^w8Adx&WgKXW=KBB{gS^niveBPvb!GW4-0#=<~;J2=YHRJP9x>5QjNzZ`I1C z(>l-jTkq0264%@BC~uw4ioJIVV0?aY-mSJF-$Jb<4(Xbm{9ybna>bxQBBN(xaNsl@ z5^Ni4bL8AE+M4mP{D+h|+ejaNdM*Sne5sfN5V)bNs+5pIxOoOimpVn`?xofzf`CrUpDJjS1u< zC<&D9V3e*V&*jB^C9()Sxt>_W-a_mO-vOgyRC`;u$UU6g*?0PA@vx9!zs$wIS5Y_|mC!03?J8hiBj`4adhU8HBpSoqFemk`npo!K(ev z#mN`lTLCDB>omSsQm3XpYFSv1u{P-|80cD-3*Z zXmjWcT-?P51~i`m!OZE^G$LU#&}Pu?9CxU}v(Mt+C(@7R1={NX&t6OHG$#~fqI*=x zfgYSlHv7<*;(zAaK8SCBYJ;!UL9x?%bRwE`A4!u5ujU0TgEjMP$L6`a*GzW{sU(jeur& zIYT*^9-NoXd7McTK9gMYe8CHmdwbVw8nXarLf>=Z1T{Db#i>hL=SLO2n?43U&$2cu zW7;1gqkzzDC=Ci5E+R3HwgsmfMV0C6vkJF#keEcs3LDrKp*ahRm>ucW7qvN{P5)se z3j=yY$ZenN7Tp`QfW#-})~^B88t4B+$GUMEoJ%$PU@NNiiNr>5WmRd-g#Bm!;fc6K zE0ha;9M|(Xpalv@*y{e#Wd=?BWW#42dVo9)_3hNRG(8HtWao)^KE&HIn=KRNIEnUaUGGJX&c>I}6j+fmTv6&hZ@fn5^B zHh13Qo$P#=K%V2XQ)He6q~s$&_zeX*i5(YRqD+`RYhtMZGwkoR^O}~>x%loNNMW0b zzL?Xb1rsWKsIFeWpN3dHxz|Mga(jb{^!PazJK!l}^eB}Lf$4+vT#OADofCohU@VQt zNn6m9a)?X6&H>UX7<^P9&yIIeASx>rvFjRmU!V_2oX`;QJx$icz=}E=@O$cxE7h)@ z&tnT=u!_i>>O$_tuQcY4&`}n&{He-Ao$HcZXHkv$cqntI$%-h&nv`2#0Q- z{Vy1h`vAh;hLy(#(l41Hb?=_tPGv{{h9WtS1;yrMv4t`JXc4*Fu<2uvOHKJ~68??{ zxu5>`Zz^Ya?4Yz-d9+2tgr`26fxgh;POHqyr!0+h)WKsI7!DC&;qQhd|DS`B3{k zvn~RPI}$h^;Z2ee0Fu#dGp=a}o+1bUisMK7b+ z491<;D*^DtwCOlprB;m%6`mHEyFwcQcsOavKp1>|JhdDYjlZDv9AKWF-KIN=z>NOF zPXCgi0iN+~I)tlKRgsW5-{5CBA!Ns3ixUXUbEwzaxWlV|*+6C|Nf6mI7#6Uq5`EYM zXJ4hqe0cI(C~)9DIItp~FcSq17y9J3AL*Abun5ilz)mkyy1sKjBFmQ_VKuEpKsh@N z8gS|C;Hxw(nf;zJfKN0G&{r*nEEyh{<=~%VuXwG_SLg7Z!d~(5AH|6dkM*4j{OF6t zmoIuW92fw8Z#d+ypQoVeyjeGQl%jRczi0lwCZZ)ZCyHWP@;Latb4Pj9w#2rFlFN8G zMPvu2DEdW@%4{mNuW@rw!>?z5GV5w{v>FnNG`TRXXRzyuczqEqi7x36X!(quGh2HP zC$f}7pv#88aMD@sKxMXckOc4b(g5gycThoRy%=&7q7SC90yWWx7of6GU)6iE8}BCK z)A03SJ%M3iST?zB4MMTt8)#l{@evbqD1wW zY>OGiObyq12Pos=KLpF*8wf z^$XZTmqFv1BQRwkj&wlX_SAm(;U(vA1Z~1Jc<`Jfx;6T|ASE-W-Rt#;fM4*=UORk> z9f!QmI9{*0*teT0dXR(1i3~UT&giWQa$bbgtrywK86U@lAShwAXNy7Xf*VQ)x9*q9 zy}F|ZGd_k`(^UTyI~~kGdRwa+M412WvAkpR05X%Hw~cl6FSdeyS5Ct)os6ET3ru^s zFzVnW{Fd+c-yRS~nHmICmQPJ__|`uwB&Sn$YPdczA^kfWYhxzK7{$G2kk}^pjTN*= z&RLkznAhtV?Pg5Xb}}AMTed~{FeKkICYU;lG zZYFWvnKm4bst_Ig7C(5tpNIL7$NCQK=TPCB9%iN!eU=QKX~+xb>7*$aZ1VlG}~TtQg!VVmHOV_%PhkoTkc&+scBw$pB~|Aqmz$W0|VE{kP5 z7QglU-N5nv%ZWmL;KmnH!qYVAa6!41k39RAy#tj==TYKDk~Z=Zv=K+86BR(<-<}*z zF5j_cY%i3j@1GsU5GK(IB9O1{iZ&gkjW9MKxd6|PtsYujzQa}cJ`|cNT4!Gv>ND9t zO3TKfo;no*!N4?00fJnGUx2+0#BJJ8Y)-M(nM=e_ z)S%XLUz0}s)|*PfbCAb-ybCgWDv}v)F6)S9eIO;=q8~qnDdpMEQW2TUpG{V#ji9Dv zh7JOjsNDf-88xY52t>z`wBr<1C&UgMf3-IL!EtWp$-a#W%$fY;MNrNjU3b7aKzn!A~?caRsiS!03YG=eFQ>m(UL=9 zak0)#yKKe>Q@p710ncX-%;?`jF?$4LUwkELL*3jvmxdrswT9f3TZ!k{|AuTVAz$R9 ze%vPb>-z#_Mu*2xb!bU;mjnYet_M%6dJ0ZSuQ%0j_gJ*f=e9b8ZiK$mMoDFF*e!ge zdu`DXN9QZsIq$Vy!D%fW9c1uca-D3B-IP_V;`r)Gg@gM@m=h&GlHD5K3R28Se`0Ut zGu(WkU7L#cI?uprhEUFp_%qvi`08tOY~a_A(aRe>SXRXxD-+v-E?4dHj~CEd_7BXr zEAUJZGxjM4N*h4J@g3%s1nQcIYnFeKD7CiM_`Y+CdPE5;0|ED!1j*?u#}J!AiUP88 ztY#0DYHoXMOumVZNx^Zcl3I@T%xW*APXXP6?Yp!jMzbzj_gecr30IxU7`20Jb!u8D zuT@&53)t1*wU?a}%#Jnr6XKv!-ea5+L*@7CB%YG67t~MztCSfRpgk)w79VpVXK+X% zwFJ#*OGEX2Z&Nw~=sw(;E%%Gi&qr;q-99<0IH_gr;WLGz>KjGRllWSb*S)XoQDJnp z-DpYe4{1e)lVtR!)yzyD;j{*eym9LSFfmwVv+oA$L5ki|b9yL~?9X?3U)13m$W_lT z?Ox?3ac&u^KbxEdB-!wEM12)cbn9h}kjTR9M)S85)iMOSStGO*oXNIX$BY<6Uis?} z`~2+B3R?ExN?M<$9Pa$=I^*RT%j2|bl>RMc) za&Nrfj2t?;ZuC`3ZFx1!`oU2KD^%bT-mbtEOs_~tB$X5jm}m!NXH0*!TMqH>D#vR7 zplF5N%9A#?HUF*$N|xJWgB;APU6+pzuX`Iay0anoK^JXIiJ^oHOs@SY0$^ zKD@N#9jCa_L{pO0)qoMc=3KXTcAH99*XpP7)Zw#B3TwXk_;Ri1&KAS{rMqD^s4$#T zePRHD)Bh4=HrL!008UsIAIGQ|jt^R1cd>p;<*pvyBJ5*VP@-PTc3kbog*7=0fN-$7WyhEdI??+D|TN}s=N~AL2ioM z6{y^rD%j~aMA1R2~8yIpl2nIH__VC8(vc8 ziO-hK`7IjO{~V_IRgzoG1hwoVf~6Tvmy!m57ta}(R=@2|{?rIJWKxT00sCp^FH{a& zhhzhyqFF(Xb;7H_5yN4<1;tg*e8sDOIg_R4GVUk`MK{*5fp3?4k7G`WE-gy1JRToo zQrvj)u;i(FqYO#n-#(RYaIyjjPAw8~D5<-iC$SQKorP}w;e4h;{4`(BESlZ%ox?jvO|lcv*)}MNYspDN_?lLXhfY))gLt4x?2Tb1G*bdBnLS`)Gz8 z@<3!CfmxN*2dm0vD1+hN>t)5iWz3hl&C8hT;f8|N5|r@4dDQRHJq_N{CE+*xn$J%v z%U>;;ZVa?up)xtw-1QLaLfHy6V*x@(4YjCz>pkEo)*88mKW3%=Z9Xq4VftAdl}a(r z`seDv*JU?CF~IL;_(l?;HWDqenv1tb!)fMO%mdO=_K{92pK1%s-f&Zvy-m7bA7x=8 z8Tq0XfOxyE5ehN%Cau>Oy>~pwsvh!ly~z7-=}Ox(sIR7?qkvt#;03~gynSijsn28n z+I?%YjbB&JtyPfa%_M+Ovf**?;-^HUd*Jnw;@?fL8m4FuMtEA`9tIl)<~%Dbptv9w zpRdIgtT+5qDYj`%!bJVE3e!?gBe|3UV!z}tvu<_z-_7Lj5yqPY741@qqBAxDmK}l& zX+h+&Q2-t#O9Isa%c#r4E#7&B+12I&8dp3^h2TnxaISz61Rv_KS`XSt-Wv&3I`P%^ z2{}863`n^z=>XJWh{1S|Wn@q3!YCe>edXs*8SCMc0KUUiO;I?qR0p%}d&{V^r4wWt z6E$cewK88$MYdk7e)s4<$)xmvZ+sE;4+~Da3W=5bH#e&345xfY(S~pnRS2vWmwbGi z5ju~{NdA*lV)V~|q~k(Muw(?j=Py%X4xfG05?%UPtlP%>EclA$^jqWMq?bNiP0wCH z;VN$Rfz{tS%?^UFZ&3<_{AOvCzd77xeaf* z6D8IZcOEEN^rQ(eNMrpoDFeK?MGN$U4b6{jJkqQzCa2o;)SOv9vVB)ElW(0OoMkda z*#jpILK2IR&Xx*J#n7`}&ToD7+$7|LP(YI$Va6Qa=hG}WU0Q;g$_O3TG*NGGts!sb z!F`+ulriwPXuzZUhUUkI+uc=nubFt+70wrq-e&tbr2|Nfp27vuuwfMadO(cC~*?)4`q*FBY+WTdnpt-z6)+0tOMpv9|y zvX1eBMaVURWv_hZ!k%OU;CfV3fEzv}!H1frvH)Wlc*lD|=8m-Ne{A?uT;C6JbMH`zzHMqL2x2i|2EC57gu6_3J^=VyU84fW5W8%Y$rNk;m(!NxC9)Z< z#*t_MURc#?0uHEt2aaaw02wzwnQ$M0zCV*gnU;#lnV%I*3?KtqL$vcc5~#XeKdEA8 z0OY!$Y*+Q5Y`D+LROaURNG#=WPQ_ZOE+8Q!OdtWgkETpTY55xFJQ8z>GJp~spjXle z5M%({e*N#I%I}BNKq>o()r~MCA05hj-Yw+8c}qmQeFMyreXkz``(y*56EQa^Gr4gs z5K%?X4nRaP|H?2sctDi^=kN97W^Ugpj4zKr1N~P4uH3m2>7#+ zoJ;)JyvqsLC7rKP=~7y>R?!kJ2UMs4;1K#>V+kR|e~pC}Ua)PzM6!OG7GPe(udn`q z3N$X_hP#>_!h}^J_;t|JkieEi;E)AXBKPzr)b;;<@PBmV(9OsIM$m>NI%+u9*&iIb z!JZ3fDgZt{vjgAG4sJjM9zET=x_urAdb78Rj#C6t^_=W{pP&Jt0rB5HMo10(Z%&SO zKbkinD6(sC62Y*$Wl19e;PkMF`uRtSHF!+gt~0TK6v!=Zwoq^^Gd-C6z>N8^`QPW4 zox7+{Bf$(hi{e|)B)=%@zNYW5qu7p#^8`)RC#XsibmICV6*i)Mf$cx* z`VqcW$v_QYKD-+Jj$#2tWe5V2qEC02152YJuUd=%2h_x!BVc>&RM(dZ;MU39%AUEEJ1>?fng3&JMX*g_$$!ewBF>kR%9=Xp zfxy3Hg4wjG570eb-isHOVrqU3i5TW7B=JX3tG&7Ukcb(x4{ef=7_tHHdh&bO5|OO) zu>Bt^Ws1oxrLLzHAzS2LN*=u4!r zSuaczfbJ_zrKHT_jpU;-0YFEYj3wpcT;6}o zm}uz9|1c9GX^v`1L)6b$M#a8kpQMAA1penPK(kLobDhV`Ml>1iN<0O`NbJ0QL1Kzj z=4F83Nwc_?{IYEKQii|RR#HPri0j7F;D$|>*Z%ih(bT$;tS`TXI$MizU6W5@`WJuX zVHFdii_ZL9Tue|uUE{MMlQ3JadEt^Y8X$-02HRgA6q2n!90xycnlXI--V)jDzg!Z% z{>R}*%3F2FaZh+0&sO!DB4*A2miam&(X%ftM! z_I>FD3s#WX-WWl?@Bh-qs>}|2)X@?>oo?MeI6M-TxO_ZKahm)+47&`o2tr|VI8NzR zG6NmZ%S2F!{u&W!MiaaIH*(5-2m>`}|+dlf%}IP-=(C zUlQjoPZ2|MWfsTv<6@Yyo6d$%&HRv{!CY3X3&`xyyX(Uuc}EXopgKT?v?nz3)YxJo zBR{~T&$L4|_}j~?>epavP}%M!e|Ed`K@nLGurtdt_YCQkCJYJg6&+hZn-tas1sfs= zadU7scpe+8A&?7Wg7yN4m6~X+?6q&snPpKrx|h>3WQhbf8O#_Y%TLd3#c5FqxgcVl zeDSE6d9@qGO4UOOwP(VA1O!WGX%c73K`s_5zs`jYBO7q+$4j_qyVKPl*x=v8v8%VD zT+pKGf`Dg^nTCp{xxURV3RlmLS03fh+(>rl7Fe9I{2jz!)*D%PPjP(0Y3^q>Od^Aa zZQbZ*?yKTrk2;I--w0;*8T%Nu%Y`qckSi9au_KZ|$+)Qx#Az4I=%E1X{ zH!i=(3@~zCueeH%C@u^<{3+2U7QkjUTGTL|>r*jtu03=2B~dNz8UXph`S_P!TIUCj z{@Q6ZlcFRbc^{0w+8O!}Xo^5cP3Vi~I9xZ~4_2*uDZ^#e! zdo8~wcG7P4@079)gi+z5{0A!&3UEablm|EcZ0pnnf9j&gQ{_8aURltAE!6czi2y>+ z0xMC^F2v*V&YNAX!GPEP#8f6OkUS)+Sn0!c@FELKO99ViWN#}wmVM!PdP(WXiSo*_ zth|>Z9u@IZC|2tFf_LQ_l5igVuiv(ukRrag&%Ct33b<(c9LBtP^ zVFPmqLQ6$D7gIex?7l4$p-y4;Dq<^FJKs7#r5;~bH@$Uh?sjhNS-{gcTkEZ`6hUJ$ zYkLrxg{V4|bSo}WB&hGXE=*QLZ}jV0rg?iPh8Q%sC*K3#d<#WJ_O?g0U{ESLYGac^ z@z~MZa=TB9gfW+_3UVqnE(+dUQ!l5VJ=S_YR5iu)V^q)>P%e5`-k1?YZn1e&v->Ob z&ViH}icltyU12rKx4!gNgIcuI{&kukGMvXqJnKm?(62E>^Jgu21SQXZ|9l*zU0}ZX zFPZj(Wpwp=OT~qNQC*Q&b#j;K40bP8`5*{r=xt&%5qvD)J>ukxKbaw_nOFUnnonXp<4g2@Xw{0Jtqm-s6D_RQbz9bY!+A#Z+CIs*o=afy1SU$R(j%z2r z_J8A2V+alhCr1vhpo;pHB3J3mP)XMFUKN!BRGRWtk9zIH9xn?zculA_UnaUz1$1EY zp*-RfLFCoe8<69#&X<;1fmIdMnpOeV>O=tD9JZqQ?bocb@Qy$|$hz7$>Zl(OT}778 zkJ7&H`BncZmKrFF6Y8|MoQD+Tt#j=BYAS3sE<`_XN+gxa@9^K66M6Rmk>l>YFl|rN z|C2-5_EM9n!AebqOS)wh)i7uCjbG!g(7qA0ZdU#wLh#ar!7l#}0+1=*Fp=(Eb6=Ab z!RBI-w7VEKhnlI{Tsm=Usw>Ype`3uMbPlRsA zOgYSGre){M&Ga~aJ*pjaZQL4dkM;~ZL*-}w-B!bP!iVV~3#sj5bD$&bX0|IK*S#Jw z)@%E>-S!(+hBnl}VN=R>A0EjUj>|A=_9yMp?9X4PB@-kTBJYPo?^nk(75PU|e5WFA zfmku>wnP8U{j+B*UZ;bIsy%_=>Yp6(66mzkouK<%G36dx64)^o?#D97>TQyFr3bZJ`J~(=xXkh{BK(%>HTWD(^?j@=l!~tT3b!kh`UVN zip=vkc-}m4@9R?JhN6}^dh;Fm40fA_M^hzbFE)&W!ZR`okG=#;ew-VD?(=P44ege8 zCS3a#k%kBE3ahrqT^$Yg*{p~T+NvPfPTbFll^%-yM&8IkI zc^8Uwae!n~h^lj-luT5S7A`jVAo<2jdQ~<(ShJWSrC>4vH<_t( z*&$h(1oj+#v`;-D2-ym2I2CFCyHBU}o1pQPR(lG6{u#G5icuSozbGS44?a@nPqj4!*td+E5xvVS2ReE$+cU)cUW*aUY%q03yQF5Yh z0jGu~%xK;(s-l7@S>+#RlC|;PBVr$%<-FlcTBXWBP;D*w9I+W5gN?OemtnXH8T{gF z2Ltnc=}kUba=*kpL;XX>^T)Exf(_XN8z+QFs%YMdH{yHgVTUWEOd%`zbgQ3dbtV&6 zs4%@Ffw<$VtZ=|y5w&+(DW*3K%-WS8x)T#+uc+I;DO4nH6&y|0Z0JVTcWLhJn@VTx zt9LTAbHOInOZj~l{;c=!B;0rUk($YP5%iCZA0~#e(yF9;MMc0o>hi9j#s=kShCdw=ME~Oz0jF&xg3@`%sI4 zKb~GWHgh#kUezBD%p}pxF+R}qvkjumwJT=l0!l=ip|aB1A}L&F%C5hccu<44&bFxk zuphO5T{=rKovLVHjs6X^etyv@iMb={@KfSZB=OS4T+}tZa43~!cm_<0P27O`MSKpTUqyW$X$4J*v zf`6?5Ig@4Elw#3+lQEf#=z;{4wav(SuUlGjtGRV_Jel?HMq|W7((&Z`&uZar@xG5T zY5J=AeYvH`d$9#Lv8O(!L~eXiOy0-NVXZX_8=aEuk1IlM z3o1~p@}r7-M>b<~+Mce$Pc22lE)gFsA%~w@mEP?*USfa$22c3Gt-wlp-1-frd!q25qIXHipyjG*qOT0rh>o3RNA}>_B(OZX{k;oxU$4Ir2?w z!|sgx)sd2tr0Zt<$X^VoJy(mQHlZ8-udd{MZCVYgZlI<4^SY6?<-8DP-~+L1F7R&7 zHT@&Ue;JA4Ya`5*&V;v?(5kYi5~i_m2bk&euSedw5GS$0kw=xQ2)fQjQ z!4-HR|MrY{ar~E!(xEE(;utP*h4kYmTx{bc-RSk~ybZiccl73Mm$$9)j(5r3Kl-Tt) z#Y57**ms>`mw_t+X-J}xWVzPK8yloe_v@ig+;>&~5IY-0df@KIPOe>W8Wm7*-m1bk zYd$A@=&nnk3nz|^w?x%4S6o$BvnB3er_V^tck-RppyEw!whgfJSs!ZS)`lQeu-&qG zfR3Fs{WaD8ZsK&(O;l^jdNVY|5VxNWFw~d?qRE=5$m7-?jWfB0qU55IB(xd7Hjl;(7rk;Eo4Eh}+VNSdfo8}y--Tu#C`|l>nxdJ#R$hcW%~tI}3P-`a zSr>?>7DdwPud2J%&hbkZ>tWTLMm-UMVmaO>D=itx5jiEaMfc~;Eh4+NTGdRKaBzJ$ zoAvd-n_%fdto+J6e<1bRpBMDJ28q-s==ayiY3AK0RmMx)CGK~mOnO`GhZk;gck|H; z`2G8_pipc;q6!{GV#>Wz20w&Ly>DvjJwV9(#W4ol-j0mLmL;a|V0r=a2XV zX_XAue`g-9)O@z@THt&nnm-w(C&1L+qq>!KtD1VcBU)j5QW!-}T<_nt`RdAWYYwkw zMAltn3XC2Vh`-}xGwPSdo0?r(k|*yX6ZqvfgreyEy-w3Z2N*I-MIIJ%ev8*r<_W$? z#3`Q~z-5&$eTgQ5%`#TMXNR7dc#jnpZFQ~Z_R>mJ3PUy9R3*zf9MAavC3JeWkXx!I z6H5`8YpS<5y<6HW8L0atQUA{^KWG@!N65zMbuVUzn)ny>*Tx65y(c%MQNl=FIra>Yg%8h0x=hpHCWZHNp`=PRI-6jv$b4fc&dJWxmO zi7z0)1M7g9g_~od=1K(46y!ti`H1FN{(w0;CLi?oo#REzPFDg17Vc>P`pTqnuKApM z3GREY#b^f&G&=(QGV}!{Ma9=>-iX1ix<~?4%m55%}>mWl~4U$;H{X;v#smK|2=#&BYRJn zo~j%!*6Xws@5B}z0q5~(-#wdJ#*y`<^L5X9r5|ev;bNQQ+`SA`b*QRtLL=}7t1oFt2JbEY@{l%AvDeg`jR^m_0h=NgpJtX0d38oV!czZ#7b9P*tq9IlwM zv1X=QIMBq!-uNZKa?%AfwTR5wz)dImyhw?N!G?4jkNRh8uSB$*rw>95FT^T=dI#?0G8Iy6Cznq>k;5!(~?LCU$ZSJ?62Pt9RZSOO)({z3eg&i|ccV4q+ z04FV)O%E)WuMGQMR0?Z+^wzb<@oFa=x+9yHjvd5P+6UkHSiF~3i_Tw>JqBW3E=J=7 z27E8veCzQ$X36Rtj%IV+Sn^&@(MJwy;G_tjRz_Mr>Nq~%?9+$ zr&Mfl-1cQ9ZRJrzFN{3jjpw4=&Q8O$p~%GX^qgVy z_PB75kAg<`z6a0zy+xaA0Cnt$JrwRr|2YP#UY8`PE6Hf+2iWP~pO+-{VJTtS;I}p- z5+f`O>!xgm%hfJDHR`h(HV7-Yjvu`31MLakl?$dp=q5ou5K7(mf-HZ0-zr5rG~TQG zq#$Nv1h)s~i`(EqM>04EKvM}i_ziFBjaTE@2VaJTnHDw3u9`~0EJ)0elW+jC+l>w7 zVc7_e;?Zo`>W~A$wIM<@yx|U|gbM^t%oViR*hS97Y{w* z{w#Z5%rDnh;Tx|z?l3BB=e&TSFPqo%7(zGwYc*B3L{x~{-F8q#voWP#;y5=}!s|V` z&+VPjwGwe8I>M12d_J9Gn$av5A)lk}HwdbZL+Tx_**;>uwNgJP%R7a^TS9&*&mXxJ z2irl%~D=Bs~^}k>rs9|4@F-2PDFtZ-dfpW1Nq0@(<<9gYgToE zttRD6e^UGn#kk(JTSnh?qRo8|QPdZ;yI>FXR1#C@M#RQhR|l@J*nWUkt&NMFG*jTB zi+;ui^EOM-snf+>u)qRiV_od{OJlg|`^B{#`OKfcFY^|`l9*6oIA?u?%Q>SlvmO9i zOBU4DpA>eiMA(XPrR$4xaU(DZu$w87DKR%eSi}KDv`){Nz-VQ3C;q$CTro3G4kZog z=15Vtfv0udcypbYwpM@Gabfk_DQSN-J>pwR6^CoO7tv=9#_RSv-MLkILMNQU7`Da3 zl;{u3QdED$VJl!X7tH%&6bH*s;LHD8exih%T6qZv;rlcDPYBRsAZ0(r+Z#!K9=HZb z3Z){=khrvrI+NKv05TV*CGXT0t`k%LD`{Dazqa2^vJdYD)bS>~>zpVY<#D9zGXtGw z*izK)8s(L-t4E{S1(G=`fl^n3T!I!Rziq>+01#Y8;wIyyN+b0dO3BUXFsuv|vNg%O zR2qrFz%;WQ+am5(@?WV=c53n>^wh+P5=|tA9ddVwfnlZ@Ww;h+`4`z8$*mDpv_jM{ z74MN8&;KMqPk0m~8fHlBVm>^CwG^Kq$S@N&uo!|Pnct0DY&>MP`=-+k9#Eu9cbTvu z`cOLK3TVVr@$4VKIuv#&fUCi7A-?l=k$PI2j%pgh*8mcd32{r5|2M36In7($ zzcL9?pKv2OoCLm*tu!aRk57(6@#Idz3zX+V9F8Mn0O2y^It|pQk$OAjjrV;NcOYE* zKPzOCL4r=`#+;&IHpB{O0+_Qq#rTB8A@OZ^?Ia5L)vQ`$wMB|!jv?e?e|O)ymA&zZ z4gn6OQcQc_iZhGd;Q}Lxb_)K^Fz#OG71g8%Bn~5SGX<2U$kJjlH3_@7G|-&TzwqsX z00@RFjnuJiI!$Q^g3DNbOeOArO61IW-o!=ebO^Vuo&tn4 zh`5tz*x2pemmL`M-NvawvQNV(3#`?vhI@(5YqX&^iPG0u=^yT3Wgu#;510L!5a8pP z6w?u-hKm-lo>96E+?7cCcp*vnQQuoi>&Ik5Ae&Kbn z%#Y%;&#*G7>)J>Jp)O`|fRwWYbzz_q34k74z6Z&dKI-Xmz|P;fM&Q0Aa&U<#4tE@X z4~kxe_X+giX-rU(KTRYszq~+qNLkPc7D-i|g=LN1w2=fg?7W*>t~Af~Sut2Bsn&Rj zAbqq6-YUF);`9>)sx{G{g-YjR6ada}oT9JmF9lN+aVd1MFZGFR#vgm3lDXY1;<*su zbMGw2o#*Rhk)8rNOqBHNiJg`~xVVWI`w+|la&LLMv_Wke5dVhiAf0YmY}}Ncvw=)- zV&q}eUncGr3Kb`>hoe@K6tiw3ZaQ5hdbyBRJiYpeil=EQ)uZrqS7gqZkNJe|3gPbk zy-+fp=8XPpc=TNdu~`tclb++p^8S%f{VB8NX-_-@yQcEjG=%uHxb~e;ZJ*_QJm5rM zOoI&c=ZrP&(N4R5&k3^`pPe!^&979zn-y z=298q$!*??nE0;}hdD;>P&Rv$-+4#q0edi9$K2))&BOq zHQMQ0_SHKw1XZoubYW`^sm;6e!tkvubPcTk3;Z$E{}eKl9FHv>SX+E#-lg!dragk1 z83C#yq4j!MvRAUvcN?GM^!j-M7d85GvUSjN3|eu{3OZT{UM#`%K!$pX{f>S1rt*}C zSM^+LB#__xms1tDwR~1q{%;!%%mGxto{HZZ7ZOs9Rich`nx4`Tl5n-(s`h1*5e7i) z)2x8%RpL1;Ym$F13*+`qNfYA*Ly(TO!yWZ!i`>&)3o|-R>V!r^HvTpH`E8>iMH3j7+1m_krJrHDBoB^O}!MP=Ae@ jWXk_U@c;dT89?ol<6kG*Wt*l;Q(B<(OfOcPcZm95Qv_Qy diff --git a/app/src/main/res/drawable/img_user_default_profile.jpg b/app/src/main/res/drawable/img_user_default_profile.jpg deleted file mode 100644 index 569284983120bcaa638e31a1774b0ac16264603f..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 15062 zcmb`uWpEu$5+!=YXfZQc%*WGf&?mU^*UDcUUdFpfNa|?hZB_=5b00992K)x#Aa~1Fl0P#NyBqRhhG!!&694rhB zEF3cYx37wfgM^Cob>Lv)VxVK-Vp0*|;So_WP<*Fg;9_Cn5*HEI(eVNKKLz;g1t3F% zY=QcNfsg<|kwL(aK|cEd?Ef+m0_0z|{_g?_3I+}V1p*E8brBTo|9J-h0SN*M4hHqP z0)Pkmx`_ma^d%I7nEUqoe+&O#I)9@-m5td<`Ts)WTKL!yQP~msZoc*Z{@C(qXN^tH zeU_xaKE5mBiI)P`P0fFO`kUCQY_`~2HvoW;I<=N@o_cuc+u~$9vP`bAwWxbeqTRl+dDY7;S>~@f6o> zaszjZKLEjlWbxFXGq!NWInws}fkOet&l$=2$A1EkvzVZy_OCxWw32}>AlgtZNv8ao zUnua-++@7AeeN$2FpL!|Ub?r!@ZS98=2z#9T(h`8F;{I>%%~?|CoWC|m;|mvjx%?=lHwbRTBA=%1JUrzWqh3FcPquyWtt*4R``t{~mf4$iJoy*vSEkrlFJkB^07 z%fIj6dOnI@#K%ZTZg1oPAaH4pQE&-fdAC*3$amva3Y(2L0=$dEFgW&}j{g6lft!`( zPw%odzx+#Hz1w*EIW46-DGK#<*8r2iUoUS|Z@Ho~mpY_1)45J!k7sqSYk$!1bRMoZ zwwXy+u?WNe8fX6~Q0NC2uk=Q037dIuw|D46$#MzvyJ#y_)90=i(R^6kl||*I^+_)7 z%Il`Aw9YyIkp5rIpTwI&eH!d!)U|GDz@fbL@;OAlVw!~cgD?4qc2l5Qa?RcC9)YBh znp{dqGvj{&!RFaSC>gUWxx4-LX6F_Kau?*eeuF80;klR;H9;+kfMwlY#s6!uPQ z`QW}R?n+>4Lb#8=8?1iZ^lM#Rckn^X`9EN>QyjAqN*7E`WhjoX&k;p>H>ZMk9)=?W za{(_)o@`GnBvG5&9sE-R z0063obew@Q`_nty;u9bmY4yEjS^NQ;z{Jm{Vyw@=4T=SkvegJK$;K3;{7+;ehLuS; zX2Q@P-d5=;J3VU1|HK0Tp!D|eJ8_1*7(m+wcCAF+92eKd@9K4wxA#ZSldha>k{r*T z^y-C8T%DLARhtDAj(5lS81$$C#umrXwJSEnbP5stq4C>`W7tdV+!8i9|J1=#40kS{ zWc`X+R;lB7<8Xs$RqNd|dONe9o)qD=P{LOmD=D?r8m>yON*YIL=4_0n?Q84Bba*!V z)3y2Jah$g@sxM3$CF5PyAQ*g{}UdrDP^@&Zy#SZ{VsfdW-qo?mv3E->dw7s^r~;vGN&lo=BybQkI#-YN75&E z{P3J#B~V}_i2E#+zciUe^zdIkBLGVFfbHhdZS~rNPvGcIvo`YZ{Ld}e8lPq5Sz<+_ zUjC{m=lt>*oKEV5SKd9=e*2+8(|?%tZ!i3amWLcv^m5Hg! zH4PJIK4$66Oby;2iclU)LiyGeg?`8l1uoX=$(dN9|ZD45z9$xQ+i~N{z z-7B5F&0b-mvMwKBD6YOVdUaysxfwEb9etp|qZ-cm4s)-NyrvkV%)`vap zccHOOq=XIUtNc2F46gP9`_X23*AmjIiRRhEtor#sae)KNl$G1C`Qg=kSf3 zM(%VhgDfC&zi{40nn-bN!u^o$uKof4|D*X8?Z_hmKtaGj!N4HkprIfkAih2hC>Q`7 z8G;xRg$Y$i0m{H3AQlOYg!zZOqGN7t4>}Q}zF}Zq9jUOAkyG3>CW|OAp%)s1)!Cm+ zY-W4sD-MGGijP190H1&ZwuMHBqDqTmiInpgri3QY_?UdCA;K~|C)=W3nbZRF=^gjw zMQq&(pxc=%Tdx`>%h3t_cS#&?x9 zwzGvOh{zQoTBJ{avBY=(_soZP*%QI*H!HpQqPc1l!9A@XXSGGHoU19%ry<{2%sn0W zRImkx2mBNp)(I8n$9F~%X6v+DY0>Qd+HF4jZv}cq>S^hyTcWB@SlQpYR*>w4rZ+1( zR{U~*4{ng=s$fmWwdh7AK_!AB))+D5(-=CDD`#`_B7`w#w1`1|mfeOM6~8wbJoDYJ z%1%uJ_zw|ePsK@WM;64+BfFUz`O_%wYT>Hbc@f1EE)08+-n*r>E!_`k^KwwKZ-@OHcorMLxFE3MbqtNqg1l@KjtC4*{x9zy%*4E7^l<<0lDiHr8 zvP9cF+13;oTU?)w%Us$Awk769m~s(IDoI*KbTaNCc%PxXrqXwX$6gR6{vb8uM@f>u zb3qWZdlN$O6x)Vqfez7+sSs*&7-jyEV2$UKngg>0zeocgSA5HQ&V8udE^Xz4H{|O2 z6kBpK=@kTbf%uo0{c4rI&>=#Sf~3aiK3$);HxxV$4Is&hzq|=1kh9aT?vhD=U>>KA z^Zh^^Z`6y5xjR}|wZ31~Q`SI`JX35M7!={VjL(Nw{F}2)@Nblr{^sT0s20T>zV{tS3zUge_85#?py5M((zalDbRl;ynzn z>N@o3a|cGwwk@GQfVE}qol085cUJLgGJuh|Aw_M9K>iZ|gI;Fe^LIB}d8SdV=}er3 zEaVKH`vtmE{P(?&Qi6=LA|v2z`y)~k{}A62ZmeAmMQeQmHR>)V>@Vl|7>VXdp^#17 z9z1;3G2r!mErUDIehHqXcJTEhJd*fu_Uzy??2w>TIl4m?*lVvN4)IY;vPR}FiC(uS z4*WZIH`gKFl12?&CCJ=qC9w6TP(*D_7xNCAr=$Y`3%VKvlkBREzVxwk?cGi68()h zuf2Wd`@DJElzm^?o;-goGjpvCP231w3sAn+=8vz{ip_8Yf)1Tcc*sUJWZ`YEl5{lV zWZbMz#Rem5`PUAed#5!ARoM~+$-+lr@?%#IbK4Q=O0wKNPg*I?8rL|)Pr>cc?4H^2 z+3PkeHdPz&99Wq^vh(TEXZKtz9cv~Pj$2n0q)_#tcLQFA%hySjXG7TbfBmhVrAd5< zi2my!;!_ml^S&G#?N;a`S1De5kp8(iJITB8m*%V`{*OiKfOmX?1=0;b&qD>IKVsx9HLnAk@ zV+ry13Dpd~d_lBuaHqS1kC-ptIuDSLr==|Egyr%*Q%jyRZQrK4O*ms9xepPqM5^BB z@qXgx<1JomYm#pF(6^0Zza_|72y$Pstv6t?2G8RXKkMqYBn1bqUufuU9M8g3T-J9Y?vj5_%cKQ{)LQl|Jb^2ZXeO?C$e+O?cYwE_?>*=QA=>DF~M zoH^z=dG!iBlBHTTMlR2cKceJJg-T4kJn~ZcS*@`=%dqoX!npFe#I?ilMfH#`%NJxb z>tj#23cm-&Z-6{Re1wPJa|BXW5L>g3O;F)`@$bK1It8Y)bBt{}c3G-n_K>ZR)sbMZ z-xQ1kM)t5(`E#7ab^vG(Wot`&upZ&4YdL0IRX8(yv#ft9bhT198xwtl#n1(uU7qJa zB>*Ur-C(8U4Rj#R!$}&u6T8vBJRkJmg5G3Y$$%MQ(z>SDlazvDOpUZWcG5W2E^;<@ zZV&^N<3A)QLU)qbkA`;>fG2E&cr&LQT|2nt2R9Z>Ql7Kg^uPJ0RAJY3s!Bo-A7&W$XEYt=y zrCtqli+uw8?WP@+m=K|7cZhx7SKXM`3|G-^^N~h0 z)EkuK!vAV)>@{i#cvG9$DK^p;-|LIubktxe8u2rS*>9^8rX+p%Yw~wK8}aldVrOGa zxGT%hVYq3==9mmXGg2L&v@PqSrncv!svcp1LtxSv*%tBGuJYc%(R8~y+$xDwI^-5` zuY5IP!&)Q7L?)~7hxCqDrf=4AJ!n25ws4P9z9OBhHa<&H%wFioa`BgY@Qz?JjExC~ zFIr|vX6-9%yO-tQ;HFG{;LN&27;!*+lA#;zP)B^%G2;0Fp zvPhCJGUzaL1t>wtZZ&+r9U8Uir)b5QCqJ<|_7|VE7-_yO4+1BczAaEt*y%>B`%GN% zd83Fty=ATSJk+>6_x_Z7vo+h`6vdr%-ag2ftQTB7An>46V{|w3_ig;oRxFZ2a8#hS zhS3Nw$1p#9OO#y2AH)jZTzl0xYv)}zYD2FMj!Z*BG|!g>8of_IUp2z%nub>ud$S8f z;r^KRy1F<@Mn?$42o(=xi9zrRmVV&(D6D+-UC|{HqJ0$tf4;s8n@;XUh99!`M}@1^ zn!djb3zUGsu-Nq6@+YG|4O#OanrFV@-+JzG;yA*@qeT01sr$O4v%9~3PNi=JR&fpy zIJ)V@D^oVlQalppdxWrC4*`sNWw)~ctbo6Gh-!x%Quym1exmx>qKKj{r&`EL{xGkW zHcjZv7fhSl(iY2WqR#nSqg;BY)i2R=2!I=p-lj)K2faP>sB*ggVC+ys(hD<@DN08_ zzUEczDuv7tIVVqij*4@o;HCNA*bG~0;R8O)hc%rcw)V4%;)HihT%ZVZttytjy?BQA zn=A-FdVzXXW|8^|Lahi_bU5L!O^Zj`r%ynL-P5AN)|dr_kL>8u1k3LpAWh)(?IG_8 z?rqeZI!L^B-;~JtPgw-2Ar6&eHE47XM{ZO_i76&sYb1M#)1!jAV9`DlNuEn>x#HKE zXA04NHL#~equcws^0k$GZ`Cw5{=jZEq!0DC&?;YHxN_}6ZJuAgtk@w}^E7Q77} znlf~p&R2XRJ4Pv@uUe8*_pbyBi)#P-&PVU&53qYMBE*B~dJwj3VrHp3BWkhi) z59l7;7g3hO{}Aja?`rKBW4-WjEZ+`PX3pPbW;T4Iaof$gi?7M;)$9ZzU-M{}yF+)$ zwQNddu$s&-7xDNw{|Xsv<<(tqqg8~7(y$8g$wd8b;TL%`I{xGv^`-3Ia*a^*W_v&V z9p+npWV!%}*hzteVm(ZR25YxW`)LUuP5Wr4PprJIGmTu?II@ZVzU*1lWPQIT- zl5^)``|@ z%y8#YxUa#j{!_GncP@jW4T1Y8-{cf1k#B>vROFi>TD2WrM#_< z$l9a`t@X-I5fi_L@YdMpEx@)=8NLMDXAw%Kip%4jcpEVt^LSxi`(|N6>&-hq3C%jL zPKgV$=m$F`92RjakHvp#BNR}1ie{u+pp<9pAvQl2S<~sK!&*ny%dZfno7lhL<{3E< zucgHrg&q?$Bph7H5lC-LMW0U@iSQmFGd^)_P*cu>Rhb(_X{f#S`oq`t=&gH1za{*=%?*?JNHNj2BtQM^K)NDQ5_h zg9Ejc)Mb}dH6iU#=FW$QsqwzYAC!B{VqK)gQydhns}!@TjtA6qBRE91Olo&N&v{dN z{{~}oxr2$3OvNg`Pxbo7=lt;2z4gjb-foB<{lgnVl=tCGYSi=b3AS;*Nug!ZDPGzS z*AbX`B#paWUo%o@Y`q1%pcmp#vk9oOHuL-zn?~l6XHmm*t^EW>q=fJ`iv$3l-=YI% z0M!=7CSeX$7?#OBoT9QkWd-&Cz7mrHU7vvGqMHz=De`EfyKz5=@(t>_YWyWK`NLKr zzk|}^tZm=~%kcWzQ1KKVy7}!%-8dY0Aj%Xl%J35aA#|OSE3{G6j2NhOeqDO5(prCx zrhm2;WYki!Av!202d1#mRwBOl$C^TvNsFja(B3iP&+UEN7l&Wz*+32T!zCgrSDL~(xIo z$}f0Ml#c^MYMuqmg2V_*b5h8j{*t+DMCfPfGs^D0(4bRMS2%Sd&?GeXP>c23DS#0` zQTB|`olcnb&vM@F#Qn?&Sl6DEIMAlLy;KyS0z{s;$w$c|`Z}n^=hU`8wVr+FA0Q7A zI(oYVQ8-SP3mmO|cj~H9nEgHiN0Q+YDPzYi4l^GFSqljQD7H*rZdVzPn2f`*9e!EsIusBSOL?Cf5~tbl(UeMjF37SDRmH z?kHgY^7?izY6)zT{cc*Do@F6FjM>fB+K9lWRum}$dHqrO6kEQf4>3F}0rd*H(vW{h zVs76Ml88Dhnb?neWx=X=Kf-2XD5uzj=FYM6Yw*K$Pz|Tp1Bz+K9-}>^oD)TB~WhnMf5fF>3%cpB=^O+F1TCgKLkF) zD2&FyW4EsPHyN$tYa;c0eKhYF_G*uQrUdY)m#`%y_U-{8ltKUdjdZYd18wb-m9~;0 zX05^MV=lXvx81&(hc$shs!im{i|!0dE;LI)nAW zPX!)FhkSPkb5Ol(Q5ukt=8#Vmi+WgFiv4I*Jcg|vL*1+GtR*8c-8e}_C~v?WF{g@^WagJjF3GUHf-RZX_(F#jSQ*+JbnD`3N{$? zn3>k265-(;uH16X`$0#&&9%UAN>=fAf?A==w>1mTt70u5IiL*cD7+&V6D<#RwAw>2 z$hqRw=r=j9jHB5jd8K6aTYNfZ0CRB&`naUmxsk(#17=ei`SB56zu*-(2i!7CR}u0_ z5*>LS3I$Myf3Fxt`#8mabi z7ltTUJ;V^Gw-b}M0wr=rOFR~-Wyc#!>^(O()^$Eq^-W`j$CeJUFP6(?x8BK@f5lfM z|KurLW)?89+PPt%MilG@>jsf1d5!%;D<#1Rh6X^BP@#3j-#erJ1cW>DAn4on-yoiB zeZ;(qp{Z65re)>e&?sDTA`HbG{NO!bKY5;k*>B2%*|wHO4G}&>FWwX`Sr+oZ>`yQw z6p0MWaL1u*SzVj>cH~tU7$`&IWClgLHsGB92LsHI(tU0QzsFj6@wj|-ibJ?0vAD6V8zqU1v;FI0sICXHWTW}nzFZB@x2#Z?g7L%0oEizofBC(a(%MldhsU7?E#HYsZbV#{3r9YLEaDO}i4 z>uKfd@uXFelj3M$ZB#0D`JhGhFEcEvS->v(fSM59R-*e_o+zkD5>zB7)UHFFx%XtQX|2IC= z)X|M7i6m}9EEY2-C$-pfC72FSGtw|Qks-mG^@wa}RIcAmL9sT5qf~o0A&@2Fr9>i6 zR$g|U*>xk%8mVmHj|*=`O&mKL9JWzn)Y7S{PeC1;UU-zDQ`235|I%~<6~5tCj8|*#fz;pyCXP7v+?qr&DwGJ%mPV z;&fPBj7q{*AWsi^{&mC17-Sib2q);ic%NPs)Zks%IdK-N`<}QYq{7_37C*SM!>^d# z6eG?lUyCqe+KB+*@r0gkoS-Tv_+?f@yd(|NB-fzNkA_vliaL5vqv}ZN*!axjWU`D4 zDw8orQpjp@l^^R4SuZFA{SvPG@Dr!ytFKzpe%{(Kv5y3W!x!=nY4eR9_ZoG5ao|%X z7@i)h{n>Le6q=ngCIjuagNNJQtK~24(HV-Cic{dSHCTJbQ>J5ANrj0;v^q;oGPKN7 zRO)|Y4++pAmH5cJ*r@;GH7KU6jc$t78(S3#rsDmkwi#iD{1 zHuNfJUNiEZ%iRLHa_tgAg{RFSo67*6fT@O_f+U{zZ=r-wz&E|?TfC}Fqv%VsH)L}M zIi%1uyV1J6w+se~Qi%Zs_Qh);T`NC1h@yjS7rB!=a2gi&uS@u8rHp=Cn}w>eiM(L9 z!BsGmf(A=-FM(%EbC`K?vkR7q`RZb$41Ch9G&~Ry7tK!Mt4Du8!JEQWTLf<<;{kl6 z@(vUC(XCdFaF(cr&ghK^mG?rQK$!eILV9hd}cppQ;m+G6WTC7pU*~Bq8~I~9esZW zA-`2v#W;ukI!ME)%14`D$-CgpQ?u$!Wq#vtVOpv>+7ie7K5DV~BAv#EOe5D(^x^NEu6Qhtk)B6B=UMn1YDfVWrI9xMoga9gB_W_f$@$KZiZ@vP2)~DFN(Ycf4>F zShj>xsa^T$43J(0xQXxEN)+Z1G@A8~_l*NB+MHwJvD3*8FTkF*_j(QTbQflETEOq{ zEcxrKEjiCc)N5-R_$D0Vo;`;#+9SsjMEQ`8#h)NAJXRqMEzPZRk?^5J@3U!9>IV}0 z5&L8+&H{CPB-OM)&O`)Uwrb7rZ<;r#L8`X%{1w@7_W*y-h>zq#`#Cjxb5CnEhlE7Z zA*vHpvMTSVqGK5g8`g{&Es?zllW8qG9m|gIg_a7nVc?A|L)}T=_pz^Bjzp!=l5WP} z%g4xU6^4LRJ@3+6KJ+wW&zIyNaepaDvd;-Q1==9Yreyxk5M!0Ep6YO~NT;`oI57Bm z5klba&EK!;ZR+aO-K6e&aCx(=KXZ$vl{$kFcvFZ!?V)_5CScgM=p^qld!1Bz_AjsE*_Kx9KIt{Z|0{O$ zo&%zmr9vA|G9;0u2bCp+^uWMWK1KN^;}Q#>^kDTqu03%cpH_80VfhK*f1ST1hiWad z_JV|PBzGES*$umcy;rGCquODQq}Im3PYogYu~|RXX(j#mJj;4p0$}jDB5ht4wgzYDm}(A>-!e=)|{o zlf9alen}oieB%Ei3V;%XI*7qS^2DjRx!s5DlaA_Zu4y&jg-%Y!*gpio!^dijWYeib)W6^kxnCMy)j zsrDXz5n#Vjoj2s-;79tSK1aFkV2U2WW6fFV*0sG~(+pvCi0v#w^XKo}2vbSu(`W#% zjGo7rcO_FlPI@?9n~7(6g`zBd+6|{?$uyN1`o&b>XFs8H;+gq0aFFl9c^YbBO3>?| zQ2w)rL^XD5SoA=^)N(A)z33TksGvhK0%+fh_X4Jz0W(nh3Ft}vD5&MwmFwkIAn<7M ze4NYJbST<>cacR1DKYy5*dAV$=A^C2PP&taenfYfUo^8T1x4&g3mFMPEP`)V zyZHo+i+re*+d^(jYa{0u6u3=ZGTh>+G&VM-Iclzog8E>^5PjfzZw_eVW1>o0adui+ zY()$*XXY`-Pt1}+#*av&rn&>)k~UB75#uJP99AtC;}9VvW1xyY=r`ffts%P`wl%@cGlU`5!0zaxD^f zv&IDBp_I?u+rl%GTb}f~UCTqTp8$iecv*4IJKetdK`r~9ylc4{?j3jYCF~Pm+QD$g z;CDjhonfDIYUAv~!63c&3HX&?4E zut#TxjrJ@D5%sT?tc7uVX1Z0tw`{P@{@>$AxGUPm9uLLM&wc&4FBTn9X5Fq;3~>}E z`NCwIF)G9DC*$#J;JhLMJPjD2}a1X-jL2tBJpi&LWrj zZ-P zC}H?6iPoN^G{kn}`doG9s=dF>!*$RhRLVU8oIg8vHw)7G(`PJ)Q;Nsvh-g3_d5>st z`^JozQYm|!j8tw!O1H1+F;SrV=hS{vGjU~#8!H^4g8BR~%Kh$yaW76Un*tT#KRMg#LrgbHzr43KEZckib59P2;jorRuHKfjID;i zy43u$9SIKYQ?z@bIVDZ0FN6&lm5ps3LgZK?FH}Ri9=V%kxnSS`?udxKdAO8@bwSEB z%t81A`WoHJ=v(P`NA-&lF3w9gH3v;13zm*8ebeI9;>0~$T3`HJP0_*rU{E*q>jMG6 z!nz1@5l8ZSh#4PXhAVP@ZYy%9@&L6D4|tBa*(vE{huk+_=7HK7r@oIJIUmvjoUQ^( zciKKjSUQ^iKJGa1EGr&+L?0$do0En6^q8K^v^st|ARl}9b0Cz~Q7%%LHHN#xE*Raa zLhMOnaO8kfwcY`*2lf#Ord7Rz~1|2#Rpvey`vsYj>dD* zwA2*gUtpjEWlDh{$mnI>w1MG_I;0^9t% z51c8BI_FDNk(4eYWv9h1?;OD6GcqOFpaxB(k?b6|>4_C`>@`-;uWa-qKNJ^l}3du>#ACHo+@O}Gq!Yq}dR z>5?$~q2Iib(r7WQl9=$^5QdQm;kX=a z@d-wu1u7!S3jQ33-Sl+1tvacwyy^Wa=iw`i4|T->*Ey82+v?=Cs-DL6+j|d6k8Eg? zDBwat1o$7js!1E$yX+ss+i^nt*Wy~yW+Y&ZV?1R(iYG>`4^ZEWTwTX7jK(wO*wvh} zP07Jh*C>w`wQ7caBuZJ+fA4x=6``2{F4yHHNYW@si5STo)&&F6fH8i6xvf^oGz|&r z@gH_u)c;!Vq`|^%+1paaAxAJx=HDx^q09Nmpho1UC`@29v4yOrOIvV@xY|Kvw35oM zY=V|+NdF@MXm6BmX=~!VhKeoMk~k+_ik^=~@zUI(A4kmGiaq$IzTrJ7ZkR3@VZ>R^ zb5sqoJnS$aw`JnLV{uXWce_~7`)G3ou?yW-@xU}jyNam=$;~j)HC48Y`HZsOTUv2vFhHyvb+R$=Q2IGRv!ZpQyaC?0}iYul7NNsMhV02M` zAXyMh`B7lmDG1w{rA~^k>?QKF6+<JT@+bZ`}*riZT!5eCL8uSvHAgZ`^U_ZeE>gNO>Z#?D}nHv z)t9>x{Bl>IUmgnVYbVM7^jBXEaOAK387NG|3JzZmi$qY!0G*LpUNImx4hju}^oMe8 z4~xjZ$c$u4M*sF%UpqVg#amRFlASO=9ig=@<#rm}IKbyG$vVT}q)%YplSZ42ZDNKI z`xYJ0=OYz6LIW;BJCp-PG0Yuxb{e5)5gy!RIhRFxT2AzXCF&;&p&+7FTxN>ZV%=3u zu&*8a$L^@Tc%kZ2S$u-tCg(olgYrG;?y-$G+xN0l-}{1M;ZK0oZt4eg+xD_+@|t0J z!`cW3f_$O&lzlKWG90lH4uwJjrw29JNHJa8Z2&yEUsL`Em&#YXwWCnjB@_8hU zmhJbJs)FgT=`NK;j(}n#Ue|mJScA%k8os?cnDBG4(4c0W?}v#!ROetEnbX#C^%=4W z2A4`u?bjEA9x5M)vcPxgclmm6n{MiVdfg1RHkuzFDtEBTtR1>zMQZ97-L)Cb5?f(n zr*e_o;*vk1a=x9lW$* zOPs^E5IqZy04xXReX}8$nn9vJ0o+_hP}MZ0x@qqOXGrRY>lJxjqzM6t=J0>Cv~8kS zY{u}9pKcH>&pGjnwdi1|XJRQ}MJA)>#~1~ zz_^@Wb<%4#TpMEkxu4$us)lH@;~`UuITx|bPgmigut9+k0?#9aBlBXPVji2`MP|8H z?NDIx$JV8X;LP&fwAj{MPSkcPEGy)1uJ6LKC5?*lej+zPezVpT@rHB=nAqp z_2J6V`RxV5F|>BcnDLfnp>w$|r&3-OuUv7z@~;@WLV`M1jsdwjJQmz^Cr;CS<@58C zuf;2z@n~H5aw>2)V0q$7WooY7wzYe-b#USsSQ_=@*annoVW2s}_sEzxAp%)UgK(?M zdxZL1JQ;1;`9rw70M%JeXedpYmGvwE^i|AD>5F{38*AcpzYGgqSL-q*~xYJrkKgm&~q5R9T-%e0TS zqE1UzV(7}*bVeA>92r?fTo4kn8TuDyrC2_IYbfC1m#LVVQXqoR1*vquR#+9x53kXO zlV+6h>To6+2G0oOvh*P7@)XmN$J{f6y{U1Y3iFF0*qrz+R=4%~=jfMA%^j)X0=3A@ z_ThaU8I~#9xYjWyvZrjTWs=*~MBn`}Up;bGkh?=&b8_7~gC}6sC%|SqVPaXUjpbc# zLeo}iw)T;fup6I{%t4an_wJ8zPmnTg&J;wOrR(dM&ULrOGWuJq z{v#pS8Py0sg?tYh@~=?Pk|V%^;QXJRL_wkC%#Yr!Ok3Q_^z&feT7=(xh3e%rR6RO2 zZZr2Le!KWlJkjHqT9qjb(5K_dB=E026nYJ~SpxlX!>p?t2b%{HR{%>b*RPZTikrcj z`tBx_I&9fRea!v>pfziOVOp|1(Q_6u&&K0dT3ki`f5<4{rJroBC*9!2t!;siFi zLNPuIn`z^Z(86--Pz&)rwvKPg39GFCjhR-`GlbxDU#RIPpzOq=u$D%e=9a~UV)fXX zQL7v;@32DL9E?~t1YYit(Ylzz7r{l%Fjw0o|F&}Dy~}^5z=0ke9nH)CYxol5Lf=3 zrL!4#6lf5H61Y7hvXk?dJ7gSF{#qhRYzmzE{{AnTGa=^r8)SuEtNF)+tHF8(A~g#s z4Ok$L`0s^P2Rju>aU*AkwLYzPx%rD!z&-hW=O@7ZB{1liQtnXRtR6z{2b<^J3Yv zxvmNcqX~r#&QEu@xcZ|l_pbx0i~Rl^r}?mrJn%L zK-SaYvsNg0F>ztwoHa@MDTHFBJ98oi=df&vQA`^8)TU_%BNbX1EIat5xYbfb5`|AM}I;2_JRdAl`ii{&VCpRoMRC#s&Txs zBMqTIC)^Y5*XFYJ71SX@lKxoFo$2q&IMQw5){MW+{5E)XY^yi!a+9?Ew$_!dM|D9s eG4^zH!4a+H_+eDkXfjCPW^irjH>>x#{67GB4nTeY diff --git a/app/src/main/res/values/colors.xml b/app/src/main/res/values/colors.xml deleted file mode 100644 index 3c07dfa6..00000000 --- a/app/src/main/res/values/colors.xml +++ /dev/null @@ -1,11 +0,0 @@ - - - #FFFF5F61 - #FFBB86FC - #FF6200EE - #FF3700B3 - #FF03DAC5 - #FF018786 - #FF000000 - #FFFFFFFF - \ No newline at end of file diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml deleted file mode 100644 index f6391a42..00000000 --- a/app/src/main/res/values/strings.xml +++ /dev/null @@ -1,157 +0,0 @@ - - MusicRoad - - - 위치와 마이크 권한이 필요합니다. - 설정에서 권한을 허용해 주세요. - 네트워크 연결이 원활하지 않습니다. - 유저 정보가 존재하지 않습니다. 앱을 재설치 해주세요. - 유저 등록 실패. 문의해주세요 - 권한 요청 - 앱 실행을 위해\n위치와 마이크 권한이 필요합니다. - 확인 - 설정(앱 정보)에서 권한을 허용해주세요. - 설정으로 이동 - - - 픽 보관함 이동 버튼 아이콘 - 설정 이동 버튼 아이콘 - 내비게이션 중앙 버튼 아이콘 - 🎧 주변에 %d개의 픽이 있습니다! - 🔇 주변에 %d개의 픽이 있습니다! - 앨범 이미지 - 님의 픽 - 픽을 담은 개수 - 내가 - 등록한 픽 - - - 픽 등록 - 님의 픽 - 등록하기 - 앨범 이미지 - 뒤로 가기 - 거리에 남길 한마디를 입력하세요. - 등록된 한마디가 없습니다. - 삭제하기 - 담은 픽 - 픽 담기 - 픽을 담은 개수 - 밀어서 뮤직비디오 보기 - 5초 앞으로 - 5초 뒤로 - 재생/일시정지 - Apple Music에서 제공하는 30초 미리보기 영상입니다. - 480p - 삭제되었습니다 - 픽 보관함에 추가되었습니다. - 픽 보관함에서 제거되었습니다. - 담은 픽이 없습니다 - 등록한 픽이 없습니다 - 일시적인 오류가 발생했습니다. - - - 취소 - 선택 삭제 - 원 윤곽선이 있는 체크 아이콘 - 전체 - 선택 - - - 최근 등록순 - 최근 담은순 - 과거 담은순 - 과거 등록순 - 담기 많은순 - 체크 아이콘 - 닫기 - - - 검색 - 검색 결과 - 노래 앨범 이미지 - 노래 검색 버튼 - 검색 결과가 없습니다. - 검색 결과를 불러올 수 없습니다. - - - 현재 위치 로딩 중... - 종료 - - - 삭제하시겠습니까? - 등록하신 픽이 삭제됩니다. - 취소 - 삭제하기 - - 픽 보관함에서 %d개의 픽이 삭제됩니다. - 선택하신 %d개의 픽이 삭제됩니다. - - - 상단 바 뒤로 가기 버튼 - 픽 목록 편집 모드 활성화 버튼 - 전체 선택 - 선택 해제 - 픽 보관함 - 등록한 픽 - - - 메뉴로 이동하기 아이콘 - 프로필 이미지 - Pick - 픽 보관함 - 등록한 픽 - 픽 보관함 메뉴 아이콘 - 등록한 픽 메뉴 아이콘 - Settings - 프로필 - 알림 - 로그아웃 - 회원 탈퇴 - 프로필 설정 메뉴 아이콘 - 알림 메뉴 아이콘 - 로그아웃 메뉴 아이콘 - 지도 아이콘 - 지도로 돌아가기 - - - 알림 설정 - 준비 중인 기능입니다! - 프로필 설정 - - 닉네임 - 닉네임에는 한글, 영문, 숫자만 사용할 수 있습니다. - 닉네임을 2글자 이상 입력해주세요. - 닉네임은 10글자 이하로 가능합니다. - 변경 사항 적용 - 변경 사항이 적용되었습니다. - 일시적인 오류가 발생했습니다. - - - 로그인 - Sign in with Google - Google Logo - - - 더 많은 기능을 이용하기 위해\n로그인이 필요합니다 - 담은 픽을 확인하기 위해\n로그인이 필요합니다 - 픽을 등록하기 위해\n로그인이 필요합니다 - 픽을 담기 위해\n로그인이 필요합니다 - 픽을 담기 위해\n로그인이 필요합니다 - 로그인이 필요합니다 - 취소 - 로그인에 실패했습니다 - 기기에 로그인된 구글 계정이 없습니다 - Google 로그인을 사용할 수 없는 기기입니다 - - - 로그아웃 하시겠습니까? - 취소 - 로그아웃 - - - 정말 탈퇴하시겠습니까? - 탈퇴 시 회원 정보와 픽 데이터가 삭제되며, 복구할 수 없습니다. - 취소 - 탈퇴 - diff --git a/app/src/main/res/values/themes.xml b/app/src/main/res/values/themes.xml deleted file mode 100644 index 052adf48..00000000 --- a/app/src/main/res/values/themes.xml +++ /dev/null @@ -1,11 +0,0 @@ - - - - - \ No newline at end of file From 9afe83cd80cabe14a2390901a695270e064972fa Mon Sep 17 00:00:00 2001 From: miller198 Date: Sun, 27 Apr 2025 23:41:12 +0900 Subject: [PATCH 60/62] [fix] mapper change userid -> uid --- .../src/main/java/com/squirtles/data/firebase/model/Mapper.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/data/firebase/src/main/java/com/squirtles/data/firebase/model/Mapper.kt b/data/firebase/src/main/java/com/squirtles/data/firebase/model/Mapper.kt index 331cd6c6..5794987c 100644 --- a/data/firebase/src/main/java/com/squirtles/data/firebase/model/Mapper.kt +++ b/data/firebase/src/main/java/com/squirtles/data/firebase/model/Mapper.kt @@ -50,7 +50,7 @@ fun FirebasePick.toPick(): Pick = Pick( comment = comment.toString(), favoriteCount = favoriteCount, createdBy = Creator( - uid = createdBy?.get("userId") ?: "", + uid = createdBy?.get("uid") ?: "", userName = createdBy?.get("userName") ?: "" ), createdAt = createdAt?.toDate()?.formatTimestamp() ?: "", From 8b53a3294d955303baf198716a5cc2b8187646ac Mon Sep 17 00:00:00 2001 From: miller198 Date: Mon, 28 Apr 2025 18:31:40 +0900 Subject: [PATCH 61/62] =?UTF-8?q?[merge]=20#173-real-time-pick=20=EB=B0=98?= =?UTF-8?q?=EC=98=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../data/firebase/BaseFirebaseDataSource.kt | 26 ++++++- .../data/pick/FirebasePickDataSource.kt | 13 +++- .../data/pick/FirebasePickDataSourceImpl.kt | 76 +++++++++---------- .../data/pick/FirebasePickRepositoryImpl.kt | 32 ++++++-- domain/pick/build.gradle.kts | 2 + .../domain/pick/FirebasePickRepository.kt | 3 +- .../domain/pick/usecase/FetchPickUseCase.kt | 3 +- .../com/squirtles/feature/map/MapScreen.kt | 61 +++++++++------ .../com/squirtles/feature/map/MapViewModel.kt | 49 +++++++----- .../squirtles/feature/map/marker/Clusterer.kt | 21 +++++ feature/map/src/main/res/values/strings.xml | 1 + 11 files changed, 194 insertions(+), 93 deletions(-) diff --git a/data/firebase/src/main/java/com/squirtles/data/firebase/BaseFirebaseDataSource.kt b/data/firebase/src/main/java/com/squirtles/data/firebase/BaseFirebaseDataSource.kt index c8b7ea12..ee2bfb44 100644 --- a/data/firebase/src/main/java/com/squirtles/data/firebase/BaseFirebaseDataSource.kt +++ b/data/firebase/src/main/java/com/squirtles/data/firebase/BaseFirebaseDataSource.kt @@ -4,7 +4,9 @@ import android.util.Log import com.google.firebase.firestore.CollectionReference import com.google.firebase.firestore.DocumentReference import com.google.firebase.firestore.DocumentSnapshot +import com.google.firebase.firestore.EventListener import com.google.firebase.firestore.FirebaseFirestore +import com.google.firebase.firestore.ListenerRegistration import com.google.firebase.firestore.Query import com.google.firebase.firestore.QuerySnapshot import com.squirtles.domain.firebase.FirebaseException @@ -41,7 +43,7 @@ open class BaseFirebaseDataSource( } query.get().await() }.onFailure { - Log.e("FirebaseDataSource", "Failed to query documents", it) + Log.e("FirebaseDataSource", "Failed to get query result documents", it) throw FirebaseException.ExecuteQueryFailedException(collection = collection.name) } } @@ -59,7 +61,25 @@ open class BaseFirebaseDataSource( .get() .await() }.onFailure { - Log.e("FirebaseDataSource", "Failed to create query for range", it) + Log.e("FirebaseDataSource", "Failed to get query result documents for range", it) + throw FirebaseException.ExecuteQueryFailedException(collection = collection.name) + } + } + + protected fun streamDocumentsInRange( + collection: FirebaseCollections, + field: FirebaseDocumentFields, + start: String, + end: String, + listener: EventListener + ): ListenerRegistration { + return try { + fetchCollection(collection) + .whereGreaterThanOrEqualTo(field.name, start) + .whereLessThanOrEqualTo(field.name, end) + .addSnapshotListener(listener) + } catch (e: Exception) { + Log.e("FirebaseDataSource", "Failed to get query stream for range", e) throw FirebaseException.ExecuteQueryFailedException(collection = collection.name) } } @@ -78,7 +98,7 @@ open class BaseFirebaseDataSource( } } - protected suspend fun setDocument(collection: FirebaseCollections, docId:String, value: Any): Result { + protected suspend fun setDocument(collection: FirebaseCollections, docId: String, value: Any): Result { return runCatching { fetchDocumentReference(collection, docId).set(value).await() }.onFailure { diff --git a/data/pick/src/main/java/com/squirtles/data/pick/FirebasePickDataSource.kt b/data/pick/src/main/java/com/squirtles/data/pick/FirebasePickDataSource.kt index 68f15477..4677c5f6 100644 --- a/data/pick/src/main/java/com/squirtles/data/pick/FirebasePickDataSource.kt +++ b/data/pick/src/main/java/com/squirtles/data/pick/FirebasePickDataSource.kt @@ -1,10 +1,21 @@ package com.squirtles.data.pick +import com.squirtles.core.model.Pick import com.squirtles.data.firebase.model.FirebasePick +import kotlinx.coroutines.flow.Flow + +data class PickWithType( + val type: PickType, + val pick: Pick +) + +enum class PickType { + UPDATED, REMOVED +} interface FirebasePickDataSource { suspend fun fetchPick(pickId: String): Result - suspend fun fetchPicksInArea(lat: Double, lng: Double, radiusInM: Double): Result> + suspend fun fetchPicksInArea(lat: Double, lng: Double, radiusInM: Double): Flow> suspend fun createPick(firebasePick: FirebasePick, userId: String): Result suspend fun deletePick(pickId: String, userId: String): Result suspend fun fetchMyPicks(userId: String): Result> diff --git a/data/pick/src/main/java/com/squirtles/data/pick/FirebasePickDataSourceImpl.kt b/data/pick/src/main/java/com/squirtles/data/pick/FirebasePickDataSourceImpl.kt index fd16f386..c29fb6d5 100644 --- a/data/pick/src/main/java/com/squirtles/data/pick/FirebasePickDataSourceImpl.kt +++ b/data/pick/src/main/java/com/squirtles/data/pick/FirebasePickDataSourceImpl.kt @@ -3,12 +3,12 @@ package com.squirtles.data.pick import android.util.Log import com.firebase.geofire.GeoFireUtils import com.firebase.geofire.GeoLocation +import com.google.firebase.firestore.DocumentChange import com.google.firebase.firestore.DocumentReference import com.google.firebase.firestore.DocumentSnapshot import com.google.firebase.firestore.FieldValue import com.google.firebase.firestore.FirebaseFirestore -import com.google.firebase.firestore.Query -import com.google.firebase.firestore.QuerySnapshot +import com.google.firebase.firestore.ListenerRegistration import com.google.firebase.firestore.toObject import com.squirtles.data.firebase.BaseFirebaseDataSource import com.squirtles.data.firebase.FirebaseCollections @@ -16,12 +16,13 @@ import com.squirtles.data.firebase.FirebaseDocumentFields import com.squirtles.data.firebase.model.FirebaseFavorite import com.squirtles.data.firebase.model.FirebasePick import com.squirtles.data.firebase.model.FirebaseUser -import kotlinx.coroutines.suspendCancellableCoroutine +import com.squirtles.data.firebase.model.toPick +import kotlinx.coroutines.channels.awaitClose +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.callbackFlow import kotlinx.coroutines.tasks.await import javax.inject.Inject import javax.inject.Singleton -import kotlin.coroutines.resume -import kotlin.coroutines.resumeWithException @Singleton class FirebasePickDataSourceImpl @Inject constructor( @@ -38,36 +39,48 @@ class FirebasePickDataSourceImpl @Inject constructor( } } - /* Fetches picks within a given radius from Firestore */ override suspend fun fetchPicksInArea( lat: Double, lng: Double, radiusInM: Double - ): Result> { - val center = GeoLocation(lat, lng) - val bounds = GeoFireUtils.getGeoHashQueryBounds(center, radiusInM) - - return runCatching { - val queryResults = bounds.map { bound -> - queryDocumentsInRange( + ): Flow> = callbackFlow { + val listeners = mutableListOf() + try { + val center = GeoLocation(lat, lng) + val bounds = GeoFireUtils.getGeoHashQueryBounds(center, radiusInM) + + bounds.forEach { bound -> + val listenerRegistration = streamDocumentsInRange( collection = FirebaseCollections.Picks, field = FirebaseDocumentFields.GeoHash, start = bound.startHash, - end = bound.endHash - ) - } - - queryResults.flatMap { querySnapshot -> - querySnapshot.getOrThrow().documents - .filter { doc -> - isAccurate(doc, center, radiusInM) - }.mapNotNull { doc -> - doc.toObject()!!.copy(id = doc.id) + end = bound.endHash, + ) { snapshot, e -> + if (e != null) { + Log.w("SnapshotListener", "listen:error", e) + throw(e) + } else { + val pickData = snapshot?.documentChanges + ?.filter { isAccurate(it.document, center, radiusInM) } + ?.map { dc -> + val pick = dc.document.toObject().toPick().copy(id = dc.document.id) + when (dc.type) { + DocumentChange.Type.ADDED, DocumentChange.Type.MODIFIED -> PickWithType(PickType.UPDATED, pick) + DocumentChange.Type.REMOVED -> PickWithType(PickType.REMOVED, pick) + } + }.orEmpty() + + trySend(pickData) } + } + listeners.add(listenerRegistration) } - }.onFailure { e -> - Log.e(TAG_LOG, "Failed to fetch picks", e) + } catch (e: Exception) { + close(e) } + + // Flow 종료 시 모든 리스너 제거 + awaitClose { listeners.forEach { it.remove() } } } /* Creates a new pick in Firestore */ @@ -164,19 +177,6 @@ class FirebasePickDataSourceImpl @Inject constructor( ).getOrThrow().documents } - private suspend fun executeQuery(query: Query): QuerySnapshot { - return suspendCancellableCoroutine { continuation -> - query.get() - .addOnSuccessListener { result -> - continuation.resume(result) - } - .addOnFailureListener { exception -> - Log.w(TAG_LOG, "Error fetching favorite documents", exception) - continuation.resumeWithException(exception) - } - } - } - companion object { private const val TAG_LOG = "FirebasePickDataSourceImpl" } diff --git a/data/pick/src/main/java/com/squirtles/data/pick/FirebasePickRepositoryImpl.kt b/data/pick/src/main/java/com/squirtles/data/pick/FirebasePickRepositoryImpl.kt index e5c42d1a..b6f44f7b 100644 --- a/data/pick/src/main/java/com/squirtles/data/pick/FirebasePickRepositoryImpl.kt +++ b/data/pick/src/main/java/com/squirtles/data/pick/FirebasePickRepositoryImpl.kt @@ -1,9 +1,11 @@ package com.squirtles.data.pick -import com.squirtles.domain.pick.FirebasePickRepository +import com.squirtles.core.model.Pick import com.squirtles.data.firebase.model.toFirebasePick import com.squirtles.data.firebase.model.toPick -import com.squirtles.core.model.Pick +import com.squirtles.domain.pick.FirebasePickRepository +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.map import javax.inject.Inject import javax.inject.Singleton @@ -39,11 +41,27 @@ class FirebasePickRepositoryImpl @Inject constructor( lat: Double, lng: Double, radiusInM: Double - ): Result> { - return runCatching { - val firebasePicks = pickDataSource.fetchPicksInArea(lat, lng, radiusInM).getOrThrow() - firebasePicks.map { it.toPick() } - } + ): Flow> { + val latestNearPick = mutableMapOf() // String : Pick Id + + return pickDataSource.fetchPicksInArea(lat, lng, radiusInM) + .map { pickWithTypeList -> + pickWithTypeList.forEach { pickWithType -> + val pick = pickWithType.pick + when (pickWithType.type) { + PickType.UPDATED -> { + latestNearPick[pick.id] = pick + } + + PickType.REMOVED -> { + latestNearPick[pick.id]?.let { + latestNearPick.remove(pick.id) + } + } + } + } + latestNearPick.values.toList() + } } override suspend fun fetchFavoritePicks(userId: String): Result> { diff --git a/domain/pick/build.gradle.kts b/domain/pick/build.gradle.kts index d45c0a97..6b0a474a 100644 --- a/domain/pick/build.gradle.kts +++ b/domain/pick/build.gradle.kts @@ -5,4 +5,6 @@ plugins { dependencies { implementation(projects.core.model) implementation(projects.domain.picklist) + + implementation(libs.kotlinx.coroutines.core) } diff --git a/domain/pick/src/main/java/com/squirtles/domain/pick/FirebasePickRepository.kt b/domain/pick/src/main/java/com/squirtles/domain/pick/FirebasePickRepository.kt index ad8cbaf4..c022f98a 100644 --- a/domain/pick/src/main/java/com/squirtles/domain/pick/FirebasePickRepository.kt +++ b/domain/pick/src/main/java/com/squirtles/domain/pick/FirebasePickRepository.kt @@ -1,12 +1,13 @@ package com.squirtles.domain.pick import com.squirtles.core.model.Pick +import kotlinx.coroutines.flow.Flow interface FirebasePickRepository { suspend fun createPick(pick: Pick): Result suspend fun deletePick(pickId: String, userId: String): Result suspend fun fetchPick(pickId: String): Result - suspend fun fetchPicksInArea(lat: Double, lng: Double, radiusInM: Double): Result> + suspend fun fetchPicksInArea(lat: Double, lng: Double, radiusInM: Double): Flow> suspend fun fetchMyPicks(userId: String): Result> suspend fun fetchFavoritePicks(userId: String): Result> } diff --git a/domain/pick/src/main/java/com/squirtles/domain/pick/usecase/FetchPickUseCase.kt b/domain/pick/src/main/java/com/squirtles/domain/pick/usecase/FetchPickUseCase.kt index ce08b33e..d4dee2f4 100644 --- a/domain/pick/src/main/java/com/squirtles/domain/pick/usecase/FetchPickUseCase.kt +++ b/domain/pick/src/main/java/com/squirtles/domain/pick/usecase/FetchPickUseCase.kt @@ -2,6 +2,7 @@ package com.squirtles.domain.pick.usecase import com.squirtles.core.model.Pick import com.squirtles.domain.pick.FirebasePickRepository +import kotlinx.coroutines.flow.Flow import javax.inject.Inject class FetchPickUseCase @Inject constructor( @@ -10,6 +11,6 @@ class FetchPickUseCase @Inject constructor( suspend operator fun invoke(pickId: String): Result = pickRepository.fetchPick(pickId) - suspend operator fun invoke(lat: Double, lng: Double, radiusInM: Double): Result> = + suspend operator fun invoke(lat: Double, lng: Double, radiusInM: Double): Flow> = pickRepository.fetchPicksInArea(lat, lng, radiusInM) } diff --git a/feature/map/src/main/java/com/squirtles/feature/map/MapScreen.kt b/feature/map/src/main/java/com/squirtles/feature/map/MapScreen.kt index 39995ac0..decd1c38 100644 --- a/feature/map/src/main/java/com/squirtles/feature/map/MapScreen.kt +++ b/feature/map/src/main/java/com/squirtles/feature/map/MapScreen.kt @@ -1,5 +1,6 @@ package com.squirtles.feature.map +import android.widget.Toast import androidx.activity.compose.BackHandler import androidx.compose.foundation.background import androidx.compose.foundation.clickable @@ -92,6 +93,14 @@ fun MapScreen( } } } + + launch { + mapViewModel.fetchPicksErrorToast + .flowWithLifecycle(lifecycleOwner.lifecycle, Lifecycle.State.STARTED) + .collect { + Toast.makeText(context, context.getString(R.string.error_message_fetch_picks_in_bounds), Toast.LENGTH_SHORT).show() + } + } } LaunchedEffect(playerState) { @@ -133,29 +142,35 @@ fun MapScreen( verticalArrangement = Arrangement.Bottom, horizontalAlignment = Alignment.CenterHorizontally ) { - clickedMarkerState.prevClickedMarker?.let { - if (clickedMarkerState.curPickId != null) { // 단말 마커 클릭 시 - showBottomSheet = false - mapViewModel.picks[clickedMarkerState.curPickId]?.let { pick -> - InfoWindow( - pick = pick, - uid = mapViewModel.getUid(), - navigateToPick = { pickId -> - onPickSummaryClick(pickId) - }, - calculateDistance = { lat, lng -> - mapViewModel.calculateDistance(lat, lng).let { distance -> - when { - distance >= 1000.0 -> "%.1fkm".format(distance / 1000.0) - distance >= 0 -> "%.0fm".format(distance) - else -> "" + if (mapViewModel.lastCameraPosition != null && + clickedMarkerState.prevClickedMarker?.position == mapViewModel.lastCameraPosition?.target + ) { + mapViewModel.resetClickedMarkerState(context) + } else { + clickedMarkerState.prevClickedMarker?.let { + if (clickedMarkerState.curPickId != null) { // 단말 마커 클릭 시 + showBottomSheet = false + mapViewModel.picks[clickedMarkerState.curPickId]?.let { pick -> + InfoWindow( + pick = pick, + uid = mapViewModel.getUid(), + navigateToPick = { pickId -> + onPickSummaryClick(pickId) + }, + calculateDistance = { lat, lng -> + mapViewModel.calculateDistance(lat, lng).let { distance -> + when { + distance >= 1000.0 -> "%.1fkm".format(distance / 1000.0) + distance >= 0 -> "%.0fm".format(distance) + else -> "" + } } } - } - ) + ) + } + } else { // 클러스터 마커 클릭 시 + showBottomSheet = true } - } else { // 클러스터 마커 클릭 시 - showBottomSheet = true } } @@ -168,7 +183,8 @@ fun MapScreen( mapViewModel.getUid()?.let { uid -> onFavoriteClick(uid) } ?: run { - signInDialogDescription = getString(context, R.string.sign_in_dialog_title_favorite_picks) + signInDialogDescription = + getString(context, R.string.sign_in_dialog_title_favorite_picks) showSignInDialog = true onSignInSuccess = onFavoriteClick } @@ -178,7 +194,8 @@ fun MapScreen( onCenterClick() mapViewModel.saveCurLocationForced() } ?: run { - signInDialogDescription = getString(context, R.string.sign_in_dialog_title_add_pick) + signInDialogDescription = + getString(context, R.string.sign_in_dialog_title_add_pick) showSignInDialog = true onSignInSuccess = { onCenterClick() diff --git a/feature/map/src/main/java/com/squirtles/feature/map/MapViewModel.kt b/feature/map/src/main/java/com/squirtles/feature/map/MapViewModel.kt index 0ef6faa7..93f90093 100644 --- a/feature/map/src/main/java/com/squirtles/feature/map/MapViewModel.kt +++ b/feature/map/src/main/java/com/squirtles/feature/map/MapViewModel.kt @@ -16,9 +16,12 @@ import com.squirtles.core.model.Pick import com.squirtles.domain.pick.usecase.FetchPickUseCase import com.squirtles.domain.user.usecase.GetCurrentUidUseCase import dagger.hilt.android.lifecycle.HiltViewModel +import kotlinx.coroutines.flow.MutableSharedFlow import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.StateFlow +import kotlinx.coroutines.flow.asSharedFlow import kotlinx.coroutines.flow.asStateFlow +import kotlinx.coroutines.flow.catch import kotlinx.coroutines.launch import javax.inject.Inject @@ -51,8 +54,12 @@ class MapViewModel @Inject constructor( private val _clickedMarkerState = MutableStateFlow(MarkerState()) val clickedMarkerState = _clickedMarkerState.asStateFlow() + private val _fetchPicksErrorToast = MutableSharedFlow() + val fetchPicksErrorToast = _fetchPicksErrorToast.asSharedFlow() + // FIXME : 네이버맵의 LocationChangeListener에서 실시간으로 변하는 위치 정보 -> 더 나은 방법이 있으면 고쳐주세요 private var _currentLocation: Location? = null + val curLocation get() = _currentLocation // LocalDataSource에 저장되는 위치 정보 // Firestore 데이터 쿼리 작업 최소화 및 위치데이터 공유 용도 @@ -120,10 +127,12 @@ class MapViewModel @Inject constructor( ) { viewModelScope.launch { val prevClickedMarker = _clickedMarkerState.value.prevClickedMarker - if (prevClickedMarker == marker) return@launch + // 클릭한 마커와 클릭되어 있는 마커가 다를 때만 크기 변경 + if (prevClickedMarker != marker) { + prevClickedMarker?.toggleSizeByClick(context, false) + marker.toggleSizeByClick(context, true) + } - prevClickedMarker?.toggleSizeByClick(context, false) - marker.toggleSizeByClick(context, true) val pickList = clusterTag?.split(",")?.mapNotNull { id -> picks[id] } _clickedMarkerState.emit(MarkerState(marker, pickList, pickId)) } @@ -142,26 +151,25 @@ class MapViewModel @Inject constructor( _centerLatLng.value?.run { val radiusInM = leftTop.distanceTo(this) fetchPickUseCase(this.latitude, this.longitude, radiusInM) - .onSuccess { pickList -> + .catch { + _fetchPicksErrorToast.emit(Unit) + } + .collect { pickList -> val newKeyTagMap: MutableMap = mutableMapOf() pickList.forEach { pick -> newKeyTagMap[MarkerKey(pick)] = pick.id _picks[pick.id] = pick } - _clickedMarkerState.value.clusterPickList?.let { clusterPickList -> // 클러스터 마커가 선택되어 있는 경우 - val updatedPickList = mutableListOf() - clusterPickList.forEach { pick -> - _picks[pick.id]?.let { updatedPick -> - updatedPickList.add(updatedPick) - } + + // 업데이트된 리스트에 기존 픽이 없으면 _picks와 clusterer에서 삭제 + val deletedKeyList = _picks.keys + .filterNot { it in newKeyTagMap.values } + .mapNotNull { pickId -> + _picks.remove(pickId)?.let { MarkerKey(it) } } - _clickedMarkerState.emit(_clickedMarkerState.value.copy(clusterPickList = updatedPickList.toList())) // 최신 픽 정보로 clusterPickList 업데이트 - } + clusterer?.addAll(newKeyTagMap) - } - .onFailure { - // TODO: NoSuchPickInRadiusException일 때 - Log.e("MapViewModel", "${it.message}") + clusterer?.removeAll(deletedKeyList) } } } @@ -170,10 +178,11 @@ class MapViewModel @Inject constructor( fun requestPickNotificationArea(location: Location, notiRadius: Double) { viewModelScope.launch { fetchPickUseCase(location.latitude, location.longitude, notiRadius) - .onSuccess { - _nearPicks.emit(it) - }.onFailure { - _nearPicks.emit(emptyList()) + .catch { + _fetchPicksErrorToast.emit(Unit) + } + .collect { pickList -> + _nearPicks.emit(pickList) } } } diff --git a/feature/map/src/main/java/com/squirtles/feature/map/marker/Clusterer.kt b/feature/map/src/main/java/com/squirtles/feature/map/marker/Clusterer.kt index d613788b..eb4d14cb 100644 --- a/feature/map/src/main/java/com/squirtles/feature/map/marker/Clusterer.kt +++ b/feature/map/src/main/java/com/squirtles/feature/map/marker/Clusterer.kt @@ -97,6 +97,17 @@ internal fun buildClusterer( } true } + + if (mapViewModel.clickedMarkerState.value.prevClickedMarker?.position == marker.position) { + mapViewModel.clickedMarkerState.value.clusterPickList?.let { + mapViewModel.setClickedMarkerState( + context = context, + marker = marker, + clusterTag = info.tag.toString() + ) + } + } + // 클러스터 마커를 클릭한 채로 configuration change 시 크기 유지 if (info.tag.toString() == mapViewModel.clickedMarkerState.value.clusterPickList?.joinToString(",") { it.id } @@ -137,6 +148,16 @@ internal fun buildClusterer( } true } + + // 2개짜리 클러스터 마커가 클릭된 상태에서 항목 삭제 시 바텀 시트 -> 인포윈도우 + if (mapViewModel.clickedMarkerState.value.prevClickedMarker?.position == marker.position) { + mapViewModel.setClickedMarkerState( + context = context, + marker = marker, + pickId = pick.id + ) + } + // 단말 마커를 클릭한 채로 configuration change 시 크기 유지 if (pick.id == mapViewModel.clickedMarkerState.value.curPickId) { mapViewModel.setClickedMarker(context, marker) diff --git a/feature/map/src/main/res/values/strings.xml b/feature/map/src/main/res/values/strings.xml index 76a73ef0..9749595a 100644 --- a/feature/map/src/main/res/values/strings.xml +++ b/feature/map/src/main/res/values/strings.xml @@ -2,6 +2,7 @@ 현재 위치 로딩 중... 종료 + 데이터를 불러오는 데 일시적인 오류가 발생했습니다. 픽 보관함 이동 버튼 아이콘 From eaa197aef9607e9dee216c2dd704beea2cbfa2e4 Mon Sep 17 00:00:00 2001 From: miller198 Date: Mon, 28 Apr 2025 19:32:32 +0900 Subject: [PATCH 62/62] =?UTF-8?q?[chore]=20consumer-rules.pro=20=EC=82=AD?= =?UTF-8?q?=EC=A0=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- core/account/consumer-rules.pro | 0 core/common/consumer-rules.pro | 0 core/mediaservice/consumer-rules.pro | 0 core/model/consumer-rules.pro | 0 core/musicplayer/consumer-rules.pro | 0 core/navigation/consumer-rules.pro | 0 core/picklist/consumer-rules.pro | 0 core/util/consumer-rules.pro | 0 data/applemusic/consumer-rules.pro | 0 data/favorite/consumer-rules.pro | 0 data/firebase/consumer-rules.pro | 0 data/location/consumer-rules.pro | 0 data/order/consumer-rules.pro | 0 data/pick/consumer-rules.pro | 0 data/user/consumer-rules.pro | 0 domain/applemusic/consumer-rules.pro | 0 domain/favorite/consumer-rules.pro | 0 domain/location/consumer-rules.pro | 0 domain/order/consumer-rules.pro | 0 domain/pick/consumer-rules.pro | 0 domain/picklist/consumer-rules.pro | 0 domain/player/consumer-rules.pro | 0 domain/user/consumer-rules.pro | 0 feature/create/consumer-rules.pro | 0 feature/detail/consumer-rules.pro | 0 feature/favorite/consumer-rules.pro | 0 feature/main/consumer-rules.pro | 0 feature/map/consumer-rules.pro | 0 feature/mypick/consumer-rules.pro | 0 feature/search/consumer-rules.pro | 0 feature/userinfo/consumer-rules.pro | 0 31 files changed, 0 insertions(+), 0 deletions(-) delete mode 100644 core/account/consumer-rules.pro delete mode 100644 core/common/consumer-rules.pro delete mode 100644 core/mediaservice/consumer-rules.pro delete mode 100644 core/model/consumer-rules.pro delete mode 100644 core/musicplayer/consumer-rules.pro delete mode 100644 core/navigation/consumer-rules.pro delete mode 100644 core/picklist/consumer-rules.pro delete mode 100644 core/util/consumer-rules.pro delete mode 100644 data/applemusic/consumer-rules.pro delete mode 100644 data/favorite/consumer-rules.pro delete mode 100644 data/firebase/consumer-rules.pro delete mode 100644 data/location/consumer-rules.pro delete mode 100644 data/order/consumer-rules.pro delete mode 100644 data/pick/consumer-rules.pro delete mode 100644 data/user/consumer-rules.pro delete mode 100644 domain/applemusic/consumer-rules.pro delete mode 100644 domain/favorite/consumer-rules.pro delete mode 100644 domain/location/consumer-rules.pro delete mode 100644 domain/order/consumer-rules.pro delete mode 100644 domain/pick/consumer-rules.pro delete mode 100644 domain/picklist/consumer-rules.pro delete mode 100644 domain/player/consumer-rules.pro delete mode 100644 domain/user/consumer-rules.pro delete mode 100644 feature/create/consumer-rules.pro delete mode 100644 feature/detail/consumer-rules.pro delete mode 100644 feature/favorite/consumer-rules.pro delete mode 100644 feature/main/consumer-rules.pro delete mode 100644 feature/map/consumer-rules.pro delete mode 100644 feature/mypick/consumer-rules.pro delete mode 100644 feature/search/consumer-rules.pro delete mode 100644 feature/userinfo/consumer-rules.pro diff --git a/core/account/consumer-rules.pro b/core/account/consumer-rules.pro deleted file mode 100644 index e69de29b..00000000 diff --git a/core/common/consumer-rules.pro b/core/common/consumer-rules.pro deleted file mode 100644 index e69de29b..00000000 diff --git a/core/mediaservice/consumer-rules.pro b/core/mediaservice/consumer-rules.pro deleted file mode 100644 index e69de29b..00000000 diff --git a/core/model/consumer-rules.pro b/core/model/consumer-rules.pro deleted file mode 100644 index e69de29b..00000000 diff --git a/core/musicplayer/consumer-rules.pro b/core/musicplayer/consumer-rules.pro deleted file mode 100644 index e69de29b..00000000 diff --git a/core/navigation/consumer-rules.pro b/core/navigation/consumer-rules.pro deleted file mode 100644 index e69de29b..00000000 diff --git a/core/picklist/consumer-rules.pro b/core/picklist/consumer-rules.pro deleted file mode 100644 index e69de29b..00000000 diff --git a/core/util/consumer-rules.pro b/core/util/consumer-rules.pro deleted file mode 100644 index e69de29b..00000000 diff --git a/data/applemusic/consumer-rules.pro b/data/applemusic/consumer-rules.pro deleted file mode 100644 index e69de29b..00000000 diff --git a/data/favorite/consumer-rules.pro b/data/favorite/consumer-rules.pro deleted file mode 100644 index e69de29b..00000000 diff --git a/data/firebase/consumer-rules.pro b/data/firebase/consumer-rules.pro deleted file mode 100644 index e69de29b..00000000 diff --git a/data/location/consumer-rules.pro b/data/location/consumer-rules.pro deleted file mode 100644 index e69de29b..00000000 diff --git a/data/order/consumer-rules.pro b/data/order/consumer-rules.pro deleted file mode 100644 index e69de29b..00000000 diff --git a/data/pick/consumer-rules.pro b/data/pick/consumer-rules.pro deleted file mode 100644 index e69de29b..00000000 diff --git a/data/user/consumer-rules.pro b/data/user/consumer-rules.pro deleted file mode 100644 index e69de29b..00000000 diff --git a/domain/applemusic/consumer-rules.pro b/domain/applemusic/consumer-rules.pro deleted file mode 100644 index e69de29b..00000000 diff --git a/domain/favorite/consumer-rules.pro b/domain/favorite/consumer-rules.pro deleted file mode 100644 index e69de29b..00000000 diff --git a/domain/location/consumer-rules.pro b/domain/location/consumer-rules.pro deleted file mode 100644 index e69de29b..00000000 diff --git a/domain/order/consumer-rules.pro b/domain/order/consumer-rules.pro deleted file mode 100644 index e69de29b..00000000 diff --git a/domain/pick/consumer-rules.pro b/domain/pick/consumer-rules.pro deleted file mode 100644 index e69de29b..00000000 diff --git a/domain/picklist/consumer-rules.pro b/domain/picklist/consumer-rules.pro deleted file mode 100644 index e69de29b..00000000 diff --git a/domain/player/consumer-rules.pro b/domain/player/consumer-rules.pro deleted file mode 100644 index e69de29b..00000000 diff --git a/domain/user/consumer-rules.pro b/domain/user/consumer-rules.pro deleted file mode 100644 index e69de29b..00000000 diff --git a/feature/create/consumer-rules.pro b/feature/create/consumer-rules.pro deleted file mode 100644 index e69de29b..00000000 diff --git a/feature/detail/consumer-rules.pro b/feature/detail/consumer-rules.pro deleted file mode 100644 index e69de29b..00000000 diff --git a/feature/favorite/consumer-rules.pro b/feature/favorite/consumer-rules.pro deleted file mode 100644 index e69de29b..00000000 diff --git a/feature/main/consumer-rules.pro b/feature/main/consumer-rules.pro deleted file mode 100644 index e69de29b..00000000 diff --git a/feature/map/consumer-rules.pro b/feature/map/consumer-rules.pro deleted file mode 100644 index e69de29b..00000000 diff --git a/feature/mypick/consumer-rules.pro b/feature/mypick/consumer-rules.pro deleted file mode 100644 index e69de29b..00000000 diff --git a/feature/search/consumer-rules.pro b/feature/search/consumer-rules.pro deleted file mode 100644 index e69de29b..00000000 diff --git a/feature/userinfo/consumer-rules.pro b/feature/userinfo/consumer-rules.pro deleted file mode 100644 index e69de29b..00000000