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

refactor(breaking): compose platform migration #600

Open
wants to merge 7 commits into
base: develop
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -242,3 +242,5 @@ fabric.properties
jacoco.exec

.idea/

.kotlin/

This file was deleted.

Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
/*
* Copyright (C) 2022 AniTrend
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/

package co.anitrend.core.android.compose

import androidx.compose.ui.unit.dp

object AniTrendDimensions {
val bottom_sheet_margin = 0.dp

val design_bottom_app_bar_height = 56.dp
val design_bottom_app_status_bar_height = 24.dp
val design_bottom_sheet_peek_height_min = 64.dp

val design_bottom_sheet_nav_divider_width = 120.dp
val design_tab_layout_min_width = 145.dp

val nav_item_selector_margin = 100.dp
val text_drawable_margin = 3.dp

val avatar_xs = 8.dp
val avatar_sm = 12.dp
val avatar_md = 16.dp
val avatar_lg = 24.dp
val avatar_xl = 36.dp
val avatar_xx = 48.dp


val series_image_xx = 205.dp
val series_image_xl = 185.dp
val series_image_lg = 165.dp
val series_image_md = 145.dp
val series_image_sm = 125.dp
val series_image_xs = 100.dp

const val series_image_aspect_ratio = 0.65f

val media_image_comfortable = 245.dp
val media_image_compact = 175.dp
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package co.anitrend.core.android.compose.design

import androidx.annotation.DrawableRes
import androidx.compose.foundation.Image
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
Expand All @@ -9,10 +10,10 @@ import androidx.compose.foundation.layout.fillMaxSize
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.material3.CircularProgressIndicator
import androidx.compose.material3.FilledTonalButton
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Surface
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
Expand All @@ -22,50 +23,80 @@ import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.tooling.preview.PreviewParameter
import androidx.compose.ui.tooling.preview.PreviewParameterProvider
import androidx.compose.ui.unit.dp
import androidx.lifecycle.LiveData
import co.anitrend.arch.core.model.IStateLayoutConfig
import co.anitrend.arch.domain.entities.LoadState
import co.anitrend.arch.domain.entities.RequestError
import co.anitrend.core.android.R
import co.anitrend.core.android.compose.AniTrendTheme
import co.anitrend.arch.ui.view.widget.model.StateLayoutConfig
import co.anitrend.core.android.ui.AniTrendPreview
import co.anitrend.core.android.ui.theme.preview.PreviewTheme
import co.anitrend.navigation.model.common.IParam
import coil.compose.rememberAsyncImagePainter
import coil.compose.rememberImagePainter
import kotlinx.coroutines.launch
import org.koin.compose.koinInject

@Composable
private fun ContentImage(
@DrawableRes drawableResource: Int?,
modifier: Modifier = Modifier
) {
if (drawableResource == null)
return

Image(
painter = rememberAsyncImagePainter(drawableResource),
contentDescription = null,
modifier = modifier.size(96.dp),
)
}

@Composable
private fun ContentText(
text: String,
modifier: Modifier = Modifier
) {
Text(
text = text,
textAlign = TextAlign.Center,
style = MaterialTheme.typography.bodyLarge,
overflow = TextOverflow.Ellipsis,
maxLines = 3,
modifier = modifier,
)
}

@Composable
private fun LoadingContent(
config: IStateLayoutConfig,
modifier: Modifier,
modifier: Modifier = Modifier,
) {
Column(
modifier = modifier,
verticalArrangement = Arrangement.Center,
) {
Image(
painter = rememberAsyncImagePainter(config.loadingDrawable),
contentDescription = "",
modifier = Modifier.size(64.dp)
.align(alignment = Alignment.CenterHorizontally),
)
Spacer(modifier = Modifier.height(8.dp))
Row(
modifier = Modifier.align(alignment = Alignment.CenterHorizontally),
Surface {
Column(
modifier = modifier,
verticalArrangement = Arrangement.Center,
horizontalAlignment = Alignment.CenterHorizontally
) {
CircularProgressIndicator(
modifier = Modifier.size(14.dp).align(alignment = Alignment.CenterVertically),
color = MaterialTheme.colorScheme.surfaceVariant,
strokeWidth = 2.dp,
trackColor = MaterialTheme.colorScheme.secondary,
)
config.loadingMessage?.also {
Spacer(modifier = Modifier.width(8.dp))
Text(
text = stringResource(it),
style = AniTrendTheme.typography.body2,
ContentImage(drawableResource = config.loadingDrawable)
Spacer(modifier = Modifier.height(8.dp))
Row(
horizontalArrangement = Arrangement.spacedBy(8.dp),
verticalAlignment = Alignment.CenterVertically,
) {
CircularProgressIndicator(
modifier = Modifier.size(16.dp)
.align(alignment = Alignment.CenterVertically),
color = MaterialTheme.colorScheme.surfaceVariant,
strokeWidth = 2.dp,
trackColor = MaterialTheme.colorScheme.secondary,
)
config.loadingMessage?.also {
ContentText(text = stringResource(it))
}
}
}
}
Expand All @@ -75,40 +106,28 @@ private fun LoadingContent(
private fun ErrorContent(
config: IStateLayoutConfig,
state: LoadState.Error,
modifier: Modifier,
modifier: Modifier = Modifier,
onClick: suspend () -> Unit,
) {
val scope = rememberCoroutineScope()
Column(
modifier = modifier,
verticalArrangement = Arrangement.Center,
) {
Image(
painter = rememberAsyncImagePainter(config.errorDrawable),
contentDescription = "",
modifier = Modifier.size(64.dp)
.align(alignment = Alignment.CenterHorizontally)
)
Spacer(modifier = Modifier.height(8.dp))
state.details.message?.also {
Text(
text = it,
style = AniTrendTheme.typography.body2,
modifier = Modifier
.align(alignment = Alignment.CenterHorizontally)
)
}
Spacer(modifier = Modifier.height(8.dp))
config.retryAction?.also {
FilledTonalButton(
onClick = { scope.launch { onClick() } },
modifier = Modifier
.align(alignment = Alignment.CenterHorizontally),
) {
Text(
text = stringResource(it),
style = AniTrendTheme.typography.body2,
)
Surface {
Column(
modifier = modifier,
verticalArrangement = Arrangement.Center,
horizontalAlignment = Alignment.CenterHorizontally
) {
ContentImage(drawableResource = config.errorDrawable)
Spacer(modifier = Modifier.height(16.dp))
state.details.message?.also {
ContentText(text = it)
}
config.retryAction?.also {
Spacer(modifier = Modifier.height(16.dp))
FilledTonalButton(
onClick = { scope.launch { onClick() } },
) {
Text(text = stringResource(it))
}
}
}
}
Expand All @@ -123,16 +142,16 @@ fun <P: IParam> ContentWrapper(
onClick: suspend () -> Unit = {},
content: @Composable () -> Unit,
) {
val modifier: Modifier = Modifier.fillMaxSize().padding(16.dp)
val modifier: Modifier = Modifier.fillMaxSize().padding(32.dp)
val loadState by stateFlow.observeAsState(LoadState.Loading())

if (param == null) {
ErrorContent(
config = config,
state = LoadState.Error(
RequestError(
topic = stringResource(R.string.app_controller_heading_missing_param),
description = stringResource(R.string.app_controller_message_missing_param),
topic = stringResource(co.anitrend.core.android.R.string.app_controller_heading_missing_param),
description = stringResource(co.anitrend.core.android.R.string.app_controller_message_missing_param),
)
),
modifier = modifier,
Expand All @@ -142,12 +161,61 @@ fun <P: IParam> ContentWrapper(
}

when (val state = loadState) {
is LoadState.Error -> ErrorContent(config = config, state = state, modifier = modifier, onClick = onClick)
is LoadState.Loading -> LoadingContent(config = config, modifier = modifier)
is LoadState.Error -> ErrorContent(
config = config,
state = state,
modifier = modifier,
onClick = onClick,
)
is LoadState.Loading -> LoadingContent(
config = config,
modifier = modifier,
)
else -> content()
}

LaunchedEffect(param) {
onLoad(param)
}
}


@AniTrendPreview.Light
@AniTrendPreview.Dark
@Composable
private fun ContentWrapperPreview(
@PreviewParameter(provider = ContentWrapperPreviewParameter::class) loadState: LoadState
) {
val modifier: Modifier = Modifier.fillMaxSize().padding(16.dp)
val config: IStateLayoutConfig = StateLayoutConfig(
loadingDrawable = co.anitrend.arch.ui.R.drawable.ic_support_empty_state,
errorDrawable = co.anitrend.arch.ui.R.drawable.ic_support_empty_state,
loadingMessage = co.anitrend.arch.ui.R.string.supportTextLoading,
defaultMessage = co.anitrend.core.android.R.string.app_controller_message_missing_param,
retryAction = co.anitrend.core.android.R.string.action_share
)
PreviewTheme {
when (loadState) {
is LoadState.Error -> ErrorContent(
config = config,
state = loadState,
modifier = modifier,
onClick = {},
)
is LoadState.Loading -> LoadingContent(
config = config,
modifier = modifier,
)
is LoadState.Idle,
is LoadState.Success -> {}
}
}
}

private class ContentWrapperPreviewParameter(
override val values: Sequence<LoadState> = sequenceOf(
LoadState.Loading(),
LoadState.Error(details = UnsupportedOperationException("Looks like no arguments were passed to this screen")),
LoadState.Idle(),
)
) : PreviewParameterProvider<LoadState>
Loading
Loading