-
Notifications
You must be signed in to change notification settings - Fork 0
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Week4 필수과제 구현 #8
base: develop
Are you sure you want to change the base?
Changes from all commits
1aee6ee
a9e3b60
d52a411
1e6859c
289ec59
ab920d5
28737b3
13d2723
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,18 +1,22 @@ | ||
package org.sopt.and | ||
|
||
import kotlinx.serialization.Serializable | ||
|
||
|
||
@Serializable | ||
sealed class Route(val route: String) { | ||
@Serializable | ||
data object Home : Route("home") | ||
|
||
data class SignIn(val email: String = "", val password: String = "") : | ||
Route("signIn?email={email}&password={password}") { | ||
fun createRoute(email: String = "", password: String = "") = | ||
"signIn?email=$email&password=$password" | ||
} | ||
@Serializable | ||
data object SignIn : Route("signIn") | ||
|
||
@Serializable | ||
data object SignUp : Route("signUp") | ||
|
||
@Serializable | ||
data object Search : Route("search") | ||
|
||
data class MyPage(val email: String) : Route("myPage?email={email}") { | ||
fun createRoute(email: String) = "myPage?email=$email" | ||
} | ||
@Serializable | ||
data object MyPage : Route("myPage") | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,32 @@ | ||
// data/ServicePool.kt | ||
package org.sopt.and.data | ||
|
||
import com.jakewharton.retrofit2.converter.kotlinx.serialization.asConverterFactory | ||
import kotlinx.serialization.json.Json | ||
import okhttp3.MediaType.Companion.toMediaType | ||
import okhttp3.OkHttpClient | ||
import okhttp3.logging.HttpLoggingInterceptor | ||
import org.sopt.and.BuildConfig | ||
import org.sopt.and.data.service.AuthService | ||
import retrofit2.Retrofit | ||
|
||
object ServicePool { | ||
private val json = Json { | ||
ignoreUnknownKeys = true | ||
coerceInputValues = true | ||
} | ||
|
||
private val okHttpClient = OkHttpClient.Builder() | ||
.addInterceptor(HttpLoggingInterceptor().apply { | ||
level = HttpLoggingInterceptor.Level.BODY | ||
}) | ||
.build() | ||
|
||
private val retrofit: Retrofit = Retrofit.Builder() | ||
.baseUrl(BuildConfig.BASE_URL) | ||
.addConverterFactory(json.asConverterFactory("application/json".toMediaType())) | ||
.client(okHttpClient) | ||
.build() | ||
|
||
val authService: AuthService by lazy { retrofit.create(AuthService::class.java) } | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,43 @@ | ||
package org.sopt.and.data.local | ||
|
||
import android.content.Context | ||
import androidx.datastore.preferences.core.edit | ||
import androidx.datastore.preferences.core.stringPreferencesKey | ||
import androidx.datastore.preferences.preferencesDataStore | ||
import kotlinx.coroutines.flow.Flow | ||
import kotlinx.coroutines.flow.map | ||
|
||
private val Context.dataStore by preferencesDataStore(name = "auth") | ||
|
||
class AuthLocalDataSource(private val context: Context) { | ||
private val tokenKey = stringPreferencesKey("token") | ||
|
||
suspend fun saveToken(token: String) { | ||
context.dataStore.edit { preferences -> | ||
preferences[tokenKey] = token | ||
} | ||
} | ||
|
||
fun getToken(): Flow<String?> = context.dataStore.data.map { preferences -> | ||
preferences[tokenKey] | ||
} | ||
|
||
suspend fun clearToken() { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 일반 함수와 suspend 함수의 차이는 무엇일까요? |
||
context.dataStore.edit { preferences -> | ||
preferences.remove(tokenKey) | ||
} | ||
} | ||
|
||
companion object { | ||
@Volatile | ||
private var instance: AuthLocalDataSource? = null | ||
|
||
fun getInstance(context: Context): AuthLocalDataSource { | ||
return instance ?: synchronized(this) { | ||
instance ?: AuthLocalDataSource(context.applicationContext).also { | ||
instance = it | ||
} | ||
} | ||
} | ||
} | ||
} |
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. AuthRequest 파일 안에 SignUpRequest, SignInRequest 을 한번데 넣어서 관리하시는군요..! 나중에 유저 인증 관련 기능이 더 복잡해질 걸 생각하면, 이런 관리 방식은 굉장히 효율적인 것 같아요!! |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,22 @@ | ||
package org.sopt.and.data.model.request | ||
|
||
import kotlinx.serialization.SerialName | ||
import kotlinx.serialization.Serializable | ||
|
||
@Serializable | ||
data class SignUpRequest( | ||
@SerialName("username") | ||
val username: String, | ||
@SerialName("password") | ||
val password: String, | ||
@SerialName("hobby") | ||
val hobby: String | ||
) | ||
|
||
@Serializable | ||
data class SignInRequest( | ||
@SerialName("username") | ||
val username: String, | ||
@SerialName("password") | ||
val password: String | ||
) |
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. AuthResponse 안에 응답 관련 클래스도 다 넣어두니 훨씬 깔끔하네요!! |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,30 @@ | ||
package org.sopt.and.data.model.response | ||
|
||
import kotlinx.serialization.SerialName | ||
import kotlinx.serialization.Serializable | ||
|
||
@Serializable | ||
data class BaseResponse<T>( | ||
@SerialName("result") | ||
val result: T? = null, | ||
@SerialName("code") | ||
val code: String? = null | ||
) | ||
Comment on lines
+7
to
+12
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. BaseResponse 쓰는거 좋은데요?? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 명세서에 겹치는 부분은 이렇게 만드는게 좋다고 하더라구요! 그래서 적용해봄쓰 |
||
|
||
@Serializable | ||
data class SignUpResponse( | ||
@SerialName("no") | ||
val no: Int | ||
) | ||
|
||
@Serializable | ||
data class SignInResponse( | ||
@SerialName("token") | ||
val token: String | ||
) | ||
|
||
@Serializable | ||
data class HobbyResponse( | ||
@SerialName("hobby") | ||
val hobby: String | ||
) |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,30 @@ | ||
package org.sopt.and.data.service | ||
|
||
import org.sopt.and.data.model.request.SignInRequest | ||
import org.sopt.and.data.model.request.SignUpRequest | ||
import org.sopt.and.data.model.response.BaseResponse | ||
import org.sopt.and.data.model.response.HobbyResponse | ||
import org.sopt.and.data.model.response.SignInResponse | ||
import org.sopt.and.data.model.response.SignUpResponse | ||
import retrofit2.Response | ||
import retrofit2.http.Body | ||
import retrofit2.http.GET | ||
import retrofit2.http.Header | ||
import retrofit2.http.POST | ||
|
||
interface AuthService { | ||
@POST("user") | ||
suspend fun signUp( | ||
@Body request: SignUpRequest | ||
): Response<BaseResponse<SignUpResponse>> | ||
|
||
@POST("login") | ||
suspend fun signIn( | ||
@Body request: SignInRequest | ||
): Response<BaseResponse<SignInResponse>> | ||
|
||
@GET("user/my-hobby") | ||
suspend fun getMyHobby( | ||
@Header("token") token: String | ||
): Response<BaseResponse<HobbyResponse>> | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,6 +1,7 @@ | ||
package org.sopt.and.domain | ||
|
||
data class User( | ||
var email: String = "", | ||
var password: String = "" | ||
val username: String = "", | ||
val password: String = "", | ||
val hobby: String = "" // hobby 필드 추가 | ||
) |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,55 @@ | ||
// presentation/component/TextInputField.kt | ||
package org.sopt.and.presentation.component | ||
|
||
import androidx.compose.foundation.layout.fillMaxWidth | ||
import androidx.compose.foundation.layout.height | ||
import androidx.compose.foundation.shape.RoundedCornerShape | ||
import androidx.compose.material3.Text | ||
import androidx.compose.material3.TextField | ||
import androidx.compose.material3.TextFieldDefaults | ||
import androidx.compose.runtime.Composable | ||
import androidx.compose.ui.Modifier | ||
import androidx.compose.ui.graphics.Color | ||
import androidx.compose.ui.unit.dp | ||
|
||
@Composable | ||
fun TextInputField( | ||
value: String, | ||
onValueChange: (String) -> Unit, | ||
modifier: Modifier = Modifier, | ||
placeholder: String = "", | ||
isError: Boolean = false, | ||
maxLength: Int = 8, | ||
singleLine: Boolean = true | ||
) { | ||
TextField( | ||
value = value, | ||
onValueChange = { | ||
if (it.length <= maxLength) { | ||
onValueChange(it) | ||
} | ||
}, | ||
modifier = modifier | ||
.fillMaxWidth() | ||
.height(60.dp), | ||
placeholder = { | ||
Text( | ||
text = placeholder, | ||
color = Color.Gray | ||
) | ||
}, | ||
colors = TextFieldDefaults.colors( | ||
focusedContainerColor = Color.DarkGray, | ||
unfocusedContainerColor = Color.DarkGray, | ||
focusedTextColor = Color.White, | ||
unfocusedTextColor = Color.White, | ||
focusedIndicatorColor = if (isError) Color.Red.copy(alpha = 0.5f) else Color.Transparent, | ||
unfocusedIndicatorColor = if (isError) Color.Red.copy(alpha = 0.5f) else Color.Transparent, | ||
errorContainerColor = Color.DarkGray, | ||
errorIndicatorColor = Color.Red.copy(alpha = 0.5f) | ||
), | ||
shape = RoundedCornerShape(5.dp), | ||
singleLine = singleLine, | ||
isError = isError | ||
) | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,6 +1,5 @@ | ||
package org.sopt.and.presentation.main | ||
|
||
import androidx.compose.foundation.layout.PaddingValues | ||
import androidx.compose.foundation.layout.padding | ||
import androidx.compose.material3.Scaffold | ||
import androidx.compose.runtime.Composable | ||
|
@@ -17,26 +16,21 @@ import org.sopt.and.presentation.navigation.NavGraph | |
@Composable | ||
fun MainScreen() { | ||
val navController = rememberNavController() | ||
var bottomNaviVisible by remember { mutableStateOf(false) } | ||
var userEmail by remember { mutableStateOf("") } | ||
var isLoggedIn by remember { mutableStateOf(false) } | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 변수명에서 조금 더 고민을 해보면 좋을 것 같습니다. 그렇기 때문에 |
||
|
||
Scaffold( | ||
bottomBar = { | ||
if (bottomNaviVisible) { | ||
if (isLoggedIn) { | ||
BottomNavigationBar( | ||
navController = navController, | ||
userEmail = userEmail | ||
navController = navController | ||
) | ||
} | ||
} | ||
) { innerPadding: PaddingValues -> | ||
) { innerPadding -> | ||
NavGraph( | ||
navController = navController, | ||
isLogined = { isLogined -> | ||
bottomNaviVisible = isLogined | ||
}, | ||
onEmailUpdated = { email -> | ||
userEmail = email | ||
isLoggedIn = { loggedIn -> | ||
isLoggedIn = loggedIn | ||
}, | ||
modifier = Modifier.padding(innerPadding) | ||
) | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
여기도 버전 카탈로그 적용해주시면 좋을 것 같네요!