diff --git a/design/api/current.api b/design/api/current.api index 0e9da44..71b2dfd 100644 --- a/design/api/current.api +++ b/design/api/current.api @@ -128,6 +128,61 @@ package com.urlaunched.android.design.ui.counter.ui { } +package com.urlaunched.android.design.ui.downloadingprogressdialog { + + public final class DownloadingProgressBarKt { + method @androidx.compose.runtime.Composable public static void AnimatedDownloadingProgressBar(optional androidx.compose.ui.Modifier modifier, @FloatRange(from=0.0, to=1.0) float progress, androidx.compose.ui.graphics.Brush trackBrush, androidx.compose.ui.graphics.Brush progressBrush, optional float progressBarHeight, optional androidx.compose.ui.graphics.Shape trackShape, optional androidx.compose.ui.graphics.Shape progressShape, optional kotlin.jvm.functions.Function1? progressText, optional com.urlaunched.android.design.ui.downloadingprogressdialog.models.ProgressTextStyle progressTextStyle, optional kotlin.jvm.functions.Function1? supportingText); + method @androidx.compose.runtime.Composable public static void AnimatedDownloadingProgressBar(optional androidx.compose.ui.Modifier modifier, @FloatRange(from=0.0, to=1.0) float progress, long trackColor, long progressColor, optional float progressBarHeight, optional androidx.compose.ui.graphics.Shape trackShape, optional androidx.compose.ui.graphics.Shape progressShape, optional kotlin.jvm.functions.Function1? progressText, optional com.urlaunched.android.design.ui.downloadingprogressdialog.models.ProgressTextStyle progressTextStyle, optional kotlin.jvm.functions.Function1? supportingText); + method @androidx.compose.runtime.Composable public static void DownloadingProgressBar(optional androidx.compose.ui.Modifier modifier, @FloatRange(from=0.0, to=1.0) float progress, androidx.compose.ui.graphics.Brush trackBrush, androidx.compose.ui.graphics.Brush progressBrush, optional String? progressText, optional com.urlaunched.android.design.ui.downloadingprogressdialog.models.ProgressTextStyle progressTextStyle, optional float progressBarHeight, optional androidx.compose.ui.graphics.Shape trackShape, optional androidx.compose.ui.graphics.Shape progressShape, optional kotlin.jvm.functions.Function1? supportingText); + method @androidx.compose.runtime.Composable public static void DownloadingProgressBar(optional androidx.compose.ui.Modifier modifier, @FloatRange(from=0.0, to=1.0) float progress, long trackColor, long progressColor, optional String? progressText, optional com.urlaunched.android.design.ui.downloadingprogressdialog.models.ProgressTextStyle progressTextStyle, optional float progressBarHeight, optional androidx.compose.ui.graphics.Shape trackShape, optional androidx.compose.ui.graphics.Shape progressShape, optional kotlin.jvm.functions.Function1? supportingText); + } + + public final class DownloadingProgressDialogKt { + method @androidx.compose.runtime.Composable public static void BaseDownloadingProgressDialog(optional androidx.compose.ui.Modifier modifier, @com.composables.core.androidx.annotation.FloatRange(from=0.0, to=1.0) float progress, optional String? progressText, optional com.urlaunched.android.design.ui.downloadingprogressdialog.models.ProgressTextStyle progressTextStyle, optional com.urlaunched.android.design.ui.downloadingprogressdialog.models.ProgressBarStyle progressBarStyle, optional kotlin.jvm.functions.Function0 onDismissRequest, optional long dialogContainerColor, optional androidx.compose.ui.graphics.Shape dialogContainerShape, optional androidx.compose.foundation.layout.PaddingValues contentPadding, optional androidx.compose.ui.window.DialogProperties dialogProperties, optional kotlin.jvm.functions.Function1? title, optional kotlin.jvm.functions.Function1? description, optional kotlin.jvm.functions.Function1? supportingText, optional kotlin.jvm.functions.Function1? bottomContent); + method @androidx.compose.runtime.Composable public static void DownloadingProgressDialog(optional androidx.compose.ui.Modifier modifier, @com.composables.core.androidx.annotation.FloatRange(from=0.0, to=1.0) float progress, optional String? progressText, optional com.urlaunched.android.design.ui.downloadingprogressdialog.models.ProgressTextStyle progressTextStyle, optional com.urlaunched.android.design.ui.downloadingprogressdialog.models.ProgressBarStyle progressBarStyle, optional long dialogContainerColor, optional kotlin.jvm.functions.Function0 onDismissRequest, optional androidx.compose.ui.graphics.Shape dialogContainerShape, optional androidx.compose.foundation.layout.PaddingValues contentPadding, optional androidx.compose.ui.window.DialogProperties dialogProperties, optional kotlin.jvm.functions.Function1? title, optional kotlin.jvm.functions.Function1? description, optional kotlin.jvm.functions.Function1? supportingText, optional kotlin.jvm.functions.Function1? bottomContent); + } + +} + +package com.urlaunched.android.design.ui.downloadingprogressdialog.models { + + public final class ProgressBarStyle { + ctor public ProgressBarStyle(optional float progressBarHeight, androidx.compose.ui.graphics.Brush trackBrush, androidx.compose.ui.graphics.Brush progressBrush, optional androidx.compose.ui.graphics.Shape trackShape, optional androidx.compose.ui.graphics.Shape progressShape); + ctor public ProgressBarStyle(optional float progressBarHeight, optional long trackColor, optional long progressColor, optional androidx.compose.ui.graphics.Shape trackShape, optional androidx.compose.ui.graphics.Shape progressShape); + method public float component1-D9Ej5fM(); + method public androidx.compose.ui.graphics.Brush component2(); + method public androidx.compose.ui.graphics.Brush component3(); + method public androidx.compose.ui.graphics.Shape component4(); + method public androidx.compose.ui.graphics.Shape component5(); + method public com.urlaunched.android.design.ui.downloadingprogressdialog.models.ProgressBarStyle copy--orJrPs(float progressBarHeight, androidx.compose.ui.graphics.Brush trackBrush, androidx.compose.ui.graphics.Brush progressBrush, androidx.compose.ui.graphics.Shape trackShape, androidx.compose.ui.graphics.Shape progressShape); + method public float getProgressBarHeight(); + method public androidx.compose.ui.graphics.Brush getProgressBrush(); + method public androidx.compose.ui.graphics.Shape getProgressShape(); + method public androidx.compose.ui.graphics.Brush getTrackBrush(); + method public androidx.compose.ui.graphics.Shape getTrackShape(); + property public final float progressBarHeight; + property public final androidx.compose.ui.graphics.Brush progressBrush; + property public final androidx.compose.ui.graphics.Shape progressShape; + property public final androidx.compose.ui.graphics.Brush trackBrush; + property public final androidx.compose.ui.graphics.Shape trackShape; + } + + public final class ProgressTextStyle { + ctor public ProgressTextStyle(optional androidx.compose.ui.text.TextStyle textStyle, optional long startColor, optional long endColor); + method public androidx.compose.ui.text.TextStyle component1(); + method public long component2-0d7_KjU(); + method public long component3-0d7_KjU(); + method public com.urlaunched.android.design.ui.downloadingprogressdialog.models.ProgressTextStyle copy-WkMS-hQ(androidx.compose.ui.text.TextStyle textStyle, long startColor, long endColor); + method public long getEndColor(); + method public long getStartColor(); + method public androidx.compose.ui.text.TextStyle getTextStyle(); + property public final long endColor; + property public final long startColor; + property public final androidx.compose.ui.text.TextStyle textStyle; + } + +} + package com.urlaunched.android.design.ui.expandabletext { public final class ExpandableTextKt { diff --git a/design/src/main/java/com/urlaunched/android/design/ui/downloadingprogressdialog/AnimateProgressAsState.kt b/design/src/main/java/com/urlaunched/android/design/ui/downloadingprogressdialog/AnimateProgressAsState.kt new file mode 100644 index 0000000..908c90c --- /dev/null +++ b/design/src/main/java/com/urlaunched/android/design/ui/downloadingprogressdialog/AnimateProgressAsState.kt @@ -0,0 +1,43 @@ +package com.urlaunched.android.design.ui.downloadingprogressdialog + +import androidx.compose.animation.core.Animatable +import androidx.compose.animation.core.tween +import androidx.compose.runtime.Composable +import androidx.compose.runtime.LaunchedEffect +import androidx.compose.runtime.State +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableLongStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.rememberCoroutineScope +import androidx.compose.runtime.setValue +import kotlinx.coroutines.launch + +private const val MIN_ANIM_DURATION = 1 +private const val MAX_ANIM_DURATION = 700 + +@Composable +internal fun animateProgressAsState(progress: Float): State { + val coroutineScope = rememberCoroutineScope() + val animatedProgress = remember { Animatable(progress) } + var lastTimestamp by remember { mutableLongStateOf(System.currentTimeMillis()) } + + LaunchedEffect(progress) { + val now = System.currentTimeMillis() + val deltaT = (now - lastTimestamp).coerceAtLeast(MIN_ANIM_DURATION.toLong()) + + coroutineScope.launch { + animatedProgress.animateTo( + targetValue = progress, + animationSpec = tween( + durationMillis = deltaT.toInt().coerceIn( + minimumValue = MIN_ANIM_DURATION, + maximumValue = MAX_ANIM_DURATION + ) + ) + ) + } + + lastTimestamp = now + } + return animatedProgress.asState() +} \ No newline at end of file diff --git a/design/src/main/java/com/urlaunched/android/design/ui/downloadingprogressdialog/DownloadingProgressBar.kt b/design/src/main/java/com/urlaunched/android/design/ui/downloadingprogressdialog/DownloadingProgressBar.kt new file mode 100644 index 0000000..c2ff893 --- /dev/null +++ b/design/src/main/java/com/urlaunched/android/design/ui/downloadingprogressdialog/DownloadingProgressBar.kt @@ -0,0 +1,281 @@ +package com.urlaunched.android.design.ui.downloadingprogressdialog + +import androidx.annotation.FloatRange +import androidx.compose.foundation.Canvas +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.BoxScope +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.shape.CircleShape +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.runtime.LaunchedEffect +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableFloatStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.setValue +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Brush +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.graphics.Path +import androidx.compose.ui.graphics.Shape +import androidx.compose.ui.graphics.SolidColor +import androidx.compose.ui.graphics.addOutline +import androidx.compose.ui.graphics.drawOutline +import androidx.compose.ui.graphics.drawscope.clipPath +import androidx.compose.ui.text.style.TextAlign +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.Dp +import androidx.compose.ui.unit.dp +import com.urlaunched.android.design.resources.dimens.Dimens +import com.urlaunched.android.design.ui.downloadingprogressdialog.models.ProgressTextStyle +import kotlinx.coroutines.delay +import kotlinx.coroutines.isActive + +@Composable +fun AnimatedDownloadingProgressBar( + modifier: Modifier = Modifier, + @FloatRange(0.0, 1.0) + progress: Float, + trackColor: Color, + progressColor: Color, + progressBarHeight: Dp = Dimens.spacingLarge, + trackShape: Shape = CircleShape, + progressShape: Shape = trackShape, + progressText: ((animatedProgress: Float) -> String)? = null, + progressTextStyle: ProgressTextStyle = ProgressTextStyle(), + supportingText: (@Composable BoxScope.() -> Unit)? = null +) { + AnimatedDownloadingProgressBar( + progress = progress, + trackBrush = SolidColor(trackColor), + progressBrush = SolidColor(progressColor), + modifier = modifier, + progressBarHeight = progressBarHeight, + trackShape = trackShape, + progressShape = progressShape, + progressText = progressText, + progressTextStyle = progressTextStyle, + supportingText = supportingText + ) +} + +@Composable +fun AnimatedDownloadingProgressBar( + modifier: Modifier = Modifier, + @FloatRange(0.0, 1.0) + progress: Float, + trackBrush: Brush, + progressBrush: Brush, + progressBarHeight: Dp = Dimens.spacingLarge, + trackShape: Shape = CircleShape, + progressShape: Shape = trackShape, + progressText: ((animatedProgress: Float) -> String)? = null, + progressTextStyle: ProgressTextStyle = ProgressTextStyle(), + supportingText: (@Composable BoxScope.() -> Unit)? = null +) { + val animatedProgress by animateProgressAsState(progress = progress) + + DownloadingProgressBar( + progress = animatedProgress, + trackBrush = trackBrush, + progressBrush = progressBrush, + modifier = modifier, + progressBarHeight = progressBarHeight, + trackShape = trackShape, + progressShape = progressShape, + supportingText = supportingText, + progressText = progressText?.invoke(animatedProgress), + progressTextStyle = progressTextStyle + ) +} + +@Composable +fun DownloadingProgressBar( + modifier: Modifier = Modifier, + @FloatRange(0.0, 1.0) + progress: Float, + trackColor: Color, + progressColor: Color, + progressText: String? = null, + progressTextStyle: ProgressTextStyle = ProgressTextStyle(), + progressBarHeight: Dp = Dimens.spacingLarge, + trackShape: Shape = CircleShape, + progressShape: Shape = trackShape, + supportingText: (@Composable BoxScope.() -> Unit)? = null +) { + DownloadingProgressBar( + progress = progress, + trackBrush = SolidColor(trackColor), + progressBrush = SolidColor(progressColor), + modifier = modifier, + progressBarHeight = progressBarHeight, + trackShape = trackShape, + progressShape = progressShape, + progressText = progressText, + progressTextStyle = progressTextStyle, + supportingText = supportingText + ) +} + +@Composable +fun DownloadingProgressBar( + modifier: Modifier = Modifier, + @FloatRange(0.0, 1.0) + progress: Float, + trackBrush: Brush, + progressBrush: Brush, + progressText: String? = null, + progressTextStyle: ProgressTextStyle = ProgressTextStyle(), + progressBarHeight: Dp = Dimens.spacingLarge, + trackShape: Shape = CircleShape, + progressShape: Shape = trackShape, + supportingText: (@Composable BoxScope.() -> Unit)? = null +) { + Column(modifier = modifier) { + Box( + contentAlignment = Alignment.Center + ) { + ProgressBar( + modifier = Modifier + .fillMaxWidth() + .height(progressBarHeight), + progress = progress, + trackBrush = trackBrush, + progressBrush = progressBrush, + trackShape = trackShape, + progressShape = progressShape + ) + + if (progressText != null) { + ProgressText( + text = progressText, + progress = progress, + startColor = progressTextStyle.startColor, + endColor = progressTextStyle.endColor, + textAlign = TextAlign.Center, + modifier = Modifier + .fillMaxWidth() + .align(Alignment.Center) + ) + } + } + + supportingText?.let { textContent -> + Box( + modifier = Modifier.fillMaxWidth(), + content = textContent + ) + } + } +} + +@Composable +private fun ProgressBar( + progress: Float, + modifier: Modifier, + trackBrush: Brush, + progressBrush: Brush, + trackShape: Shape, + progressShape: Shape +) { + Canvas(modifier = modifier) { + val trackOutline = trackShape.createOutline(size, layoutDirection, this) + + drawOutline( + brush = trackBrush, + outline = trackOutline + ) + + val progressWidth = size.width * progress + val progressSize = size.copy(width = progressWidth) + val progressOutline = progressShape.createOutline(progressSize, layoutDirection, this) + + clipPath( + path = Path().apply { addOutline(trackOutline) } + ) { + drawOutline( + brush = progressBrush, + outline = progressOutline + ) + } + } +} + +@Preview +@Composable +private fun GradientProgressBarPreview() { + val progress = remember { 0.6f } + + DownloadingProgressBar( + progress = progress, + progressBarHeight = 50.dp, + progressBrush = Brush.horizontalGradient( + 0f to Color.Cyan, + progress to Color.Magenta + ), + trackBrush = SolidColor(Color.LightGray), + supportingText = { + Row( + modifier = Modifier.fillMaxWidth(), + horizontalArrangement = Arrangement.SpaceBetween + ) { + listOf(progress * 100f, 100f).forEach { value -> + Text( + text = "${"%.1f".format(value)} MB", + color = Color.Gray, + style = MaterialTheme.typography.bodySmall, + modifier = Modifier.padding( + horizontal = Dimens.spacingSmall, + vertical = Dimens.spacingTinyHalf + ) + ) + } + } + } + ) +} + +@Preview +@Composable +private fun SolidProgressBarPreview() { + DownloadingProgressBar( + progress = 0.75f, + progressBarHeight = 50.dp, + progressColor = Color.Yellow, + trackColor = Color.LightGray + ) +} + +@Preview +@Composable +private fun AnimatedProgressBarPreview() { + var progress by remember { mutableFloatStateOf(0.49f) } + + LaunchedEffect(Unit) { + while (isActive) { + delay(500) + if (progress < 1f) { + progress += 0.1f + } else { + progress = 0f + } + } + } + + AnimatedDownloadingProgressBar( + progress = progress, + progressBarHeight = 50.dp, + progressColor = Color.Red, + trackColor = Color.LightGray, + progressText = { animatedProgress -> + "${"%.1f".format(animatedProgress * 100f)} %" + } + ) +} \ No newline at end of file diff --git a/design/src/main/java/com/urlaunched/android/design/ui/downloadingprogressdialog/DownloadingProgressDialog.kt b/design/src/main/java/com/urlaunched/android/design/ui/downloadingprogressdialog/DownloadingProgressDialog.kt new file mode 100644 index 0000000..cc3d4e7 --- /dev/null +++ b/design/src/main/java/com/urlaunched/android/design/ui/downloadingprogressdialog/DownloadingProgressDialog.kt @@ -0,0 +1,233 @@ +package com.urlaunched.android.design.ui.downloadingprogressdialog + +import androidx.compose.foundation.background +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.BoxScope +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.ColumnScope +import androidx.compose.foundation.layout.PaddingValues +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.Spacer +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.BasicAlertDialog +import androidx.compose.material3.ExperimentalMaterial3Api +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.Text +import androidx.compose.material3.TextButton +import androidx.compose.runtime.Composable +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.tooling.preview.Preview +import androidx.compose.ui.window.DialogProperties +import com.composables.core.androidx.annotation.FloatRange +import com.urlaunched.android.design.resources.dimens.Dimens +import com.urlaunched.android.design.ui.downloadingprogressdialog.models.ProgressBarStyle +import com.urlaunched.android.design.ui.downloadingprogressdialog.models.ProgressTextStyle + +@Composable +fun DownloadingProgressDialog( + modifier: Modifier = Modifier, + @FloatRange(0.0, 1.0) + progress: Float, + progressText: String? = null, + progressTextStyle: ProgressTextStyle = ProgressTextStyle(), + progressBarStyle: ProgressBarStyle = ProgressBarStyle(), + dialogContainerColor: Color = Color.White, + onDismissRequest: () -> Unit = {}, + dialogContainerShape: Shape = RoundedCornerShape(Dimens.cornerRadiusLarge), + contentPadding: PaddingValues = PaddingValues(Dimens.spacingNormal), + dialogProperties: DialogProperties = DialogProperties(), + title: (@Composable ColumnScope.() -> Unit)? = null, + description: (@Composable ColumnScope.() -> Unit)? = null, + supportingText: (@Composable BoxScope.() -> Unit)? = null, + bottomContent: (@Composable ColumnScope.() -> Unit)? = null +) { + BaseDownloadingProgressDialog( + progress = progress, + modifier = modifier, + onDismissRequest = onDismissRequest, + progressBarStyle = progressBarStyle, + dialogProperties = dialogProperties, + dialogContainerColor = dialogContainerColor, + dialogContainerShape = dialogContainerShape, + contentPadding = contentPadding, + progressText = progressText, + progressTextStyle = progressTextStyle, + title = title?.let { titleContent -> + @Composable { + titleContent() + + Spacer(modifier = Modifier.height(Dimens.spacingSmall)) + } + }, + description = description?.let { descriptionContent -> + @Composable { + descriptionContent() + + Spacer(modifier = Modifier.height(Dimens.spacingBigSpecial)) + } + }, + supportingText = supportingText, + bottomContent = bottomContent?.let { bottomContent -> + @Composable { + Column( + modifier = Modifier.fillMaxWidth(), + horizontalAlignment = Alignment.End + ) { + Spacer(modifier = Modifier.height(Dimens.spacingLarge)) + + bottomContent() + } + } + } + ) +} + +@OptIn(ExperimentalMaterial3Api::class) +@Composable +fun BaseDownloadingProgressDialog( + modifier: Modifier = Modifier, + @FloatRange(0.0, 1.0) + progress: Float, + progressText: String? = null, + progressTextStyle: ProgressTextStyle = ProgressTextStyle(), + progressBarStyle: ProgressBarStyle = ProgressBarStyle(), + onDismissRequest: () -> Unit = {}, + dialogContainerColor: Color = Color.White, + dialogContainerShape: Shape = RoundedCornerShape(Dimens.cornerRadiusLarge), + contentPadding: PaddingValues = PaddingValues(Dimens.spacingNormal), + dialogProperties: DialogProperties = DialogProperties(), + title: (@Composable ColumnScope.() -> Unit)? = null, + description: (@Composable ColumnScope.() -> Unit)? = null, + supportingText: (@Composable BoxScope.() -> Unit)? = null, + bottomContent: (@Composable ColumnScope.() -> Unit)? = null +) { + BasicAlertDialog( + modifier = modifier, + onDismissRequest = onDismissRequest, + properties = dialogProperties, + content = { + Column( + modifier = Modifier + .background( + color = dialogContainerColor, + shape = dialogContainerShape + ) + .padding(contentPadding) + ) { + title?.invoke(this@Column) + + description?.invoke(this@Column) + + DownloadingProgressBar( + progress = progress, + modifier = Modifier.fillMaxWidth(), + progressBarHeight = progressBarStyle.progressBarHeight, + trackShape = progressBarStyle.trackShape, + progressShape = progressBarStyle.progressShape, + progressBrush = progressBarStyle.progressBrush, + trackBrush = progressBarStyle.trackBrush, + progressText = progressText, + progressTextStyle = progressTextStyle, + supportingText = supportingText + ) + + bottomContent?.invoke(this@Column) + } + } + ) +} + +@Preview(showBackground = true) +@Composable +private fun BaseDownloadingProgressDialogPreview() { + val progress = remember { 0.73f } + + BaseDownloadingProgressDialog( + progress = progress, + title = { + Text( + text = "Downloading file", + style = MaterialTheme.typography.headlineMedium + ) + }, + description = { + Text( + text = "The Adventures of Sherlock Holmes", + style = MaterialTheme.typography.bodyMedium + ) + }, + progressText = "${progress * 100f}%", + supportingText = { + Row( + modifier = Modifier.fillMaxWidth(), + horizontalArrangement = Arrangement.SpaceBetween + ) { + listOf(progress * 100, 100).forEach { value -> + Text( + text = "$value MB", + color = Color.Gray, + style = MaterialTheme.typography.bodySmall + ) + } + } + }, + bottomContent = { + TextButton( + onClick = {} + ) { + Text("Cancel") + } + } + ) +} + +@Preview(showBackground = true) +@Composable +private fun DownloadingProgressDialogPreview() { + val progress = remember { 0.5f } + + DownloadingProgressDialog( + progress = progress, + title = { + Text( + text = "Downloading file", + style = MaterialTheme.typography.headlineMedium + ) + }, + description = { + Text( + text = "The Adventures of Sherlock Holmes", + style = MaterialTheme.typography.bodyMedium + ) + }, + progressText = "${progress * 100f}%", + supportingText = { + Row( + modifier = Modifier.fillMaxWidth(), + horizontalArrangement = Arrangement.SpaceBetween + ) { + listOf(progress * 100, 100).forEach { value -> + Text( + text = "$value MB", + color = Color.Gray, + style = MaterialTheme.typography.bodySmall + ) + } + } + }, + bottomContent = { + TextButton( + onClick = {} + ) { + Text("Cancel") + } + } + ) +} \ No newline at end of file diff --git a/design/src/main/java/com/urlaunched/android/design/ui/downloadingprogressdialog/ProgressText.kt b/design/src/main/java/com/urlaunched/android/design/ui/downloadingprogressdialog/ProgressText.kt new file mode 100644 index 0000000..00ad947 --- /dev/null +++ b/design/src/main/java/com/urlaunched/android/design/ui/downloadingprogressdialog/ProgressText.kt @@ -0,0 +1,117 @@ +package com.urlaunched.android.design.ui.downloadingprogressdialog + +import androidx.annotation.FloatRange +import androidx.compose.foundation.background +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.fillMaxHeight +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.size +import androidx.compose.foundation.shape.CircleShape +import androidx.compose.material3.LocalTextStyle +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +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.graphics.Brush +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.platform.LocalLayoutDirection +import androidx.compose.ui.text.TextLayoutResult +import androidx.compose.ui.text.TextStyle +import androidx.compose.ui.text.font.FontFamily +import androidx.compose.ui.text.font.FontStyle +import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.text.style.TextAlign +import androidx.compose.ui.text.style.TextDecoration +import androidx.compose.ui.text.style.TextOverflow +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.TextUnit +import androidx.compose.ui.unit.dp +import com.urlaunched.android.design.ui.downloadingprogressdialog.models.ProgressDirection + +@Composable +internal fun ProgressText( + modifier: Modifier = Modifier, + text: String, + @FloatRange(0.0, 1.0) + progress: Float, + startColor: Color, + endColor: Color, + progressDirection: ProgressDirection = ProgressDirection.Horizontal, + fontSize: TextUnit = TextUnit.Unspecified, + fontStyle: FontStyle? = null, + fontWeight: FontWeight? = null, + fontFamily: FontFamily? = null, + letterSpacing: TextUnit = TextUnit.Unspecified, + textDecoration: TextDecoration? = null, + textAlign: TextAlign? = null, + lineHeight: TextUnit = TextUnit.Unspecified, + overflow: TextOverflow = TextOverflow.Clip, + softWrap: Boolean = true, + maxLines: Int = Int.MAX_VALUE, + minLines: Int = 1, + onTextLayout: ((TextLayoutResult) -> Unit)? = null, + style: TextStyle = LocalTextStyle.current +) { + Text( + text = text, + overflow = overflow, + softWrap = softWrap, + maxLines = maxLines, + minLines = minLines, + fontSize = fontSize, + fontStyle = fontStyle, + fontFamily = fontFamily, + fontWeight = fontWeight, + letterSpacing = letterSpacing, + textDecoration = textDecoration, + onTextLayout = onTextLayout, + lineHeight = lineHeight, + textAlign = textAlign, + modifier = modifier, + style = style.copy( + brush = Brush.linearGradient( + colorStops = arrayOf( + 0f to startColor, + progress to startColor, + progress to endColor, + 1f to endColor + ), + start = progressDirection.getStartOffset(LocalLayoutDirection.current), + end = progressDirection.getEndOffset(LocalLayoutDirection.current) + ) + ) + ) +} + +@Preview +@Composable +private fun ProgressTextPreview() { + val progress = remember { 0.59f } + + Box( + modifier = Modifier + .size(200.dp, 50.dp) + .clip(CircleShape) + .background(Color.LightGray) + ) { + Box( + Modifier + .fillMaxWidth(progress) + .fillMaxHeight() + .background(Color.Yellow) + ) + + ProgressText( + text = "PROGRESS TEXT", + progress = progress, + startColor = Color.Red, + endColor = Color.Gray, + textAlign = TextAlign.Center, + modifier = Modifier + .fillMaxWidth() + .align(Alignment.Center) + ) + } +} \ No newline at end of file diff --git a/design/src/main/java/com/urlaunched/android/design/ui/downloadingprogressdialog/constants/DownloadingProgressDialogDimens.kt b/design/src/main/java/com/urlaunched/android/design/ui/downloadingprogressdialog/constants/DownloadingProgressDialogDimens.kt new file mode 100644 index 0000000..7be39b2 --- /dev/null +++ b/design/src/main/java/com/urlaunched/android/design/ui/downloadingprogressdialog/constants/DownloadingProgressDialogDimens.kt @@ -0,0 +1,7 @@ +package com.urlaunched.android.design.ui.downloadingprogressdialog.constants + +import androidx.compose.ui.unit.dp + +internal object DownloadingProgressDialogDimens { + val defaultProgressBarHeight = 36.dp +} \ No newline at end of file diff --git a/design/src/main/java/com/urlaunched/android/design/ui/downloadingprogressdialog/models/ProgressBarStyle.kt b/design/src/main/java/com/urlaunched/android/design/ui/downloadingprogressdialog/models/ProgressBarStyle.kt new file mode 100644 index 0000000..c8fb47d --- /dev/null +++ b/design/src/main/java/com/urlaunched/android/design/ui/downloadingprogressdialog/models/ProgressBarStyle.kt @@ -0,0 +1,31 @@ +package com.urlaunched.android.design.ui.downloadingprogressdialog.models + +import androidx.compose.foundation.shape.CircleShape +import androidx.compose.ui.graphics.Brush +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.graphics.Shape +import androidx.compose.ui.graphics.SolidColor +import androidx.compose.ui.unit.Dp +import com.urlaunched.android.design.ui.downloadingprogressdialog.constants.DownloadingProgressDialogDimens + +data class ProgressBarStyle( + val progressBarHeight: Dp = DownloadingProgressDialogDimens.defaultProgressBarHeight, + val trackBrush: Brush, + val progressBrush: Brush, + val trackShape: Shape = CircleShape, + val progressShape: Shape = trackShape +) { + constructor( + progressBarHeight: Dp = DownloadingProgressDialogDimens.defaultProgressBarHeight, + trackColor: Color = Color.LightGray, + progressColor: Color = Color.Blue, + trackShape: Shape = CircleShape, + progressShape: Shape = trackShape + ) : this( + progressBarHeight = progressBarHeight, + trackBrush = SolidColor(trackColor), + progressBrush = SolidColor(progressColor), + trackShape = trackShape, + progressShape = progressShape + ) +} \ No newline at end of file diff --git a/design/src/main/java/com/urlaunched/android/design/ui/downloadingprogressdialog/models/ProgressDirection.kt b/design/src/main/java/com/urlaunched/android/design/ui/downloadingprogressdialog/models/ProgressDirection.kt new file mode 100644 index 0000000..2bab2fb --- /dev/null +++ b/design/src/main/java/com/urlaunched/android/design/ui/downloadingprogressdialog/models/ProgressDirection.kt @@ -0,0 +1,40 @@ +package com.urlaunched.android.design.ui.downloadingprogressdialog.models + +import androidx.compose.ui.geometry.Offset +import androidx.compose.ui.unit.LayoutDirection + +internal interface ProgressDirection { + fun getStartOffset(layoutDirection: LayoutDirection): Offset + fun getEndOffset(layoutDirection: LayoutDirection): Offset + + object Horizontal : ProgressDirection { + override fun getStartOffset(layoutDirection: LayoutDirection): Offset = when (layoutDirection) { + LayoutDirection.Ltr -> LeftToRight.getStartOffset(layoutDirection) + LayoutDirection.Rtl -> RightToLeft.getStartOffset(layoutDirection) + } + override fun getEndOffset(layoutDirection: LayoutDirection): Offset = when (layoutDirection) { + LayoutDirection.Ltr -> LeftToRight.getEndOffset(layoutDirection) + LayoutDirection.Rtl -> RightToLeft.getEndOffset(layoutDirection) + } + } + + object LeftToRight : ProgressDirection { + override fun getStartOffset(layoutDirection: LayoutDirection): Offset = Offset.Companion.Zero + override fun getEndOffset(layoutDirection: LayoutDirection): Offset = Offset(Float.POSITIVE_INFINITY, 0f) + } + + object RightToLeft : ProgressDirection { + override fun getStartOffset(layoutDirection: LayoutDirection): Offset = Offset(Float.POSITIVE_INFINITY, 0f) + override fun getEndOffset(layoutDirection: LayoutDirection): Offset = Offset.Companion.Zero + } + + object TopToBottom : ProgressDirection { + override fun getStartOffset(layoutDirection: LayoutDirection): Offset = Offset.Companion.Zero + override fun getEndOffset(layoutDirection: LayoutDirection): Offset = Offset(0f, Float.POSITIVE_INFINITY) + } + + object BottomToTop : ProgressDirection { + override fun getStartOffset(layoutDirection: LayoutDirection): Offset = Offset(0f, Float.POSITIVE_INFINITY) + override fun getEndOffset(layoutDirection: LayoutDirection): Offset = Offset.Companion.Zero + } +} \ No newline at end of file diff --git a/design/src/main/java/com/urlaunched/android/design/ui/downloadingprogressdialog/models/ProgressTextStyle.kt b/design/src/main/java/com/urlaunched/android/design/ui/downloadingprogressdialog/models/ProgressTextStyle.kt new file mode 100644 index 0000000..d7ec0a2 --- /dev/null +++ b/design/src/main/java/com/urlaunched/android/design/ui/downloadingprogressdialog/models/ProgressTextStyle.kt @@ -0,0 +1,10 @@ +package com.urlaunched.android.design.ui.downloadingprogressdialog.models + +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.text.TextStyle + +data class ProgressTextStyle( + val textStyle: TextStyle = TextStyle(), + val startColor: Color = Color.White, + val endColor: Color = Color.Gray +) \ No newline at end of file