Skip to content
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

Feature/boolti 331 프로필 고도화 UI #335

Merged
merged 8 commits into from
Nov 18, 2024
25 changes: 25 additions & 0 deletions domain/src/main/java/com/nexters/boolti/domain/model/Sns.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
package com.nexters.boolti.domain.model

data class Sns(
val id: String,
val type: SnsType,
val username: String,
) {
enum class SnsType {
INSTAGRAM, YOUTUBE;

companion object {
fun fromString(type: String?): SnsType? = when (type?.trim()?.uppercase()) {
"INSTAGRAM" -> INSTAGRAM
"YOUTUBE" -> YOUTUBE
else -> null
}
}
}
}

val Sns.url: String
get() = when (type) {
Sns.SnsType.INSTAGRAM -> "https://www.instagram.com/$username"
Sns.SnsType.YOUTUBE -> "https://www.youtube.com/@$username"
}
7 changes: 5 additions & 2 deletions domain/src/main/java/com/nexters/boolti/domain/model/User.kt
Original file line number Diff line number Diff line change
Expand Up @@ -6,22 +6,25 @@ sealed interface User {
val userCode: String
val introduction: String
val link: List<Link>
val sns: List<Sns>

class My(
data class My(
val id: String,
override val nickname: String = "",
val email: String = "",
override val photo: String? = null,
override val userCode: String = "",
override val introduction: String = "",
override val sns: List<Sns> = emptyList(),
override val link: List<Link> = emptyList(),
) : User

class Others(
data class Others(
override val nickname: String = "",
override val photo: String? = null,
override val userCode: String = "",
override val introduction: String = "",
override val sns: List<Sns> = emptyList(),
override val link: List<Link> = emptyList(),
) : User
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,138 @@
package com.nexters.boolti.presentation.component

import androidx.annotation.DrawableRes
import androidx.compose.foundation.BorderStroke
import androidx.compose.foundation.background
import androidx.compose.foundation.interaction.MutableInteractionSource
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.BoxScope
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.shape.CircleShape
import androidx.compose.material3.Icon
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Surface
import androidx.compose.runtime.Composable
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.Shape
import androidx.compose.ui.graphics.vector.ImageVector
import androidx.compose.ui.res.vectorResource
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.dp
import com.nexters.boolti.presentation.R
import com.nexters.boolti.presentation.theme.BooltiTheme
import com.nexters.boolti.presentation.theme.Grey30
import com.nexters.boolti.presentation.theme.Grey85

@Composable
fun SelectableIcon(
@DrawableRes iconRes: Int,
selected: Boolean,
modifier: Modifier = Modifier,
shape: Shape = CircleShape,
backgroundColor: Color = Grey85,
iconTint: Color = Grey30,
iconSize: Dp? = 28.dp,
contentPadding: Dp = 10.dp,
contentDescription: String? = null,
onClick: () -> Unit = {},
) {
SelectableIcon(
selected = selected,
modifier = modifier,
shape = shape,
backgroundColor = backgroundColor,
contentPadding = contentPadding,
onClick = onClick,
) {
Icon(
modifier = if (iconSize == null) Modifier.matchParentSize() else Modifier.size(iconSize),
imageVector = ImageVector.vectorResource(iconRes),
tint = iconTint,
contentDescription = contentDescription,
)
}
}

@Composable
fun SelectableIcon(
selected: Boolean,
modifier: Modifier = Modifier,
shape: Shape = CircleShape,
iconTint: Color = Grey30,
backgroundColor: Color = Grey85,
contentPadding: Dp = 10.dp,
onClick: () -> Unit = {},
interactionSource: MutableInteractionSource = remember { MutableInteractionSource() },
icon: @Composable BoxScope.() -> Unit,
) {
Surface(
modifier = modifier,
shape = shape,
border = if (selected) BorderStroke(1.dp, MaterialTheme.colorScheme.primary) else null,
color = backgroundColor,
onClick = onClick,
contentColor = iconTint,
interactionSource = interactionSource,
content = {
Box(
modifier = Modifier.padding(contentPadding),
contentAlignment = Alignment.Center,
) { icon() }
},
)
}

@Preview
@Composable
private fun SelectableIconPreview() {
BooltiTheme {
var selectedSns = remember { mutableStateOf("youtube") }

Row(
modifier = Modifier
.background(MaterialTheme.colorScheme.background)
.padding(20.dp),
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.spacedBy(12.dp),
) {
// Low Level Composable
SelectableIcon(
selected = selectedSns.value == "instagram",
onClick = { selectedSns.value = "instagram" },
) {
Icon(
imageVector = ImageVector.vectorResource(R.drawable.ic_logo_instagram),
tint = Grey30,
contentDescription = null,
)
}

// High Level Composable
// iconSize 가 null 이 아니고 modifier 에 크기를 지정하지 않으면 iconSize + contentPadding 만큼의 크기를 가진다.
SelectableIcon(
selected = selectedSns.value == "youtube",
iconRes = R.drawable.ic_logo_youtube,
iconSize = 40.dp,
onClick = { selectedSns.value = "youtube" },
)

// High Level Composable
// iconSize 가 null 이면 전체 크기에서 contentPadding을 뺀 만큼 차지한다. 이때 modifier에 크기를 지정해야 함
SelectableIcon(
modifier = Modifier.size(80.dp),
selected = selectedSns.value == "youtube",
iconRes = R.drawable.ic_logo_youtube,
iconSize = null,
onClick = { selectedSns.value = "youtube" },
)
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
package com.nexters.boolti.presentation.extension

import androidx.annotation.DrawableRes
import com.nexters.boolti.domain.model.Sns
import com.nexters.boolti.domain.model.Sns.SnsType.INSTAGRAM
import com.nexters.boolti.domain.model.Sns.SnsType.YOUTUBE
import com.nexters.boolti.presentation.R

val Sns.SnsType.label: String
get() = when (this) {
INSTAGRAM -> "instagram"
YOUTUBE -> "youtube"
}

val Sns.SnsType.icon: Int
@DrawableRes
get() = when (this) {
INSTAGRAM -> R.drawable.ic_logo_instagram
YOUTUBE -> R.drawable.ic_logo_youtube
}
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ import com.nexters.boolti.presentation.screen.payment.PaymentCompleteScreen
import com.nexters.boolti.presentation.screen.profile.ProfileScreen
import com.nexters.boolti.presentation.screen.profileedit.link.ProfileLinkEditScreen
import com.nexters.boolti.presentation.screen.profileedit.profile.ProfileEditScreen
import com.nexters.boolti.presentation.screen.profileedit.sns.ProfileSnsEditScreen
import com.nexters.boolti.presentation.screen.qr.HostedShowScreen
import com.nexters.boolti.presentation.screen.qr.QrFullScreen
import com.nexters.boolti.presentation.screen.refund.RefundScreen
Expand All @@ -43,10 +44,10 @@ import com.nexters.boolti.presentation.screen.reservations.ReservationsScreen
import com.nexters.boolti.presentation.screen.showdetail.ShowDetailContentScreen
import com.nexters.boolti.presentation.screen.showdetail.ShowDetailScreen
import com.nexters.boolti.presentation.screen.showdetail.ShowImagesScreen
import com.nexters.boolti.presentation.screen.showregistration.addShowRegistration
import com.nexters.boolti.presentation.screen.signout.SignoutScreen
import com.nexters.boolti.presentation.screen.ticket.detail.TicketDetailScreen
import com.nexters.boolti.presentation.screen.ticketing.TicketingScreen
import com.nexters.boolti.presentation.screen.showregistration.addShowRegistration
import com.nexters.boolti.presentation.theme.BooltiTheme
import com.nexters.boolti.presentation.util.SnackbarController
import com.nexters.boolti.presentation.util.rememberNavControllerWithLog
Expand Down Expand Up @@ -223,6 +224,35 @@ fun MainNavigation(modifier: Modifier, onClickQrScan: (showId: String, showName:
navigateTo = navController::navigate,
popBackStack = navController::popBackStack,
)
ProfileSnsEditScreen(
modifier = modifier,
onAddSns = { type, username ->
navController.previousBackStackEntry
?.savedStateHandle
?.apply {
set("newSnsType", type.name)
set("newSnsUsername", username)
}
navController.popBackStack()
},
onEditSns = { id, type, username ->
navController.previousBackStackEntry
?.savedStateHandle
?.apply {
set("editSnsId", id)
set("editSnsType", type.name)
set("editSnsUsername", username)
}
HamBP marked this conversation as resolved.
Show resolved Hide resolved
navController.popBackStack()
},
onRemoveSns = { id ->
navController.previousBackStackEntry
?.savedStateHandle
?.set("removeSnsId", id)
navController.popBackStack()
},
popBackStack = navController::popBackStack,
)
ProfileLinkEditScreen(
modifier = modifier,
onAddLink = { linkName, url ->
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package com.nexters.boolti.presentation.screen
import androidx.navigation.NavType
import androidx.navigation.navArgument
import com.nexters.boolti.domain.model.Link
import com.nexters.boolti.domain.model.Sns

sealed class MainDestination(val route: String) {
data object Home : MainDestination(route = "home")
Expand Down Expand Up @@ -58,7 +59,8 @@ sealed class MainDestination(val route: String) {
data object Qr : MainDestination(route = "qr")

data object Reservations : MainDestination(route = "reservations")
data object ReservationDetail : MainDestination(route = "reservations/{reservationId}?isGift={isGift}") {
data object ReservationDetail :
MainDestination(route = "reservations/{reservationId}?isGift={isGift}") {
val arguments = listOf(
navArgument("reservationId") { type = NavType.StringType },
navArgument("isGift") { type = NavType.BoolType },
Expand Down Expand Up @@ -103,6 +105,28 @@ sealed class MainDestination(val route: String) {
}

data object ProfileEdit : MainDestination(route = "profileEdit")
data object ProfileSnsEdit :
MainDestination(route = "profileSnsEdit?sns={$snsType}&id={$linkId}&title={$linkTitle}&username={$username}") {
val arguments = listOf(
navArgument(snsType) {
type = NavType.StringType
nullable = true
},
navArgument(linkId) {
type = NavType.StringType
nullable = true
},
navArgument(username) {
type = NavType.StringType
nullable = true
},
)

fun createRoute(): String = "profileSnsEdit"
fun createRoute(sns: Sns): String =
"profileSnsEdit?sns=${sns.type}&id=${sns.id}&username=${sns.username}"
}

data object ProfileLinkEdit :
MainDestination(route = "profileLinkEdit?id={$linkId}&title={$linkTitle}&url={$url}") {
val arguments = listOf(
Expand Down Expand Up @@ -142,4 +166,6 @@ const val isInviteTicket = "isInviteTicket"
const val userCode = "userCode"
const val linkId = "linkId"
const val linkTitle = "linkTitle"
const val url = "url"
const val url = "url"
const val snsType = "snsType"
const val username = "username"
Loading
Loading