diff --git a/.editorconfig b/.editorconfig
new file mode 100644
index 00000000..77c75f79
--- /dev/null
+++ b/.editorconfig
@@ -0,0 +1,4 @@
+[*.{kt,kts}]
+ij_kotlin_allow_trailing_comma = true
+ij_kotlin_allow_trailing_comma_on_call_site = true
+ktlint_function_naming_ignore_when_annotated_with = Composable, Test
diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md
index ca22c10d..f7a94a9f 100644
--- a/.github/pull_request_template.md
+++ b/.github/pull_request_template.md
@@ -1,8 +1,11 @@
## 관련 이슈번호
+
close #
## 작업 사항
+
## 기타 사항
+
diff --git a/.github/workflows/android.yml b/.github/workflows/android.yml
index 4884378c..811a8b4b 100644
--- a/.github/workflows/android.yml
+++ b/.github/workflows/android.yml
@@ -7,7 +7,7 @@ on:
jobs:
build:
runs-on: ubuntu-latest
-
+
steps:
- uses: actions/checkout@v3
@@ -32,8 +32,15 @@ jobs:
- name: Grant execute permission for gradlew
run: chmod +x gradlew
+ - name: Access Google Client Id
+ env:
+ GOOGLE_CLIENT_ID: ${{ secrets.GOOGLE_CLIENT_ID }}
+ run: |
+ echo "GOOGLE_CLIENT_ID=\"$GOOGLE_CLIENT_ID\"" >> local.properties
+
- name: Run test
run: ./gradlew test --parallel
- name: Run ktlint
run: ./gradlew ktlintCheck
+
diff --git a/app/build.gradle.kts b/app/build.gradle.kts
index dd3326e5..27881a38 100644
--- a/app/build.gradle.kts
+++ b/app/build.gradle.kts
@@ -18,3 +18,13 @@ android {
}
}
+dependencies {
+ implementation(libs.androidx.core.splashscreen)
+ implementation(project(":feature:login"))
+ implementation(project(":core:interceptor"))
+ implementation(project(":core:data"))
+ implementation(project(":core:network"))
+ implementation(project(":core:datastore"))
+ implementation(project(":core:domain"))
+ implementation(project(":core:designsystem"))
+}
diff --git a/app/src/androidTest/java/com/withpeace/withpeace/ExampleInstrumentedTest.kt b/app/src/androidTest/java/com/withpeace/withpeace/ExampleInstrumentedTest.kt
index a855b0aa..fabc1c37 100644
--- a/app/src/androidTest/java/com/withpeace/withpeace/ExampleInstrumentedTest.kt
+++ b/app/src/androidTest/java/com/withpeace/withpeace/ExampleInstrumentedTest.kt
@@ -1,13 +1,11 @@
package com.withpeace.withpeace
-import androidx.test.platform.app.InstrumentationRegistry
import androidx.test.ext.junit.runners.AndroidJUnit4
-
+import androidx.test.platform.app.InstrumentationRegistry
+import org.junit.Assert.*
import org.junit.Test
import org.junit.runner.RunWith
-import org.junit.Assert.*
-
/**
* Instrumented test, which will execute on an Android device.
*
diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index 7591e8b8..868b88e0 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -1,6 +1,9 @@
+ xmlns:tools="http://schemas.android.com/tools"
+ package="com.withpeace.withpeace">
+
+
+ android:theme="@style/Theme.Withpeace.Starting">
@@ -26,4 +29,4 @@
-
\ No newline at end of file
+
diff --git a/app/src/main/java/com/withpeace/withpeace/MainActivity.kt b/app/src/main/java/com/withpeace/withpeace/MainActivity.kt
index d0234cf5..df59ed52 100644
--- a/app/src/main/java/com/withpeace/withpeace/MainActivity.kt
+++ b/app/src/main/java/com/withpeace/withpeace/MainActivity.kt
@@ -1,48 +1,46 @@
package com.withpeace.withpeace
import android.os.Bundle
+import android.util.Log
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
-import androidx.compose.foundation.layout.fillMaxSize
-import androidx.compose.material3.MaterialTheme
-import androidx.compose.material3.Surface
-import androidx.compose.material3.Text
-import androidx.compose.runtime.Composable
-import androidx.compose.ui.Modifier
-import androidx.compose.ui.tooling.preview.Preview
-import com.withpeace.withpeace.ui.theme.WithpeaceTheme
+import androidx.activity.viewModels
+import androidx.core.splashscreen.SplashScreen.Companion.installSplashScreen
+import androidx.lifecycle.Lifecycle
+import androidx.lifecycle.lifecycleScope
+import androidx.lifecycle.repeatOnLifecycle
+import com.withpeace.withpeace.core.designsystem.theme.WithpeaceTheme
+import com.withpeace.withpeace.feature.login.navigation.LOGIN_ROUTE
import dagger.hilt.android.AndroidEntryPoint
+import kotlinx.coroutines.delay
+import kotlinx.coroutines.launch
@AndroidEntryPoint
class MainActivity : ComponentActivity() {
+ private val viewModel by viewModels()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
+ val splashScreen = installSplashScreen()
+ splashScreen.setKeepOnScreenCondition { true }
+ lifecycleScope.launch {
+ repeatOnLifecycle(Lifecycle.State.STARTED){
+ viewModel.isLogin.collect { isLogin ->
+ if(isLogin) {
+ Log.d("covy","Main 화면으로 이동")
+ } else {
+ composeStart(LOGIN_ROUTE)
+ }
+ delay(2000L)
+ splashScreen.setKeepOnScreenCondition { false }
+ }
+ }
+ }
+ }
+ private fun ComponentActivity.composeStart(startDestination: String) {
setContent {
WithpeaceTheme {
- // A surface container using the 'background' color from the theme
- Surface(
- modifier = Modifier.fillMaxSize(),
- color = MaterialTheme.colorScheme.background
- ) {
- Greeting("Android")
- }
+ WithpeaceApp(startDestination = startDestination)
}
}
}
}
-
-@Composable
-fun Greeting(name: String, modifier: Modifier = Modifier) {
- Text(
- text = "Hello $name!",
- modifier = modifier
- )
-}
-
-@Preview(showBackground = true)
-@Composable
-fun GreetingPreview() {
- WithpeaceTheme {
- Greeting("Android")
- }
-}
\ No newline at end of file
diff --git a/app/src/main/java/com/withpeace/withpeace/MainViewModel.kt b/app/src/main/java/com/withpeace/withpeace/MainViewModel.kt
new file mode 100644
index 00000000..e9145a9a
--- /dev/null
+++ b/app/src/main/java/com/withpeace/withpeace/MainViewModel.kt
@@ -0,0 +1,31 @@
+package com.withpeace.withpeace
+
+import androidx.lifecycle.ViewModel
+import androidx.lifecycle.viewModelScope
+import com.withpeace.withpeace.core.domain.repository.TokenRepository
+import dagger.hilt.android.lifecycle.HiltViewModel
+import kotlinx.coroutines.flow.MutableSharedFlow
+import kotlinx.coroutines.flow.SharedFlow
+import kotlinx.coroutines.flow.asSharedFlow
+import kotlinx.coroutines.flow.firstOrNull
+import kotlinx.coroutines.launch
+import javax.inject.Inject
+
+@HiltViewModel
+class MainViewModel @Inject constructor(
+ private val tokenRepository: TokenRepository
+) : ViewModel() {
+ private val _isLogin: MutableSharedFlow = MutableSharedFlow()
+ val isLogin = _isLogin.asSharedFlow()
+
+ init {
+ viewModelScope.launch {
+ val token = tokenRepository.getAccessToken().firstOrNull()
+ if(token==null) {
+ _isLogin.emit(false)
+ } else {
+ _isLogin.emit(true)
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/withpeace/withpeace/WithPeaceApplication.kt b/app/src/main/java/com/withpeace/withpeace/WithPeaceApplication.kt
index cd59d3f1..8390979d 100644
--- a/app/src/main/java/com/withpeace/withpeace/WithPeaceApplication.kt
+++ b/app/src/main/java/com/withpeace/withpeace/WithPeaceApplication.kt
@@ -4,5 +4,5 @@ import android.app.Application
import dagger.hilt.android.HiltAndroidApp
@HiltAndroidApp
-class WithPeaceApplication: Application() {
+class WithPeaceApplication : Application() {
}
\ No newline at end of file
diff --git a/app/src/main/java/com/withpeace/withpeace/WithpeaceApp.kt b/app/src/main/java/com/withpeace/withpeace/WithpeaceApp.kt
new file mode 100644
index 00000000..c6ffc1b4
--- /dev/null
+++ b/app/src/main/java/com/withpeace/withpeace/WithpeaceApp.kt
@@ -0,0 +1,34 @@
+package com.withpeace.withpeace
+
+import androidx.compose.foundation.layout.fillMaxSize
+import androidx.compose.material3.Scaffold
+import androidx.compose.material3.SnackbarHost
+import androidx.compose.material3.SnackbarHostState
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.remember
+import androidx.compose.runtime.rememberCoroutineScope
+import androidx.compose.ui.Modifier
+import com.withpeace.withpeace.navigation.WithpeaceNavHost
+import kotlinx.coroutines.launch
+
+
+@Composable
+fun WithpeaceApp(
+ startDestination: String
+) {
+ val snackBarHostState = remember { SnackbarHostState() }
+ val coroutineScope = rememberCoroutineScope()
+ fun showSnackBar(message: String) = coroutineScope.launch {
+ snackBarHostState.showSnackbar(message)
+ }
+
+ Scaffold(
+ modifier = Modifier.fillMaxSize(),
+ snackbarHost = { SnackbarHost(snackBarHostState) },
+ ) {
+ WithpeaceNavHost(
+ onShowSnackBar = ::showSnackBar,
+ )
+ it
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/withpeace/withpeace/navigation/NavHost.kt b/app/src/main/java/com/withpeace/withpeace/navigation/NavHost.kt
new file mode 100644
index 00000000..b4c6c9d4
--- /dev/null
+++ b/app/src/main/java/com/withpeace/withpeace/navigation/NavHost.kt
@@ -0,0 +1,26 @@
+package com.withpeace.withpeace.navigation
+
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.Modifier
+import androidx.navigation.NavHostController
+import androidx.navigation.compose.NavHost
+import androidx.navigation.compose.rememberNavController
+import com.withpeace.withpeace.feature.login.navigation.LOGIN_ROUTE
+import com.withpeace.withpeace.feature.login.navigation.loginNavGraph
+
+
+@Composable
+fun WithpeaceNavHost(
+ modifier: Modifier = Modifier,
+ navController: NavHostController = rememberNavController(),
+ startDestination: String = LOGIN_ROUTE,
+ onShowSnackBar: (message: String) -> Unit,
+) {
+ NavHost(
+ modifier = modifier,
+ navController = navController,
+ startDestination = startDestination,
+ ) {
+ loginNavGraph(onShowSnackBar = onShowSnackBar)
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/withpeace/withpeace/ui/theme/Color.kt b/app/src/main/java/com/withpeace/withpeace/ui/theme/Color.kt
deleted file mode 100644
index da096639..00000000
--- a/app/src/main/java/com/withpeace/withpeace/ui/theme/Color.kt
+++ /dev/null
@@ -1,11 +0,0 @@
-package com.withpeace.withpeace.ui.theme
-
-import androidx.compose.ui.graphics.Color
-
-val Purple80 = Color(0xFFD0BCFF)
-val PurpleGrey80 = Color(0xFFCCC2DC)
-val Pink80 = Color(0xFFEFB8C8)
-
-val Purple40 = Color(0xFF6650a4)
-val PurpleGrey40 = Color(0xFF625b71)
-val Pink40 = Color(0xFF7D5260)
\ No newline at end of file
diff --git a/app/src/main/java/com/withpeace/withpeace/ui/theme/Theme.kt b/app/src/main/java/com/withpeace/withpeace/ui/theme/Theme.kt
deleted file mode 100644
index 278371c2..00000000
--- a/app/src/main/java/com/withpeace/withpeace/ui/theme/Theme.kt
+++ /dev/null
@@ -1,70 +0,0 @@
-package com.withpeace.withpeace.ui.theme
-
-import android.app.Activity
-import android.os.Build
-import androidx.compose.foundation.isSystemInDarkTheme
-import androidx.compose.material3.MaterialTheme
-import androidx.compose.material3.darkColorScheme
-import androidx.compose.material3.dynamicDarkColorScheme
-import androidx.compose.material3.dynamicLightColorScheme
-import androidx.compose.material3.lightColorScheme
-import androidx.compose.runtime.Composable
-import androidx.compose.runtime.SideEffect
-import androidx.compose.ui.graphics.toArgb
-import androidx.compose.ui.platform.LocalContext
-import androidx.compose.ui.platform.LocalView
-import androidx.core.view.WindowCompat
-
-private val DarkColorScheme = darkColorScheme(
- primary = Purple80,
- secondary = PurpleGrey80,
- tertiary = Pink80
-)
-
-private val LightColorScheme = lightColorScheme(
- primary = Purple40,
- secondary = PurpleGrey40,
- tertiary = Pink40
-
- /* Other default colors to override
- background = Color(0xFFFFFBFE),
- surface = Color(0xFFFFFBFE),
- onPrimary = Color.White,
- onSecondary = Color.White,
- onTertiary = Color.White,
- onBackground = Color(0xFF1C1B1F),
- onSurface = Color(0xFF1C1B1F),
- */
-)
-
-@Composable
-fun WithpeaceTheme(
- darkTheme: Boolean = isSystemInDarkTheme(),
- // Dynamic color is available on Android 12+
- dynamicColor: Boolean = true,
- content: @Composable () -> Unit
-) {
- val colorScheme = when {
- dynamicColor && Build.VERSION.SDK_INT >= Build.VERSION_CODES.S -> {
- val context = LocalContext.current
- if (darkTheme) dynamicDarkColorScheme(context) else dynamicLightColorScheme(context)
- }
-
- darkTheme -> DarkColorScheme
- else -> LightColorScheme
- }
- val view = LocalView.current
- if (!view.isInEditMode) {
- SideEffect {
- val window = (view.context as Activity).window
- window.statusBarColor = colorScheme.primary.toArgb()
- WindowCompat.getInsetsController(window, view).isAppearanceLightStatusBars = darkTheme
- }
- }
-
- MaterialTheme(
- colorScheme = colorScheme,
- typography = Typography,
- content = content
- )
-}
\ No newline at end of file
diff --git a/app/src/main/java/com/withpeace/withpeace/ui/theme/Type.kt b/app/src/main/java/com/withpeace/withpeace/ui/theme/Type.kt
deleted file mode 100644
index bf02778e..00000000
--- a/app/src/main/java/com/withpeace/withpeace/ui/theme/Type.kt
+++ /dev/null
@@ -1,34 +0,0 @@
-package com.withpeace.withpeace.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
diff --git a/app/src/main/res/values/themes.xml b/app/src/main/res/values/themes.xml
index 8bb25279..d31100cb 100644
--- a/app/src/main/res/values/themes.xml
+++ b/app/src/main/res/values/themes.xml
@@ -1,5 +1,21 @@
-
-
\ No newline at end of file
+
+
+
+
diff --git a/app/src/test/java/com/withpeace/withpeace/ExampleUnitTest.kt b/app/src/test/java/com/withpeace/withpeace/ExampleUnitTest.kt
index 7386af31..944b793d 100644
--- a/app/src/test/java/com/withpeace/withpeace/ExampleUnitTest.kt
+++ b/app/src/test/java/com/withpeace/withpeace/ExampleUnitTest.kt
@@ -1,9 +1,8 @@
package com.withpeace.withpeace
+import org.junit.Assert.assertEquals
import org.junit.Test
-import org.junit.Assert.*
-
/**
* Example local unit test, which will execute on the development machine (host).
*
diff --git a/build-logic/src/main/kotlin/convention.android.base.gradle.kts b/build-logic/src/main/kotlin/convention.android.base.gradle.kts
index 8c406ca4..ff1cd935 100644
--- a/build-logic/src/main/kotlin/convention.android.base.gradle.kts
+++ b/build-logic/src/main/kotlin/convention.android.base.gradle.kts
@@ -32,9 +32,12 @@ android {
excludes += "/META-INF/*"
}
}
+ buildFeatures {
+ buildConfig = true
+ }
}
-dependencies{
+dependencies {
"androidTestImplementation"(libs.findLibrary("androidx.test.ext").get())
"androidTestImplementation"(libs.findLibrary("androidx-test-espresso-core").get())
"androidTestImplementation"(libs.findLibrary("junit4").get())
diff --git a/build-logic/src/main/kotlin/convention.feature.gradle.kts b/build-logic/src/main/kotlin/convention.feature.gradle.kts
index 551b7340..756f561f 100644
--- a/build-logic/src/main/kotlin/convention.feature.gradle.kts
+++ b/build-logic/src/main/kotlin/convention.feature.gradle.kts
@@ -6,3 +6,9 @@ plugins {
id("convention.android.compose")
id("convention.android.hilt")
}
+
+dependencies{
+ implementation(project(":core:data"))
+ implementation(project(":core:domain"))
+ implementation(project(":core:designsystem"))
+}
\ No newline at end of file
diff --git a/core/data/build.gradle.kts b/core/data/build.gradle.kts
index 5ea0b847..0332be59 100644
--- a/core/data/build.gradle.kts
+++ b/core/data/build.gradle.kts
@@ -12,5 +12,6 @@ android {
dependencies {
implementation(project(":core:network"))
implementation(project(":core:domain"))
+ implementation(project(":core:datastore"))
implementation(libs.skydoves.sandwich)
}
diff --git a/core/data/src/androidTest/java/com/withpeace/withpeace/core/data/ExampleInstrumentedTest.kt b/core/data/src/androidTest/java/com/withpeace/withpeace/core/data/ExampleInstrumentedTest.kt
index b3b26a3a..410df2d5 100644
--- a/core/data/src/androidTest/java/com/withpeace/withpeace/core/data/ExampleInstrumentedTest.kt
+++ b/core/data/src/androidTest/java/com/withpeace/withpeace/core/data/ExampleInstrumentedTest.kt
@@ -1,13 +1,11 @@
package com.withpeace.withpeace.core.data
-import androidx.test.platform.app.InstrumentationRegistry
import androidx.test.ext.junit.runners.AndroidJUnit4
-
+import androidx.test.platform.app.InstrumentationRegistry
+import org.junit.Assert.*
import org.junit.Test
import org.junit.runner.RunWith
-import org.junit.Assert.*
-
/**
* Instrumented test, which will execute on an Android device.
*
diff --git a/core/data/src/main/AndroidManifest.xml b/core/data/src/main/AndroidManifest.xml
index a5918e68..44008a43 100644
--- a/core/data/src/main/AndroidManifest.xml
+++ b/core/data/src/main/AndroidManifest.xml
@@ -1,4 +1,4 @@
-
+
\ No newline at end of file
diff --git a/core/data/src/main/kotlin/com/withpeace/withpeace/core/data/di/RepositoryModule.kt b/core/data/src/main/kotlin/com/withpeace/withpeace/core/data/di/RepositoryModule.kt
new file mode 100644
index 00000000..9d9305a0
--- /dev/null
+++ b/core/data/src/main/kotlin/com/withpeace/withpeace/core/data/di/RepositoryModule.kt
@@ -0,0 +1,18 @@
+package com.withpeace.withpeace.core.data.di
+
+import com.withpeace.withpeace.core.data.repository.DefaultTokenRepository
+import com.withpeace.withpeace.core.domain.repository.TokenRepository
+import dagger.Binds
+import dagger.Module
+import dagger.hilt.InstallIn
+import dagger.hilt.components.SingletonComponent
+import javax.inject.Singleton
+
+@Module
+@InstallIn(SingletonComponent::class)
+interface RepositoryModule {
+
+ @Binds
+ @Singleton
+ fun bindsTokenRepository(defaultTokenRepository: DefaultTokenRepository): TokenRepository
+}
\ No newline at end of file
diff --git a/core/data/src/main/kotlin/com/withpeace/withpeace/core/data/mapper/TokenMapper.kt b/core/data/src/main/kotlin/com/withpeace/withpeace/core/data/mapper/TokenMapper.kt
index e7d7c9c2..b95516fa 100644
--- a/core/data/src/main/kotlin/com/withpeace/withpeace/core/data/mapper/TokenMapper.kt
+++ b/core/data/src/main/kotlin/com/withpeace/withpeace/core/data/mapper/TokenMapper.kt
@@ -6,6 +6,6 @@ import com.withpeace.withpeace.core.network.di.response.TokenResponse
fun TokenResponse.toDomain(): Token {
return Token(
accessToken = accessToken,
- refreshToken = refreshToken
+ refreshToken = refreshToken,
)
}
\ No newline at end of file
diff --git a/core/data/src/main/kotlin/com/withpeace/withpeace/core/data/repository/DefaultTokenRepository.kt b/core/data/src/main/kotlin/com/withpeace/withpeace/core/data/repository/DefaultTokenRepository.kt
index a443f048..7fa7acb8 100644
--- a/core/data/src/main/kotlin/com/withpeace/withpeace/core/data/repository/DefaultTokenRepository.kt
+++ b/core/data/src/main/kotlin/com/withpeace/withpeace/core/data/repository/DefaultTokenRepository.kt
@@ -1,29 +1,81 @@
package com.withpeace.withpeace.core.data.repository
-import com.skydoves.sandwich.message
+import com.skydoves.sandwich.messageOrNull
import com.skydoves.sandwich.suspendMapSuccess
-import com.skydoves.sandwich.suspendOnError
+import com.skydoves.sandwich.suspendOnFailure
+import com.skydoves.sandwich.suspendOnSuccess
import com.withpeace.withpeace.core.data.mapper.toDomain
+import com.withpeace.withpeace.core.datastore.dataStore.TokenPreferenceDataSource
import com.withpeace.withpeace.core.domain.model.Token
import com.withpeace.withpeace.core.domain.repository.TokenRepository
+import com.withpeace.withpeace.core.network.di.request.SignUpRequest
import com.withpeace.withpeace.core.network.di.service.AuthService
+import com.withpeace.withpeace.core.network.di.service.LoginService
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.flow
import kotlinx.coroutines.flow.flowOn
import javax.inject.Inject
-
-class DefaultTokenRepository @Inject constructor(
- private val authService: AuthService
+class DefaultTokenRepository
+@Inject
+constructor(
+ private val tokenPreferenceDataSource: TokenPreferenceDataSource,
+ private val loginService: LoginService,
+ private val authService: AuthService,
) : TokenRepository {
+ override fun getAccessToken(): Flow {
+ return tokenPreferenceDataSource.accessToken
+ }
+
+ override fun getRefreshToken(): Flow {
+ return tokenPreferenceDataSource.refreshToken
+ }
+
+ override suspend fun updateAccessToken(accessToken: String) {
+ tokenPreferenceDataSource.updateAccessToken(accessToken)
+ }
+
+ override suspend fun updateRefreshToken(refreshToken: String) {
+ tokenPreferenceDataSource.updateRefreshToken(refreshToken)
+ }
- override fun googleLogin(onError: (String?) -> Unit): Flow = flow {
- authService.googleLogin()
- .suspendMapSuccess {
- emit(data.toDomain())
- }.suspendOnError {
- onError(message())
- }
+ override suspend fun signUp(
+ onError: (String?) -> Unit,
+ email: String,
+ nickname: String,
+ deviceToken: String?,
+ ): Flow = flow {
+ authService.signUp(
+ SignUpRequest(
+ email = email,
+ nickname = nickname,
+ deviceToken = deviceToken,
+ ),
+ ).suspendMapSuccess {
+ emit(data.toDomain())
+ }.suspendOnFailure {
+ onError(messageOrNull)
+ }
}.flowOn(Dispatchers.IO)
-}
\ No newline at end of file
+
+
+ override fun googleLogin(
+ idToken: String,
+ onError: (String?) -> Unit,
+ ): Flow =
+ flow {
+ loginService.googleLogin(AUTHORIZATION_FORMAT.format(idToken))
+ .suspendMapSuccess {
+ emit(data.toDomain())
+ updateAccessToken(data.accessToken)
+ updateRefreshToken(data.refreshToken)
+ }.suspendOnFailure {
+ onError(messageOrNull)
+ }
+ }.flowOn(Dispatchers.IO)
+
+ companion object {
+ private const val AUTHORIZATION_FORMAT = "Bearer %s"
+ }
+}
diff --git a/core/data/src/test/java/com/withpeace/withpeace/core/data/ExampleUnitTest.kt b/core/data/src/test/java/com/withpeace/withpeace/core/data/ExampleUnitTest.kt
index 61be3bed..2dce1955 100644
--- a/core/data/src/test/java/com/withpeace/withpeace/core/data/ExampleUnitTest.kt
+++ b/core/data/src/test/java/com/withpeace/withpeace/core/data/ExampleUnitTest.kt
@@ -1,9 +1,8 @@
package com.withpeace.withpeace.core.data
+import org.junit.Assert.assertEquals
import org.junit.Test
-import org.junit.Assert.*
-
/**
* Example local unit test, which will execute on the development machine (host).
*
diff --git a/core/datastore/.gitignore b/core/datastore/.gitignore
new file mode 100644
index 00000000..42afabfd
--- /dev/null
+++ b/core/datastore/.gitignore
@@ -0,0 +1 @@
+/build
\ No newline at end of file
diff --git a/core/datastore/build.gradle.kts b/core/datastore/build.gradle.kts
new file mode 100644
index 00000000..6b784dd2
--- /dev/null
+++ b/core/datastore/build.gradle.kts
@@ -0,0 +1,13 @@
+plugins {
+ id("com.android.library")
+ id("convention.android.base")
+ id("convention.android.hilt")
+}
+
+android {
+ namespace = "com.withpeace.withpeace.core.datastore"
+}
+
+dependencies {
+ implementation(libs.androidx.datastore)
+}
\ No newline at end of file
diff --git a/core/datastore/consumer-rules.pro b/core/datastore/consumer-rules.pro
new file mode 100644
index 00000000..e69de29b
diff --git a/core/datastore/proguard-rules.pro b/core/datastore/proguard-rules.pro
new file mode 100644
index 00000000..481bb434
--- /dev/null
+++ b/core/datastore/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/google-login/src/androidTest/java/com/woosuk/google_login/ExampleInstrumentedTest.kt b/core/datastore/src/androidTest/java/com/withpeace/withpeace/core/datastore/ExampleInstrumentedTest.kt
similarity index 80%
rename from google-login/src/androidTest/java/com/woosuk/google_login/ExampleInstrumentedTest.kt
rename to core/datastore/src/androidTest/java/com/withpeace/withpeace/core/datastore/ExampleInstrumentedTest.kt
index f7fbd359..1e831671 100644
--- a/google-login/src/androidTest/java/com/woosuk/google_login/ExampleInstrumentedTest.kt
+++ b/core/datastore/src/androidTest/java/com/withpeace/withpeace/core/datastore/ExampleInstrumentedTest.kt
@@ -1,13 +1,11 @@
-package com.woosuk.google_login
+package com.withpeace.withpeace.core.datastore
-import androidx.test.platform.app.InstrumentationRegistry
import androidx.test.ext.junit.runners.AndroidJUnit4
-
+import androidx.test.platform.app.InstrumentationRegistry
+import org.junit.Assert.*
import org.junit.Test
import org.junit.runner.RunWith
-import org.junit.Assert.*
-
/**
* Instrumented test, which will execute on an Android device.
*
@@ -19,6 +17,6 @@ class ExampleInstrumentedTest {
fun useAppContext() {
// Context of the app under test.
val appContext = InstrumentationRegistry.getInstrumentation().targetContext
- assertEquals("com.woosuk.google_login.test", appContext.packageName)
+ assertEquals("com.withpeace.withpeace.core.datastore.test", appContext.packageName)
}
-}
+}
\ No newline at end of file
diff --git a/core/datastore/src/main/AndroidManifest.xml b/core/datastore/src/main/AndroidManifest.xml
new file mode 100644
index 00000000..44008a43
--- /dev/null
+++ b/core/datastore/src/main/AndroidManifest.xml
@@ -0,0 +1,4 @@
+
+
+
+
\ No newline at end of file
diff --git a/core/datastore/src/main/java/com/withpeace/withpeace/core/datastore/dataStore/DefaultTokenPreferenceDataSource.kt b/core/datastore/src/main/java/com/withpeace/withpeace/core/datastore/dataStore/DefaultTokenPreferenceDataSource.kt
new file mode 100644
index 00000000..6b5cca17
--- /dev/null
+++ b/core/datastore/src/main/java/com/withpeace/withpeace/core/datastore/dataStore/DefaultTokenPreferenceDataSource.kt
@@ -0,0 +1,40 @@
+package com.withpeace.withpeace.core.datastore.dataStore
+
+import androidx.datastore.core.DataStore
+import androidx.datastore.preferences.core.Preferences
+import androidx.datastore.preferences.core.edit
+import androidx.datastore.preferences.core.stringPreferencesKey
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.map
+import javax.inject.Inject
+import javax.inject.Named
+
+class DefaultTokenPreferenceDataSource @Inject constructor(
+ @Named("auth") private val dataStore: DataStore,
+) : TokenPreferenceDataSource {
+
+ override val accessToken: Flow = dataStore.data.map { preferences ->
+ preferences[ACCESS_TOKEN]
+ }
+
+ override val refreshToken: Flow = dataStore.data.map { preferences ->
+ preferences[REFRESH_TOKEN]
+ }
+
+ override suspend fun updateAccessToken(accessToken: String) {
+ dataStore.edit { preferences ->
+ preferences[ACCESS_TOKEN] = accessToken
+ }
+ }
+
+ override suspend fun updateRefreshToken(refreshToken: String) {
+ dataStore.edit { preferences ->
+ preferences[REFRESH_TOKEN] = refreshToken
+ }
+ }
+
+ companion object {
+ private val ACCESS_TOKEN = stringPreferencesKey("ACCESS_TOKEN")
+ private val REFRESH_TOKEN = stringPreferencesKey("REFRESH_TOKEN")
+ }
+}
\ No newline at end of file
diff --git a/core/datastore/src/main/java/com/withpeace/withpeace/core/datastore/dataStore/TokenPreferenceDataSource.kt b/core/datastore/src/main/java/com/withpeace/withpeace/core/datastore/dataStore/TokenPreferenceDataSource.kt
new file mode 100644
index 00000000..7d5df2be
--- /dev/null
+++ b/core/datastore/src/main/java/com/withpeace/withpeace/core/datastore/dataStore/TokenPreferenceDataSource.kt
@@ -0,0 +1,14 @@
+package com.withpeace.withpeace.core.datastore.dataStore
+
+import kotlinx.coroutines.flow.Flow
+
+interface TokenPreferenceDataSource {
+
+ val accessToken: Flow
+
+ val refreshToken: Flow
+
+ suspend fun updateRefreshToken(refreshToken: String)
+
+ suspend fun updateAccessToken(accessToken: String)
+}
\ No newline at end of file
diff --git a/core/datastore/src/main/java/com/withpeace/withpeace/core/datastore/di/DataStoreModule.kt b/core/datastore/src/main/java/com/withpeace/withpeace/core/datastore/di/DataStoreModule.kt
new file mode 100644
index 00000000..8215224f
--- /dev/null
+++ b/core/datastore/src/main/java/com/withpeace/withpeace/core/datastore/di/DataStoreModule.kt
@@ -0,0 +1,29 @@
+package com.withpeace.withpeace.core.datastore.di
+
+import android.content.Context
+import androidx.datastore.core.DataStore
+import androidx.datastore.preferences.core.Preferences
+import androidx.datastore.preferences.preferencesDataStore
+import dagger.Module
+import dagger.Provides
+import dagger.hilt.InstallIn
+import dagger.hilt.android.qualifiers.ApplicationContext
+import dagger.hilt.components.SingletonComponent
+import javax.inject.Named
+import javax.inject.Singleton
+
+@Module
+@InstallIn(SingletonComponent::class)
+object DataStoreModule {
+
+ private const val AUTH_DATASTORE_NAME = "AUTH_PREFERENCES"
+
+ private val Context.authDataStore: DataStore by preferencesDataStore(name = AUTH_DATASTORE_NAME)
+
+ @Provides
+ @Singleton
+ @Named("auth")
+ fun providesTokenDataStore(
+ @ApplicationContext context: Context,
+ ): DataStore = context.authDataStore
+}
\ No newline at end of file
diff --git a/core/datastore/src/main/java/com/withpeace/withpeace/core/datastore/di/PreferenceDataSourceModule.kt b/core/datastore/src/main/java/com/withpeace/withpeace/core/datastore/di/PreferenceDataSourceModule.kt
new file mode 100644
index 00000000..e0beaa82
--- /dev/null
+++ b/core/datastore/src/main/java/com/withpeace/withpeace/core/datastore/di/PreferenceDataSourceModule.kt
@@ -0,0 +1,20 @@
+package com.withpeace.withpeace.core.datastore.di
+
+import com.withpeace.withpeace.core.datastore.dataStore.DefaultTokenPreferenceDataSource
+import com.withpeace.withpeace.core.datastore.dataStore.TokenPreferenceDataSource
+import dagger.Binds
+import dagger.Module
+import dagger.hilt.InstallIn
+import dagger.hilt.components.SingletonComponent
+import javax.inject.Singleton
+
+@Module
+@InstallIn(SingletonComponent::class)
+interface PreferenceDataSourceModule {
+
+ @Binds
+ @Singleton
+ fun bindsTokenPreferenceDataSource(
+ defaultTokenPreferenceDataSource: DefaultTokenPreferenceDataSource,
+ ): TokenPreferenceDataSource
+}
\ No newline at end of file
diff --git a/google-login/src/test/java/com/woosuk/google_login/ExampleUnitTest.kt b/core/datastore/src/test/java/com/withpeace/withpeace/core/datastore/ExampleUnitTest.kt
similarity index 77%
rename from google-login/src/test/java/com/woosuk/google_login/ExampleUnitTest.kt
rename to core/datastore/src/test/java/com/withpeace/withpeace/core/datastore/ExampleUnitTest.kt
index a93ea3c8..c98a0772 100644
--- a/google-login/src/test/java/com/woosuk/google_login/ExampleUnitTest.kt
+++ b/core/datastore/src/test/java/com/withpeace/withpeace/core/datastore/ExampleUnitTest.kt
@@ -1,9 +1,8 @@
-package com.woosuk.google_login
+package com.withpeace.withpeace.core.datastore
+import org.junit.Assert.assertEquals
import org.junit.Test
-import org.junit.Assert.*
-
/**
* Example local unit test, which will execute on the development machine (host).
*
@@ -14,4 +13,4 @@ class ExampleUnitTest {
fun addition_isCorrect() {
assertEquals(4, 2 + 2)
}
-}
+}
\ No newline at end of file
diff --git a/core/designsystem/.gitignore b/core/designsystem/.gitignore
new file mode 100644
index 00000000..42afabfd
--- /dev/null
+++ b/core/designsystem/.gitignore
@@ -0,0 +1 @@
+/build
\ No newline at end of file
diff --git a/core/designsystem/build.gradle.kts b/core/designsystem/build.gradle.kts
new file mode 100644
index 00000000..d518a6ce
--- /dev/null
+++ b/core/designsystem/build.gradle.kts
@@ -0,0 +1,9 @@
+plugins {
+ id("com.android.library")
+ id("convention.android.base")
+ id("convention.android.compose")
+}
+
+android {
+ namespace = "com.withpeace.withpeace.core.designsystem"
+}
diff --git a/core/designsystem/consumer-rules.pro b/core/designsystem/consumer-rules.pro
new file mode 100644
index 00000000..e69de29b
diff --git a/core/designsystem/proguard-rules.pro b/core/designsystem/proguard-rules.pro
new file mode 100644
index 00000000..481bb434
--- /dev/null
+++ b/core/designsystem/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/designsystem/src/main/AndroidManifest.xml b/core/designsystem/src/main/AndroidManifest.xml
new file mode 100644
index 00000000..8bdb7e14
--- /dev/null
+++ b/core/designsystem/src/main/AndroidManifest.xml
@@ -0,0 +1,4 @@
+
+
+
+
diff --git a/core/designsystem/src/main/java/com/withpeace/withpeace/core/designsystem/theme/Color.kt b/core/designsystem/src/main/java/com/withpeace/withpeace/core/designsystem/theme/Color.kt
new file mode 100644
index 00000000..a6fe57cb
--- /dev/null
+++ b/core/designsystem/src/main/java/com/withpeace/withpeace/core/designsystem/theme/Color.kt
@@ -0,0 +1,41 @@
+package com.withpeace.withpeace.core.designsystem.theme
+
+import androidx.compose.ui.graphics.Color
+
+val Purple80 = Color(0xFFD0BCFF)
+val PurpleGrey80 = Color(0xFFCCC2DC)
+val Pink80 = Color(0xFFEFB8C8)
+
+val Purple40 = Color(0xFF6650a4)
+val PurpleGrey40 = Color(0xFF625b71)
+val Pink40 = Color(0xFF7D5260)
+
+val mainpink = Color(0xFFFEA0A1)
+val subPink = Color(0xFFF5D6DB)
+val subApricot = Color(0xFFFED9C9)
+val subBlue = Color(0xFFDFF2F9)
+
+val systemBlack = Color(0xFF212529)
+val systemWhite = Color.White
+val systemGray1 = Color(0xFF3D3D3D)
+val systemGray2 = Color(0xFFA7A7A7)
+val systemGray3 = Color(0xFFECECEF)
+val systemError = Color(0xFFF0474B)
+val systemSuccess = Color(0xFF3BD569)
+
+data class WithPeaceColor(
+ val MainPink: Color = mainpink,
+ val SubPink: Color = subPink,
+ val SubApricot: Color = subApricot,
+ val SubBlue: Color = subBlue,
+ val SystemBlack: Color = systemBlack,
+ val SystemWhite: Color = systemWhite,
+ val SystemGray1: Color = systemGray1,
+ val SystemGray2: Color = systemGray2,
+ val SystemGray3: Color = systemGray3,
+ val SystemError: Color = systemError,
+ val SystemSuccess: Color = systemSuccess,
+)
+
+val lightColor = WithPeaceColor()
+val darkColor = WithPeaceColor()
diff --git a/core/designsystem/src/main/java/com/withpeace/withpeace/core/designsystem/theme/Padding.kt b/core/designsystem/src/main/java/com/withpeace/withpeace/core/designsystem/theme/Padding.kt
new file mode 100644
index 00000000..6323cee1
--- /dev/null
+++ b/core/designsystem/src/main/java/com/withpeace/withpeace/core/designsystem/theme/Padding.kt
@@ -0,0 +1,9 @@
+package com.withpeace.withpeace.core.designsystem.theme
+
+import androidx.compose.ui.unit.Dp
+import androidx.compose.ui.unit.dp
+
+data class WithPeacePadding(
+ val BasicHorizontalPadding: Dp = 24.dp,
+ val BasicContentPadding: Dp = 8.dp,
+)
diff --git a/core/designsystem/src/main/java/com/withpeace/withpeace/core/designsystem/theme/Theme.kt b/core/designsystem/src/main/java/com/withpeace/withpeace/core/designsystem/theme/Theme.kt
new file mode 100644
index 00000000..c6dfdb62
--- /dev/null
+++ b/core/designsystem/src/main/java/com/withpeace/withpeace/core/designsystem/theme/Theme.kt
@@ -0,0 +1,50 @@
+package com.withpeace.withpeace.core.designsystem.theme
+
+import androidx.compose.foundation.isSystemInDarkTheme
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.CompositionLocalProvider
+import androidx.compose.runtime.staticCompositionLocalOf
+
+val LocalCustomColors =
+ staticCompositionLocalOf {
+ WithPeaceColor()
+ }
+
+val LocalCustomTypography =
+ staticCompositionLocalOf {
+ WithPeaceTypography()
+ }
+val LocalCustomPadding =
+ staticCompositionLocalOf {
+ WithPeacePadding()
+ }
+
+@Composable
+fun WithpeaceTheme(
+ darkTheme: Boolean = isSystemInDarkTheme(),
+ content: @Composable () -> Unit,
+) {
+ val colorScheme =
+ when {
+ darkTheme -> darkColor
+ else -> lightColor
+ }
+ CompositionLocalProvider(
+ LocalCustomColors provides colorScheme,
+ LocalCustomTypography provides WithPeaceTypography(),
+ LocalCustomPadding provides WithPeacePadding(),
+ content = content,
+ )
+}
+
+object WithpeaceTheme {
+ val colors: WithPeaceColor
+ @Composable
+ get() = LocalCustomColors.current
+ val typography: WithPeaceTypography
+ @Composable
+ get() = LocalCustomTypography.current
+ val padding: WithPeacePadding
+ @Composable
+ get() = LocalCustomPadding.current
+}
diff --git a/core/designsystem/src/main/java/com/withpeace/withpeace/core/designsystem/theme/Type.kt b/core/designsystem/src/main/java/com/withpeace/withpeace/core/designsystem/theme/Type.kt
new file mode 100644
index 00000000..4e0df278
--- /dev/null
+++ b/core/designsystem/src/main/java/com/withpeace/withpeace/core/designsystem/theme/Type.kt
@@ -0,0 +1,135 @@
+package com.withpeace.withpeace.core.designsystem.theme
+
+import androidx.compose.material3.Typography
+import androidx.compose.runtime.Immutable
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.text.TextStyle
+import androidx.compose.ui.text.font.Font
+import androidx.compose.ui.text.font.FontFamily
+import androidx.compose.ui.text.font.FontWeight
+import androidx.compose.ui.unit.sp
+import com.withpeace.withpeace.core.designsystem.R
+
+// 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
+ )
+ */
+ )
+
+val NotoSansFont =
+ FontFamily(
+ Font(
+ resId = R.font.notosans_kr_medium,
+ weight = FontWeight.Bold,
+ ),
+ )
+
+val PretendardFont =
+ FontFamily(
+ Font(
+ resId = R.font.pretendard_bold,
+ weight = FontWeight.Bold,
+ ),
+ Font(
+ resId = R.font.pretendard_regular,
+ weight = FontWeight.Normal,
+ ),
+ Font(
+ resId = R.font.pretendard_extra_bold,
+ weight = FontWeight.ExtraBold,
+ ),
+ Font(
+ resId = R.font.pretendard_extra_light,
+ weight = FontWeight.ExtraLight,
+ ),
+ Font(
+ resId = R.font.pretendard_extra_light,
+ weight = FontWeight.Light,
+ ),
+ Font(
+ resId = R.font.pretendard_medium,
+ weight = FontWeight.Medium,
+ ),
+ Font(
+ resId = R.font.pretendard_semi_bold,
+ weight = FontWeight.SemiBold,
+ ),
+ Font(
+ resId = R.font.pretendard_thin,
+ weight = FontWeight.Thin,
+ ),
+ )
+
+@Immutable
+data class WithPeaceTypography(
+ val notoSans: TextStyle =
+ TextStyle(
+ fontFamily = NotoSansFont,
+ fontWeight = FontWeight.Medium,
+ fontSize = 16.sp,
+ lineHeight = 23.17.sp,
+ ),
+ val heading: TextStyle =
+ TextStyle(
+ fontFamily = PretendardFont,
+ fontWeight = FontWeight.Bold,
+ fontSize = 24.sp,
+ lineHeight = 33.6.sp,
+ letterSpacing = (-0.096).sp,
+ ),
+ val title1: TextStyle =
+ TextStyle(
+ fontFamily = PretendardFont,
+ fontWeight = FontWeight.Bold,
+ fontSize = 20.sp,
+ lineHeight = 28.sp,
+ letterSpacing = (-0.08).sp,
+ ),
+ val title2: TextStyle =
+ TextStyle(
+ fontFamily = PretendardFont,
+ fontWeight = FontWeight.Bold,
+ fontSize = 18.sp,
+ lineHeight = 25.2.sp,
+ letterSpacing = (-0.4).sp,
+ ),
+ val body: TextStyle =
+ TextStyle(
+ fontFamily = PretendardFont,
+ fontWeight = FontWeight.Normal,
+ fontSize = 16.sp,
+ lineHeight = 22.4.sp,
+ letterSpacing = (-0.4).sp,
+ ),
+ val caption: TextStyle =
+ TextStyle(
+ fontFamily = PretendardFont,
+ fontWeight = FontWeight.Normal,
+ fontSize = 14.sp,
+ lineHeight = 19.6.sp,
+ letterSpacing = (-0.4).sp,
+ ),
+)
diff --git a/core/designsystem/src/main/res/font/notosans_kr_medium.ttf b/core/designsystem/src/main/res/font/notosans_kr_medium.ttf
new file mode 100644
index 00000000..5311c8a3
Binary files /dev/null and b/core/designsystem/src/main/res/font/notosans_kr_medium.ttf differ
diff --git a/core/designsystem/src/main/res/font/pretendard_black.ttf b/core/designsystem/src/main/res/font/pretendard_black.ttf
new file mode 100644
index 00000000..d0c1db81
Binary files /dev/null and b/core/designsystem/src/main/res/font/pretendard_black.ttf differ
diff --git a/core/designsystem/src/main/res/font/pretendard_bold.ttf b/core/designsystem/src/main/res/font/pretendard_bold.ttf
new file mode 100644
index 00000000..fb07fc65
Binary files /dev/null and b/core/designsystem/src/main/res/font/pretendard_bold.ttf differ
diff --git a/core/designsystem/src/main/res/font/pretendard_extra_bold.ttf b/core/designsystem/src/main/res/font/pretendard_extra_bold.ttf
new file mode 100644
index 00000000..9d5fe072
Binary files /dev/null and b/core/designsystem/src/main/res/font/pretendard_extra_bold.ttf differ
diff --git a/core/designsystem/src/main/res/font/pretendard_extra_light.ttf b/core/designsystem/src/main/res/font/pretendard_extra_light.ttf
new file mode 100644
index 00000000..09e65428
Binary files /dev/null and b/core/designsystem/src/main/res/font/pretendard_extra_light.ttf differ
diff --git a/core/designsystem/src/main/res/font/pretendard_light.ttf b/core/designsystem/src/main/res/font/pretendard_light.ttf
new file mode 100644
index 00000000..2e8541d6
Binary files /dev/null and b/core/designsystem/src/main/res/font/pretendard_light.ttf differ
diff --git a/core/designsystem/src/main/res/font/pretendard_medium.ttf b/core/designsystem/src/main/res/font/pretendard_medium.ttf
new file mode 100644
index 00000000..1db67c68
Binary files /dev/null and b/core/designsystem/src/main/res/font/pretendard_medium.ttf differ
diff --git a/core/designsystem/src/main/res/font/pretendard_regular.ttf b/core/designsystem/src/main/res/font/pretendard_regular.ttf
new file mode 100644
index 00000000..01147e99
Binary files /dev/null and b/core/designsystem/src/main/res/font/pretendard_regular.ttf differ
diff --git a/core/designsystem/src/main/res/font/pretendard_semi_bold.ttf b/core/designsystem/src/main/res/font/pretendard_semi_bold.ttf
new file mode 100644
index 00000000..9f2690f0
Binary files /dev/null and b/core/designsystem/src/main/res/font/pretendard_semi_bold.ttf differ
diff --git a/core/designsystem/src/main/res/font/pretendard_thin.ttf b/core/designsystem/src/main/res/font/pretendard_thin.ttf
new file mode 100644
index 00000000..fe9825f1
Binary files /dev/null and b/core/designsystem/src/main/res/font/pretendard_thin.ttf differ
diff --git a/core/domain/src/main/java/com/withpeace/withpeace/core/domain/model/Token.kt b/core/domain/src/main/java/com/withpeace/withpeace/core/domain/model/Token.kt
index 8fddfc8b..f8ca3553 100644
--- a/core/domain/src/main/java/com/withpeace/withpeace/core/domain/model/Token.kt
+++ b/core/domain/src/main/java/com/withpeace/withpeace/core/domain/model/Token.kt
@@ -2,5 +2,5 @@ package com.withpeace.withpeace.core.domain.model
data class Token(
val accessToken: String,
- val refreshToken: String
+ val refreshToken: String,
)
\ No newline at end of file
diff --git a/core/domain/src/main/java/com/withpeace/withpeace/core/domain/repository/TokenRepository.kt b/core/domain/src/main/java/com/withpeace/withpeace/core/domain/repository/TokenRepository.kt
index fef27c35..c2f14965 100644
--- a/core/domain/src/main/java/com/withpeace/withpeace/core/domain/repository/TokenRepository.kt
+++ b/core/domain/src/main/java/com/withpeace/withpeace/core/domain/repository/TokenRepository.kt
@@ -5,5 +5,23 @@ import kotlinx.coroutines.flow.Flow
interface TokenRepository {
- fun googleLogin(onError: (message: String?) -> Unit): Flow
+ fun getAccessToken(): Flow
+
+ fun getRefreshToken(): Flow
+
+ suspend fun updateAccessToken(accessToken: String)
+
+ suspend fun updateRefreshToken(refreshToken: String)
+
+ suspend fun signUp(
+ onError: (message: String?) -> Unit,
+ email: String,
+ nickname: String,
+ deviceToken: String?,
+ ): Flow
+
+ fun googleLogin(
+ idToken: String,
+ onError: (message: String?) -> Unit,
+ ): Flow
}
\ No newline at end of file
diff --git a/core/interceptor/.gitignore b/core/interceptor/.gitignore
new file mode 100644
index 00000000..42afabfd
--- /dev/null
+++ b/core/interceptor/.gitignore
@@ -0,0 +1 @@
+/build
\ No newline at end of file
diff --git a/core/interceptor/build.gradle.kts b/core/interceptor/build.gradle.kts
new file mode 100644
index 00000000..1967e9b0
--- /dev/null
+++ b/core/interceptor/build.gradle.kts
@@ -0,0 +1,17 @@
+plugins {
+ id("com.android.library")
+ id("convention.android.base")
+ id("convention.android.hilt")
+}
+
+android {
+ namespace = "com.withpeace.withpeace.core.interceptor"
+}
+
+dependencies {
+ implementation(libs.kotlinx.serialization.json)
+ implementation(libs.retrofit.core)
+ implementation(libs.okhttp.logging)
+ implementation(project(":core:datastore"))
+ implementation(project(":core:network"))
+}
\ No newline at end of file
diff --git a/core/interceptor/consumer-rules.pro b/core/interceptor/consumer-rules.pro
new file mode 100644
index 00000000..e69de29b
diff --git a/core/interceptor/proguard-rules.pro b/core/interceptor/proguard-rules.pro
new file mode 100644
index 00000000..481bb434
--- /dev/null
+++ b/core/interceptor/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/interceptor/src/androidTest/java/com/withpeace/withpeace/core/interceptor/ExampleInstrumentedTest.kt b/core/interceptor/src/androidTest/java/com/withpeace/withpeace/core/interceptor/ExampleInstrumentedTest.kt
new file mode 100644
index 00000000..5d4149a9
--- /dev/null
+++ b/core/interceptor/src/androidTest/java/com/withpeace/withpeace/core/interceptor/ExampleInstrumentedTest.kt
@@ -0,0 +1,22 @@
+package com.withpeace.withpeace.core.interceptor
+
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.platform.app.InstrumentationRegistry
+import org.junit.Assert.*
+import org.junit.Test
+import org.junit.runner.RunWith
+
+/**
+ * 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.withpeace.withpeace.core.interceptor.test", appContext.packageName)
+ }
+}
\ No newline at end of file
diff --git a/core/interceptor/src/main/AndroidManifest.xml b/core/interceptor/src/main/AndroidManifest.xml
new file mode 100644
index 00000000..44008a43
--- /dev/null
+++ b/core/interceptor/src/main/AndroidManifest.xml
@@ -0,0 +1,4 @@
+
+
+
+
\ No newline at end of file
diff --git a/core/interceptor/src/main/java/com/withpeace/withpeace/core/interceptor/AuthInterceptor.kt b/core/interceptor/src/main/java/com/withpeace/withpeace/core/interceptor/AuthInterceptor.kt
new file mode 100644
index 00000000..305606f8
--- /dev/null
+++ b/core/interceptor/src/main/java/com/withpeace/withpeace/core/interceptor/AuthInterceptor.kt
@@ -0,0 +1,87 @@
+package com.withpeace.withpeace.core.interceptor
+
+import com.withpeace.withpeace.core.datastore.dataStore.TokenPreferenceDataSource
+import com.withpeace.withpeace.core.network.di.response.TokenResponse
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.flow.firstOrNull
+import kotlinx.coroutines.runBlocking
+import kotlinx.coroutines.withContext
+import kotlinx.serialization.json.Json
+import okhttp3.Interceptor
+import okhttp3.OkHttpClient
+import okhttp3.Request
+import okhttp3.Response
+import javax.inject.Inject
+
+class AuthInterceptor @Inject constructor(
+ private val tokenPreferenceDataSource: TokenPreferenceDataSource,
+) : Interceptor {
+ private val client = OkHttpClient.Builder().build()
+
+ override fun intercept(chain: Interceptor.Chain): Response {
+ val accessToken = runBlocking { tokenPreferenceDataSource.accessToken.firstOrNull() }
+ val tokenAddedRequest =
+ chain.request()
+ .newBuilder()
+ .addHeader(
+ ACCESS_TOKEN_HEADER,
+ TOKEN_FORMAT.format(accessToken),
+ ).build()
+ var response = chain.proceed(tokenAddedRequest)
+
+ if (response.code == 401) {
+ val refreshToken = runBlocking { tokenPreferenceDataSource.refreshToken.firstOrNull() }
+ if (refreshToken != null) {
+ runCatching {
+ refreshAccessToken(refreshToken)
+ }.onSuccess { tokenResponse ->
+ runBlocking {
+ tokenPreferenceDataSource.updateAccessToken(tokenResponse.accessToken)
+ tokenPreferenceDataSource.updateRefreshToken(tokenResponse.refreshToken)
+ }
+ response =
+ chain.proceed(
+ chain.request().newBuilder().addHeader(
+ ACCESS_TOKEN_HEADER,
+ TOKEN_FORMAT.format(tokenResponse.accessToken),
+ ).build(),
+ )
+ }
+ }
+ }
+ return response
+ }
+
+ private fun refreshAccessToken(refreshToken: String): TokenResponse {
+ val response: Response =
+ runBlocking {
+ withContext(Dispatchers.IO) {
+ client.newCall(createAccessTokenRefreshRequest(refreshToken)).execute()
+ }
+ }
+ if (response.isSuccessful) {
+ return response.toDto()
+ }
+ throw IllegalArgumentException()
+ }
+
+ private fun createAccessTokenRefreshRequest(refreshToken: String): Request {
+ return Request.Builder()
+ .url(REFRESH_URL)
+ .addHeader(REFRESH_TOKEN_FORMAT, TOKEN_FORMAT.format(refreshToken))
+ .build()
+ }
+
+ private inline fun Response.toDto(): T {
+ body?.let {
+ return Json.decodeFromString(it.string())
+ } ?: throw IllegalArgumentException()
+ }
+
+ companion object {
+ private const val REFRESH_URL = "http://49.50.160.170:8080/api/v1/auth/refresh"
+ private const val REFRESH_TOKEN_FORMAT = "ReAuthorization"
+ private const val ACCESS_TOKEN_HEADER = "Authorization"
+ private const val TOKEN_FORMAT = "Bearer %s"
+ }
+}
diff --git a/core/interceptor/src/main/java/com/withpeace/withpeace/core/interceptor/InterceptorModule.kt b/core/interceptor/src/main/java/com/withpeace/withpeace/core/interceptor/InterceptorModule.kt
new file mode 100644
index 00000000..f1d968a1
--- /dev/null
+++ b/core/interceptor/src/main/java/com/withpeace/withpeace/core/interceptor/InterceptorModule.kt
@@ -0,0 +1,25 @@
+package com.withpeace.withpeace.core.interceptor
+
+import android.content.Context
+import com.withpeace.withpeace.core.datastore.dataStore.TokenPreferenceDataSource
+import dagger.Module
+import dagger.Provides
+import dagger.hilt.InstallIn
+import dagger.hilt.android.qualifiers.ApplicationContext
+import dagger.hilt.components.SingletonComponent
+import okhttp3.Interceptor
+import javax.inject.Singleton
+
+@Module
+@InstallIn(SingletonComponent::class)
+object InterceptorModule {
+
+ @Provides
+ @Singleton
+ fun provideHeaderInterceptor(
+ tokenPreferenceDataSource: TokenPreferenceDataSource,
+ ): Interceptor =
+ AuthInterceptor(tokenPreferenceDataSource)
+
+
+}
\ No newline at end of file
diff --git a/core/interceptor/src/test/java/com/withpeace/withpeace/core/interceptor/ExampleUnitTest.kt b/core/interceptor/src/test/java/com/withpeace/withpeace/core/interceptor/ExampleUnitTest.kt
new file mode 100644
index 00000000..03abd607
--- /dev/null
+++ b/core/interceptor/src/test/java/com/withpeace/withpeace/core/interceptor/ExampleUnitTest.kt
@@ -0,0 +1,16 @@
+package com.withpeace.withpeace.core.interceptor
+
+import org.junit.Assert.assertEquals
+import org.junit.Test
+
+/**
+ * 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)
+ }
+}
\ No newline at end of file
diff --git a/core/network/src/androidTest/java/com/withpeace/withpeace/core/network/ExampleInstrumentedTest.kt b/core/network/src/androidTest/java/com/withpeace/withpeace/core/network/ExampleInstrumentedTest.kt
index 75fdeedb..dd396ff4 100644
--- a/core/network/src/androidTest/java/com/withpeace/withpeace/core/network/ExampleInstrumentedTest.kt
+++ b/core/network/src/androidTest/java/com/withpeace/withpeace/core/network/ExampleInstrumentedTest.kt
@@ -1,13 +1,11 @@
package com.withpeace.withpeace.core.network
-import androidx.test.platform.app.InstrumentationRegistry
import androidx.test.ext.junit.runners.AndroidJUnit4
-
+import androidx.test.platform.app.InstrumentationRegistry
+import org.junit.Assert.*
import org.junit.Test
import org.junit.runner.RunWith
-import org.junit.Assert.*
-
/**
* Instrumented test, which will execute on an Android device.
*
diff --git a/core/network/src/main/AndroidManifest.xml b/core/network/src/main/AndroidManifest.xml
index a5918e68..44008a43 100644
--- a/core/network/src/main/AndroidManifest.xml
+++ b/core/network/src/main/AndroidManifest.xml
@@ -1,4 +1,4 @@
-
+
\ No newline at end of file
diff --git a/core/network/src/main/java/com/withpeace/withpeace/core/network/di/AuthInterceptor.kt b/core/network/src/main/java/com/withpeace/withpeace/core/network/di/AuthInterceptor.kt
new file mode 100644
index 00000000..e69de29b
diff --git a/core/network/src/main/java/com/withpeace/withpeace/core/network/di/NetworkModule.kt b/core/network/src/main/java/com/withpeace/withpeace/core/network/di/di/NetworkModule.kt
similarity index 66%
rename from core/network/src/main/java/com/withpeace/withpeace/core/network/di/NetworkModule.kt
rename to core/network/src/main/java/com/withpeace/withpeace/core/network/di/di/NetworkModule.kt
index e84b59a3..dae7fe46 100644
--- a/core/network/src/main/java/com/withpeace/withpeace/core/network/di/NetworkModule.kt
+++ b/core/network/src/main/java/com/withpeace/withpeace/core/network/di/di/NetworkModule.kt
@@ -1,4 +1,4 @@
-package com.withpeace.withpeace.core.network.di
+package com.withpeace.withpeace.core.network.di.di
import com.jakewharton.retrofit2.converter.kotlinx.serialization.asConverterFactory
import com.skydoves.sandwich.adapters.ApiResponseCallAdapterFactory
@@ -7,11 +7,13 @@ import dagger.Provides
import dagger.hilt.InstallIn
import dagger.hilt.components.SingletonComponent
import kotlinx.serialization.json.Json
+import okhttp3.Interceptor
import okhttp3.MediaType.Companion.toMediaType
import okhttp3.OkHttpClient
import okhttp3.logging.HttpLoggingInterceptor
import retrofit2.Converter
import retrofit2.Retrofit
+import javax.inject.Named
import javax.inject.Singleton
@Module
@@ -39,34 +41,45 @@ object NetworkModule {
}
}
-// @Provides
-// @Singleton
-// fun provideHeaderInterceptor(chain: Interceptor.Chain) {
-// val requestBuilder = chain.request().newBuilder()
-// var apiKey = BuildConfig.X_RIOT_TOKEN
-// requestBuilder.addHeader("X-Riot-Token", apiKey)
-// chain.proceed(requestBuilder.build())
-// }
-
@Singleton
@Provides
- fun provideOkhttpClient(httpLoggingInterceptor: HttpLoggingInterceptor): OkHttpClient {
+ fun provideOkhttpClient(
+ authInterceptor: Interceptor,
+ httpLoggingInterceptor: HttpLoggingInterceptor,
+ ): OkHttpClient {
return OkHttpClient.Builder().apply {
- // addInterceptor(AccessTokenInterceptor) TODO("토큰 인터셉터 할당")
+ addInterceptor(authInterceptor)
addInterceptor(httpLoggingInterceptor)
}.build()
}
-
+ @Named("general")
@Provides
@Singleton
- fun provideRetrofitClient(
+ fun provideTokenRetrofitClient(
okHttpClient: OkHttpClient,
- converterFactory: Converter.Factory
+ converterFactory: Converter.Factory,
): Retrofit {
return Retrofit.Builder()
.client(okHttpClient)
- .baseUrl("https://asia.api.riotgames.com/") // TODO("BaseUrl 수정")
+ .baseUrl("http://49.50.160.170:8080/")
+ .addConverterFactory(converterFactory)
+ .addCallAdapterFactory(ApiResponseCallAdapterFactory.create())
+ .build()
+ }
+
+
+ /**
+ * todo: 네이밍 수정
+ */
+ @Named("initial")
+ @Provides
+ @Singleton
+ fun provideRetrofitClient(
+ converterFactory: Converter.Factory,
+ ): Retrofit {
+ return Retrofit.Builder()
+ .baseUrl("http://49.50.160.170:8080/")
.addConverterFactory(converterFactory)
.addCallAdapterFactory(ApiResponseCallAdapterFactory.create())
.build()
diff --git a/core/network/src/main/java/com/withpeace/withpeace/core/network/di/di/ServiceModule.kt b/core/network/src/main/java/com/withpeace/withpeace/core/network/di/di/ServiceModule.kt
new file mode 100644
index 00000000..4c598414
--- /dev/null
+++ b/core/network/src/main/java/com/withpeace/withpeace/core/network/di/di/ServiceModule.kt
@@ -0,0 +1,26 @@
+package com.withpeace.withpeace.core.network.di.di
+
+import com.withpeace.withpeace.core.network.di.service.AuthService
+import com.withpeace.withpeace.core.network.di.service.LoginService
+import dagger.Module
+import dagger.Provides
+import dagger.hilt.InstallIn
+import dagger.hilt.components.SingletonComponent
+import retrofit2.Retrofit
+import javax.inject.Named
+import javax.inject.Singleton
+
+@Module
+@InstallIn(SingletonComponent::class)
+object ServiceModule {
+
+ @Provides
+ @Singleton
+ fun providesAuthService(@Named("general") retrofit: Retrofit): AuthService =
+ retrofit.create(AuthService::class.java)
+
+ @Provides
+ @Singleton
+ fun providesLoginService(@Named("initial") retrofit: Retrofit): LoginService =
+ retrofit.create(LoginService::class.java)
+}
\ No newline at end of file
diff --git a/core/network/src/main/java/com/withpeace/withpeace/core/network/di/request/SignUpRequest.kt b/core/network/src/main/java/com/withpeace/withpeace/core/network/di/request/SignUpRequest.kt
index af72b92d..d69487fb 100644
--- a/core/network/src/main/java/com/withpeace/withpeace/core/network/di/request/SignUpRequest.kt
+++ b/core/network/src/main/java/com/withpeace/withpeace/core/network/di/request/SignUpRequest.kt
@@ -1,5 +1,8 @@
package com.withpeace.withpeace.core.network.di.request
+import kotlinx.serialization.Serializable
+
+@Serializable
data class SignUpRequest(
val email: String,
val nickname: String,
diff --git a/core/network/src/main/java/com/withpeace/withpeace/core/network/di/response/TokenResponse.kt b/core/network/src/main/java/com/withpeace/withpeace/core/network/di/response/TokenResponse.kt
index 26350bd7..7f0c414f 100644
--- a/core/network/src/main/java/com/withpeace/withpeace/core/network/di/response/TokenResponse.kt
+++ b/core/network/src/main/java/com/withpeace/withpeace/core/network/di/response/TokenResponse.kt
@@ -1,5 +1,8 @@
package com.withpeace.withpeace.core.network.di.response
+import kotlinx.serialization.Serializable
+
+@Serializable
data class TokenResponse(
val accessToken: String,
val refreshToken: String,
diff --git a/core/network/src/main/java/com/withpeace/withpeace/core/network/di/service/AuthService.kt b/core/network/src/main/java/com/withpeace/withpeace/core/network/di/service/AuthService.kt
index 546e1ed0..fffa79ad 100644
--- a/core/network/src/main/java/com/withpeace/withpeace/core/network/di/service/AuthService.kt
+++ b/core/network/src/main/java/com/withpeace/withpeace/core/network/di/service/AuthService.kt
@@ -5,18 +5,18 @@ import com.withpeace.withpeace.core.network.di.request.SignUpRequest
import com.withpeace.withpeace.core.network.di.response.BaseResponse
import com.withpeace.withpeace.core.network.di.response.TokenResponse
import retrofit2.http.Body
+import retrofit2.http.Header
import retrofit2.http.POST
interface AuthService {
- @POST("/api/v1/auth/google")
- fun googleLogin(): ApiResponse>
-
@POST("/api/v1/auth/register")
- fun signUp(@Body signUpRequest: SignUpRequest): ApiResponse>
+ suspend fun signUp(
+ @Body signUpRequest: SignUpRequest,
+ ): ApiResponse>
@POST("/api/v1/auth/refresh")
- fun refreshAccessToken(): ApiResponse>
+ suspend fun refreshAccessToken(): ApiResponse>
@POST("/api/v1/auth/logout")
- fun logout(): ApiResponse>
+ suspend fun logout(): ApiResponse>
}
\ No newline at end of file
diff --git a/core/network/src/main/java/com/withpeace/withpeace/core/network/di/service/LoginService.kt b/core/network/src/main/java/com/withpeace/withpeace/core/network/di/service/LoginService.kt
new file mode 100644
index 00000000..deee313d
--- /dev/null
+++ b/core/network/src/main/java/com/withpeace/withpeace/core/network/di/service/LoginService.kt
@@ -0,0 +1,16 @@
+package com.withpeace.withpeace.core.network.di.service
+
+import com.skydoves.sandwich.ApiResponse
+import com.withpeace.withpeace.core.network.di.response.BaseResponse
+import com.withpeace.withpeace.core.network.di.response.TokenResponse
+import retrofit2.http.Header
+import retrofit2.http.POST
+
+interface LoginService {
+
+ @POST("/api/v1/auth/google")
+ suspend fun googleLogin(
+ @Header("Authorization")
+ idToken: String,
+ ): ApiResponse>
+}
\ No newline at end of file
diff --git a/core/network/src/test/java/com/withpeace/withpeace/core/network/ExampleUnitTest.kt b/core/network/src/test/java/com/withpeace/withpeace/core/network/ExampleUnitTest.kt
index 9b8a7dd4..3a01bec8 100644
--- a/core/network/src/test/java/com/withpeace/withpeace/core/network/ExampleUnitTest.kt
+++ b/core/network/src/test/java/com/withpeace/withpeace/core/network/ExampleUnitTest.kt
@@ -1,9 +1,8 @@
package com.withpeace.withpeace.core.network
+import org.junit.Assert.assertEquals
import org.junit.Test
-import org.junit.Assert.*
-
/**
* Example local unit test, which will execute on the development machine (host).
*
diff --git a/feature/login/.gitignore b/feature/login/.gitignore
new file mode 100644
index 00000000..42afabfd
--- /dev/null
+++ b/feature/login/.gitignore
@@ -0,0 +1 @@
+/build
\ No newline at end of file
diff --git a/feature/login/build.gradle.kts b/feature/login/build.gradle.kts
new file mode 100644
index 00000000..0167a052
--- /dev/null
+++ b/feature/login/build.gradle.kts
@@ -0,0 +1,11 @@
+plugins {
+ id("convention.feature")
+}
+
+android {
+ namespace = "com.withpeace.withpeace.feature.login"
+}
+
+dependencies {
+ implementation(project(":google-login"))
+}
diff --git a/feature/login/consumer-rules.pro b/feature/login/consumer-rules.pro
new file mode 100644
index 00000000..e69de29b
diff --git a/feature/login/proguard-rules.pro b/feature/login/proguard-rules.pro
new file mode 100644
index 00000000..481bb434
--- /dev/null
+++ b/feature/login/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/login/src/main/AndroidManifest.xml b/feature/login/src/main/AndroidManifest.xml
new file mode 100644
index 00000000..7179a81c
--- /dev/null
+++ b/feature/login/src/main/AndroidManifest.xml
@@ -0,0 +1,4 @@
+
+
+
+
diff --git a/feature/login/src/main/java/com/withpeace/withpeace/feature/login/LoginScreen.kt b/feature/login/src/main/java/com/withpeace/withpeace/feature/login/LoginScreen.kt
new file mode 100644
index 00000000..dd2db9f9
--- /dev/null
+++ b/feature/login/src/main/java/com/withpeace/withpeace/feature/login/LoginScreen.kt
@@ -0,0 +1,137 @@
+package com.withpeace.withpeace.feature.login
+
+import android.util.Log
+import androidx.compose.foundation.BorderStroke
+import androidx.compose.foundation.Image
+import androidx.compose.foundation.layout.Arrangement
+import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.PaddingValues
+import androidx.compose.foundation.layout.Spacer
+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.shape.RoundedCornerShape
+import androidx.compose.material3.Button
+import androidx.compose.material3.ButtonDefaults
+import androidx.compose.material3.Text
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.LaunchedEffect
+import androidx.compose.runtime.rememberCoroutineScope
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.platform.LocalContext
+import androidx.compose.ui.res.painterResource
+import androidx.compose.ui.res.stringResource
+import androidx.compose.ui.text.style.TextAlign
+import androidx.compose.ui.tooling.preview.Preview
+import androidx.compose.ui.unit.dp
+import androidx.hilt.navigation.compose.hiltViewModel
+import com.withpeace.withpeace.core.designsystem.theme.WithpeaceTheme
+import com.withpeace.withpeace.googlelogin.GoogleLoginManager
+
+@Composable
+fun LoginRoute(
+ viewModel: LoginViewModel = hiltViewModel(),
+ onShowSnackBar: (message: String) -> Unit,
+) {
+ LoginScreen(
+ onGoogleLogin = viewModel::googleLogin,
+ )
+ LaunchedEffect(key1 = null) {
+ viewModel.loginUiEvent.collect { uiEvent ->
+ when (uiEvent) {
+ is LoginUiEvent.SignUpSuccess -> {
+ onShowSnackBar("LoginRoute: 로그인 성공")
+ }
+
+ is LoginUiEvent.SignUpFail -> {
+ onShowSnackBar("LoginRoute: 로그인 실패")
+ }
+ }
+ }
+ }
+}
+
+@Composable
+fun LoginScreen(
+ onGoogleLogin: (idToken: String) -> Unit = {},
+) {
+ val googleLoginManager = GoogleLoginManager(context = LocalContext.current)
+ val coroutineScope = rememberCoroutineScope()
+
+ Column(
+ modifier = Modifier.fillMaxSize(),
+ horizontalAlignment = Alignment.CenterHorizontally,
+ verticalArrangement = Arrangement.SpaceBetween,
+ ) {
+ Column(
+ horizontalAlignment = Alignment.CenterHorizontally,
+ ) {
+ Spacer(modifier = Modifier.height(152.dp))
+ Image(
+ painter = painterResource(id = R.drawable.app_logo),
+ contentDescription = stringResource(R.string.app_logo_content_description),
+ )
+ Spacer(modifier = Modifier.height(40.dp))
+ Text(
+ style = WithpeaceTheme.typography.title1,
+ text = stringResource(R.string.welcome_to_withpeace),
+ textAlign = TextAlign.Center,
+ )
+ Spacer(modifier = Modifier.height(24.dp))
+ Text(
+ style = WithpeaceTheme.typography.body,
+ text = stringResource(R.string.welcome_introduction),
+ textAlign = TextAlign.Center,
+ )
+ }
+ Button(
+ onClick = {
+ googleLoginManager.startLogin(
+ coroutineScope = coroutineScope,
+ onSuccessLogin = onGoogleLogin,
+ onFailLogin = { Log.e("woogi", "LoginScreen: 로그인 실패") },
+ )
+ },
+ contentPadding = PaddingValues(0.dp),
+ modifier = Modifier
+ .padding(
+ bottom = 40.dp,
+ end = WithpeaceTheme.padding.BasicHorizontalPadding,
+ start = WithpeaceTheme.padding.BasicHorizontalPadding,
+ )
+ .fillMaxWidth(),
+ border = BorderStroke(width = 1.dp, color = WithpeaceTheme.colors.SystemBlack),
+ colors = ButtonDefaults.buttonColors(containerColor = WithpeaceTheme.colors.SystemWhite),
+ shape = RoundedCornerShape(9.dp),
+ ) {
+ Box(
+ modifier = Modifier.fillMaxWidth(),
+ ) {
+ Image(
+ modifier = Modifier
+ .padding(16.dp)
+ .align(Alignment.CenterStart)
+ .size(24.dp),
+ painter = painterResource(id = R.drawable.img_google_logo),
+ contentDescription = stringResource(R.string.image_google_logo),
+ )
+ Text(
+ modifier = Modifier.align(Alignment.Center),
+ color = WithpeaceTheme.colors.SystemBlack,
+ style = WithpeaceTheme.typography.notoSans,
+ text = stringResource(R.string.login_to_google),
+ )
+ }
+ }
+ }
+}
+
+@Preview(widthDp = 400, heightDp = 900, showBackground = true)
+@Composable
+fun LoginScreenPreview() {
+ LoginScreen()
+}
diff --git a/feature/login/src/main/java/com/withpeace/withpeace/feature/login/LoginUiEvent.kt b/feature/login/src/main/java/com/withpeace/withpeace/feature/login/LoginUiEvent.kt
new file mode 100644
index 00000000..536efdd5
--- /dev/null
+++ b/feature/login/src/main/java/com/withpeace/withpeace/feature/login/LoginUiEvent.kt
@@ -0,0 +1,8 @@
+package com.withpeace.withpeace.feature.login
+
+sealed interface LoginUiEvent {
+
+ data object SignUpSuccess: LoginUiEvent
+
+ data class SignUpFail(val message: String): LoginUiEvent
+}
\ No newline at end of file
diff --git a/feature/login/src/main/java/com/withpeace/withpeace/feature/login/LoginViewModel.kt b/feature/login/src/main/java/com/withpeace/withpeace/feature/login/LoginViewModel.kt
new file mode 100644
index 00000000..1f56fea8
--- /dev/null
+++ b/feature/login/src/main/java/com/withpeace/withpeace/feature/login/LoginViewModel.kt
@@ -0,0 +1,55 @@
+package com.withpeace.withpeace.feature.login
+
+import android.util.Log
+import androidx.lifecycle.ViewModel
+import androidx.lifecycle.viewModelScope
+import com.withpeace.withpeace.core.domain.repository.TokenRepository
+import dagger.hilt.android.lifecycle.HiltViewModel
+import kotlinx.coroutines.flow.MutableSharedFlow
+import kotlinx.coroutines.flow.asSharedFlow
+import kotlinx.coroutines.flow.collect
+import kotlinx.coroutines.launch
+import javax.inject.Inject
+
+@HiltViewModel
+class LoginViewModel @Inject constructor(
+ private val tokenRepository: TokenRepository,
+) : ViewModel() {
+
+ private val _loginUiEvent: MutableSharedFlow = MutableSharedFlow()
+ val loginUiEvent = _loginUiEvent.asSharedFlow()
+
+ fun googleLogin(idToken: String) {
+ viewModelScope.launch {
+ tokenRepository.googleLogin(idToken) {
+ Log.e("woogi", it ?: "메시지 없음")
+ launch {
+ _loginUiEvent.emit(LoginUiEvent.SignUpFail(it ?: "메시지 없음"))
+ }
+ }.collect { token ->
+ tokenRepository.updateAccessToken(token.accessToken)
+ tokenRepository.updateRefreshToken(token.refreshToken)
+ signUp()
+ }
+ }
+ }
+
+ private fun signUp() {
+ viewModelScope.launch {
+ tokenRepository.signUp(
+ email = "abasdfasdf",
+ nickname = "haha",
+ deviceToken = null,
+ onError = {
+ launch {
+ _loginUiEvent.emit(LoginUiEvent.SignUpFail(it ?: "메시지 없음"))
+ }
+ }
+ ).collect { token ->
+ tokenRepository.updateAccessToken(token.accessToken)
+ tokenRepository.updateRefreshToken(token.refreshToken)
+ _loginUiEvent.emit(LoginUiEvent.SignUpSuccess)
+ }
+ }
+ }
+}
diff --git a/feature/login/src/main/java/com/withpeace/withpeace/feature/login/navigation/LoginNavigation.kt b/feature/login/src/main/java/com/withpeace/withpeace/feature/login/navigation/LoginNavigation.kt
new file mode 100644
index 00000000..499cd550
--- /dev/null
+++ b/feature/login/src/main/java/com/withpeace/withpeace/feature/login/navigation/LoginNavigation.kt
@@ -0,0 +1,21 @@
+package com.withpeace.withpeace.feature.login.navigation
+
+import androidx.navigation.NavController
+import androidx.navigation.NavGraphBuilder
+import androidx.navigation.NavOptions
+import androidx.navigation.compose.composable
+import com.withpeace.withpeace.feature.login.LoginRoute
+
+const val LOGIN_ROUTE = "loginRoute"
+
+fun NavController.navigateLogin(navOptions: NavOptions? = null) {
+ navigate(LOGIN_ROUTE, navOptions)
+}
+
+fun NavGraphBuilder.loginNavGraph(
+ onShowSnackBar: (message: String) -> Unit,
+) {
+ composable(route = LOGIN_ROUTE) {
+ LoginRoute(onShowSnackBar = onShowSnackBar)
+ }
+}
\ No newline at end of file
diff --git a/feature/login/src/main/res/drawable/app_logo.png b/feature/login/src/main/res/drawable/app_logo.png
new file mode 100644
index 00000000..544764ca
Binary files /dev/null and b/feature/login/src/main/res/drawable/app_logo.png differ
diff --git a/feature/login/src/main/res/drawable/img_google_logo.png b/feature/login/src/main/res/drawable/img_google_logo.png
new file mode 100644
index 00000000..5bae1042
Binary files /dev/null and b/feature/login/src/main/res/drawable/img_google_logo.png differ
diff --git a/feature/login/src/main/res/values/strings.xml b/feature/login/src/main/res/values/strings.xml
new file mode 100644
index 00000000..d569aac8
--- /dev/null
+++ b/feature/login/src/main/res/values/strings.xml
@@ -0,0 +1,8 @@
+
+
+ app logo
+ 위드피스에 오신 것을 환영합니다
+ image_google_logo
+ Google로 로그인하기
+ 1인 가구의 모든 것\n 유용한 정보를 함께 공유해보세요!
+
\ No newline at end of file
diff --git a/google-login/build.gradle.kts b/google-login/build.gradle.kts
index e5a9f73e..c16ef1fa 100644
--- a/google-login/build.gradle.kts
+++ b/google-login/build.gradle.kts
@@ -1,43 +1,30 @@
+import com.android.build.gradle.internal.cxx.configure.gradleLocalProperties
+
+fun getLocalPropertyString(propertyKey: String): String {
+ return gradleLocalProperties(rootDir).getProperty(propertyKey)
+}
+
plugins {
- alias(libs.plugins.android.library)
- alias(libs.plugins.kotlin.android)
+ id("com.android.library")
+ id("convention.android.compose")
+ id("convention.android.hilt")
+ id("convention.coroutine")
}
android {
- namespace = "com.woosuk.google_login"
- compileSdk = 34
+ namespace = "com.withpeace.withpeace.googlelogin"
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"
+ buildConfigField(
+ "String",
+ "GOOGLE_CLIENT_ID",
+ getLocalPropertyString("GOOGLE_CLIENT_ID"),
+ )
}
}
dependencies {
-
- implementation(libs.androidx.core.ktx)
- implementation(libs.androidx.appcompat)
- implementation(libs.material)
- testImplementation(libs.junit4)
- androidTestImplementation(libs.junit)
- androidTestImplementation(libs.androidx.test.espresso.core)
+ implementation(libs.google.login)
+ implementation(libs.androidx.credentials)
+ implementation(libs.androidx.credentials.play.service)
}
diff --git a/google-login/src/main/AndroidManifest.xml b/google-login/src/main/AndroidManifest.xml
index 8bdb7e14..c45e6dfd 100644
--- a/google-login/src/main/AndroidManifest.xml
+++ b/google-login/src/main/AndroidManifest.xml
@@ -1,4 +1,4 @@
-
+
diff --git a/google-login/src/main/kotlin/com/withpeace/withpeace/googlelogin/GoogleLoginManager.kt b/google-login/src/main/kotlin/com/withpeace/withpeace/googlelogin/GoogleLoginManager.kt
new file mode 100644
index 00000000..5ad52d7b
--- /dev/null
+++ b/google-login/src/main/kotlin/com/withpeace/withpeace/googlelogin/GoogleLoginManager.kt
@@ -0,0 +1,57 @@
+package com.withpeace.withpeace.googlelogin
+
+import android.content.Context
+import androidx.credentials.Credential
+import androidx.credentials.CredentialManager
+import androidx.credentials.CustomCredential
+import androidx.credentials.GetCredentialRequest
+import androidx.credentials.GetCredentialResponse
+import com.google.android.libraries.identity.googleid.GetSignInWithGoogleOption
+import com.google.android.libraries.identity.googleid.GoogleIdTokenCredential
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.launch
+
+class GoogleLoginManager(val context: Context) {
+ private val credentialManager = CredentialManager.create(context)
+
+ private val googleIdOption: GetSignInWithGoogleOption =
+ GetSignInWithGoogleOption.Builder(BuildConfig.GOOGLE_CLIENT_ID)
+ .build()
+
+ private val credentialRequest = GetCredentialRequest(listOf(googleIdOption))
+
+ fun startLogin(
+ coroutineScope: CoroutineScope,
+ onSuccessLogin: (String) -> Unit,
+ onFailLogin: (String?) -> Unit,
+ ) {
+ coroutineScope.launch {
+ runCatching {
+ val result = credentialManager.getCredential(context, credentialRequest)
+ handleSignIn(result, onSuccessLogin, onFailLogin)
+ }
+ }
+ }
+
+ private fun handleSignIn(
+ result: GetCredentialResponse,
+ onSuccessLogin: (String) -> Unit,
+ onFailLogin: (String?) -> Unit,
+ ) {
+ val credential = result.credential
+ if (credential.isCustomAndRightType()) {
+ runCatching {
+ GoogleIdTokenCredential.createFrom(credential.data)
+ }.onSuccess {
+ onSuccessLogin(it.idToken)
+ }.onFailure {
+ onFailLogin(it.toString())
+ }
+ } else {
+ onFailLogin(null)
+ }
+ }
+
+ private fun Credential.isCustomAndRightType() =
+ this is CustomCredential && type == GoogleIdTokenCredential.TYPE_GOOGLE_ID_TOKEN_CREDENTIAL
+}
diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml
index 1683179a..b620c3c7 100644
--- a/gradle/libs.versions.toml
+++ b/gradle/libs.versions.toml
@@ -55,6 +55,10 @@ material = "1.11.0"
material3Android = "1.2.0"
multidex = "2.0.1"
+google-login = "1.1.0"
+
+credential = "1.2.0"
+
[libraries]
android-gradlePlugin = { group = "com.android.tools.build", name = "gradle", version.ref = "androidGradlePlugin" }
android-desugarJdkLibs = { group = "com.android.tools", name = "desugar_jdk_libs", version.ref = "androidDesugarJdkLibs" }
@@ -80,6 +84,9 @@ androidx-compose-ui-testManifest = { group = "androidx.compose.ui", name = "ui-t
androidx-compose-navigation = { group = "androidx.navigation", name = "navigation-compose", version.ref = "androidxComposeNavigation" }
androidx-compose-navigation-test = { group = "androidx.navigation", name = "navigation-testing", version.ref = "androidxComposeNavigation" }
+androidx-credentials = { group = "androidx.credentials", name = "credentials", version.ref = "credential" }
+androidx-credentials-play-service = { group = "androidx.credentials", name = "credentials-play-services-auth", version.ref = "credential" }
+
hilt-core = { group = "com.google.dagger", name = "hilt-core", version.ref = "hilt" }
hilt-compiler = { group = "com.google.dagger", name = "hilt-compiler", version.ref = "hilt" }
@@ -132,6 +139,8 @@ mockk = { group = "io.mockk", name = "mockk", version.ref = "mockk" }
multidex = { group = "androidx.multidex", name = "multidex", version.ref = "multidex" }
+google-login = { group = "com.google.android.libraries.identity.googleid", name = "googleid", version.ref = "google-login" }
+
# verify
verify-detektFormatting = { module = "io.gitlab.arturbosch.detekt:detekt-formatting", version.ref = "detekt" }
diff --git a/settings.gradle.kts b/settings.gradle.kts
index df827c44..f73b4e73 100644
--- a/settings.gradle.kts
+++ b/settings.gradle.kts
@@ -17,6 +17,10 @@ dependencyResolutionManagement {
rootProject.name = "withpeace"
include(":app")
include(":google-login")
+include(":feature:login")
include(":core:network")
include(":core:data")
include(":core:domain")
+include(":core:datastore")
+include(":core:designsystem")
+include(":core:interceptor")