Sharing animation

This commit is contained in:
2021-09-08 00:48:20 +02:00
parent 3397efe47f
commit 09ce7b6fb8
15 changed files with 469 additions and 99 deletions
+1 -1
View File
@@ -130,5 +130,5 @@ dependencies {
testImplementation(Libs.mockk_core)
androidTestImplementation(Libs.mockk_android)
debugImplementation(Libs.code_scanner)
implementation(Libs.code_scanner)
}
+13
View File
@@ -39,6 +39,16 @@
</intent-filter>
</activity>
<provider
android:name="androidx.core.content.FileProvider"
android:authorities="${applicationId}.provider"
android:exported="false"
android:grantUriPermissions="true">
<meta-data
android:name="android.support.FILE_PROVIDER_PATHS"
android:resource="@xml/provider_paths" />
</provider>
<provider
android:name="androidx.startup.InitializationProvider"
android:authorities="${applicationId}.androidx-startup"
@@ -50,6 +60,9 @@
<meta-data
android:name="com.ericampire.android.androidstudycase.app.initializer.MavericksInitializer"
android:value="androidx.startup" />
<meta-data
android:name="com.ericampire.android.androidstudycase.app.initializer.DownloaderInitializer"
android:value="androidx.startup" />
</provider>
</application>
@@ -0,0 +1,19 @@
package com.ericampire.android.androidstudycase.app.initializer
import android.content.Context
import androidx.startup.Initializer
import com.downloader.PRDownloader
import com.downloader.PRDownloaderConfig
class DownloaderInitializer : Initializer<Unit> {
override fun create(context: Context) {
val config = PRDownloaderConfig.newBuilder()
.setDatabaseEnabled(true)
.build()
PRDownloader.initialize(context, config)
}
override fun dependencies(): MutableList<Class<out Initializer<*>>> {
return mutableListOf()
}
}
@@ -32,6 +32,8 @@ import com.ericampire.android.androidstudycase.presentation.screen.explore.busin
import com.ericampire.android.androidstudycase.presentation.screen.explore.business.ExploreViewModel
import com.ericampire.android.androidstudycase.presentation.screen.explore.business.ExploreViewState
import com.ericampire.android.androidstudycase.presentation.theme.AppColor
import com.ericampire.android.androidstudycase.util.extension.copyTextToClipboard
import com.ericampire.android.androidstudycase.util.extension.downloadAndShare
import com.google.accompanist.pager.ExperimentalPagerApi
import com.google.accompanist.pager.HorizontalPager
import com.google.accompanist.pager.pagerTabIndicatorOffset
@@ -55,6 +57,11 @@ fun ExploreScreen(
val tabItems = stringArrayResource(id = R.array.explore_item)
val pagerState = rememberPagerState(pageCount = tabItems.size)
val bottomSheetState = rememberModalBottomSheetState(ModalBottomSheetValue.Hidden)
var selectedAnimation by remember { mutableStateOf<Lottiefile?>(null) }
var fileUrl by remember { mutableStateOf<String?>(null) }
LaunchedEffect(viewModel) {
viewModel.submitAction(ExploreAction.FindRecentFile)
}
@@ -69,96 +76,144 @@ fun ExploreScreen(
}
}
Scaffold(
topBar = {
Column(
modifier = Modifier
.fillMaxWidth()
.background(color = AppColor.Black001),
content = {
TopActionBar()
TabRow(
modifier = Modifier.fillMaxWidth(),
selectedTabIndex = pagerState.currentPage,
backgroundColor = Color.Transparent,
indicator = { tabPositions ->
TabRowDefaults.Indicator(
modifier = Modifier
.pagerTabIndicatorOffset(pagerState, tabPositions)
.padding(horizontal = 32.dp)
.clip(RoundedCornerShape(topStart = 12.dp, topEnd = 12.dp)),
height = 3.dp,
color = MaterialTheme.colors.primary
)
},
tabs = {
tabItems.forEachIndexed { index, title ->
Tab(
selected = index == pagerState.currentPage,
selectedContentColor = MaterialTheme.colors.primary,
unselectedContentColor = Color.White,
onClick = {
coroutineScope.launch {
pagerState.animateScrollToPage(index)
}
},
content = {
Text(
modifier = Modifier.padding(vertical = 12.dp),
text = title,
style = MaterialTheme.typography.h6.copy(
fontWeight = FontWeight.Normal
),
textAlign = TextAlign.Center,
)
}
)
}
}
)
LaunchedEffect(fileUrl) {
if (fileUrl != null) {
context.downloadAndShare(
url = fileUrl!!,
onError = {
Timber.e(it)
},
onSuccess = {
selectedAnimation = null
fileUrl = null
coroutineScope.launch {
bottomSheetState.hide()
}
}
)
}
}
ModalBottomSheetLayout(
sheetShape = RoundedCornerShape(topEnd = 24.dp, topStart = 24.dp),
sheetState = bottomSheetState,
sheetContent = {
ShareBottomSheet(
lottiefile = selectedAnimation,
isLoading = fileUrl != null,
onCopyLink = {
context.copyTextToClipboard(it)
coroutineScope.launch {
bottomSheetState.hide()
}
},
onShareGifFile = { fileUrl = it },
onShareJsonFile = { fileUrl = it },
onShareVideoFile = { fileUrl = it }
)
},
content = { contentPadding ->
Crossfade(modifier = Modifier.padding(contentPadding), targetState = state) {
Box(
modifier = Modifier.fillMaxSize(),
contentAlignment = Alignment.Center,
content = {
when (it) {
Uninitialized -> {
LoadingAnimation(
waveColor = MaterialTheme.colors.primary.copy(alpha = 0.5f),
arcColor = MaterialTheme.colors.primaryVariant
)
}
is Loading -> {
LoadingAnimation(
waveColor = MaterialTheme.colors.primary.copy(alpha = 0.5f),
arcColor = MaterialTheme.colors.primaryVariant
)
}
is Success -> {
val animations = it.invoke()
if (animations.isEmpty()) {
// Todo: Empty instead of Loading View
LoadingAnimation(
waveColor = MaterialTheme.colors.primary.copy(alpha = 0.5f),
arcColor = MaterialTheme.colors.primaryVariant
content = {
Scaffold(
topBar = {
Column(
modifier = Modifier
.fillMaxWidth()
.background(color = AppColor.Black001),
content = {
TopActionBar()
TabRow(
modifier = Modifier.fillMaxWidth(),
selectedTabIndex = pagerState.currentPage,
backgroundColor = Color.Transparent,
indicator = { tabPositions ->
TabRowDefaults.Indicator(
modifier = Modifier
.pagerTabIndicatorOffset(pagerState, tabPositions)
.padding(horizontal = 32.dp)
.clip(RoundedCornerShape(topStart = 12.dp, topEnd = 12.dp)),
height = 3.dp,
color = MaterialTheme.colors.primary
)
} else {
HorizontalPager(state = pagerState) {
ExploreContent(files = animations)
},
tabs = {
tabItems.forEachIndexed { index, title ->
Tab(
selected = index == pagerState.currentPage,
selectedContentColor = MaterialTheme.colors.primary,
unselectedContentColor = Color.White,
onClick = {
coroutineScope.launch {
pagerState.animateScrollToPage(index)
}
},
content = {
Text(
modifier = Modifier.padding(vertical = 12.dp),
text = title,
style = MaterialTheme.typography.h6.copy(
fontWeight = FontWeight.Normal
),
textAlign = TextAlign.Center,
)
}
)
}
}
)
}
)
},
content = { contentPadding ->
Crossfade(modifier = Modifier.padding(contentPadding), targetState = state) {
Box(
modifier = Modifier.fillMaxSize(),
contentAlignment = Alignment.Center,
content = {
when (it) {
Uninitialized -> {
LoadingAnimation(
waveColor = MaterialTheme.colors.primary.copy(alpha = 0.5f),
arcColor = MaterialTheme.colors.primaryVariant
)
}
is Loading -> {
LoadingAnimation(
waveColor = MaterialTheme.colors.primary.copy(alpha = 0.5f),
arcColor = MaterialTheme.colors.primaryVariant
)
}
is Success -> {
val animations = it.invoke()
if (animations.isEmpty()) {
// Todo: Empty instead of Loading View
LoadingAnimation(
waveColor = MaterialTheme.colors.primary.copy(alpha = 0.5f),
arcColor = MaterialTheme.colors.primaryVariant
)
} else {
HorizontalPager(state = pagerState) {
ExploreContent(
files = animations,
onShareClick = {
selectedAnimation = it
coroutineScope.launch {
bottomSheetState.animateTo(ModalBottomSheetValue.Expanded)
}
}
)
}
}
}
is Fail -> {
Timber.e(it.error.localizedMessage)
}
}
}
is Fail -> {
Timber.e(it.error.localizedMessage)
}
}
)
}
)
}
}
)
}
)
}
@@ -167,6 +222,7 @@ fun ExploreScreen(
@Composable
private fun ExploreContent(
modifier: Modifier = Modifier,
onShareClick: (Lottiefile) -> Unit,
files: List<Lottiefile>
) {
LazyColumn(
@@ -184,7 +240,8 @@ private fun ExploreContent(
items(items = files, key = { it.toString() }) { lottieFile ->
LottieFileItemView(
lottiefile = lottieFile,
onClick = {}
onClick = {},
onShareClick = onShareClick,
)
Divider(
@@ -31,7 +31,8 @@ import com.ericampire.android.androidstudycase.util.LottieFileProvider
fun LottieFileItemView(
modifier: Modifier = Modifier,
lottiefile: Lottiefile,
onClick: (Lottiefile) -> Unit
onClick: (Lottiefile) -> Unit,
onShareClick: (Lottiefile) -> Unit,
) {
Card(
modifier = modifier,
@@ -89,7 +90,9 @@ fun LottieFileItemView(
onLikeClick = { /*TODO*/ },
onCommentClick = { /*TODO*/ },
onAddCollectionClick = { /*TODO*/ },
onShareClick = { }
onShareClick = {
onShareClick(lottiefile)
}
)
}
)
@@ -203,7 +206,8 @@ fun LottiefileItemViewPreview(@PreviewParameter(LottieFileProvider::class) data:
content = {
LottieFileItemView(
lottiefile = data,
onClick = {}
onClick = {},
onShareClick = {},
)
}
)
@@ -0,0 +1,201 @@
package com.ericampire.android.androidstudycase.presentation.screen.explore.ui
import androidx.compose.animation.ExperimentalAnimationApi
import androidx.compose.foundation.background
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.*
import androidx.compose.material.*
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.rounded.InsertDriveFile
import androidx.compose.material.icons.rounded.Link
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.alpha
import androidx.compose.ui.draw.clip
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.vector.ImageVector
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.tooling.preview.PreviewParameter
import androidx.compose.ui.unit.dp
import com.ericampire.android.androidstudycase.R
import com.ericampire.android.androidstudycase.domain.entity.Lottiefile
import com.ericampire.android.androidstudycase.presentation.theme.AndroidStudyCaseTheme
import com.ericampire.android.androidstudycase.presentation.theme.AppColor
import com.ericampire.android.androidstudycase.util.LottieFileProvider
@Composable
fun ShareBottomSheet(
modifier: Modifier = Modifier,
isLoading: Boolean = false,
lottiefile: Lottiefile?,
onCopyLink: (String) -> Unit,
onShareJsonFile: (String?) -> Unit,
onShareGifFile: (String?) -> Unit,
onShareVideoFile: (String?) -> Unit,
) {
val progressBarAlpha = if (isLoading) 1f else 0f
Column(
modifier = modifier
.background(AppColor.Black001),
content = {
LinearProgressIndicator(
modifier = Modifier
.fillMaxWidth()
.alpha(progressBarAlpha)
)
Column(
modifier = modifier
.padding(16.dp)
.fillMaxWidth(),
verticalArrangement = Arrangement.spacedBy(16.dp),
horizontalAlignment = Alignment.CenterHorizontally,
content = {
Text(
text = stringResource(id = R.string.txt_share_animation),
textAlign = TextAlign.Center,
style = MaterialTheme.typography.h4.copy(
color = MaterialTheme.colors.onSurface
),
)
Column(
modifier = Modifier
.fillMaxWidth()
.clip(MaterialTheme.shapes.medium)
.background(AppColor.Arsenic)
.clickable { onCopyLink(lottiefile?.lottieUrl ?: "") }
.padding(18.dp),
verticalArrangement = Arrangement.spacedBy(10.dp),
horizontalAlignment = Alignment.CenterHorizontally,
content = {
Icon(
modifier = Modifier.size(48.dp),
tint = AppColor.SlateGray,
imageVector = Icons.Rounded.Link,
contentDescription = null
)
Text(
textAlign = TextAlign.Center,
text = stringResource(id = R.string.txt_share_animation),
style = MaterialTheme.typography.body1.copy(
color = MaterialTheme.colors.onSurface
),
)
}
)
Column(
modifier = Modifier
.clip(MaterialTheme.shapes.medium)
.fillMaxWidth(),
verticalArrangement = Arrangement.Center,
horizontalAlignment = Alignment.CenterHorizontally,
content = {
ShareItem(
modifier = Modifier.clickable {
onShareJsonFile(lottiefile?.lottieUrl)
},
title = "Lottie JSON",
fileExtension = ".json",
icon = Icons.Rounded.InsertDriveFile
)
Divider(modifier = Modifier
.fillMaxWidth()
.background(Color.DarkGray))
ShareItem(
modifier = Modifier.clickable {
onShareGifFile(lottiefile?.gifUrl)
},
title = "Animated GIF",
fileExtension = ".gif",
icon = Icons.Rounded.InsertDriveFile
)
Divider(modifier = Modifier
.fillMaxWidth()
.background(Color.DarkGray))
ShareItem(
modifier = Modifier.clickable {
onShareVideoFile(lottiefile?.videoUrl)
},
title = "Video MP4",
fileExtension = ".mp4",
icon = Icons.Rounded.InsertDriveFile
)
}
)
}
)
}
)
}
@Composable
private fun ShareItem(
modifier: Modifier = Modifier,
title: String,
fileExtension: String,
icon: ImageVector
) {
Row(
horizontalArrangement = Arrangement.SpaceBetween,
verticalAlignment = Alignment.CenterVertically,
modifier = modifier
.background(AppColor.Arsenic)
.padding(24.dp)
.fillMaxWidth(),
content = {
Row(
horizontalArrangement = Arrangement.spacedBy(24.dp),
verticalAlignment = Alignment.CenterVertically,
content = {
Icon(
imageVector = icon,
tint = AppColor.SlateGray,
contentDescription = null
)
Text(
text = title,
style = MaterialTheme.typography.h4.copy(
color = MaterialTheme.colors.onSurface
),
)
}
)
Text(
text = fileExtension,
style = MaterialTheme.typography.body1.copy(
color = Color.Gray
),
)
}
)
}
@ExperimentalAnimationApi
@ExperimentalMaterialApi
@Preview()
@Composable
fun ShareBottomSheetPreview(@PreviewParameter(LottieFileProvider::class) data: Lottiefile) {
AndroidStudyCaseTheme(darkTheme = true) {
Box(
modifier = Modifier.fillMaxSize(),
contentAlignment = Alignment.Center,
content = {
ShareBottomSheet(
lottiefile = data,
isLoading = true,
onCopyLink = {},
onShareGifFile = {},
onShareJsonFile = {},
onShareVideoFile = {}
)
}
)
}
}
@@ -1,6 +1,5 @@
package com.ericampire.android.androidstudycase.presentation.screen.home.ui
import androidx.compose.animation.AnimatedVisibility
import androidx.compose.animation.ExperimentalAnimationApi
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.*
@@ -12,6 +11,7 @@ import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.alpha
import androidx.compose.ui.draw.clip
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.style.TextAlign
@@ -32,6 +32,8 @@ fun LoginBottomSheet(
onLoginClick: () -> Unit,
) {
val progressBarAlpha = if (isLoading) 1f else 0f
val composition by rememberLottieComposition(
spec = LottieCompositionSpec.RawRes(R.raw.people_communicating)
)
@@ -45,9 +47,11 @@ fun LoginBottomSheet(
.clip(MaterialTheme.shapes.medium)
.background(AppColor.Black001),
content = {
AnimatedVisibility(isLoading) {
LinearProgressIndicator(modifier = Modifier.fillMaxWidth())
}
LinearProgressIndicator(
modifier = Modifier
.fillMaxWidth()
.alpha(progressBarAlpha)
)
Column(
modifier = Modifier.padding(24.dp),
@@ -100,7 +104,7 @@ fun LoginBottomSheet(
@ExperimentalAnimationApi
@ExperimentalMaterialApi
@Preview()
@Preview
@Composable
fun LoginDialogViewPreview() {
AndroidStudyCaseTheme(darkTheme = true) {
@@ -5,6 +5,8 @@ import androidx.compose.ui.graphics.Color
object AppColor {
val SlateGray = Color(0xFF606E7C)
val Arsenic = Color(0xFF3C3C3C)
val Teal = Color(0xFF1C7373)
val PaleBlue = Color(0xFFD3F6F6)
val PrimaryColor = Color(0xFF2BEAED)
+1
View File
@@ -5,4 +5,5 @@
<string name="txt_login_title">1000s of lottie animations</string>
<string name="txt_login_description">from top creators waiting to be discover, save and share from the palm of your hand</string>
<string name="txt_connect_with_facebook">Continue with Facebook</string>
<string name="txt_share_animation">Share animation</string>
</resources>
+6
View File
@@ -0,0 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<paths>
<external-path
name="external_files"
path="." />
</paths>