diff --git a/composeApp/build.gradle.kts b/composeApp/build.gradle.kts index 3fc144b..9c8c42f 100644 --- a/composeApp/build.gradle.kts +++ b/composeApp/build.gradle.kts @@ -65,6 +65,7 @@ kotlin { implementation(libs.coil.network) implementation(libs.coil.compose) + implementation(libs.coil.svg) } appleMain.dependencies { diff --git a/composeApp/src/commonMain/composeResources/values/strings.xml b/composeApp/src/commonMain/composeResources/values/strings.xml index 3e3528f..4957c15 100644 --- a/composeApp/src/commonMain/composeResources/values/strings.xml +++ b/composeApp/src/commonMain/composeResources/values/strings.xml @@ -16,5 +16,11 @@ Evento registrado con éxito Ocurrió un error al registrar el evento Añadir al calendario + + + + Organizers + Ex Organizers + \ No newline at end of file diff --git a/composeApp/src/commonMain/kotlin/App.kt b/composeApp/src/commonMain/kotlin/App.kt index c72108f..c9e2bfd 100644 --- a/composeApp/src/commonMain/kotlin/App.kt +++ b/composeApp/src/commonMain/kotlin/App.kt @@ -14,6 +14,7 @@ import androidx.navigation.navArgument import core.ui.AdpDestination import core.ui.AdpTheme import core.ui.components.AdpBottomNavigationBar +import features.community.CommunityDetailScreen import features.eventDetail.EventDetailScreen import features.main.HomeRoute @@ -32,9 +33,9 @@ fun App() { } }, onLiveEventTap = { - navController.navigate(AdpDestination.LiveEvent.route) { + /*navController.navigate(AdpDestination.LiveEvent.route) { this.launchSingleTop = true - } + }*/ }, onInfoTap = { navController.navigate(AdpDestination.CommunityDetails.route) { @@ -67,10 +68,9 @@ fun App() { EventDetailScreen() } composable( - route = AdpDestination.CommunityDetails.route, - arguments = listOf(navArgument("eventId", builder = { type = StringType })) + route = AdpDestination.CommunityDetails.route ) { - // CommunityDetailScreen() + CommunityDetailScreen() } composable( route = AdpDestination.LiveEvent.route, diff --git a/composeApp/src/commonMain/kotlin/data/repository/MockCommunityRepository.kt b/composeApp/src/commonMain/kotlin/data/repository/MockCommunityRepository.kt index c99caad..c50c7cf 100644 --- a/composeApp/src/commonMain/kotlin/data/repository/MockCommunityRepository.kt +++ b/composeApp/src/commonMain/kotlin/data/repository/MockCommunityRepository.kt @@ -49,7 +49,7 @@ class MockCommunityRepository( url = "https://www.instagram.com/androiddevperu/" ), SocialMedia( - icon = "https://cdn.simpleicons.org/linkedin", + icon = "https://cdn-icons-png.flaticon.com/512/174/174857.png", url = "https://www.linkedin.com/company/android-dev-peru" ), SocialMedia( diff --git a/composeApp/src/commonMain/kotlin/features/community/CommunityDetailScreen.kt b/composeApp/src/commonMain/kotlin/features/community/CommunityDetailScreen.kt index 1f5ec02..ac5fb31 100644 --- a/composeApp/src/commonMain/kotlin/features/community/CommunityDetailScreen.kt +++ b/composeApp/src/commonMain/kotlin/features/community/CommunityDetailScreen.kt @@ -1,2 +1,225 @@ package features.community +import adpmeetups.composeapp.generated.resources.Res +import adpmeetups.composeapp.generated.resources.communityTitle_ex_organizer +import adpmeetups.composeapp.generated.resources.communityTitle_organizer +import androidx.compose.foundation.background +import androidx.compose.foundation.clickable +import androidx.compose.foundation.isSystemInDarkTheme +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.PaddingValues +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.lazy.LazyColumn +import androidx.compose.foundation.lazy.LazyRow +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.material.MaterialTheme +import androidx.compose.material.Text +import androidx.compose.runtime.Composable +import androidx.compose.runtime.collectAsState +import androidx.compose.runtime.getValue +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.clip +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.graphics.ColorFilter +import androidx.compose.ui.layout.ContentScale +import androidx.compose.ui.platform.LocalUriHandler +import androidx.compose.ui.platform.UriHandler +import androidx.compose.ui.unit.dp +import androidx.lifecycle.viewmodel.compose.viewModel +import coil3.compose.AsyncImage +import coil3.compose.LocalPlatformContext +import coil3.request.ImageRequest +import coil3.svg.SvgDecoder +import core.DomainInjector +import domain.models.Community +import domain.models.Organizer +import domain.models.SocialMedia +import org.jetbrains.compose.resources.stringResource + +@Composable +fun CommunityDetailScreen( + vm: CommunityDetailViewModel = viewModel { + CommunityDetailViewModel( + DomainInjector.getCommunityInfo + ) + } +) { + + val uiState by vm.uiState.collectAsState() + val uriHandler = LocalUriHandler.current + + uiState.community?.let { + CommunityDetailLayout( + modifier = Modifier.background(MaterialTheme.colors.background), + community = it, + organizers = it.organizers, + exOrganizers = it.exOrganizers, + socialMedia = it.socialMedia, + uriHandler = uriHandler + ) + } +} + +@Composable +private fun CommunityDetailLayout( + modifier: Modifier = Modifier, + community: Community?, + organizers: List, + exOrganizers: List, + socialMedia: List, + uriHandler: UriHandler +) { + LazyColumn ( + modifier = modifier.fillMaxSize() + ) { + item { + AsyncImage( + modifier = Modifier + .fillMaxWidth(), + contentScale = ContentScale.FillWidth, + model= community?.topBanner, + contentDescription = community?.name, + ) + + Column( + modifier = Modifier.padding(16.dp) + ) { + Text( + text = community?.name.orEmpty(), + style = MaterialTheme.typography.h5 + ) + + Spacer(modifier = Modifier.size(8.dp)) + + Text(text = community?.description.orEmpty()) + } + + Spacer(modifier = Modifier.size(8.dp)) + } + + item { + Text( + modifier = Modifier.padding(16.dp), + text = stringResource(Res.string.communityTitle_organizer) + ) + } + + organizers.forEach { organizer -> + item { + OrganizerItem(organizer = organizer) + Spacer(modifier = Modifier.height(16.dp)) + } + } + + item { + Text( + modifier = Modifier.padding(16.dp), + text = stringResource(Res.string.communityTitle_ex_organizer) + ) + } + + exOrganizers.forEach { exOrganizer -> + item { + OrganizerItem(organizer = exOrganizer) + Spacer(modifier = Modifier.height(16.dp)) + } + } + + item { + SocialMediaBar( + modifier = Modifier + .fillMaxWidth() + .padding(vertical = 12.dp), + socialMedia = socialMedia, + uriHandler = uriHandler + ) + } + } +} + +@Composable +fun OrganizerItem(modifier: Modifier = Modifier, organizer: Organizer?) { + Row( + modifier = modifier + .fillMaxWidth() + .padding(16.dp), + verticalAlignment = Alignment.CenterVertically + ) { + AsyncImage( + modifier = Modifier + .clip(androidx.compose.foundation.shape.CircleShape) + .size(80.dp), + contentScale = ContentScale.Crop, + model = organizer?.photo, + contentDescription = organizer?.name, + ) + + Text( + modifier = Modifier.padding(start = 16.dp), + text = organizer?.name.orEmpty(), + style = MaterialTheme.typography.subtitle1 + ) + } +} + +@Composable +fun SocialMediaBar( + modifier: Modifier = Modifier, + socialMedia: List, + uriHandler: UriHandler +) { + LazyRow( + modifier = modifier.fillMaxWidth().padding(top = 16.dp), + horizontalArrangement = Arrangement.SpaceEvenly, + contentPadding = PaddingValues(horizontal = 16.dp) + ) { + items(socialMedia.size) { social -> + SocialMediaItem( + socialMedia = socialMedia[social], + uriHandler = uriHandler + ) + } + } +} + +@Composable +fun SocialMediaItem( + modifier: Modifier = Modifier, + socialMedia: SocialMedia, + uriHandler: UriHandler +) { + Box( + modifier = modifier + .fillMaxWidth() + .padding(vertical = 4.dp), + contentAlignment = Alignment.Center + ) { + AsyncImage( + modifier = Modifier + .clip(RoundedCornerShape(8.dp)) + .size(24.dp) + .clickable { + uriHandler.openUri(socialMedia.url) + }, + contentScale = ContentScale.Fit, + model = ImageRequest + .Builder(LocalPlatformContext.current) + .data(socialMedia.icon) + .decoderFactory(SvgDecoder.Factory()) + .build(), + contentDescription = socialMedia.icon, + colorFilter = ColorFilter.tint( + if(isSystemInDarkTheme()) Color.White else Color.Black + ) + ) + } +} diff --git a/composeApp/src/commonMain/kotlin/features/community/CommunityDetailViewModel.kt b/composeApp/src/commonMain/kotlin/features/community/CommunityDetailViewModel.kt index ed128ef..6f1e32c 100644 --- a/composeApp/src/commonMain/kotlin/features/community/CommunityDetailViewModel.kt +++ b/composeApp/src/commonMain/kotlin/features/community/CommunityDetailViewModel.kt @@ -1,4 +1,48 @@ package features.community -class CommunityDetailViewModel { -} \ No newline at end of file +import androidx.lifecycle.ViewModel +import androidx.lifecycle.viewModelScope +import domain.AdpError +import domain.models.Community +import domain.usecase.GetCommunityInfo +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.SharingStarted +import kotlinx.coroutines.flow.StateFlow +import kotlinx.coroutines.flow.onStart +import kotlinx.coroutines.flow.stateIn +import kotlinx.coroutines.flow.update +import kotlinx.coroutines.launch + +class CommunityDetailViewModel( + private val getCommunityInfo: GetCommunityInfo +): ViewModel() { + + private val _uiState = MutableStateFlow(CommunityDetailUiState()) + val uiState: StateFlow = _uiState + .onStart { + getCommunityDetails() + }.stateIn( + viewModelScope, + SharingStarted.WhileSubscribed(), + CommunityDetailUiState() + ) + + private fun getCommunityDetails() { + // TODO add kotlinx-coroutines-swing to make viewModelScope + // available in desktop + viewModelScope.launch { + _uiState.update { it.copy(loading = true, error = null) } + getCommunityInfo.invoke().onSuccess { community -> + _uiState.update { it.copy(loading = false, community = community, error = null) } + }.onFailure { + _uiState.update { it.copy(loading = false, error = null) } + } + } + } +} + +data class CommunityDetailUiState( + val loading: Boolean = true, + val community: Community? = null, + val error: AdpError? = null +) \ No newline at end of file diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 4c19e62..a86fdbd 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -50,6 +50,7 @@ koin-core = { module = "io.insert-koin:koin-core", version.ref = "koin" } androidx-viewmodel = { group = "org.jetbrains.androidx.lifecycle", name = "lifecycle-viewmodel-compose", version.ref = "jetbrains-viewModel" } coil-compose = { module = "io.coil-kt.coil3:coil-compose", version.ref = "coil" } coil-network = { module = "io.coil-kt.coil3:coil-network-ktor3", version.ref = "coil" } +coil-svg = { module = "io.coil-kt.coil3:coil-svg", version.ref = "coil" } kotlinx-datetime = { module = "org.jetbrains.kotlinx:kotlinx-datetime", version.ref = "kotlinxDatetime" }