From 4b925d5433b269cd9252e14f67124d1dc75e2039 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=9C=A4=EC=B0=AC?= Date: Sun, 21 Apr 2024 04:39:42 +0900 Subject: [PATCH 01/11] =?UTF-8?q?Feat:[snsproejct]=20#10=20=EC=95=B1=20?= =?UTF-8?q?=EB=84=A4=EC=9E=84=20=EB=B3=80=EA=B2=BD=20=EB=B0=8F=20coil=20?= =?UTF-8?q?=EB=9D=BC=EC=9D=B4=EB=B8=8C=EB=9F=AC=EB=A6=AC=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/src/main/res/values/strings.xml | 3 --- presentation/build.gradle.kts | 3 +++ 2 files changed, 3 insertions(+), 3 deletions(-) delete mode 100644 app/src/main/res/values/strings.xml diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml deleted file mode 100644 index e3d5841..0000000 --- a/app/src/main/res/values/strings.xml +++ /dev/null @@ -1,3 +0,0 @@ - - SnsProject - \ No newline at end of file diff --git a/presentation/build.gradle.kts b/presentation/build.gradle.kts index 68ac01f..efeebd1 100644 --- a/presentation/build.gradle.kts +++ b/presentation/build.gradle.kts @@ -77,4 +77,7 @@ dependencies { implementation(libs.orbit.viewmodel) implementation(libs.orbit.compose) testImplementation(libs.orbit.test) + + //coil + implementation(libs.coil.compose) } From 647457c6e38d0c361f30a64e8aab69895080e3d9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=9C=A4=EC=B0=AC?= Date: Sun, 21 Apr 2024 04:41:08 +0900 Subject: [PATCH 02/11] =?UTF-8?q?Feat:[snsproject]=20MainActivity=20?= =?UTF-8?q?=EC=9C=84=EC=B9=98=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- presentation/src/main/AndroidManifest.xml | 4 +- .../example/presentation/SplashActivity.kt | 1 + .../example/presentation/ui/theme/Theme.kt | 49 +++++++++---------- 3 files changed, 28 insertions(+), 26 deletions(-) diff --git a/presentation/src/main/AndroidManifest.xml b/presentation/src/main/AndroidManifest.xml index 97720e3..9340c19 100644 --- a/presentation/src/main/AndroidManifest.xml +++ b/presentation/src/main/AndroidManifest.xml @@ -5,7 +5,7 @@ - + @@ -15,6 +15,8 @@ + + diff --git a/presentation/src/main/java/com/example/presentation/SplashActivity.kt b/presentation/src/main/java/com/example/presentation/SplashActivity.kt index 1912f4e..85a4f3a 100644 --- a/presentation/src/main/java/com/example/presentation/SplashActivity.kt +++ b/presentation/src/main/java/com/example/presentation/SplashActivity.kt @@ -6,6 +6,7 @@ import androidx.appcompat.app.AppCompatActivity import androidx.lifecycle.lifecycleScope import com.example.domain.usecase.login.GetTokenUseCase import com.example.presentation.login.LoginActivity +import com.example.presentation.main.MainActivity import dagger.hilt.android.AndroidEntryPoint import javax.inject.Inject import kotlinx.coroutines.launch diff --git a/presentation/src/main/java/com/example/presentation/ui/theme/Theme.kt b/presentation/src/main/java/com/example/presentation/ui/theme/Theme.kt index 0221d4e..fb67b65 100644 --- a/presentation/src/main/java/com/example/presentation/ui/theme/Theme.kt +++ b/presentation/src/main/java/com/example/presentation/ui/theme/Theme.kt @@ -10,6 +10,7 @@ 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.Color import androidx.compose.ui.graphics.toArgb import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.platform.LocalView @@ -26,42 +27,40 @@ private val DarkColorScheme = private val LightColorScheme = lightColorScheme( primary = Purple40, + onPrimary = Purple80, secondary = PurpleGrey40, tertiary = Pink40, background = PurpleGrey80, - /* 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), - */ - ) + surface = Color(0xFFFFFBFE), + onSecondary = Color.White, + onTertiary = Color.White, + onBackground = Color(0xFF1C1B1F), + onSurface = Color(0xFF1C1B1F), + + ) @Composable fun SnsProjectTheme( darkTheme: Boolean = isSystemInDarkTheme(), // Dynamic color is available on Android 12+ - dynamicColor: Boolean = true, +// 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, - ) - } - } - +// dynamicColor && Build.VERSION.SDK_INT >= Build.VERSION_CODES.S -> { +// val context = LocalContext.current +// if (darkTheme) { +// dynamicDarkColorScheme( +// context, +// ) +// } else { +// dynamicLightColorScheme( +// context, +// ) +// } +// } + darkTheme -> DarkColorScheme else -> LightColorScheme } @@ -73,7 +72,7 @@ fun SnsProjectTheme( WindowCompat.getInsetsController(window, view).isAppearanceLightStatusBars = darkTheme } } - + MaterialTheme( colorScheme = colorScheme, typography = Typography, From 688bb5b7750ac3cb21df8da5a2f8b92fcc6f6a1f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=9C=A4=EC=B0=AC?= Date: Sun, 21 Apr 2024 04:42:21 +0900 Subject: [PATCH 03/11] =?UTF-8?q?Feat:[snsproject]=20=EB=A9=94=EC=9D=B8=20?= =?UTF-8?q?=ED=99=94=EB=A9=B4=20=EA=B5=AC=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 탑바에는 앱 이름을, 컨텐트에는 각 루트에 맞는 화면을 구성, 하단 바에는 3개의 탭이 존재(게시글, 게시글 추가 탭, 설정 탭) --- .../presentation/{ => main}/MainActivity.kt | 3 +- .../presentation/main/MainBottomBar.kt | 100 ++++++++++++++++++ .../example/presentation/main/MainNavHost.kt | 48 +++++++++ .../example/presentation/main/MainRoute.kt | 17 +++ 4 files changed, 167 insertions(+), 1 deletion(-) rename presentation/src/main/java/com/example/presentation/{ => main}/MainActivity.kt (87%) create mode 100644 presentation/src/main/java/com/example/presentation/main/MainBottomBar.kt create mode 100644 presentation/src/main/java/com/example/presentation/main/MainNavHost.kt create mode 100644 presentation/src/main/java/com/example/presentation/main/MainRoute.kt diff --git a/presentation/src/main/java/com/example/presentation/MainActivity.kt b/presentation/src/main/java/com/example/presentation/main/MainActivity.kt similarity index 87% rename from presentation/src/main/java/com/example/presentation/MainActivity.kt rename to presentation/src/main/java/com/example/presentation/main/MainActivity.kt index 0ddacc9..ce987cc 100644 --- a/presentation/src/main/java/com/example/presentation/MainActivity.kt +++ b/presentation/src/main/java/com/example/presentation/main/MainActivity.kt @@ -1,4 +1,4 @@ -package com.example.presentation +package com.example.presentation.main import android.os.Bundle import androidx.activity.compose.setContent @@ -12,6 +12,7 @@ class MainActivity : AppCompatActivity() { super.onCreate(savedInstanceState) setContent { SnsProjectTheme { + MainNavHost() } } } diff --git a/presentation/src/main/java/com/example/presentation/main/MainBottomBar.kt b/presentation/src/main/java/com/example/presentation/main/MainBottomBar.kt new file mode 100644 index 0000000..c5e57cb --- /dev/null +++ b/presentation/src/main/java/com/example/presentation/main/MainBottomBar.kt @@ -0,0 +1,100 @@ +package com.example.presentation.main + +import android.content.Intent +import androidx.compose.foundation.background +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.padding +import androidx.compose.material3.HorizontalDivider +import androidx.compose.material3.Icon +import androidx.compose.material3.IconButton +import androidx.compose.material3.MaterialTheme +import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.setValue +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.platform.LocalContext +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.dp +import androidx.navigation.NavController +import androidx.navigation.compose.currentBackStackEntryAsState +import com.example.presentation.main.writing.WritingActivity +import com.example.presentation.ui.theme.SnsProjectTheme + +@Composable +fun MainBottomBar(navController: NavController) { + val context = LocalContext.current + val navBackStackEntry by navController.currentBackStackEntryAsState() + val currentRoute = navBackStackEntry?.destination?.route?.let { currentRoute -> + MainRoute.entries.find { route -> currentRoute==route.route } + } ?: MainRoute.BOARD + + MainBottomBar(currentRoute = currentRoute, onItemClick = {newRoute -> + if(newRoute == MainRoute.WRITING) { + context.startActivity( + Intent(context, WritingActivity::class.java) + ) + }else { + navController.navigate(route = newRoute.route) { + navController.graph.startDestinationRoute?.let { + popUpTo(it) { + saveState = true + } + this.launchSingleTop = true + this.restoreState = true + } + } + } + }) +} + +@Composable +private fun MainBottomBar( + currentRoute: MainRoute, + onItemClick: (MainRoute) -> Unit +) { + Column( + modifier = Modifier.background(MaterialTheme.colorScheme.primary) + ) { + HorizontalDivider() + Row(modifier = Modifier + .fillMaxWidth() + .padding(16.dp), + horizontalArrangement = Arrangement.SpaceBetween) { + MainRoute.entries.forEach { route -> + IconButton(onClick = { onItemClick(route) }) { + Icon( + imageVector = route.icon, + contentDescription = route.contentDescription, + tint = if (currentRoute==route) { + Color.Black + } else { + Color.White + } + ) + } + } + } + } +} + + +@Preview +@Composable +private fun MainBottomBarPreview() { + SnsProjectTheme { + var currentRoute by remember { + mutableStateOf(MainRoute.BOARD) + } + + MainBottomBar( + currentRoute = currentRoute, + onItemClick = { newRoute -> currentRoute = newRoute} + ) + } +} diff --git a/presentation/src/main/java/com/example/presentation/main/MainNavHost.kt b/presentation/src/main/java/com/example/presentation/main/MainNavHost.kt new file mode 100644 index 0000000..d533f8d --- /dev/null +++ b/presentation/src/main/java/com/example/presentation/main/MainNavHost.kt @@ -0,0 +1,48 @@ +package com.example.presentation.main + +import androidx.compose.foundation.layout.padding +import androidx.compose.material3.ExperimentalMaterial3Api +import androidx.compose.material3.Scaffold +import androidx.compose.material3.Surface +import androidx.compose.material3.Text +import androidx.compose.material3.TopAppBar +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import androidx.compose.ui.res.stringResource +import androidx.navigation.compose.NavHost +import androidx.navigation.compose.composable +import androidx.navigation.compose.rememberNavController +import com.example.presentation.R +import com.example.presentation.main.board.BoardScreen +import com.example.presentation.main.setting.SettingScreen + +@OptIn(ExperimentalMaterial3Api::class) +@Composable +fun MainNavHost() { + val navController = rememberNavController() + Surface { + Scaffold( + topBar = { + TopAppBar( + title = { + Text(text = stringResource(id = R.string.app_name)) + }, + ) + }, + content = {paddingValues -> + NavHost(modifier = Modifier.padding(paddingValues), navController = navController, startDestination = MainRoute.BOARD.route){ + composable(route = MainRoute.BOARD.route) { + BoardScreen() + } + composable(route = MainRoute.SETTING.route) { + SettingScreen() + } + } + }, + bottomBar = { + MainBottomBar(navController = navController) + } + ) + } + +} diff --git a/presentation/src/main/java/com/example/presentation/main/MainRoute.kt b/presentation/src/main/java/com/example/presentation/main/MainRoute.kt new file mode 100644 index 0000000..5f120da --- /dev/null +++ b/presentation/src/main/java/com/example/presentation/main/MainRoute.kt @@ -0,0 +1,17 @@ +package com.example.presentation.main + +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.filled.AccountCircle +import androidx.compose.material.icons.filled.AddCircle +import androidx.compose.material.icons.filled.Home +import androidx.compose.ui.graphics.vector.ImageVector + +enum class MainRoute( + val route: String, + val contentDescription: String, + val icon: ImageVector +) { + BOARD(route = "BoardScreen", contentDescription = "글목록", icon = Icons.Default.Home), + WRITING(route = "WritingScreen", contentDescription = "글쓰기", icon = Icons.Default.AddCircle), + SETTING(route = "SettingScreen", contentDescription = "내 정보", icon = Icons.Default.AccountCircle) +} From f10a8d58bf6986a80ded62311712d102e4eeb67e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=9C=A4=EC=B0=AC?= Date: Sun, 21 Apr 2024 04:43:01 +0900 Subject: [PATCH 04/11] =?UTF-8?q?Feat:[snsproejct]=20=EA=B2=8C=EC=8B=9C?= =?UTF-8?q?=EA=B8=80=20=EC=B6=94=EA=B0=80=20=ED=99=94=EB=A9=B4=EC=9D=80=20?= =?UTF-8?q?=EB=94=B0=EB=A1=9C=20=EC=95=A1=ED=8B=B0=EB=B9=84=ED=8B=B0?= =?UTF-8?q?=EB=A1=9C=20=EA=B5=AC=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../presentation/main/writing/WritingActivity.kt | 13 +++++++++++++ presentation/src/main/res/values/strings.xml | 3 +++ 2 files changed, 16 insertions(+) create mode 100644 presentation/src/main/java/com/example/presentation/main/writing/WritingActivity.kt create mode 100644 presentation/src/main/res/values/strings.xml diff --git a/presentation/src/main/java/com/example/presentation/main/writing/WritingActivity.kt b/presentation/src/main/java/com/example/presentation/main/writing/WritingActivity.kt new file mode 100644 index 0000000..572332c --- /dev/null +++ b/presentation/src/main/java/com/example/presentation/main/writing/WritingActivity.kt @@ -0,0 +1,13 @@ +package com.example.presentation.main.writing + +import android.os.Bundle +import androidx.appcompat.app.AppCompatActivity +import dagger.hilt.android.AndroidEntryPoint + +@AndroidEntryPoint +class WritingActivity : AppCompatActivity() { + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + + } +} diff --git a/presentation/src/main/res/values/strings.xml b/presentation/src/main/res/values/strings.xml new file mode 100644 index 0000000..9618eef --- /dev/null +++ b/presentation/src/main/res/values/strings.xml @@ -0,0 +1,3 @@ + + Connected + From f3ce863d46b9ab3dd63829b8e3c2407455af538e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=9C=A4=EC=B0=AC?= Date: Sun, 21 Apr 2024 04:43:46 +0900 Subject: [PATCH 05/11] =?UTF-8?q?Feat:[snsproject]=20Board=20=EB=B0=8F=20S?= =?UTF-8?q?etting=20=ED=99=94=EB=A9=B4=20=EA=B5=AC=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../example/presentation/login/LoginScreen.kt | 2 +- .../presentation/main/board/BoardScreen.kt | 9 ++ .../main/setting/SettingScreen.kt | 141 ++++++++++++++++++ 3 files changed, 151 insertions(+), 1 deletion(-) create mode 100644 presentation/src/main/java/com/example/presentation/main/board/BoardScreen.kt create mode 100644 presentation/src/main/java/com/example/presentation/main/setting/SettingScreen.kt diff --git a/presentation/src/main/java/com/example/presentation/login/LoginScreen.kt b/presentation/src/main/java/com/example/presentation/login/LoginScreen.kt index f4b7b08..aa91c02 100644 --- a/presentation/src/main/java/com/example/presentation/login/LoginScreen.kt +++ b/presentation/src/main/java/com/example/presentation/login/LoginScreen.kt @@ -19,7 +19,7 @@ import androidx.compose.ui.text.input.PasswordVisualTransformation import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp import androidx.hilt.navigation.compose.hiltViewModel -import com.example.presentation.MainActivity +import com.example.presentation.main.MainActivity import com.example.presentation.component.LoginTextField import com.example.presentation.component.SubmitButton import com.example.presentation.ui.theme.SnsProjectTheme diff --git a/presentation/src/main/java/com/example/presentation/main/board/BoardScreen.kt b/presentation/src/main/java/com/example/presentation/main/board/BoardScreen.kt new file mode 100644 index 0000000..b5442de --- /dev/null +++ b/presentation/src/main/java/com/example/presentation/main/board/BoardScreen.kt @@ -0,0 +1,9 @@ +package com.example.presentation.main.board + +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable + +@Composable +fun BoardScreen() { + Text(text = "Board Screen") +} diff --git a/presentation/src/main/java/com/example/presentation/main/setting/SettingScreen.kt b/presentation/src/main/java/com/example/presentation/main/setting/SettingScreen.kt new file mode 100644 index 0000000..f9c73bd --- /dev/null +++ b/presentation/src/main/java/com/example/presentation/main/setting/SettingScreen.kt @@ -0,0 +1,141 @@ +package com.example.presentation.main.setting + +import android.content.Intent +import android.util.Log +import android.widget.Toast +import androidx.compose.foundation.background +import androidx.compose.foundation.border +import androidx.compose.foundation.clickable +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size +import androidx.compose.foundation.shape.CircleShape +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.filled.Settings +import androidx.compose.material3.Button +import androidx.compose.material3.Icon +import androidx.compose.material3.IconButton +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.platform.LocalContext +import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.dp +import androidx.hilt.navigation.compose.hiltViewModel +import com.example.presentation.component.ProfileImage +import com.example.presentation.login.LoginActivity +import com.example.presentation.ui.theme.SnsProjectTheme +import org.orbitmvi.orbit.compose.collectAsState +import org.orbitmvi.orbit.compose.collectSideEffect + +@Composable +fun SettingScreen( + viewModel: SettingViewModel = hiltViewModel() +) { + val state = viewModel.collectAsState().value + val context = LocalContext.current + viewModel.collectSideEffect { sideEffect: SettingSideEffect -> + when (sideEffect) { + is SettingSideEffect.Toast -> { + Log.e("SettingScreen", sideEffect.message) + Toast.makeText(context, sideEffect.message, Toast.LENGTH_SHORT).show() + } + is SettingSideEffect.NavigateToLoginActivity -> { + context.startActivity( + Intent(context, LoginActivity::class.java).apply { + flags = Intent.FLAG_ACTIVITY_CLEAR_TASK or Intent.FLAG_ACTIVITY_NEW_TASK + } + ) + } + } + } + + SettingScreen( + username = state.username, + profileImageUrl = state.profileImageUrl, + onNameChangeClick = { }, + onLogoutClick = viewModel::onLogoutClick , + onImageChangeClick = { } + ) +} + + +@Composable +fun SettingScreen( + username: String, + profileImageUrl: String?, + onImageChangeClick: () -> Unit, + onNameChangeClick: () -> Unit, + onLogoutClick: () -> Unit +) { + Column( + modifier = Modifier.fillMaxSize(), + horizontalAlignment = Alignment.CenterHorizontally, + verticalArrangement = Arrangement.Center + ) { + Box { + ProfileImage( + modifier = Modifier.size(150.dp), + profileImageUrl = profileImageUrl + ) + + IconButton( + modifier = Modifier.align(Alignment.BottomEnd), + onClick = onImageChangeClick + ) { + Box( + modifier = Modifier + .size(30.dp) + .border(width = 1.dp, color = Color.Gray, shape = CircleShape) + .background(color = Color.White, shape = CircleShape) + ) { + Icon( + modifier = Modifier + .align(Alignment.Center) + .size(20.dp), + imageVector = Icons.Default.Settings, + contentDescription = null, + tint = MaterialTheme.colorScheme.primary + ) + } + } + } + Text( + modifier = Modifier + .padding(top = 8.dp) + .clickable { onNameChangeClick() }, + text = username, + style = MaterialTheme.typography.headlineMedium, + fontWeight = FontWeight.Bold, + color = Color.White + ) + Button( + modifier = Modifier.padding(top = 16.dp), + onClick = onLogoutClick + ) { + Text(text = "로그아웃") + } + } +} + + +@Preview +@Composable +private fun SettingScreenPreview() { + SnsProjectTheme { + SettingScreen( + username = "YYYY", + profileImageUrl = null, + onImageChangeClick = {}, + onLogoutClick = {}, + onNameChangeClick = {} + ) + } +} From 669624ced737655a4028f2432fb9243833f8d0c4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=9C=A4=EC=B0=AC?= Date: Sun, 21 Apr 2024 04:44:12 +0900 Subject: [PATCH 06/11] =?UTF-8?q?Feat:[snsproject]=20setting=20=ED=99=94?= =?UTF-8?q?=EB=A9=B4=EC=97=90=EC=84=9C=20=EC=82=AC=EC=9A=A9=ED=95=A0=20?= =?UTF-8?q?=EC=9D=B4=EB=AF=B8=EC=A7=80=20=ED=99=94=EB=A9=B4=20=EA=B5=AC?= =?UTF-8?q?=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../presentation/component/ProfileImage.kt | 60 +++++++++++++++++++ 1 file changed, 60 insertions(+) create mode 100644 presentation/src/main/java/com/example/presentation/component/ProfileImage.kt diff --git a/presentation/src/main/java/com/example/presentation/component/ProfileImage.kt b/presentation/src/main/java/com/example/presentation/component/ProfileImage.kt new file mode 100644 index 0000000..63c86ef --- /dev/null +++ b/presentation/src/main/java/com/example/presentation/component/ProfileImage.kt @@ -0,0 +1,60 @@ +package com.example.presentation.component + +import androidx.compose.foundation.BorderStroke +import androidx.compose.foundation.Image +import androidx.compose.foundation.border +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size +import androidx.compose.foundation.shape.CircleShape +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.filled.Person +import androidx.compose.runtime.Composable +import androidx.compose.runtime.remember +import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.clip +import androidx.compose.ui.graphics.Brush +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.graphics.ColorFilter +import androidx.compose.ui.graphics.vector.rememberVectorPainter +import androidx.compose.ui.layout.ContentScale +import androidx.compose.ui.unit.Dp +import androidx.compose.ui.unit.dp +import coil.compose.AsyncImage +import coil.compose.rememberAsyncImagePainter + +@Composable +fun ProfileImage( + modifier: Modifier, + profileImageUrl: String? = null, + borderWidth: Dp = 4.dp +) { + val rainbowColorsBrush = remember { + Brush.sweepGradient( + listOf( + Color(0xFF9575CD), + Color(0xFFBA68C8), + Color(0xFFE57373), + Color(0xFFFFB74D), + Color(0xFFFFF176), + Color(0xFFAED581), + Color(0xFF4DD0E1), + Color(0xFF9575CD) + ) + ) + } + Image( + painter = profileImageUrl?.let { url -> + rememberAsyncImagePainter(model = url, contentScale = ContentScale.Crop) + } ?: rememberVectorPainter(image = Icons.Default.Person), + contentDescription = "프로필 사진", + colorFilter = if(profileImageUrl == null) ColorFilter.tint(Color.White) else null, + contentScale = ContentScale.Crop, + modifier = modifier + .border( + BorderStroke(borderWidth, rainbowColorsBrush), + CircleShape + ) + .padding(borderWidth) + .clip(CircleShape) + ) +} From 8e2a050d9f8c1b5a3e70cb72776103a4c658c7a3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=9C=A4=EC=B0=AC?= Date: Sun, 21 Apr 2024 04:44:55 +0900 Subject: [PATCH 07/11] =?UTF-8?q?Feat:[snsproject]=20SettingViewModel=20?= =?UTF-8?q?=EA=B5=AC=EC=84=B1=20=EB=B0=8F=20ClearTokenUseCase=20=EB=B0=98?= =?UTF-8?q?=ED=99=98=EA=B0=92=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../data/usecase/ClearTokenUseCaseImpl.kt | 2 +- .../domain/usecase/login/ClearTokenUseCase.kt | 2 +- .../main/setting/SettingViewModel.kt | 63 +++++++++++++++++++ 3 files changed, 65 insertions(+), 2 deletions(-) create mode 100644 presentation/src/main/java/com/example/presentation/main/setting/SettingViewModel.kt diff --git a/data/src/main/java/com/example/data/usecase/ClearTokenUseCaseImpl.kt b/data/src/main/java/com/example/data/usecase/ClearTokenUseCaseImpl.kt index e4f4107..5ea1c54 100644 --- a/data/src/main/java/com/example/data/usecase/ClearTokenUseCaseImpl.kt +++ b/data/src/main/java/com/example/data/usecase/ClearTokenUseCaseImpl.kt @@ -8,7 +8,7 @@ class ClearTokenUseCaseImpl @Inject constructor( private val userDataStore: UserDataStore ) : ClearTokenUseCase { - override suspend fun invoke() { + override suspend fun invoke(): Result = runCatching { userDataStore.clear() } } diff --git a/domain/src/main/java/com/example/domain/usecase/login/ClearTokenUseCase.kt b/domain/src/main/java/com/example/domain/usecase/login/ClearTokenUseCase.kt index f7c03b1..d81d29e 100644 --- a/domain/src/main/java/com/example/domain/usecase/login/ClearTokenUseCase.kt +++ b/domain/src/main/java/com/example/domain/usecase/login/ClearTokenUseCase.kt @@ -1,5 +1,5 @@ package com.example.domain.usecase.login interface ClearTokenUseCase { - suspend operator fun invoke() + suspend operator fun invoke() : Result } diff --git a/presentation/src/main/java/com/example/presentation/main/setting/SettingViewModel.kt b/presentation/src/main/java/com/example/presentation/main/setting/SettingViewModel.kt new file mode 100644 index 0000000..0ee9a6e --- /dev/null +++ b/presentation/src/main/java/com/example/presentation/main/setting/SettingViewModel.kt @@ -0,0 +1,63 @@ +package com.example.presentation.main.setting + +import android.util.Log +import androidx.compose.runtime.Immutable +import androidx.lifecycle.ViewModel +import com.example.domain.usecase.login.ClearTokenUseCase +import com.example.domain.usecase.main.setting.GetMyUserUseCase +import dagger.hilt.android.lifecycle.HiltViewModel +import javax.inject.Inject +import kotlinx.coroutines.CoroutineExceptionHandler +import org.orbitmvi.orbit.Container +import org.orbitmvi.orbit.ContainerHost +import org.orbitmvi.orbit.syntax.simple.intent +import org.orbitmvi.orbit.syntax.simple.postSideEffect +import org.orbitmvi.orbit.syntax.simple.reduce +import org.orbitmvi.orbit.viewmodel.container + +@HiltViewModel +class SettingViewModel @Inject constructor( + private val clearTokenUseCase: ClearTokenUseCase, + private val getMyUserUseCase: GetMyUserUseCase, +) : ViewModel(), ContainerHost { + + override val container: Container = container( + initialState = SettingState(), + buildSettings = { + this.exceptionHandler = CoroutineExceptionHandler { _, throwable -> + intent { postSideEffect(SettingSideEffect.Toast(throwable.message ?: "")) } + } + } + ) + + init { + load() + } + + private fun load() = intent { + val user = getMyUserUseCase().getOrThrow() + Log.e("SettingViewModel", "user : $user" ) + reduce { + state.copy( + profileImageUrl = user.profileImageUrl, + username = user.username + ) + } + } + + fun onLogoutClick() = intent { + clearTokenUseCase().getOrThrow() + postSideEffect(SettingSideEffect.NavigateToLoginActivity) + } +} + +@Immutable +data class SettingState( + val profileImageUrl: String? = null, + val username: String = "" +) + +sealed interface SettingSideEffect { + class Toast(val message: String) : SettingSideEffect + data object NavigateToLoginActivity : SettingSideEffect +} From 873bb9bb3d84735cfba33c7d8dfa0816732441aa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=9C=A4=EC=B0=AC?= Date: Sun, 21 Apr 2024 04:45:26 +0900 Subject: [PATCH 08/11] =?UTF-8?q?Feat:[snsproject]=20domain=20user=20model?= =?UTF-8?q?=20=EB=B0=8F=20userdto=20=EA=B5=AC=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/com/example/data/model/UserDto.kt | 24 +++++++++++++++++++ .../java/com/example/domain/model/User.kt | 8 +++++++ 2 files changed, 32 insertions(+) create mode 100644 data/src/main/java/com/example/data/model/UserDto.kt create mode 100644 domain/src/main/java/com/example/domain/model/User.kt diff --git a/data/src/main/java/com/example/data/model/UserDto.kt b/data/src/main/java/com/example/data/model/UserDto.kt new file mode 100644 index 0000000..ecb39e5 --- /dev/null +++ b/data/src/main/java/com/example/data/model/UserDto.kt @@ -0,0 +1,24 @@ +package com.example.data.model + +import com.example.domain.model.User +import com.google.gson.annotations.SerializedName + +data class UserDto( + @SerializedName("id") + val id: Long, + @SerializedName("loginId") + val loginId: String, + @SerializedName("userName") + val userName: String, + @SerializedName("extraUserInfo") + val extraUserInfo: String, + @SerializedName("profileFilePath") + val profileFilePath: String?, +) { + fun toUser() = User( + id = id, + loginId = loginId, + profileImageUrl = profileFilePath, + username = userName + ) +} diff --git a/domain/src/main/java/com/example/domain/model/User.kt b/domain/src/main/java/com/example/domain/model/User.kt new file mode 100644 index 0000000..c1bf341 --- /dev/null +++ b/domain/src/main/java/com/example/domain/model/User.kt @@ -0,0 +1,8 @@ +package com.example.domain.model + +data class User( + val id: Long, + val loginId: String, + val username: String, + val profileImageUrl: String? = null +) From 99018cf17f3462bb8c306dfee71b32e849237328 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=9C=A4=EC=B0=AC?= Date: Sun, 21 Apr 2024 04:45:55 +0900 Subject: [PATCH 09/11] =?UTF-8?q?Feat:[snsproject]=20=EC=9C=A0=EC=A0=80=20?= =?UTF-8?q?=EC=A0=95=EB=B3=B4=20=ED=99=95=EC=9D=B8=ED=95=98=EB=8A=94=20?= =?UTF-8?q?=EB=84=A4=ED=8A=B8=EC=9B=8C=ED=81=AC=20=EB=B6=80=EB=B6=84=20?= =?UTF-8?q?=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../com/example/data/di/RetrofitModule.kt | 6 +++- .../java/com/example/data/di/UserModule.kt | 5 ++++ .../example/data/retrofit/AddInterceptor.kt | 28 +++++++++++++++++++ .../com/example/data/retrofit/UserService.kt | 6 ++++ 4 files changed, 44 insertions(+), 1 deletion(-) create mode 100644 data/src/main/java/com/example/data/retrofit/AddInterceptor.kt diff --git a/data/src/main/java/com/example/data/di/RetrofitModule.kt b/data/src/main/java/com/example/data/di/RetrofitModule.kt index fac8960..3c2d8d7 100644 --- a/data/src/main/java/com/example/data/di/RetrofitModule.kt +++ b/data/src/main/java/com/example/data/di/RetrofitModule.kt @@ -1,13 +1,16 @@ package com.example.data.di import com.example.data.BuildConfig +import com.example.data.retrofit.AddInterceptor import com.example.data.retrofit.UserService import com.google.gson.GsonBuilder import dagger.Module import dagger.Provides import dagger.hilt.InstallIn import dagger.hilt.components.SingletonComponent +import okhttp3.Interceptor import okhttp3.OkHttpClient +import okhttp3.Request import retrofit2.Retrofit import retrofit2.converter.gson.GsonConverterFactory @@ -16,9 +19,10 @@ import retrofit2.converter.gson.GsonConverterFactory class RetrofitModule { @Provides - fun provideOkHttp(): OkHttpClient { + fun provideOkHttp(interceptor: AddInterceptor): OkHttpClient { return OkHttpClient .Builder() + .addInterceptor(interceptor) .build() } diff --git a/data/src/main/java/com/example/data/di/UserModule.kt b/data/src/main/java/com/example/data/di/UserModule.kt index 5b3ebaa..b912336 100644 --- a/data/src/main/java/com/example/data/di/UserModule.kt +++ b/data/src/main/java/com/example/data/di/UserModule.kt @@ -5,11 +5,13 @@ import com.example.data.usecase.GetTokenUseCaseImpl import com.example.data.usecase.LoginUseCaseImpl import com.example.data.usecase.SetTokenUseCaseImpl import com.example.data.usecase.SignUpUseCaseImpl +import com.example.data.usecase.main.setting.GetMyUserUseCaseImpl import com.example.domain.usecase.login.ClearTokenUseCase import com.example.domain.usecase.login.GetTokenUseCase import com.example.domain.usecase.login.LoginUseCase import com.example.domain.usecase.login.SetTokenUseCase import com.example.domain.usecase.login.SignUpUseCase +import com.example.domain.usecase.main.setting.GetMyUserUseCase import dagger.Binds import dagger.Module import dagger.hilt.InstallIn @@ -33,4 +35,7 @@ abstract class UserModule { @Binds abstract fun bindClearUseCase(clearTokenUseCaseImpl: ClearTokenUseCaseImpl) : ClearTokenUseCase + + @Binds + abstract fun bindGetMyUserUseCase(getMyUserUseCaseImpl: GetMyUserUseCaseImpl) : GetMyUserUseCase } diff --git a/data/src/main/java/com/example/data/retrofit/AddInterceptor.kt b/data/src/main/java/com/example/data/retrofit/AddInterceptor.kt new file mode 100644 index 0000000..7691672 --- /dev/null +++ b/data/src/main/java/com/example/data/retrofit/AddInterceptor.kt @@ -0,0 +1,28 @@ +package com.example.data.retrofit + +import com.example.data.UserDataStore +import javax.inject.Inject +import kotlinx.coroutines.runBlocking +import okhttp3.Interceptor +import okhttp3.Response + +class AddInterceptor @Inject constructor( + private val userDataStore: UserDataStore +) : Interceptor { + + override fun intercept(chain: Interceptor.Chain): Response { + + val token: String? = runBlocking { userDataStore.getToken() } + return chain.proceed( + chain.request() + .newBuilder() + .run { + token?.let { + this.addHeader("Token", token) + } ?: this + } + .addHeader("Content-Type", "application/json; charset=UTF-8") + .build() + ) + } +} diff --git a/data/src/main/java/com/example/data/retrofit/UserService.kt b/data/src/main/java/com/example/data/retrofit/UserService.kt index 39b8cb9..20f86e8 100644 --- a/data/src/main/java/com/example/data/retrofit/UserService.kt +++ b/data/src/main/java/com/example/data/retrofit/UserService.kt @@ -1,8 +1,10 @@ package com.example.data.retrofit import com.example.data.model.CommonResponse +import com.example.data.model.UserDto import okhttp3.RequestBody import retrofit2.http.Body +import retrofit2.http.GET import retrofit2.http.Headers import retrofit2.http.POST @@ -18,4 +20,8 @@ interface UserService { suspend fun signUp( @Body requestBody: RequestBody ): CommonResponse + + @GET("users/my-page") + @Headers("Content-Type:application/json; charset=UTF-8") + suspend fun myPage(): CommonResponse } From acaaea3d127ce31ac6dd2f0bafde83407ed1f6e4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=9C=A4=EC=B0=AC?= Date: Sun, 21 Apr 2024 04:46:17 +0900 Subject: [PATCH 10/11] =?UTF-8?q?Feat:[snsproject]=20=EC=9C=A0=EC=A0=80=20?= =?UTF-8?q?=ED=99=95=EC=9D=B8=20=EC=A0=95=EB=B3=B4=EB=A5=BC=20=EB=B0=9B?= =?UTF-8?q?=EC=95=84=EC=98=A4=EB=8A=94=20UseCase=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../main/setting/GetMyUserUseCaseImpl.kt | 17 +++++++++++++++++ .../usecase/main/setting/GetMyUserUseCase.kt | 7 +++++++ 2 files changed, 24 insertions(+) create mode 100644 data/src/main/java/com/example/data/usecase/main/setting/GetMyUserUseCaseImpl.kt create mode 100644 domain/src/main/java/com/example/domain/usecase/main/setting/GetMyUserUseCase.kt diff --git a/data/src/main/java/com/example/data/usecase/main/setting/GetMyUserUseCaseImpl.kt b/data/src/main/java/com/example/data/usecase/main/setting/GetMyUserUseCaseImpl.kt new file mode 100644 index 0000000..f8ded63 --- /dev/null +++ b/data/src/main/java/com/example/data/usecase/main/setting/GetMyUserUseCaseImpl.kt @@ -0,0 +1,17 @@ +package com.example.data.usecase.main.setting + +import android.util.Log +import com.example.data.retrofit.UserService +import com.example.domain.model.User +import com.example.domain.usecase.main.setting.GetMyUserUseCase +import javax.inject.Inject + +class GetMyUserUseCaseImpl @Inject constructor( + private val userService: UserService +) : GetMyUserUseCase { + override suspend fun invoke(): Result = runCatching { + val userDto = userService.myPage().data + Log.e("GetMyUserUseCaseImpl", "userDto: $userDto") + userDto.toUser() + } +} diff --git a/domain/src/main/java/com/example/domain/usecase/main/setting/GetMyUserUseCase.kt b/domain/src/main/java/com/example/domain/usecase/main/setting/GetMyUserUseCase.kt new file mode 100644 index 0000000..d7bf0b4 --- /dev/null +++ b/domain/src/main/java/com/example/domain/usecase/main/setting/GetMyUserUseCase.kt @@ -0,0 +1,7 @@ +package com.example.domain.usecase.main.setting + +import com.example.domain.model.User + +interface GetMyUserUseCase { + suspend operator fun invoke() : Result +} From 86b7f65b88e8c891537a53bc3db61a8e4016b41f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=9C=A4=EC=B0=AC?= Date: Sun, 21 Apr 2024 04:47:01 +0900 Subject: [PATCH 11/11] Fix:[snsproeject] ktlintFormat --- presentation/build.gradle.kts | 4 +- .../presentation/component/ProfileImage.kt | 55 ++++++++------- .../example/presentation/login/LoginScreen.kt | 2 +- .../presentation/main/MainBottomBar.kt | 48 +++++++------ .../example/presentation/main/MainNavHost.kt | 7 +- .../example/presentation/main/MainRoute.kt | 8 +-- .../main/setting/SettingScreen.kt | 55 +++++++-------- .../main/setting/SettingViewModel.kt | 70 ++++++++++--------- .../main/writing/WritingActivity.kt | 1 - .../example/presentation/ui/theme/Theme.kt | 11 +-- 10 files changed, 132 insertions(+), 129 deletions(-) diff --git a/presentation/build.gradle.kts b/presentation/build.gradle.kts index efeebd1..81529af 100644 --- a/presentation/build.gradle.kts +++ b/presentation/build.gradle.kts @@ -77,7 +77,7 @@ dependencies { implementation(libs.orbit.viewmodel) implementation(libs.orbit.compose) testImplementation(libs.orbit.test) - - //coil + + // coil implementation(libs.coil.compose) } diff --git a/presentation/src/main/java/com/example/presentation/component/ProfileImage.kt b/presentation/src/main/java/com/example/presentation/component/ProfileImage.kt index 63c86ef..2076b47 100644 --- a/presentation/src/main/java/com/example/presentation/component/ProfileImage.kt +++ b/presentation/src/main/java/com/example/presentation/component/ProfileImage.kt @@ -4,7 +4,6 @@ import androidx.compose.foundation.BorderStroke import androidx.compose.foundation.Image import androidx.compose.foundation.border import androidx.compose.foundation.layout.padding -import androidx.compose.foundation.layout.size import androidx.compose.foundation.shape.CircleShape import androidx.compose.material.icons.Icons import androidx.compose.material.icons.filled.Person @@ -19,42 +18,44 @@ import androidx.compose.ui.graphics.vector.rememberVectorPainter import androidx.compose.ui.layout.ContentScale import androidx.compose.ui.unit.Dp import androidx.compose.ui.unit.dp -import coil.compose.AsyncImage import coil.compose.rememberAsyncImagePainter @Composable fun ProfileImage( modifier: Modifier, profileImageUrl: String? = null, - borderWidth: Dp = 4.dp + borderWidth: Dp = 4.dp, ) { - val rainbowColorsBrush = remember { - Brush.sweepGradient( - listOf( - Color(0xFF9575CD), - Color(0xFFBA68C8), - Color(0xFFE57373), - Color(0xFFFFB74D), - Color(0xFFFFF176), - Color(0xFFAED581), - Color(0xFF4DD0E1), - Color(0xFF9575CD) + val rainbowColorsBrush = + remember { + Brush.sweepGradient( + listOf( + Color(0xFF9575CD), + Color(0xFFBA68C8), + Color(0xFFE57373), + Color(0xFFFFB74D), + Color(0xFFFFF176), + Color(0xFFAED581), + Color(0xFF4DD0E1), + Color(0xFF9575CD), + ), ) - ) - } + } Image( - painter = profileImageUrl?.let { url -> - rememberAsyncImagePainter(model = url, contentScale = ContentScale.Crop) - } ?: rememberVectorPainter(image = Icons.Default.Person), + painter = + profileImageUrl?.let { url -> + rememberAsyncImagePainter(model = url, contentScale = ContentScale.Crop) + } ?: rememberVectorPainter(image = Icons.Default.Person), contentDescription = "프로필 사진", - colorFilter = if(profileImageUrl == null) ColorFilter.tint(Color.White) else null, + colorFilter = if (profileImageUrl == null) ColorFilter.tint(Color.White) else null, contentScale = ContentScale.Crop, - modifier = modifier - .border( - BorderStroke(borderWidth, rainbowColorsBrush), - CircleShape - ) - .padding(borderWidth) - .clip(CircleShape) + modifier = + modifier + .border( + BorderStroke(borderWidth, rainbowColorsBrush), + CircleShape, + ) + .padding(borderWidth) + .clip(CircleShape), ) } diff --git a/presentation/src/main/java/com/example/presentation/login/LoginScreen.kt b/presentation/src/main/java/com/example/presentation/login/LoginScreen.kt index aa91c02..19ee0f6 100644 --- a/presentation/src/main/java/com/example/presentation/login/LoginScreen.kt +++ b/presentation/src/main/java/com/example/presentation/login/LoginScreen.kt @@ -19,9 +19,9 @@ import androidx.compose.ui.text.input.PasswordVisualTransformation import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp import androidx.hilt.navigation.compose.hiltViewModel -import com.example.presentation.main.MainActivity import com.example.presentation.component.LoginTextField import com.example.presentation.component.SubmitButton +import com.example.presentation.main.MainActivity import com.example.presentation.ui.theme.SnsProjectTheme import org.orbitmvi.orbit.compose.collectAsState import org.orbitmvi.orbit.compose.collectSideEffect diff --git a/presentation/src/main/java/com/example/presentation/main/MainBottomBar.kt b/presentation/src/main/java/com/example/presentation/main/MainBottomBar.kt index c5e57cb..0716e10 100644 --- a/presentation/src/main/java/com/example/presentation/main/MainBottomBar.kt +++ b/presentation/src/main/java/com/example/presentation/main/MainBottomBar.kt @@ -30,16 +30,17 @@ import com.example.presentation.ui.theme.SnsProjectTheme fun MainBottomBar(navController: NavController) { val context = LocalContext.current val navBackStackEntry by navController.currentBackStackEntryAsState() - val currentRoute = navBackStackEntry?.destination?.route?.let { currentRoute -> - MainRoute.entries.find { route -> currentRoute==route.route } - } ?: MainRoute.BOARD - - MainBottomBar(currentRoute = currentRoute, onItemClick = {newRoute -> - if(newRoute == MainRoute.WRITING) { + val currentRoute = + navBackStackEntry?.destination?.route?.let { currentRoute -> + MainRoute.entries.find { route -> currentRoute == route.route } + } ?: MainRoute.BOARD + + MainBottomBar(currentRoute = currentRoute, onItemClick = { newRoute -> + if (newRoute == MainRoute.WRITING) { context.startActivity( - Intent(context, WritingActivity::class.java) + Intent(context, WritingActivity::class.java), ) - }else { + } else { navController.navigate(route = newRoute.route) { navController.graph.startDestinationRoute?.let { popUpTo(it) { @@ -56,26 +57,30 @@ fun MainBottomBar(navController: NavController) { @Composable private fun MainBottomBar( currentRoute: MainRoute, - onItemClick: (MainRoute) -> Unit + onItemClick: (MainRoute) -> Unit, ) { Column( - modifier = Modifier.background(MaterialTheme.colorScheme.primary) + modifier = Modifier.background(MaterialTheme.colorScheme.primary), ) { HorizontalDivider() - Row(modifier = Modifier - .fillMaxWidth() - .padding(16.dp), - horizontalArrangement = Arrangement.SpaceBetween) { + Row( + modifier = + Modifier + .fillMaxWidth() + .padding(16.dp), + horizontalArrangement = Arrangement.SpaceBetween, + ) { MainRoute.entries.forEach { route -> IconButton(onClick = { onItemClick(route) }) { Icon( imageVector = route.icon, contentDescription = route.contentDescription, - tint = if (currentRoute==route) { - Color.Black - } else { - Color.White - } + tint = + if (currentRoute == route) { + Color.Black + } else { + Color.White + }, ) } } @@ -83,7 +88,6 @@ private fun MainBottomBar( } } - @Preview @Composable private fun MainBottomBarPreview() { @@ -91,10 +95,10 @@ private fun MainBottomBarPreview() { var currentRoute by remember { mutableStateOf(MainRoute.BOARD) } - + MainBottomBar( currentRoute = currentRoute, - onItemClick = { newRoute -> currentRoute = newRoute} + onItemClick = { newRoute -> currentRoute = newRoute }, ) } } diff --git a/presentation/src/main/java/com/example/presentation/main/MainNavHost.kt b/presentation/src/main/java/com/example/presentation/main/MainNavHost.kt index d533f8d..1c9b9ab 100644 --- a/presentation/src/main/java/com/example/presentation/main/MainNavHost.kt +++ b/presentation/src/main/java/com/example/presentation/main/MainNavHost.kt @@ -29,8 +29,8 @@ fun MainNavHost() { }, ) }, - content = {paddingValues -> - NavHost(modifier = Modifier.padding(paddingValues), navController = navController, startDestination = MainRoute.BOARD.route){ + content = { paddingValues -> + NavHost(modifier = Modifier.padding(paddingValues), navController = navController, startDestination = MainRoute.BOARD.route) { composable(route = MainRoute.BOARD.route) { BoardScreen() } @@ -41,8 +41,7 @@ fun MainNavHost() { }, bottomBar = { MainBottomBar(navController = navController) - } + }, ) } - } diff --git a/presentation/src/main/java/com/example/presentation/main/MainRoute.kt b/presentation/src/main/java/com/example/presentation/main/MainRoute.kt index 5f120da..efa3991 100644 --- a/presentation/src/main/java/com/example/presentation/main/MainRoute.kt +++ b/presentation/src/main/java/com/example/presentation/main/MainRoute.kt @@ -9,9 +9,9 @@ import androidx.compose.ui.graphics.vector.ImageVector enum class MainRoute( val route: String, val contentDescription: String, - val icon: ImageVector + val icon: ImageVector, ) { - BOARD(route = "BoardScreen", contentDescription = "글목록", icon = Icons.Default.Home), - WRITING(route = "WritingScreen", contentDescription = "글쓰기", icon = Icons.Default.AddCircle), - SETTING(route = "SettingScreen", contentDescription = "내 정보", icon = Icons.Default.AccountCircle) + BOARD(route = "BoardScreen", contentDescription = "글목록", icon = Icons.Default.Home), + WRITING(route = "WritingScreen", contentDescription = "글쓰기", icon = Icons.Default.AddCircle), + SETTING(route = "SettingScreen", contentDescription = "내 정보", icon = Icons.Default.AccountCircle), } diff --git a/presentation/src/main/java/com/example/presentation/main/setting/SettingScreen.kt b/presentation/src/main/java/com/example/presentation/main/setting/SettingScreen.kt index f9c73bd..96d6b8c 100644 --- a/presentation/src/main/java/com/example/presentation/main/setting/SettingScreen.kt +++ b/presentation/src/main/java/com/example/presentation/main/setting/SettingScreen.kt @@ -36,9 +36,7 @@ import org.orbitmvi.orbit.compose.collectAsState import org.orbitmvi.orbit.compose.collectSideEffect @Composable -fun SettingScreen( - viewModel: SettingViewModel = hiltViewModel() -) { +fun SettingScreen(viewModel: SettingViewModel = hiltViewModel()) { val state = viewModel.collectAsState().value val context = LocalContext.current viewModel.collectSideEffect { sideEffect: SettingSideEffect -> @@ -51,81 +49,82 @@ fun SettingScreen( context.startActivity( Intent(context, LoginActivity::class.java).apply { flags = Intent.FLAG_ACTIVITY_CLEAR_TASK or Intent.FLAG_ACTIVITY_NEW_TASK - } + }, ) } } } - + SettingScreen( username = state.username, profileImageUrl = state.profileImageUrl, onNameChangeClick = { }, - onLogoutClick = viewModel::onLogoutClick , - onImageChangeClick = { } + onLogoutClick = viewModel::onLogoutClick, + onImageChangeClick = { }, ) } - @Composable fun SettingScreen( username: String, profileImageUrl: String?, onImageChangeClick: () -> Unit, onNameChangeClick: () -> Unit, - onLogoutClick: () -> Unit + onLogoutClick: () -> Unit, ) { Column( modifier = Modifier.fillMaxSize(), horizontalAlignment = Alignment.CenterHorizontally, - verticalArrangement = Arrangement.Center + verticalArrangement = Arrangement.Center, ) { Box { ProfileImage( modifier = Modifier.size(150.dp), - profileImageUrl = profileImageUrl + profileImageUrl = profileImageUrl, ) - + IconButton( modifier = Modifier.align(Alignment.BottomEnd), - onClick = onImageChangeClick + onClick = onImageChangeClick, ) { Box( - modifier = Modifier - .size(30.dp) - .border(width = 1.dp, color = Color.Gray, shape = CircleShape) - .background(color = Color.White, shape = CircleShape) + modifier = + Modifier + .size(30.dp) + .border(width = 1.dp, color = Color.Gray, shape = CircleShape) + .background(color = Color.White, shape = CircleShape), ) { Icon( - modifier = Modifier - .align(Alignment.Center) - .size(20.dp), + modifier = + Modifier + .align(Alignment.Center) + .size(20.dp), imageVector = Icons.Default.Settings, contentDescription = null, - tint = MaterialTheme.colorScheme.primary + tint = MaterialTheme.colorScheme.primary, ) } } } Text( - modifier = Modifier - .padding(top = 8.dp) - .clickable { onNameChangeClick() }, + modifier = + Modifier + .padding(top = 8.dp) + .clickable { onNameChangeClick() }, text = username, style = MaterialTheme.typography.headlineMedium, fontWeight = FontWeight.Bold, - color = Color.White + color = Color.White, ) Button( modifier = Modifier.padding(top = 16.dp), - onClick = onLogoutClick + onClick = onLogoutClick, ) { Text(text = "로그아웃") } } } - @Preview @Composable private fun SettingScreenPreview() { @@ -135,7 +134,7 @@ private fun SettingScreenPreview() { profileImageUrl = null, onImageChangeClick = {}, onLogoutClick = {}, - onNameChangeClick = {} + onNameChangeClick = {}, ) } } diff --git a/presentation/src/main/java/com/example/presentation/main/setting/SettingViewModel.kt b/presentation/src/main/java/com/example/presentation/main/setting/SettingViewModel.kt index 0ee9a6e..9e2b42d 100644 --- a/presentation/src/main/java/com/example/presentation/main/setting/SettingViewModel.kt +++ b/presentation/src/main/java/com/example/presentation/main/setting/SettingViewModel.kt @@ -16,48 +16,54 @@ import org.orbitmvi.orbit.syntax.simple.reduce import org.orbitmvi.orbit.viewmodel.container @HiltViewModel -class SettingViewModel @Inject constructor( - private val clearTokenUseCase: ClearTokenUseCase, - private val getMyUserUseCase: GetMyUserUseCase, -) : ViewModel(), ContainerHost { - - override val container: Container = container( - initialState = SettingState(), - buildSettings = { - this.exceptionHandler = CoroutineExceptionHandler { _, throwable -> - intent { postSideEffect(SettingSideEffect.Toast(throwable.message ?: "")) } - } - } - ) - - init { - load() - } - - private fun load() = intent { - val user = getMyUserUseCase().getOrThrow() - Log.e("SettingViewModel", "user : $user" ) - reduce { - state.copy( - profileImageUrl = user.profileImageUrl, - username = user.username +class SettingViewModel + @Inject + constructor( + private val clearTokenUseCase: ClearTokenUseCase, + private val getMyUserUseCase: GetMyUserUseCase, + ) : ViewModel(), ContainerHost { + override val container: Container = + container( + initialState = SettingState(), + buildSettings = { + this.exceptionHandler = + CoroutineExceptionHandler { _, throwable -> + intent { postSideEffect(SettingSideEffect.Toast(throwable.message ?: "")) } + } + }, ) + + init { + load() } + + private fun load() = + intent { + val user = getMyUserUseCase().getOrThrow() + Log.e("SettingViewModel", "user : $user") + reduce { + state.copy( + profileImageUrl = user.profileImageUrl, + username = user.username, + ) + } + } + + fun onLogoutClick() = + intent { + clearTokenUseCase().getOrThrow() + postSideEffect(SettingSideEffect.NavigateToLoginActivity) + } } - - fun onLogoutClick() = intent { - clearTokenUseCase().getOrThrow() - postSideEffect(SettingSideEffect.NavigateToLoginActivity) - } -} @Immutable data class SettingState( val profileImageUrl: String? = null, - val username: String = "" + val username: String = "", ) sealed interface SettingSideEffect { class Toast(val message: String) : SettingSideEffect + data object NavigateToLoginActivity : SettingSideEffect } diff --git a/presentation/src/main/java/com/example/presentation/main/writing/WritingActivity.kt b/presentation/src/main/java/com/example/presentation/main/writing/WritingActivity.kt index 572332c..7d53f88 100644 --- a/presentation/src/main/java/com/example/presentation/main/writing/WritingActivity.kt +++ b/presentation/src/main/java/com/example/presentation/main/writing/WritingActivity.kt @@ -8,6 +8,5 @@ import dagger.hilt.android.AndroidEntryPoint class WritingActivity : AppCompatActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) - } } diff --git a/presentation/src/main/java/com/example/presentation/ui/theme/Theme.kt b/presentation/src/main/java/com/example/presentation/ui/theme/Theme.kt index fb67b65..561b7e0 100644 --- a/presentation/src/main/java/com/example/presentation/ui/theme/Theme.kt +++ b/presentation/src/main/java/com/example/presentation/ui/theme/Theme.kt @@ -1,18 +1,14 @@ package com.example.presentation.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.Color import androidx.compose.ui.graphics.toArgb -import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.platform.LocalView import androidx.core.view.WindowCompat @@ -36,8 +32,7 @@ private val LightColorScheme = onTertiary = Color.White, onBackground = Color(0xFF1C1B1F), onSurface = Color(0xFF1C1B1F), - - ) + ) @Composable fun SnsProjectTheme( @@ -60,7 +55,7 @@ fun SnsProjectTheme( // ) // } // } - + darkTheme -> DarkColorScheme else -> LightColorScheme } @@ -72,7 +67,7 @@ fun SnsProjectTheme( WindowCompat.getInsetsController(window, view).isAppearanceLightStatusBars = darkTheme } } - + MaterialTheme( colorScheme = colorScheme, typography = Typography,