Skip to content

Commit

Permalink
Merge pull request #89 from DroidKaigi/feature/86
Browse files Browse the repository at this point in the history
Add empty profilecard module
  • Loading branch information
mikanIchinose authored Jul 4, 2024
2 parents e483b19 + 8302a55 commit 1177eb3
Show file tree
Hide file tree
Showing 12 changed files with 372 additions and 11 deletions.
1 change: 1 addition & 0 deletions app-android/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,7 @@ dependencies {
implementation(projects.feature.contributors)
implementation(projects.feature.sessions)
implementation(projects.feature.eventmap)
implementation(projects.feature.profilecard)
implementation(projects.core.model)
implementation(projects.core.data)
implementation(projects.core.designsystem)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,9 @@ import io.github.droidkaigi.confsched.sessions.sessionScreens
import io.github.droidkaigi.confsched.sessions.timetableScreenRoute
import io.github.droidkaigi.confsched.share.ShareNavigator
import io.github.droidkaigi.confsched.ui.NavHostWithSharedAxisX
import io.github.droidkaigi.confshed.profilecard.navigateProfileCardScreen
import io.github.droidkaigi.confshed.profilecard.profileCardScreen
import io.github.droidkaigi.confshed.profilecard.profileCardScreenRoute
import kotlinx.collections.immutable.PersistentList

@Composable
Expand Down Expand Up @@ -115,6 +118,7 @@ private fun NavGraphBuilder.mainScreen(
onNavigationIconClick = navController::popBackStack,
onEventMapItemClick = externalNavController::navigate,
)
profileCardScreen(contentPadding)
},
)
}
Expand All @@ -125,6 +129,7 @@ class KaigiAppMainNestedGraphStateHolder : MainNestedGraphStateHolder {
override fun routeToTab(route: String): MainScreenTab? {
return when (route) {
timetableScreenRoute -> Timetable
profileCardScreenRoute -> ProfileCard
else -> null
}
}
Expand All @@ -137,7 +142,7 @@ class KaigiAppMainNestedGraphStateHolder : MainNestedGraphStateHolder {
Timetable -> mainNestedNavController.navigateTimetableScreen()
EventMap -> mainNestedNavController.navigateEventMapScreen()
About -> TODO()
ProfileCard -> TODO()
ProfileCard -> mainNestedNavController.navigateProfileCardScreen()
}
}
}
Expand Down
1 change: 1 addition & 0 deletions app-ios-shared/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@ kotlin {
api(projects.feature.sessions)
api(projects.feature.eventmap)
api(projects.feature.contributors)
api(projects.feature.profilecard)
implementation(libs.kotlinxCoroutinesCore)
implementation(libs.skieAnnotation)
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
package io.github.droidkaigi.confsched.model

data class ProfileCard(
val nickname: String,
val occupation: String?,
val link: String?,
val imageUri: String?,
val theme: ProfileCardTheme,
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
package io.github.droidkaigi.confsched.model

enum class ProfileCardTheme {
Default,
}
Original file line number Diff line number Diff line change
Expand Up @@ -137,19 +137,20 @@ enum class MainScreenTab(
contentDescription = MainStrings.EventMap.asString(),
),

@OptIn(ExperimentalResourceApi::class)
ProfileCard(
icon = IconRepresentation.Drawable(drawableId = Res.drawable.icon_achievement_outline),
selectedIcon = IconRepresentation.Drawable(drawableId = Res.drawable.icon_achievement_fill),
label = MainStrings.Achievements.asString(),
contentDescription = MainStrings.Achievements.asString(),
),
About(
icon = IconRepresentation.Vector(Icons.Outlined.Info),
selectedIcon = IconRepresentation.Vector(Icons.Filled.Info),
label = MainStrings.About.asString(),
contentDescription = MainStrings.About.asString(),
),

@OptIn(ExperimentalResourceApi::class)
ProfileCard(
icon = IconRepresentation.Drawable(drawableId = Res.drawable.icon_achievement_outline),
selectedIcon = IconRepresentation.Drawable(drawableId = Res.drawable.icon_achievement_fill),
label = MainStrings.ProfileCard.asString(),
contentDescription = MainStrings.ProfileCard.asString(),
),
}

data class MainScreenUiState(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import io.github.droidkaigi.confsched.designsystem.strings.StringsBindings
sealed class MainStrings : Strings<MainStrings>(Bindings) {
data object Timetable : MainStrings()
data object EventMap : MainStrings()
data object Achievements : MainStrings()
data object ProfileCard : MainStrings()
data object About : MainStrings()
data object Contributors : MainStrings()
class Time(val hours: Int, val minutes: Int) : MainStrings()
Expand All @@ -17,7 +17,7 @@ sealed class MainStrings : Strings<MainStrings>(Bindings) {
when (item) {
Timetable -> "Timetable"
EventMap -> "Event Map"
Achievements -> "Achievements"
ProfileCard -> "Profile Card"
About -> "About"
Contributors -> "Contributors"
is Time -> "${item.hours}${item.minutes}"
Expand All @@ -27,7 +27,7 @@ sealed class MainStrings : Strings<MainStrings>(Bindings) {
when (item) {
Timetable -> "Timetable"
EventMap -> "Event Map"
Achievements -> "Achievements"
ProfileCard -> "Profile Card"
About -> "About"
Contributors -> "Contributors"
is Time -> "${item.hours}:${item.minutes}"
Expand Down
1 change: 1 addition & 0 deletions feature/profilecard/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
/build
29 changes: 29 additions & 0 deletions feature/profilecard/build.gradle.kts
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
plugins {
id("droidkaigi.convention.kmpfeature")
}

android.namespace = "io.github.droidkaigi.confsched.feature.profilecard"
kotlin {
sourceSets {
commonMain {
dependencies {
implementation(projects.core.designsystem)
implementation(projects.core.ui)
implementation(projects.core.model)

implementation(libs.composeNavigation)
implementation(compose.materialIconsExtended)
}
}
androidTarget {
dependencies {
implementation(libs.composeMaterialWindowSize)
}
}
androidUnitTest {
dependencies {
implementation(projects.core.testing)
}
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,238 @@
package io.github.droidkaigi.confshed.profilecard

import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.layout.WindowInsets
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.padding
import androidx.compose.material3.Button
import androidx.compose.material3.CircularProgressIndicator
import androidx.compose.material3.Scaffold
import androidx.compose.material3.SnackbarHost
import androidx.compose.material3.SnackbarHostState
import androidx.compose.material3.Text
import androidx.compose.material3.TextField
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.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalLayoutDirection
import androidx.navigation.NavController
import androidx.navigation.NavGraph.Companion.findStartDestination
import androidx.navigation.NavGraphBuilder
import androidx.navigation.compose.composable
import io.github.droidkaigi.confsched.compose.EventEmitter
import io.github.droidkaigi.confsched.compose.rememberEventEmitter
import io.github.droidkaigi.confsched.model.ProfileCard
import io.github.droidkaigi.confsched.model.ProfileCardTheme
import io.github.droidkaigi.confsched.ui.SnackbarMessageEffect
import io.github.droidkaigi.confsched.ui.UserMessageStateHolder
import io.github.droidkaigi.confshed.profilecard.ProfileCardUiState.Edit

const val profileCardScreenRoute = "profilecard"
internal const val ProfileCardScreenTestTag = "ProfileCardTestTag"

fun NavGraphBuilder.profileCardScreen(
contentPadding: PaddingValues,
) {
composable(profileCardScreenRoute) {
ProfileCardScreen(contentPadding)
}
}

fun NavController.navigateProfileCardScreen() {
navigate(profileCardScreenRoute) {
popUpTo(checkNotNull(graph.findStartDestination().route)) {
saveState = true
}
launchSingleTop = true
restoreState = true
}
}

internal sealed interface ProfileCardUiState {
data class Edit(
val nickname: String,
val occupation: String?,
val link: String?,
val imageUri: String?,
val theme: ProfileCardTheme,
) : ProfileCardUiState {
companion object {
fun initial() = Edit(
nickname = "",
occupation = null,
link = null,
imageUri = null,
theme = ProfileCardTheme.Default,
)
}
}

data class Card(
val nickname: String,
val occupation: String?,
val link: String?,
val imageUri: String?,
val theme: ProfileCardTheme,
) : ProfileCardUiState
}

internal data class ProfileCardScreenUiState(
val isLoading: Boolean,
val contentUiState: ProfileCardUiState,
val userMessageStateHolder: UserMessageStateHolder,
)

internal fun ProfileCard.toUiState() =
ProfileCardUiState.Card(
nickname = nickname,
occupation = occupation,
link = link,
imageUri = imageUri,
theme = theme,
)

@Composable
fun ProfileCardScreen(
contentPadding: PaddingValues,
modifier: Modifier = Modifier,
) {
ProfileCardScreen(
contentPadding = contentPadding,
modifier = modifier,
rememberEventEmitter(),
)
}

@Composable
internal fun ProfileCardScreen(
contentPadding: PaddingValues,
modifier: Modifier = Modifier,
eventEmitter: EventEmitter<ProfileCardScreenEvent> = rememberEventEmitter(),
uiState: ProfileCardScreenUiState = profileCardScreenPresenter(eventEmitter),
) {
val snackbarHostState = remember { SnackbarHostState() }
val layoutDirection = LocalLayoutDirection.current

SnackbarMessageEffect(
snackbarHostState = snackbarHostState,
userMessageStateHolder = uiState.userMessageStateHolder,
)

Scaffold(
modifier = modifier,
snackbarHost = { SnackbarHost(hostState = snackbarHostState) },
contentWindowInsets = WindowInsets(
left = contentPadding.calculateLeftPadding(layoutDirection),
top = contentPadding.calculateTopPadding(),
right = contentPadding.calculateRightPadding(layoutDirection),
bottom = contentPadding.calculateBottomPadding(),
),
) { padding ->
when (val contentUiState = uiState.contentUiState) {
is ProfileCardUiState.Edit -> {
EditScreen(
uiState = contentUiState,
onClickCreate = {
eventEmitter.tryEmit(EditScreenEvent.CreateProfileCard(it))
},
contentPadding = padding,
)
}

is ProfileCardUiState.Card -> {
CardScreen(
uiState = contentUiState,
onClickReset = {
eventEmitter.tryEmit(CardScreenEvent.Reset)
},
contentPadding = padding,
)
}
}
if (uiState.isLoading) {
Box(
contentAlignment = Alignment.Center,
modifier = Modifier.padding(padding).fillMaxSize(),
) {
CircularProgressIndicator()
}
}
}
}

@Composable
internal fun EditScreen(
uiState: Edit,
onClickCreate: (ProfileCard) -> Unit,
modifier: Modifier = Modifier,
contentPadding: PaddingValues = PaddingValues(),
) {
var nickname by remember { mutableStateOf(uiState.nickname) }
var occupation by remember { mutableStateOf(uiState.occupation) }
var link by remember { mutableStateOf(uiState.link) }
var imageUri by remember { mutableStateOf(uiState.imageUri) }

Column(
modifier = modifier.padding(contentPadding),
) {
Text("ProfileCardEdit")
TextField(
value = nickname,
onValueChange = { nickname = it },
placeholder = { Text("Nickname") },
)
TextField(
value = occupation ?: "",
onValueChange = { occupation = it },
placeholder = { Text("Occupation") },
)
TextField(
value = link ?: "",
onValueChange = { link = it },
placeholder = { Text("Link") },
)
Button({
onClickCreate(
ProfileCard(
nickname = nickname,
occupation = occupation,
link = link,
imageUri = imageUri,
theme = uiState.theme,
),
)
}) {
Text("Create")
}
}
}

@Composable
internal fun CardScreen(
uiState: ProfileCardUiState.Card,
onClickReset: () -> Unit,
modifier: Modifier = Modifier,
contentPadding: PaddingValues = PaddingValues(),
) {
Column(
modifier = modifier.padding(contentPadding),
) {
Text("ProfileCard")
Text(uiState.nickname)
if (uiState.occupation != null) {
Text(uiState.occupation)
}
if (uiState.link != null) {
Text(uiState.link)
}
Button(onClickReset) {
Text("Reset")
}
}
}
Loading

0 comments on commit 1177eb3

Please sign in to comment.