diff --git a/app/build.gradle b/app/build.gradle index f1f75682..387b6aca 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -118,6 +118,7 @@ dependencies { implementation libs.glide ksp libs.glide.ksp implementation libs.coil + implementation libs.coil.compose implementation libs.coil.network.okhttp // Firebase diff --git a/app/src/main/java/com/runnect/runnect/presentation/mypage/MyPageFragment.kt b/app/src/main/java/com/runnect/runnect/presentation/mypage/MyPageFragment.kt index 08c0633b..b6b0f781 100644 --- a/app/src/main/java/com/runnect/runnect/presentation/mypage/MyPageFragment.kt +++ b/app/src/main/java/com/runnect/runnect/presentation/mypage/MyPageFragment.kt @@ -1,57 +1,111 @@ package com.runnect.runnect.presentation.mypage import android.app.Activity -import android.app.Activity.RESULT_OK import android.content.Intent import android.os.Bundle +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup import androidx.activity.result.ActivityResultLauncher import androidx.activity.result.contract.ActivityResultContracts -import androidx.core.view.isVisible +import androidx.compose.runtime.collectAsState +import androidx.compose.runtime.getValue +import androidx.compose.ui.platform.ComposeView +import androidx.compose.ui.platform.ViewCompositionStrategy +import androidx.fragment.app.Fragment import androidx.fragment.app.activityViewModels import androidx.fragment.app.commit import androidx.fragment.app.replace -import coil3.load import com.kakao.sdk.common.util.KakaoCustomTabsClient import com.kakao.sdk.talk.TalkApiClient import com.runnect.runnect.BuildConfig import com.runnect.runnect.R -import com.runnect.runnect.binding.BaseVisitorFragment -import com.runnect.runnect.databinding.FragmentMyPageBinding +import com.runnect.runnect.presentation.event.VisitorModeManager +import com.runnect.runnect.presentation.login.LoginActivity import com.runnect.runnect.presentation.mypage.editname.MyPageEditNameActivity import com.runnect.runnect.presentation.mypage.history.MyHistoryActivity import com.runnect.runnect.presentation.mypage.reward.MyRewardActivity import com.runnect.runnect.presentation.mypage.setting.MySettingFragment import com.runnect.runnect.presentation.mypage.upload.MyUploadActivity +import com.runnect.runnect.presentation.ui.theme.RunnectTheme import com.runnect.runnect.util.analytics.Analytics import com.runnect.runnect.util.analytics.EventName.EVENT_CLICK_GOAL_REWARD import com.runnect.runnect.util.analytics.EventName.EVENT_CLICK_RUNNING_RECORD import com.runnect.runnect.util.analytics.EventName.EVENT_CLICK_UPLOADED_COURSE import com.runnect.runnect.util.extension.getStampResId -import com.runnect.runnect.util.extension.repeatOnStarted -import com.runnect.runnect.util.extension.showSnackbar import dagger.hilt.android.AndroidEntryPoint -import kotlinx.coroutines.flow.collectLatest +import javax.inject.Inject @AndroidEntryPoint -class MyPageFragment : BaseVisitorFragment(R.layout.fragment_my_page) { +class MyPageFragment : Fragment() { + @Inject + lateinit var visitorModeManager: VisitorModeManager + private val viewModel: MyPageViewModel by activityViewModels() private lateinit var resultEditNameLauncher: ActivityResultLauncher - override val visitorContainer by lazy { binding.clVisitorMode } - override val contentViews by lazy { listOf(binding.constraintInside) } - - override fun onContentModeInit() { - binding.lifecycleOwner = this@MyPageFragment.viewLifecycleOwner - viewModel.intent(MyPageIntent.LoadUserInfo) - addListener() - addObserver() + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) setResultEditNameLauncher() } + override fun onCreateView( + inflater: LayoutInflater, + container: ViewGroup?, + savedInstanceState: Bundle? + ): View { + return ComposeView(requireContext()).apply { + setViewCompositionStrategy(ViewCompositionStrategy.DisposeOnViewTreeLifecycleDestroyed) + setContent { + RunnectTheme { + if (visitorModeManager.isVisitorMode) { + VisitorModeScreen( + onSignUpClick = { navigateToLogin() } + ) + } else { + val state by viewModel.state.collectAsState() + + val stampResId = if (!state.isLoading) { + getStampResourceId(state.stampId) + } else { + R.drawable.user_profile_basic + } + + MyPageScreen( + state = state.copy(profileImgResId = stampResId), + onEditProfileClick = { navigateToEditName() }, + onHistoryClick = { + Analytics.logClickedItemEvent(EVENT_CLICK_RUNNING_RECORD) + navigateTo() + }, + onRewardClick = { + Analytics.logClickedItemEvent(EVENT_CLICK_GOAL_REWARD) + navigateTo() + }, + onUploadClick = { + Analytics.logClickedItemEvent(EVENT_CLICK_UPLOADED_COURSE) + navigateTo() + }, + onSettingClick = { moveToSettingFragment() }, + onKakaoInquiryClick = { inquiryKakao() } + ) + } + } + } + } + } + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + if (!visitorModeManager.isVisitorMode) { + viewModel.intent(MyPageIntent.LoadUserInfo) + } + } + private fun setResultEditNameLauncher() { resultEditNameLauncher = registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { result -> - if (result.resultCode == RESULT_OK) { + if (result.resultCode == Activity.RESULT_OK) { val name = result.data?.getStringExtra(EXTRA_NICK_NAME) ?: viewModel.currentState.nickname viewModel.intent(MyPageIntent.UpdateNickname(name)) @@ -59,97 +113,41 @@ class MyPageFragment : BaseVisitorFragment(R.layout.fragm } } - private fun addListener() { - with(binding) { - ivMyPageEditFrame.setOnClickListener { - val intent = Intent(requireContext(), MyPageEditNameActivity::class.java) - intent.putExtra(EXTRA_NICK_NAME, viewModel.currentState.nickname) - val stampResId = getStampResourceId() - intent.putExtra(EXTRA_PROFILE, stampResId) - resultEditNameLauncher.launch(intent) - } - - viewMyPageMainRewardFrame.setOnClickListener { - Analytics.logClickedItemEvent(EVENT_CLICK_GOAL_REWARD) - navigateTo() - } - viewMyPageMainHistoryFrame.setOnClickListener { - Analytics.logClickedItemEvent(EVENT_CLICK_RUNNING_RECORD) - navigateTo() - } - - viewMyPageMainUploadFrame.setOnClickListener { - Analytics.logClickedItemEvent(EVENT_CLICK_UPLOADED_COURSE) - navigateTo() - } - viewMyPageMainSettingFrame.setOnClickListener { - moveToSettingFragment() - } - viewMyPageMainKakaoChannelInquiryFrame.setOnClickListener { - inquiryKakao() - } - } + private fun navigateToEditName() { + val intent = Intent(requireContext(), MyPageEditNameActivity::class.java) + intent.putExtra(EXTRA_NICK_NAME, viewModel.currentState.nickname) + val stampResId = getStampResourceId(viewModel.currentState.stampId) + intent.putExtra(EXTRA_PROFILE, stampResId) + resultEditNameLauncher.launch(intent) } private fun moveToSettingFragment() { val bundle = Bundle().apply { putString(ACCOUNT_INFO_TAG, viewModel.currentState.email) } requireActivity().supportFragmentManager.commit { - this.setCustomAnimations(R.anim.slide_in_right, R.anim.slide_out_left) + setCustomAnimations(R.anim.slide_in_right, R.anim.slide_out_left) replace(R.id.fl_main, args = bundle) } } - private fun addObserver() { - repeatOnStarted { - viewModel.state.collectLatest { state -> - bindState(state) - } - } - } - - private fun bindState(state: MyPageUiState) { - setLoadingState(state.isLoading) - - if (!state.isLoading && state.error == null) { - with(binding) { - tvMyPageUserName.text = state.nickname - tvMyPageUserLv.text = state.level - pbMyPageProgress.progress = state.levelPercent - tvMyPageProgressCurrent.text = state.levelPercent.toString() - ivMyPageProfile.load(state.profileImgResId) - } - - val stampResId = getStampResourceId() - viewModel.intent(MyPageIntent.UpdateProfileImg(stampResId)) - } - - state.error?.let { - context?.showSnackbar(anchorView = binding.root, message = it) - } - } - private fun inquiryKakao() { val url = TalkApiClient.instance.channelChatUrl(BuildConfig.KAKAO_CHANNEL_ID) KakaoCustomTabsClient.openWithDefault(requireActivity(), url) } - private fun getStampResourceId(): Int { + private fun navigateToLogin() { + startActivity(Intent(requireContext(), LoginActivity::class.java)) + requireActivity().finish() + } + + private fun getStampResourceId(stampId: String): Int { return requireContext().getStampResId( - stampId = viewModel.currentState.stampId, + stampId = stampId, resNameParam = RES_NAME, resType = RES_STAMP_TYPE, packageName = requireContext().packageName ) } - private fun setLoadingState(isLoading: Boolean) { - with(binding) { - indeterminateBar.isVisible = isLoading - ivMyPageEditFrame.isClickable = !isLoading - viewMyPageMainSettingFrame.isClickable = !isLoading - } - } - private inline fun navigateTo() { startActivity(Intent(requireContext(), T::class.java)) requireActivity().overridePendingTransition( diff --git a/app/src/main/java/com/runnect/runnect/presentation/mypage/MyPageScreen.kt b/app/src/main/java/com/runnect/runnect/presentation/mypage/MyPageScreen.kt new file mode 100644 index 00000000..24b042f0 --- /dev/null +++ b/app/src/main/java/com/runnect/runnect/presentation/mypage/MyPageScreen.kt @@ -0,0 +1,326 @@ +package com.runnect.runnect.presentation.mypage + +import androidx.compose.foundation.Image +import androidx.compose.foundation.background +import androidx.compose.foundation.clickable +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.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.layout.width +import androidx.compose.foundation.rememberScrollState +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.foundation.verticalScroll +import androidx.compose.material3.CircularProgressIndicator +import androidx.compose.material3.HorizontalDivider +import androidx.compose.material3.LinearProgressIndicator +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.Scaffold +import androidx.compose.material3.SnackbarHost +import androidx.compose.material3.SnackbarHostState +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.runtime.LaunchedEffect +import androidx.compose.runtime.remember +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.clip +import androidx.compose.ui.res.painterResource +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.unit.dp +import coil3.compose.AsyncImage +import com.runnect.runnect.R +import com.runnect.runnect.presentation.ui.theme.G1 +import com.runnect.runnect.presentation.ui.theme.G2 +import com.runnect.runnect.presentation.ui.theme.G3 +import com.runnect.runnect.presentation.ui.theme.G4 +import com.runnect.runnect.presentation.ui.theme.M1 +import com.runnect.runnect.presentation.ui.theme.M3 +import com.runnect.runnect.presentation.ui.theme.RunnectTheme + +@Composable +fun MyPageScreen( + state: MyPageUiState, + onEditProfileClick: () -> Unit, + onHistoryClick: () -> Unit, + onRewardClick: () -> Unit, + onUploadClick: () -> Unit, + onSettingClick: () -> Unit, + onKakaoInquiryClick: () -> Unit, +) { + val snackbarHostState = remember { SnackbarHostState() } + + LaunchedEffect(state.error) { + state.error?.let { snackbarHostState.showSnackbar(it) } + } + + Scaffold( + snackbarHost = { SnackbarHost(snackbarHostState) } + ) { paddingValues -> + Column( + modifier = Modifier + .fillMaxSize() + .padding(paddingValues) + ) { + MyPageToolbar() + + if (state.isLoading) { + Box( + modifier = Modifier + .weight(1f) + .fillMaxWidth(), + contentAlignment = Alignment.Center + ) { + CircularProgressIndicator(color = G3) + } + } else { + Column( + modifier = Modifier + .weight(1f) + .verticalScroll(rememberScrollState()) + ) { + ProfileSection( + nickname = state.nickname, + profileImgResId = state.profileImgResId, + onEditClick = onEditProfileClick + ) + LevelProgressSection( + level = state.level, + levelPercent = state.levelPercent + ) + MenuSection( + onHistoryClick = onHistoryClick, + onRewardClick = onRewardClick, + onUploadClick = onUploadClick, + onSettingClick = onSettingClick, + onKakaoInquiryClick = onKakaoInquiryClick + ) + VersionSection() + } + } + } + } +} + +@Composable +private fun MyPageToolbar() { + val textStyle = RunnectTheme.textStyle + Box( + modifier = Modifier + .fillMaxWidth() + .height(56.dp) + .padding(start = 16.dp), + contentAlignment = Alignment.CenterStart + ) { + Text( + text = stringResource(R.string.my_page_title), + style = textStyle.bold20, + color = G1 + ) + } +} + +@Composable +private fun ProfileSection( + nickname: String, + profileImgResId: Int, + onEditClick: () -> Unit +) { + val textStyle = RunnectTheme.textStyle + Row( + modifier = Modifier + .fillMaxWidth() + .height(85.dp) + .padding(horizontal = 23.dp), + verticalAlignment = Alignment.CenterVertically + ) { + AsyncImage( + model = profileImgResId, + contentDescription = null, + modifier = Modifier.size(63.dp) + ) + Spacer(modifier = Modifier.width(10.dp)) + Text( + text = nickname, + style = textStyle.bold17, + color = M1 + ) + Spacer(modifier = Modifier.weight(1f)) + Row( + modifier = Modifier + .clip(RoundedCornerShape(14.dp)) + .clickable(onClick = onEditClick) + .background(MaterialTheme.colorScheme.background) + .padding(horizontal = 11.dp, vertical = 6.dp), + verticalAlignment = Alignment.CenterVertically + ) { + Image( + painter = painterResource(R.drawable.ic_mypage_nickname_edit), + contentDescription = null + ) + Spacer(modifier = Modifier.width(4.dp)) + Text( + text = stringResource(R.string.my_page_edit), + style = textStyle.medium12, + color = M1 + ) + } + } +} + +@Composable +private fun LevelProgressSection( + level: String, + levelPercent: Int +) { + val textStyle = RunnectTheme.textStyle + val clampedPercent = levelPercent.coerceIn(0, 100) + + Column( + modifier = Modifier + .fillMaxWidth() + .background(M3.copy(alpha = 0.6f)) + .padding(horizontal = 22.dp, vertical = 20.dp) + ) { + Row { + Text( + text = stringResource(R.string.my_page_lv_indicator), + style = textStyle.bold15, + color = G1 + ) + Text( + text = level, + style = textStyle.bold15, + color = G1 + ) + } + Spacer(modifier = Modifier.height(6.dp)) + LinearProgressIndicator( + progress = { clampedPercent / 100f }, + modifier = Modifier + .fillMaxWidth() + .height(11.dp) + .clip(RoundedCornerShape(5.dp)), + color = M1, + trackColor = G4, + ) + Spacer(modifier = Modifier.height(10.dp)) + Row( + modifier = Modifier.fillMaxWidth(), + horizontalArrangement = Arrangement.End + ) { + Text( + text = clampedPercent.toString(), + style = textStyle.semiBold13, + color = G1 + ) + Text( + text = stringResource(R.string.my_page_progress_max), + style = textStyle.semiBold13, + color = G2 + ) + } + } +} + +@Composable +private fun MenuSection( + onHistoryClick: () -> Unit, + onRewardClick: () -> Unit, + onUploadClick: () -> Unit, + onSettingClick: () -> Unit, + onKakaoInquiryClick: () -> Unit, +) { + Column { + MenuItem( + title = stringResource(R.string.my_page_history_title), + onClick = onHistoryClick + ) + MenuItem( + title = stringResource(R.string.my_page_reward_title), + onClick = onRewardClick + ) + MenuItem( + title = stringResource(R.string.my_page_upload_title), + onClick = onUploadClick + ) + MenuItem( + title = stringResource(R.string.my_page_setting_title), + onClick = onSettingClick + ) + MenuItem( + title = stringResource(R.string.my_page_kakao_channel_inquiry), + onClick = onKakaoInquiryClick, + showDivider = false + ) + } +} + +@Composable +private fun MenuItem( + title: String, + onClick: () -> Unit, + showDivider: Boolean = true +) { + val textStyle = RunnectTheme.textStyle + Column { + Row( + modifier = Modifier + .fillMaxWidth() + .height(62.dp) + .clickable(onClick = onClick) + .padding(horizontal = 15.dp), + verticalAlignment = Alignment.CenterVertically + ) { + Image( + painter = painterResource(R.drawable.all_star), + contentDescription = null + ) + Spacer(modifier = Modifier.width(8.dp)) + Text( + text = title, + style = textStyle.medium15, + color = G1 + ) + Spacer(modifier = Modifier.weight(1f)) + Image( + painter = painterResource(R.drawable.all_front_arrow), + contentDescription = null, + modifier = Modifier.padding(end = 9.dp) + ) + } + if (showDivider) { + HorizontalDivider(color = G4, thickness = 1.dp) + } + } +} + +@Composable +private fun VersionSection() { + val textStyle = RunnectTheme.textStyle + Row( + modifier = Modifier + .fillMaxWidth() + .height(62.dp) + .padding(horizontal = 16.dp), + verticalAlignment = Alignment.CenterVertically, + horizontalArrangement = Arrangement.SpaceBetween + ) { + Text( + text = stringResource(R.string.my_page_version_title), + style = textStyle.medium15, + color = G2 + ) + Text( + text = stringResource(R.string.my_page_version), + style = textStyle.regular14, + color = G2 + ) + } +} diff --git a/app/src/main/java/com/runnect/runnect/presentation/mypage/VisitorModeScreen.kt b/app/src/main/java/com/runnect/runnect/presentation/mypage/VisitorModeScreen.kt new file mode 100644 index 00000000..eccac390 --- /dev/null +++ b/app/src/main/java/com/runnect/runnect/presentation/mypage/VisitorModeScreen.kt @@ -0,0 +1,68 @@ +package com.runnect.runnect.presentation.mypage + +import androidx.compose.foundation.Image +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Column +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.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.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.res.painterResource +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.text.style.TextAlign +import androidx.compose.ui.unit.dp +import androidx.compose.ui.unit.sp +import com.runnect.runnect.R +import com.runnect.runnect.presentation.ui.theme.G2 +import com.runnect.runnect.presentation.ui.theme.M1 +import com.runnect.runnect.presentation.ui.theme.RunnectTheme +import com.runnect.runnect.presentation.ui.theme.White + +@Composable +fun VisitorModeScreen( + onSignUpClick: () -> Unit +) { + val textStyle = RunnectTheme.textStyle + Column( + modifier = Modifier.fillMaxSize(), + horizontalAlignment = Alignment.CenterHorizontally, + verticalArrangement = Arrangement.Center + ) { + Image( + painter = painterResource(R.drawable.finish_run), + contentDescription = null + ) + Spacer(modifier = Modifier.height(13.dp)) + Text( + text = stringResource(R.string.visitor_mode_mypage_message), + style = textStyle.medium13, + color = G2, + textAlign = TextAlign.Center, + lineHeight = 18.sp + ) + Spacer(modifier = Modifier.height(22.dp)) + Button( + onClick = onSignUpClick, + modifier = Modifier + .fillMaxWidth() + .padding(horizontal = 73.dp) + .height(40.dp), + shape = RoundedCornerShape(10.dp), + colors = ButtonDefaults.buttonColors(containerColor = M1) + ) { + Text( + text = stringResource(R.string.visitor_mode_signup_btn), + style = textStyle.semiBold15, + color = White + ) + } + } +} diff --git a/app/src/main/java/com/runnect/runnect/presentation/ui/theme/Color.kt b/app/src/main/java/com/runnect/runnect/presentation/ui/theme/Color.kt new file mode 100644 index 00000000..f5aed94f --- /dev/null +++ b/app/src/main/java/com/runnect/runnect/presentation/ui/theme/Color.kt @@ -0,0 +1,25 @@ +package com.runnect.runnect.presentation.ui.theme + +import androidx.compose.ui.graphics.Color + +// Primary Brand +val M1 = Color(0xFF593EEC) +val M2 = Color(0xFF7E71FF) +val M3 = Color(0xFFF2F3FF) +val M5 = Color(0xFFD5D4FF) +val M6 = Color(0xFF9C9AFD) +val M7 = Color(0xFFD1C9FF) + +// Grayscale +val G1 = Color(0xFF171717) +val G2 = Color(0xFF8B8B8B) +val G3 = Color(0xFFC1C1C1) +val G4 = Color(0xFFECECEC) +val G5 = Color(0xFFF3F3F3) +val G6 = Color(0xFFD9D9D9) + +// Semantic +val White = Color(0xFFFFFFFF) +val Red = Color(0xFFFF473A) +val Green = Color(0xFF5CFF62) +val Blue = Color(0xFF55B4FF) diff --git a/app/src/main/java/com/runnect/runnect/presentation/ui/theme/Theme.kt b/app/src/main/java/com/runnect/runnect/presentation/ui/theme/Theme.kt new file mode 100644 index 00000000..69ebb83c --- /dev/null +++ b/app/src/main/java/com/runnect/runnect/presentation/ui/theme/Theme.kt @@ -0,0 +1,49 @@ +package com.runnect.runnect.presentation.ui.theme + +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.lightColorScheme +import androidx.compose.runtime.Composable +import androidx.compose.runtime.CompositionLocalProvider +import androidx.compose.runtime.ReadOnlyComposable + +private val RunnectColorScheme = lightColorScheme( + primary = M1, + onPrimary = White, + primaryContainer = M3, + onPrimaryContainer = M1, + secondary = M2, + onSecondary = White, + secondaryContainer = M5, + onSecondaryContainer = M1, + background = White, + onBackground = G1, + surface = White, + onSurface = G1, + surfaceVariant = G5, + onSurfaceVariant = G2, + outline = G3, + outlineVariant = G4, + error = Red, + onError = White, +) + +@Composable +fun RunnectTheme( + content: @Composable () -> Unit +) { + CompositionLocalProvider( + LocalRunnectTextStyles provides RunnectTextStyles() + ) { + MaterialTheme( + colorScheme = RunnectColorScheme, + content = content + ) + } +} + +object RunnectTheme { + val textStyle: RunnectTextStyles + @Composable + @ReadOnlyComposable + get() = LocalRunnectTextStyles.current +} diff --git a/app/src/main/java/com/runnect/runnect/presentation/ui/theme/Type.kt b/app/src/main/java/com/runnect/runnect/presentation/ui/theme/Type.kt new file mode 100644 index 00000000..92d7ebf0 --- /dev/null +++ b/app/src/main/java/com/runnect/runnect/presentation/ui/theme/Type.kt @@ -0,0 +1,104 @@ +package com.runnect.runnect.presentation.ui.theme + +import androidx.compose.runtime.Immutable +import androidx.compose.runtime.staticCompositionLocalOf +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.dp +import androidx.compose.ui.unit.sp +import com.runnect.runnect.R + +val PretendardFontFamily = FontFamily( + Font(R.font.pretendard_thin, FontWeight.Thin), + Font(R.font.pretendard_extralight, FontWeight.ExtraLight), + Font(R.font.pretendard_light, FontWeight.Light), + Font(R.font.pretendard_regular, FontWeight.Normal), + Font(R.font.pretendard_medium, FontWeight.Medium), + Font(R.font.pretendard_semibold, FontWeight.SemiBold), + Font(R.font.pretendard_bold, FontWeight.Bold), + Font(R.font.pretendard_extrabold, FontWeight.ExtraBold), + Font(R.font.pretendard_black, FontWeight.Black), +) + +@Immutable +data class RunnectTextStyles( + val bold28: TextStyle = TextStyle( + fontFamily = PretendardFontFamily, + fontWeight = FontWeight.Bold, + fontSize = 28.dp.value.sp, + ), + val bold22: TextStyle = TextStyle( + fontFamily = PretendardFontFamily, + fontWeight = FontWeight.Bold, + fontSize = 22.dp.value.sp, + ), + val bold20: TextStyle = TextStyle( + fontFamily = PretendardFontFamily, + fontWeight = FontWeight.Bold, + fontSize = 20.dp.value.sp, + ), + val bold17: TextStyle = TextStyle( + fontFamily = PretendardFontFamily, + fontWeight = FontWeight.Bold, + fontSize = 17.dp.value.sp, + ), + val bold15: TextStyle = TextStyle( + fontFamily = PretendardFontFamily, + fontWeight = FontWeight.Bold, + fontSize = 15.dp.value.sp, + ), + val semiBold17: TextStyle = TextStyle( + fontFamily = PretendardFontFamily, + fontWeight = FontWeight.SemiBold, + fontSize = 17.dp.value.sp, + ), + val semiBold15: TextStyle = TextStyle( + fontFamily = PretendardFontFamily, + fontWeight = FontWeight.SemiBold, + fontSize = 15.dp.value.sp, + ), + val semiBold13: TextStyle = TextStyle( + fontFamily = PretendardFontFamily, + fontWeight = FontWeight.SemiBold, + fontSize = 13.dp.value.sp, + ), + val medium15: TextStyle = TextStyle( + fontFamily = PretendardFontFamily, + fontWeight = FontWeight.Medium, + fontSize = 15.dp.value.sp, + ), + val medium14: TextStyle = TextStyle( + fontFamily = PretendardFontFamily, + fontWeight = FontWeight.Medium, + fontSize = 14.dp.value.sp, + ), + val medium13: TextStyle = TextStyle( + fontFamily = PretendardFontFamily, + fontWeight = FontWeight.Medium, + fontSize = 13.dp.value.sp, + ), + val medium12: TextStyle = TextStyle( + fontFamily = PretendardFontFamily, + fontWeight = FontWeight.Medium, + fontSize = 12.dp.value.sp, + ), + val regular16: TextStyle = TextStyle( + fontFamily = PretendardFontFamily, + fontWeight = FontWeight.Normal, + fontSize = 16.dp.value.sp, + ), + val regular14: TextStyle = TextStyle( + fontFamily = PretendardFontFamily, + fontWeight = FontWeight.Normal, + fontSize = 14.dp.value.sp, + ), + val regular12: TextStyle = TextStyle( + fontFamily = PretendardFontFamily, + fontWeight = FontWeight.Normal, + fontSize = 12.dp.value.sp, + ), +) + +val LocalRunnectTextStyles = staticCompositionLocalOf { RunnectTextStyles() } diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 23772504..0af7aaaf 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -138,6 +138,7 @@ kotlinx-coroutines-android = { group = "org.jetbrains.kotlinx", name = "kotlinx- glide = { group = "com.github.bumptech.glide", name = "glide", version.ref = "glide" } glide-ksp = { group = "com.github.bumptech.glide", name = "ksp", version.ref = "glide" } coil = { group = "io.coil-kt.coil3", name = "coil-android", version.ref = "coil" } +coil-compose = { group = "io.coil-kt.coil3", name = "coil-compose", version.ref = "coil" } coil-network-okhttp = { group = "io.coil-kt.coil3", name = "coil-network-okhttp", version.ref = "coil" } # Firebase (BOM 관리 - 개별 버전 불필요)