diff --git a/.github/workflows/develop_branch.yml b/.github/workflows/develop_branch.yml
index 1af3e99b..28ba3704 100644
--- a/.github/workflows/develop_branch.yml
+++ b/.github/workflows/develop_branch.yml
@@ -24,6 +24,7 @@ jobs:
- name: Generate local.properties
env:
KAKAO_NATIVE_APP_KEY: ${{ secrets.KAKAO_NATIVE_APP_KEY }}
+ KAKAO_REST_API_KEY: ${{ secrets.KAKAO_REST_API_KEY }}
BITNAGIL_DEV_URL: ${{ secrets.BITNAGIL_DEV_URL }}
BITNAGIL_PROD_URL: ${{ secrets.BITNAGIL_PROD_URL }}
RELEASE_KEY_ALIAS: ${{ secrets.RELEASE_KEY_ALIAS }}
@@ -31,6 +32,7 @@ jobs:
RELEASE_STORE_PASSWORD: ${{ secrets.RELEASE_STORE_PASSWORD }}
run: |
echo "kakao.native.app.key=$KAKAO_NATIVE_APP_KEY" >> local.properties
+ echo "kakao.rest.api.key=$KAKAO_REST_API_KEY" >> local.properties
echo "bitnagil.dev.url=$BITNAGIL_DEV_URL" >> local.properties
echo "bitnagil.prod.url=$BITNAGIL_PROD_URL" >> local.properties
echo "release.key.alias=$RELEASE_KEY_ALIAS" >> local.properties
diff --git a/app/build.gradle.kts b/app/build.gradle.kts
index 1bf1f6a0..2519d233 100644
--- a/app/build.gradle.kts
+++ b/app/build.gradle.kts
@@ -46,6 +46,17 @@ android {
name = "KAKAO_NATIVE_APP_KEY",
value = "\"$kakaoNativeAppKey\"",
)
+
+ val kakaoRestApiKey =
+ (properties["kakao.rest.api.key"] as? String)
+ ?: System.getenv("KAKAO_REST_API_KEY")
+ ?: throw GradleException("KAKAO_REST_API_KEY 값이 없습니다.")
+
+ buildConfigField(
+ type = "String",
+ name = "KAKAO_REST_API_KEY",
+ value = "\"$kakaoRestApiKey\"",
+ )
}
buildTypes {
diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index f643fa4b..28c5ae01 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -3,6 +3,11 @@
xmlns:tools="http://schemas.android.com/tools">
+
+
+
+
+
+
+
+
+
diff --git a/app/src/main/java/com/threegap/bitnagil/MainNavHost.kt b/app/src/main/java/com/threegap/bitnagil/MainNavHost.kt
index d80369e0..4810d5b1 100644
--- a/app/src/main/java/com/threegap/bitnagil/MainNavHost.kt
+++ b/app/src/main/java/com/threegap/bitnagil/MainNavHost.kt
@@ -13,6 +13,7 @@ import com.threegap.bitnagil.presentation.login.LoginScreenContainer
import com.threegap.bitnagil.presentation.onboarding.OnBoardingScreenContainer
import com.threegap.bitnagil.presentation.onboarding.OnBoardingViewModel
import com.threegap.bitnagil.presentation.onboarding.model.navarg.OnBoardingScreenArg
+import com.threegap.bitnagil.presentation.report.ReportScreenContainer
import com.threegap.bitnagil.presentation.routinelist.RoutineListScreenContainer
import com.threegap.bitnagil.presentation.setting.SettingScreenContainer
import com.threegap.bitnagil.presentation.splash.SplashScreenContainer
@@ -134,6 +135,11 @@ fun MainNavHost(
launchSingleTop = true
}
},
+ navigateToReport = {
+ navigator.navController.navigate(Route.Report) {
+ launchSingleTop = true
+ }
+ },
)
}
@@ -289,5 +295,15 @@ fun MainNavHost(
},
)
}
+
+ composable {
+ ReportScreenContainer(
+ navigateToBack = {
+ if (navigator.navController.previousBackStackEntry != null) {
+ navigator.navController.popBackStack()
+ }
+ },
+ )
+ }
}
}
diff --git a/app/src/main/java/com/threegap/bitnagil/Route.kt b/app/src/main/java/com/threegap/bitnagil/Route.kt
index 504f2993..93e1cd0e 100644
--- a/app/src/main/java/com/threegap/bitnagil/Route.kt
+++ b/app/src/main/java/com/threegap/bitnagil/Route.kt
@@ -48,4 +48,7 @@ sealed interface Route {
@Serializable
data object Guide : Route
+
+ @Serializable
+ data object Report : Route
}
diff --git a/app/src/main/java/com/threegap/bitnagil/di/core/NetworkModule.kt b/app/src/main/java/com/threegap/bitnagil/di/core/NetworkModule.kt
index 9d253400..1792fa7b 100644
--- a/app/src/main/java/com/threegap/bitnagil/di/core/NetworkModule.kt
+++ b/app/src/main/java/com/threegap/bitnagil/di/core/NetworkModule.kt
@@ -5,6 +5,9 @@ import android.content.Intent
import com.threegap.bitnagil.BuildConfig
import com.threegap.bitnagil.MainActivity
import com.threegap.bitnagil.datastore.auth.storage.AuthTokenDataStore
+import com.threegap.bitnagil.network.Auth
+import com.threegap.bitnagil.network.Kakao
+import com.threegap.bitnagil.network.NoneAuth
import com.threegap.bitnagil.network.auth.AuthInterceptor
import com.threegap.bitnagil.network.auth.TokenAuthenticator
import com.threegap.bitnagil.network.token.ReissueService
@@ -30,6 +33,8 @@ import javax.inject.Singleton
@InstallIn(SingletonComponent::class)
object NetworkModule {
private const val APPLICATION_JSON = "application/json"
+ private const val REST_API_KEY = BuildConfig.KAKAO_REST_API_KEY
+ private const val KAKAO_URL = "https://dapi.kakao.com"
@Provides
@Singleton
@@ -74,6 +79,43 @@ object NetworkModule {
override suspend fun clearTokens() = dataStore.clearAuthToken()
}
+ @Provides
+ @Singleton
+ @Kakao
+ fun provideKakaoAuthInterceptor(): Interceptor =
+ Interceptor { chain ->
+ val request = chain.request().newBuilder()
+ .addHeader("Authorization", "KakaoAK $REST_API_KEY")
+ .build()
+ chain.proceed(request)
+ }
+
+ @Provides
+ @Singleton
+ @Kakao
+ fun provideKakaoOkHttpClient(
+ httpLoggingInterceptor: HttpLoggingInterceptor,
+ @Kakao kakaoAuthInterceptor: Interceptor,
+ ): OkHttpClient = OkHttpClient.Builder()
+ .addInterceptor(kakaoAuthInterceptor)
+ .addInterceptor(httpLoggingInterceptor)
+ .connectTimeout(10L, TimeUnit.SECONDS)
+ .writeTimeout(30L, TimeUnit.SECONDS)
+ .readTimeout(30L, TimeUnit.SECONDS)
+ .build()
+
+ @Provides
+ @Singleton
+ @Kakao
+ fun provideKakaoRetrofit(
+ converterFactory: Converter.Factory,
+ @Kakao okHttpClient: OkHttpClient,
+ ): Retrofit = Retrofit.Builder()
+ .baseUrl(KAKAO_URL)
+ .addConverterFactory(converterFactory)
+ .client(okHttpClient)
+ .build()
+
@Provides
@Singleton
fun provideTokenAuthenticator(
diff --git a/app/src/main/java/com/threegap/bitnagil/di/data/DataSourceModule.kt b/app/src/main/java/com/threegap/bitnagil/di/data/DataSourceModule.kt
index 21a92847..0d399e5a 100644
--- a/app/src/main/java/com/threegap/bitnagil/di/data/DataSourceModule.kt
+++ b/app/src/main/java/com/threegap/bitnagil/di/data/DataSourceModule.kt
@@ -1,15 +1,23 @@
package com.threegap.bitnagil.di.data
+import com.threegap.bitnagil.data.address.datasource.AddressDataSource
+import com.threegap.bitnagil.data.address.datasource.LocationDataSource
+import com.threegap.bitnagil.data.address.datasourceImpl.AddressDataSourceImpl
+import com.threegap.bitnagil.data.address.datasourceImpl.LocationDataSourceImpl
import com.threegap.bitnagil.data.auth.datasource.AuthLocalDataSource
import com.threegap.bitnagil.data.auth.datasource.AuthRemoteDataSource
import com.threegap.bitnagil.data.auth.datasourceimpl.AuthLocalDataSourceImpl
import com.threegap.bitnagil.data.auth.datasourceimpl.AuthRemoteDataSourceImpl
import com.threegap.bitnagil.data.emotion.datasource.EmotionDataSource
import com.threegap.bitnagil.data.emotion.datasourceImpl.EmotionDataSourceImpl
+import com.threegap.bitnagil.data.file.datasource.FileDataSource
+import com.threegap.bitnagil.data.file.datasourceImpl.FileDataSourceImpl
import com.threegap.bitnagil.data.onboarding.datasource.OnBoardingDataSource
import com.threegap.bitnagil.data.onboarding.datasourceImpl.OnBoardingDataSourceImpl
import com.threegap.bitnagil.data.recommendroutine.datasource.RecommendRoutineDataSource
import com.threegap.bitnagil.data.recommendroutine.datasourceImpl.RecommendRoutineDataSourceImpl
+import com.threegap.bitnagil.data.report.datasource.ReportDataSource
+import com.threegap.bitnagil.data.report.datasourceImpl.ReportDataSourceImpl
import com.threegap.bitnagil.data.routine.datasource.RoutineRemoteDataSource
import com.threegap.bitnagil.data.routine.datasourceImpl.RoutineRemoteDataSourceImpl
import com.threegap.bitnagil.data.user.datasource.UserDataSource
@@ -63,4 +71,20 @@ abstract class DataSourceModule {
@Binds
@Singleton
abstract fun bindVersionDataSource(versionDataSourceImpl: VersionDataSourceImpl): VersionDataSource
+
+ @Binds
+ @Singleton
+ abstract fun bindLocationDataSource(locationDataSourceImpl: LocationDataSourceImpl): LocationDataSource
+
+ @Binds
+ @Singleton
+ abstract fun bindAddressDataSource(addressDataSourceImpl: AddressDataSourceImpl): AddressDataSource
+
+ @Binds
+ @Singleton
+ abstract fun bindFileDataSource(impl: FileDataSourceImpl): FileDataSource
+
+ @Binds
+ @Singleton
+ abstract fun bindReportDataSource(impl: ReportDataSourceImpl): ReportDataSource
}
diff --git a/app/src/main/java/com/threegap/bitnagil/di/data/RepositoryModule.kt b/app/src/main/java/com/threegap/bitnagil/di/data/RepositoryModule.kt
index 93fb9e37..cc8ddb90 100644
--- a/app/src/main/java/com/threegap/bitnagil/di/data/RepositoryModule.kt
+++ b/app/src/main/java/com/threegap/bitnagil/di/data/RepositoryModule.kt
@@ -1,17 +1,23 @@
package com.threegap.bitnagil.di.data
+import com.threegap.bitnagil.data.address.repositoryImpl.AddressRepositoryImpl
import com.threegap.bitnagil.data.auth.repositoryimpl.AuthRepositoryImpl
import com.threegap.bitnagil.data.emotion.repositoryImpl.EmotionRepositoryImpl
+import com.threegap.bitnagil.data.file.repositoryImpl.FileRepositoryImpl
import com.threegap.bitnagil.data.onboarding.repositoryImpl.OnBoardingRepositoryImpl
import com.threegap.bitnagil.data.recommendroutine.repositoryImpl.RecommendRoutineRepositoryImpl
+import com.threegap.bitnagil.data.report.repositoryImpl.ReportRepositoryImpl
import com.threegap.bitnagil.data.routine.repositoryImpl.RoutineRepositoryImpl
import com.threegap.bitnagil.data.user.repositoryImpl.UserRepositoryImpl
import com.threegap.bitnagil.data.version.repositoryImpl.VersionRepositoryImpl
import com.threegap.bitnagil.data.writeroutine.repositoryImpl.WriteRoutineRepositoryImpl
+import com.threegap.bitnagil.domain.address.repository.AddressRepository
import com.threegap.bitnagil.domain.auth.repository.AuthRepository
import com.threegap.bitnagil.domain.emotion.repository.EmotionRepository
+import com.threegap.bitnagil.domain.file.repository.FileRepository
import com.threegap.bitnagil.domain.onboarding.repository.OnBoardingRepository
import com.threegap.bitnagil.domain.recommendroutine.repository.RecommendRoutineRepository
+import com.threegap.bitnagil.domain.report.repository.ReportRepository
import com.threegap.bitnagil.domain.routine.repository.RoutineRepository
import com.threegap.bitnagil.domain.user.repository.UserRepository
import com.threegap.bitnagil.domain.version.repository.VersionRepository
@@ -57,4 +63,16 @@ abstract class RepositoryModule {
@Binds
@Singleton
abstract fun bindVersionRepository(versionRepositoryImpl: VersionRepositoryImpl): VersionRepository
+
+ @Binds
+ @Singleton
+ abstract fun bindAddressRepository(addressRepositoryImpl: AddressRepositoryImpl): AddressRepository
+
+ @Binds
+ @Singleton
+ abstract fun bindFileRepository(impl: FileRepositoryImpl): FileRepository
+
+ @Binds
+ @Singleton
+ abstract fun bindReportRepository(impl: ReportRepositoryImpl): ReportRepository
}
diff --git a/app/src/main/java/com/threegap/bitnagil/di/data/ServiceModule.kt b/app/src/main/java/com/threegap/bitnagil/di/data/ServiceModule.kt
index 66376c96..602d4a26 100644
--- a/app/src/main/java/com/threegap/bitnagil/di/data/ServiceModule.kt
+++ b/app/src/main/java/com/threegap/bitnagil/di/data/ServiceModule.kt
@@ -1,15 +1,19 @@
package com.threegap.bitnagil.di.data
+import com.threegap.bitnagil.data.address.service.AddressService
import com.threegap.bitnagil.data.auth.service.AuthService
import com.threegap.bitnagil.data.emotion.service.EmotionService
+import com.threegap.bitnagil.data.file.service.FileService
import com.threegap.bitnagil.data.onboarding.service.OnBoardingService
import com.threegap.bitnagil.data.recommendroutine.service.RecommendRoutineService
+import com.threegap.bitnagil.data.report.service.ReportService
import com.threegap.bitnagil.data.routine.service.RoutineService
import com.threegap.bitnagil.data.user.service.UserService
import com.threegap.bitnagil.data.version.service.VersionService
import com.threegap.bitnagil.data.writeroutine.service.WriteRoutineService
-import com.threegap.bitnagil.di.core.Auth
-import com.threegap.bitnagil.di.core.NoneAuth
+import com.threegap.bitnagil.network.Auth
+import com.threegap.bitnagil.network.Kakao
+import com.threegap.bitnagil.network.NoneAuth
import com.threegap.bitnagil.network.token.ReissueService
import dagger.Module
import dagger.Provides
@@ -66,4 +70,19 @@ object ServiceModule {
@Singleton
fun provideVersionService(@NoneAuth retrofit: Retrofit): VersionService =
retrofit.create(VersionService::class.java)
+
+ @Provides
+ @Singleton
+ fun provideAddressService(@Kakao retrofit: Retrofit): AddressService =
+ retrofit.create(AddressService::class.java)
+
+ @Provides
+ @Singleton
+ fun provideFileService(@Auth retrofit: Retrofit): FileService =
+ retrofit.create(FileService::class.java)
+
+ @Provides
+ @Singleton
+ fun provideReportService(@Auth retrofit: Retrofit): ReportService =
+ retrofit.create(ReportService::class.java)
}
diff --git a/app/src/main/java/com/threegap/bitnagil/navigation/home/HomeNavHost.kt b/app/src/main/java/com/threegap/bitnagil/navigation/home/HomeNavHost.kt
index 7025bc7d..249a9aef 100644
--- a/app/src/main/java/com/threegap/bitnagil/navigation/home/HomeNavHost.kt
+++ b/app/src/main/java/com/threegap/bitnagil/navigation/home/HomeNavHost.kt
@@ -43,6 +43,7 @@ fun HomeNavHost(
navigateToRegisterRoutine: (String?) -> Unit,
navigateToEmotion: () -> Unit,
navigateToRoutineList: (String) -> Unit,
+ navigateToReport: () -> Unit,
) {
val navigator = rememberHomeNavigator()
var showFloatingOverlay by remember { mutableStateOf(false) }
@@ -106,6 +107,11 @@ fun HomeNavHost(
BitnagilFloatingActionMenu(
actions = listOf(
+ FloatingActionItem(
+ icon = R.drawable.ic_complaint,
+ text = "제보하기",
+ onClick = { navigateToReport() },
+ ),
FloatingActionItem(
icon = R.drawable.ic_routine_add,
text = "루틴 등록",
diff --git a/core/designsystem/src/main/java/com/threegap/bitnagil/designsystem/component/atom/BitnagilFloatingButton.kt b/core/designsystem/src/main/java/com/threegap/bitnagil/designsystem/component/atom/BitnagilFloatingButton.kt
index ed883b23..391b246e 100644
--- a/core/designsystem/src/main/java/com/threegap/bitnagil/designsystem/component/atom/BitnagilFloatingButton.kt
+++ b/core/designsystem/src/main/java/com/threegap/bitnagil/designsystem/component/atom/BitnagilFloatingButton.kt
@@ -17,6 +17,7 @@ import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
+import androidx.compose.foundation.layout.width
import androidx.compose.foundation.shape.CircleShape
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material3.Text
@@ -101,7 +102,7 @@ fun BitnagilFloatingActionMenu(
),
) {
Column(
- modifier = Modifier.padding(16.dp),
+ modifier = Modifier.padding(vertical = 16.dp),
verticalArrangement = Arrangement.spacedBy(24.dp),
) {
actions.forEach { action ->
@@ -173,6 +174,8 @@ private fun FloatingActionMenuItem(
horizontalArrangement = Arrangement.spacedBy(14.dp),
modifier = modifier
.scale(scale)
+ .padding(horizontal = 16.dp)
+ .width(112.dp)
.clickableWithoutRipple { onClick() },
) {
BitnagilIcon(
diff --git a/core/designsystem/src/main/java/com/threegap/bitnagil/designsystem/component/atom/BitnagilTextButton.kt b/core/designsystem/src/main/java/com/threegap/bitnagil/designsystem/component/atom/BitnagilTextButton.kt
index b6898558..9afed8d6 100644
--- a/core/designsystem/src/main/java/com/threegap/bitnagil/designsystem/component/atom/BitnagilTextButton.kt
+++ b/core/designsystem/src/main/java/com/threegap/bitnagil/designsystem/component/atom/BitnagilTextButton.kt
@@ -95,13 +95,20 @@ data class BitnagilTextButtonColor(
) {
companion object {
@Composable
- fun default(): BitnagilTextButtonColor = BitnagilTextButtonColor(
- defaultBackgroundColor = BitnagilTheme.colors.coolGray10,
- pressedBackgroundColor = BitnagilTheme.colors.coolGray5,
- disabledBackgroundColor = BitnagilTheme.colors.coolGray96,
- defaultTextColor = BitnagilTheme.colors.white,
- pressedTextColor = BitnagilTheme.colors.coolGray20,
- disabledTextColor = BitnagilTheme.colors.white,
+ fun default(
+ defaultBackgroundColor: Color = BitnagilTheme.colors.coolGray10,
+ pressedBackgroundColor: Color = BitnagilTheme.colors.coolGray5,
+ disabledBackgroundColor: Color = BitnagilTheme.colors.coolGray96,
+ defaultTextColor: Color = BitnagilTheme.colors.white,
+ pressedTextColor: Color = BitnagilTheme.colors.coolGray20,
+ disabledTextColor: Color = BitnagilTheme.colors.white,
+ ): BitnagilTextButtonColor = BitnagilTextButtonColor(
+ defaultBackgroundColor = defaultBackgroundColor,
+ pressedBackgroundColor = pressedBackgroundColor,
+ disabledBackgroundColor = disabledBackgroundColor,
+ defaultTextColor = defaultTextColor,
+ pressedTextColor = pressedTextColor,
+ disabledTextColor = disabledTextColor,
)
@Composable
diff --git a/core/designsystem/src/main/java/com/threegap/bitnagil/designsystem/component/atom/BitnagilTextField.kt b/core/designsystem/src/main/java/com/threegap/bitnagil/designsystem/component/atom/BitnagilTextField.kt
new file mode 100644
index 00000000..4417c1ac
--- /dev/null
+++ b/core/designsystem/src/main/java/com/threegap/bitnagil/designsystem/component/atom/BitnagilTextField.kt
@@ -0,0 +1,89 @@
+package com.threegap.bitnagil.designsystem.component.atom
+
+import androidx.compose.foundation.background
+import androidx.compose.foundation.interaction.MutableInteractionSource
+import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.shape.RoundedCornerShape
+import androidx.compose.foundation.text.BasicTextField
+import androidx.compose.foundation.text.KeyboardActions
+import androidx.compose.foundation.text.KeyboardOptions
+import androidx.compose.material3.Text
+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.text.TextLayoutResult
+import androidx.compose.ui.text.TextStyle
+import androidx.compose.ui.text.input.VisualTransformation
+import androidx.compose.ui.tooling.preview.Preview
+import androidx.compose.ui.unit.dp
+import com.threegap.bitnagil.designsystem.BitnagilTheme
+
+@Composable
+fun BitnagilTextField(
+ value: String,
+ onValueChange: (String) -> Unit,
+ modifier: Modifier = Modifier,
+ enabled: Boolean = true,
+ textStyle: TextStyle = BitnagilTheme.typography.body2Medium,
+ textColor: Color = BitnagilTheme.colors.coolGray10,
+ keyboardOptions: KeyboardOptions = KeyboardOptions.Default,
+ keyboardActions: KeyboardActions = KeyboardActions.Default,
+ singleLine: Boolean = false,
+ maxLines: Int = if (singleLine) 1 else Int.MAX_VALUE,
+ minLines: Int = 1,
+ visualTransformation: VisualTransformation = VisualTransformation.None,
+ onTextLayout: (TextLayoutResult) -> Unit = {},
+ interactionSource: MutableInteractionSource = remember { MutableInteractionSource() },
+ placeholder: @Composable (() -> Unit)? = null,
+) {
+ BasicTextField(
+ value = value,
+ onValueChange = onValueChange,
+ modifier = modifier
+ .fillMaxWidth()
+ .background(
+ color = BitnagilTheme.colors.coolGray99,
+ shape = RoundedCornerShape(12.dp),
+ )
+ .padding(vertical = 16.dp, horizontal = 20.dp),
+ textStyle = textStyle.copy(color = textColor),
+ enabled = enabled,
+ keyboardOptions = keyboardOptions,
+ keyboardActions = keyboardActions,
+ singleLine = singleLine,
+ maxLines = maxLines,
+ minLines = minLines,
+ visualTransformation = visualTransformation,
+ onTextLayout = onTextLayout,
+ interactionSource = interactionSource,
+ ) { innerTextField ->
+ Box(
+ contentAlignment = Alignment.TopStart,
+ ) {
+ if (value.isEmpty() && placeholder != null) {
+ placeholder()
+ }
+ innerTextField()
+ }
+ }
+}
+
+@Preview
+@Composable
+private fun Preview() {
+ BitnagilTextField(
+ value = "",
+ onValueChange = {},
+ placeholder = {
+ Text(
+ text = "기타사항(직접 입력)",
+ color = BitnagilTheme.colors.coolGray80,
+ style = BitnagilTheme.typography.subtitle1Medium,
+ )
+ },
+ )
+}
diff --git a/presentation/src/main/java/com/threegap/bitnagil/presentation/setting/component/block/LogoutConfirmDialog.kt b/core/designsystem/src/main/java/com/threegap/bitnagil/designsystem/component/block/BitnagilAlertDialog.kt
similarity index 81%
rename from presentation/src/main/java/com/threegap/bitnagil/presentation/setting/component/block/LogoutConfirmDialog.kt
rename to core/designsystem/src/main/java/com/threegap/bitnagil/designsystem/component/block/BitnagilAlertDialog.kt
index efe64301..af64bafe 100644
--- a/presentation/src/main/java/com/threegap/bitnagil/presentation/setting/component/block/LogoutConfirmDialog.kt
+++ b/core/designsystem/src/main/java/com/threegap/bitnagil/designsystem/component/block/BitnagilAlertDialog.kt
@@ -1,4 +1,4 @@
-package com.threegap.bitnagil.presentation.setting.component.block
+package com.threegap.bitnagil.designsystem.component.block
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Arrangement
@@ -22,7 +22,11 @@ import com.threegap.bitnagil.designsystem.component.atom.BitnagilTextButtonColor
@OptIn(ExperimentalMaterial3Api::class)
@Composable
-fun LogoutConfirmDialog(
+fun BitnagilAlertDialog(
+ title: String,
+ description: String,
+ dismissButtonText: String,
+ confirmButtonText: String,
onDismiss: () -> Unit,
onConfirm: () -> Unit,
modifier: Modifier = Modifier,
@@ -45,7 +49,7 @@ fun LogoutConfirmDialog(
verticalArrangement = Arrangement.Center,
) {
Text(
- text = "로그아웃할까요?",
+ text = title,
color = BitnagilTheme.colors.coolGray10,
style = BitnagilTheme.typography.subtitle1SemiBold,
)
@@ -53,7 +57,7 @@ fun LogoutConfirmDialog(
Spacer(modifier = Modifier.height(6.dp))
Text(
- text = "이 기기에서 계정이 로그아웃되고, 다시\n로그인해야 서비스를 계속 이용할 수 있어요.",
+ text = description,
color = BitnagilTheme.colors.coolGray40,
style = BitnagilTheme.typography.body2Medium,
)
@@ -65,7 +69,7 @@ fun LogoutConfirmDialog(
horizontalArrangement = Arrangement.spacedBy(12.dp),
) {
BitnagilTextButton(
- text = "취소",
+ text = dismissButtonText,
onClick = onDismiss,
colors = BitnagilTextButtonColor.cancel(),
textStyle = BitnagilTheme.typography.body2Medium,
@@ -73,7 +77,7 @@ fun LogoutConfirmDialog(
)
BitnagilTextButton(
- text = "로그아웃",
+ text = confirmButtonText,
onClick = onConfirm,
textStyle = BitnagilTheme.typography.body2Medium,
modifier = Modifier.weight(1f),
@@ -85,8 +89,12 @@ fun LogoutConfirmDialog(
@Preview
@Composable
-private fun LogoutConfirmDialogPreview() {
- LogoutConfirmDialog(
+private fun Preview() {
+ BitnagilAlertDialog(
+ title = "로그아웃 할까요?",
+ description = "이 기기에서 계정이 로그아웃되고, 다시\n로그인해야 서비스를 계속 이용할 수 있어요.",
+ dismissButtonText = "취소",
+ confirmButtonText = "로그아웃",
onDismiss = {},
onConfirm = {},
)
diff --git a/core/designsystem/src/main/res/drawable-hdpi/img_pomo_loading.webp b/core/designsystem/src/main/res/drawable-hdpi/img_pomo_loading.webp
new file mode 100644
index 00000000..7f81da5f
Binary files /dev/null and b/core/designsystem/src/main/res/drawable-hdpi/img_pomo_loading.webp differ
diff --git a/core/designsystem/src/main/res/drawable-mdpi/img_pomo_loading.webp b/core/designsystem/src/main/res/drawable-mdpi/img_pomo_loading.webp
new file mode 100644
index 00000000..3ebfb84e
Binary files /dev/null and b/core/designsystem/src/main/res/drawable-mdpi/img_pomo_loading.webp differ
diff --git a/core/designsystem/src/main/res/drawable-xhdpi/img_pomo_loading.webp b/core/designsystem/src/main/res/drawable-xhdpi/img_pomo_loading.webp
new file mode 100644
index 00000000..21d836bf
Binary files /dev/null and b/core/designsystem/src/main/res/drawable-xhdpi/img_pomo_loading.webp differ
diff --git a/core/designsystem/src/main/res/drawable-xxhdpi/img_pomo_loading.webp b/core/designsystem/src/main/res/drawable-xxhdpi/img_pomo_loading.webp
new file mode 100644
index 00000000..62063a17
Binary files /dev/null and b/core/designsystem/src/main/res/drawable-xxhdpi/img_pomo_loading.webp differ
diff --git a/core/designsystem/src/main/res/drawable-xxxhdpi/img_pomo_loading.webp b/core/designsystem/src/main/res/drawable-xxxhdpi/img_pomo_loading.webp
new file mode 100644
index 00000000..c8c77c8e
Binary files /dev/null and b/core/designsystem/src/main/res/drawable-xxxhdpi/img_pomo_loading.webp differ
diff --git a/core/designsystem/src/main/res/drawable/ic_btn_close_sm.xml b/core/designsystem/src/main/res/drawable/ic_btn_close_sm.xml
new file mode 100644
index 00000000..658dc4ee
--- /dev/null
+++ b/core/designsystem/src/main/res/drawable/ic_btn_close_sm.xml
@@ -0,0 +1,19 @@
+
+
+
+
+
+
+
diff --git a/core/designsystem/src/main/res/drawable/ic_camera.xml b/core/designsystem/src/main/res/drawable/ic_camera.xml
new file mode 100644
index 00000000..a23169f7
--- /dev/null
+++ b/core/designsystem/src/main/res/drawable/ic_camera.xml
@@ -0,0 +1,13 @@
+
+
+
diff --git a/core/designsystem/src/main/res/drawable/ic_camera_gray.xml b/core/designsystem/src/main/res/drawable/ic_camera_gray.xml
new file mode 100644
index 00000000..1d968a9e
--- /dev/null
+++ b/core/designsystem/src/main/res/drawable/ic_camera_gray.xml
@@ -0,0 +1,23 @@
+
+
+
+
+
diff --git a/core/designsystem/src/main/res/drawable/ic_car.xml b/core/designsystem/src/main/res/drawable/ic_car.xml
new file mode 100644
index 00000000..2c11c1a7
--- /dev/null
+++ b/core/designsystem/src/main/res/drawable/ic_car.xml
@@ -0,0 +1,12 @@
+
+
+
+
+
+
diff --git a/core/designsystem/src/main/res/drawable/ic_complaint.xml b/core/designsystem/src/main/res/drawable/ic_complaint.xml
new file mode 100644
index 00000000..0a3c7adb
--- /dev/null
+++ b/core/designsystem/src/main/res/drawable/ic_complaint.xml
@@ -0,0 +1,38 @@
+
+
+
+
+
+
+
+
+
diff --git a/core/designsystem/src/main/res/drawable/ic_hammer.xml b/core/designsystem/src/main/res/drawable/ic_hammer.xml
new file mode 100644
index 00000000..1120d1b2
--- /dev/null
+++ b/core/designsystem/src/main/res/drawable/ic_hammer.xml
@@ -0,0 +1,18 @@
+
+
+
+
+
+
+
+
diff --git a/core/designsystem/src/main/res/drawable/ic_light.xml b/core/designsystem/src/main/res/drawable/ic_light.xml
new file mode 100644
index 00000000..17127aeb
--- /dev/null
+++ b/core/designsystem/src/main/res/drawable/ic_light.xml
@@ -0,0 +1,29 @@
+
+
+
+
+
+
+
+
+
diff --git a/core/designsystem/src/main/res/drawable/ic_loading.xml b/core/designsystem/src/main/res/drawable/ic_loading.xml
new file mode 100644
index 00000000..b42a852d
--- /dev/null
+++ b/core/designsystem/src/main/res/drawable/ic_loading.xml
@@ -0,0 +1,40 @@
+
+
+
+
+
+
+
+
+
+
diff --git a/core/designsystem/src/main/res/drawable/ic_location.xml b/core/designsystem/src/main/res/drawable/ic_location.xml
new file mode 100644
index 00000000..71f8bf6a
--- /dev/null
+++ b/core/designsystem/src/main/res/drawable/ic_location.xml
@@ -0,0 +1,38 @@
+
+
+
+
+
+
+
+
diff --git a/core/designsystem/src/main/res/drawable/ic_outside_gray.xml b/core/designsystem/src/main/res/drawable/ic_outside_gray.xml
new file mode 100644
index 00000000..2fd18396
--- /dev/null
+++ b/core/designsystem/src/main/res/drawable/ic_outside_gray.xml
@@ -0,0 +1,33 @@
+
+
+
+
+
+
+
diff --git a/core/designsystem/src/main/res/drawable/ic_report.xml b/core/designsystem/src/main/res/drawable/ic_report.xml
deleted file mode 100644
index aa86b3dc..00000000
--- a/core/designsystem/src/main/res/drawable/ic_report.xml
+++ /dev/null
@@ -1,56 +0,0 @@
-
-
-
-
-
-
-
-
-
-
diff --git a/core/designsystem/src/main/res/drawable/ic_water.xml b/core/designsystem/src/main/res/drawable/ic_water.xml
new file mode 100644
index 00000000..5e6f2439
--- /dev/null
+++ b/core/designsystem/src/main/res/drawable/ic_water.xml
@@ -0,0 +1,15 @@
+
+
+
+
+
+
+
diff --git a/core/designsystem/src/main/res/drawable/icon_down_arrow_black.xml b/core/designsystem/src/main/res/drawable/icon_down_arrow_black.xml
new file mode 100644
index 00000000..7d076be8
--- /dev/null
+++ b/core/designsystem/src/main/res/drawable/icon_down_arrow_black.xml
@@ -0,0 +1,16 @@
+
+
+
+
+
+
diff --git a/core/network/build.gradle.kts b/core/network/build.gradle.kts
index 36852524..f3aa8160 100644
--- a/core/network/build.gradle.kts
+++ b/core/network/build.gradle.kts
@@ -13,4 +13,5 @@ dependencies {
implementation(libs.bundles.retrofit)
implementation(platform(libs.okhttp.bom))
implementation(libs.bundles.okhttp)
+ implementation(libs.javax.inject)
}
diff --git a/app/src/main/java/com/threegap/bitnagil/di/core/Qualifier.kt b/core/network/src/main/java/com/threegap/bitnagil/network/Qualifier.kt
similarity index 61%
rename from app/src/main/java/com/threegap/bitnagil/di/core/Qualifier.kt
rename to core/network/src/main/java/com/threegap/bitnagil/network/Qualifier.kt
index d01cd30a..5fd9294e 100644
--- a/app/src/main/java/com/threegap/bitnagil/di/core/Qualifier.kt
+++ b/core/network/src/main/java/com/threegap/bitnagil/network/Qualifier.kt
@@ -1,4 +1,4 @@
-package com.threegap.bitnagil.di.core
+package com.threegap.bitnagil.network
import javax.inject.Qualifier
@@ -9,3 +9,7 @@ annotation class NoneAuth
@Qualifier
@Retention(AnnotationRetention.BINARY)
annotation class Auth
+
+@Qualifier
+@Retention(AnnotationRetention.BINARY)
+annotation class Kakao
diff --git a/data/build.gradle.kts b/data/build.gradle.kts
index 7f7e00f7..fcee75af 100644
--- a/data/build.gradle.kts
+++ b/data/build.gradle.kts
@@ -18,4 +18,6 @@ dependencies {
implementation(libs.bundles.okhttp)
implementation(platform(libs.retrofit.bom))
implementation(libs.bundles.retrofit)
+ implementation(libs.play.services.location)
+ implementation(libs.kotlinx.coroutines.play)
}
diff --git a/data/src/main/java/com/threegap/bitnagil/data/address/datasource/AddressDataSource.kt b/data/src/main/java/com/threegap/bitnagil/data/address/datasource/AddressDataSource.kt
new file mode 100644
index 00000000..6eab8826
--- /dev/null
+++ b/data/src/main/java/com/threegap/bitnagil/data/address/datasource/AddressDataSource.kt
@@ -0,0 +1,7 @@
+package com.threegap.bitnagil.data.address.datasource
+
+import com.threegap.bitnagil.data.address.model.response.Coord2AddressResponse
+
+interface AddressDataSource {
+ suspend fun fetchCurrentAddress(longitude: Double, latitude: Double): Result
+}
diff --git a/data/src/main/java/com/threegap/bitnagil/data/address/datasource/LocationDataSource.kt b/data/src/main/java/com/threegap/bitnagil/data/address/datasource/LocationDataSource.kt
new file mode 100644
index 00000000..e013ed57
--- /dev/null
+++ b/data/src/main/java/com/threegap/bitnagil/data/address/datasource/LocationDataSource.kt
@@ -0,0 +1,7 @@
+package com.threegap.bitnagil.data.address.datasource
+
+import com.threegap.bitnagil.domain.address.model.CurrentLocation
+
+interface LocationDataSource {
+ suspend fun fetchCurrentLocation(): Result
+}
diff --git a/data/src/main/java/com/threegap/bitnagil/data/address/datasourceImpl/AddressDataSourceImpl.kt b/data/src/main/java/com/threegap/bitnagil/data/address/datasourceImpl/AddressDataSourceImpl.kt
new file mode 100644
index 00000000..d80e6ae0
--- /dev/null
+++ b/data/src/main/java/com/threegap/bitnagil/data/address/datasourceImpl/AddressDataSourceImpl.kt
@@ -0,0 +1,20 @@
+package com.threegap.bitnagil.data.address.datasourceImpl
+
+import com.threegap.bitnagil.data.address.datasource.AddressDataSource
+import com.threegap.bitnagil.data.address.model.response.Coord2AddressResponse
+import com.threegap.bitnagil.data.address.service.AddressService
+import javax.inject.Inject
+
+class AddressDataSourceImpl @Inject constructor(
+ private val addressService: AddressService,
+) : AddressDataSource {
+
+ override suspend fun fetchCurrentAddress(longitude: Double, latitude: Double): Result {
+ return runCatching {
+ addressService.fetchCurrentAddress(
+ longitude = longitude.toString(),
+ latitude = latitude.toString(),
+ )
+ }
+ }
+}
diff --git a/data/src/main/java/com/threegap/bitnagil/data/address/datasourceImpl/LocationDataSourceImpl.kt b/data/src/main/java/com/threegap/bitnagil/data/address/datasourceImpl/LocationDataSourceImpl.kt
new file mode 100644
index 00000000..da74ec0a
--- /dev/null
+++ b/data/src/main/java/com/threegap/bitnagil/data/address/datasourceImpl/LocationDataSourceImpl.kt
@@ -0,0 +1,23 @@
+package com.threegap.bitnagil.data.address.datasourceImpl
+
+import android.annotation.SuppressLint
+import com.google.android.gms.location.FusedLocationProviderClient
+import com.threegap.bitnagil.data.address.datasource.LocationDataSource
+import com.threegap.bitnagil.domain.address.model.CurrentLocation
+import kotlinx.coroutines.tasks.await
+import javax.inject.Inject
+
+class LocationDataSourceImpl @Inject constructor(
+ private val fusedLocationClient: FusedLocationProviderClient,
+) : LocationDataSource {
+
+ @SuppressLint("MissingPermission")
+ override suspend fun fetchCurrentLocation(): Result {
+ return runCatching {
+ val location = fusedLocationClient.lastLocation.await()
+ ?: throw Exception("Location not available")
+
+ CurrentLocation(latitude = location.latitude, longitude = location.longitude)
+ }
+ }
+}
diff --git a/data/src/main/java/com/threegap/bitnagil/data/address/di/LocationModule.kt b/data/src/main/java/com/threegap/bitnagil/data/address/di/LocationModule.kt
new file mode 100644
index 00000000..c77e1975
--- /dev/null
+++ b/data/src/main/java/com/threegap/bitnagil/data/address/di/LocationModule.kt
@@ -0,0 +1,21 @@
+package com.threegap.bitnagil.data.address.di
+
+import android.content.Context
+import com.google.android.gms.location.FusedLocationProviderClient
+import com.google.android.gms.location.LocationServices
+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 LocationModule {
+
+ @Provides
+ @Singleton
+ fun provideFusedLocationProviderClient(@ApplicationContext context: Context): FusedLocationProviderClient =
+ LocationServices.getFusedLocationProviderClient(context)
+}
diff --git a/data/src/main/java/com/threegap/bitnagil/data/address/model/response/Coord2AddressResponse.kt b/data/src/main/java/com/threegap/bitnagil/data/address/model/response/Coord2AddressResponse.kt
new file mode 100644
index 00000000..5eb0bbe4
--- /dev/null
+++ b/data/src/main/java/com/threegap/bitnagil/data/address/model/response/Coord2AddressResponse.kt
@@ -0,0 +1,75 @@
+package com.threegap.bitnagil.data.address.model.response
+
+import kotlinx.serialization.SerialName
+import kotlinx.serialization.Serializable
+
+@Serializable
+data class Coord2AddressResponse(
+ @SerialName("meta")
+ val meta: Meta,
+ @SerialName("documents")
+ val documents: List,
+)
+
+@Serializable
+data class Meta(
+ @SerialName("total_count")
+ val totalCount: Int,
+)
+
+@Serializable
+data class Document(
+ @SerialName("road_address")
+ val roadAddress: RoadAddress,
+ @SerialName("address")
+ val address: Address,
+)
+
+@Serializable
+data class Address(
+ @SerialName("address_name")
+ val addressName: String,
+ @SerialName("region_1depth_name")
+ val region1depthName: String,
+ @SerialName("region_2depth_name")
+ val region2depthName: String,
+ @SerialName("region_3depth_name")
+ val region3depthName: String,
+ @SerialName("mountain_yn")
+ val mountainYn: String,
+ @SerialName("main_address_no")
+ val mainAddressNo: String,
+ @SerialName("sub_address_no")
+ val subAddressNo: String,
+)
+
+@Serializable
+data class RoadAddress(
+ @SerialName("address_name")
+ val addressName: String,
+ @SerialName("region_1depth_name")
+ val region1depthName: String,
+ @SerialName("region_2depth_name")
+ val region2depthName: String,
+ @SerialName("region_3depth_name")
+ val region3depthName: String,
+ @SerialName("road_name")
+ val roadName: String,
+ @SerialName("underground_yn")
+ val undergroundYn: String,
+ @SerialName("main_building_no")
+ val mainBuildingNo: String,
+ @SerialName("sub_building_no")
+ val subBuildingNo: String,
+ @SerialName("building_name")
+ val buildingName: String,
+ @SerialName("zone_no")
+ val zoneNo: String,
+)
+
+fun Coord2AddressResponse.toAddress(): String? {
+ return this.documents
+ .firstOrNull()
+ ?.roadAddress
+ ?.addressName
+}
diff --git a/data/src/main/java/com/threegap/bitnagil/data/address/repositoryImpl/AddressRepositoryImpl.kt b/data/src/main/java/com/threegap/bitnagil/data/address/repositoryImpl/AddressRepositoryImpl.kt
new file mode 100644
index 00000000..7be79d21
--- /dev/null
+++ b/data/src/main/java/com/threegap/bitnagil/data/address/repositoryImpl/AddressRepositoryImpl.kt
@@ -0,0 +1,25 @@
+package com.threegap.bitnagil.data.address.repositoryImpl
+
+import com.threegap.bitnagil.data.address.datasource.AddressDataSource
+import com.threegap.bitnagil.data.address.datasource.LocationDataSource
+import com.threegap.bitnagil.data.address.model.response.toAddress
+import com.threegap.bitnagil.domain.address.model.CurrentLocation
+import com.threegap.bitnagil.domain.address.repository.AddressRepository
+import javax.inject.Inject
+
+class AddressRepositoryImpl @Inject constructor(
+ private val locationDataSource: LocationDataSource,
+ private val addressDataSource: AddressDataSource,
+) : AddressRepository {
+
+ override suspend fun fetchCurrentLocation(): Result {
+ return locationDataSource.fetchCurrentLocation()
+ }
+
+ override suspend fun fetchCurrentAddress(longitude: Double, latitude: Double): Result {
+ return addressDataSource.fetchCurrentAddress(longitude = longitude, latitude = latitude)
+ .mapCatching {
+ it.toAddress() ?: throw Exception("Address not found")
+ }
+ }
+}
diff --git a/data/src/main/java/com/threegap/bitnagil/data/address/service/AddressService.kt b/data/src/main/java/com/threegap/bitnagil/data/address/service/AddressService.kt
new file mode 100644
index 00000000..52dcc6e2
--- /dev/null
+++ b/data/src/main/java/com/threegap/bitnagil/data/address/service/AddressService.kt
@@ -0,0 +1,13 @@
+package com.threegap.bitnagil.data.address.service
+
+import com.threegap.bitnagil.data.address.model.response.Coord2AddressResponse
+import retrofit2.http.GET
+import retrofit2.http.Query
+
+interface AddressService {
+ @GET("/v2/local/geo/coord2address.json")
+ suspend fun fetchCurrentAddress(
+ @Query("x") longitude: String,
+ @Query("y") latitude: String,
+ ): Coord2AddressResponse
+}
diff --git a/data/src/main/java/com/threegap/bitnagil/data/file/datasource/FileDataSource.kt b/data/src/main/java/com/threegap/bitnagil/data/file/datasource/FileDataSource.kt
new file mode 100644
index 00000000..62bf670b
--- /dev/null
+++ b/data/src/main/java/com/threegap/bitnagil/data/file/datasource/FileDataSource.kt
@@ -0,0 +1,12 @@
+package com.threegap.bitnagil.data.file.datasource
+
+import com.threegap.bitnagil.data.file.model.request.FileInfoRequestDto
+
+interface FileDataSource {
+ /**
+ * presigned URL 요청
+ * @param fileInfos 파일 정보 리스트 (prefix + fileName)
+ * @return key: S3 경로, value: presigned URL
+ */
+ suspend fun fetchPresignedUrls(fileInfos: List): Result