Skip to content

Commit

Permalink
auto scroll like a tiktok feature
Browse files Browse the repository at this point in the history
  • Loading branch information
osik2000 committed Dec 11, 2023
1 parent 745c05c commit 183d1fa
Show file tree
Hide file tree
Showing 6 changed files with 129 additions and 145 deletions.
3 changes: 3 additions & 0 deletions app/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,9 @@ dependencies {
implementation("com.google.firebase:firebase-auth")
implementation("com.google.android.gms:play-services-auth:20.7.0")

// Tiktok-like scrolling
implementation("com.google.accompanist:accompanist-pager:0.33.2-alpha")

// Coil - e.g. Photos from URL
implementation("io.coil-kt:coil-compose:2.5.0")
implementation("io.coil-kt:coil-gif:2.5.0")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -43,14 +43,14 @@ class DatabaseService {
}
}

fun getUserById(id: String, onSuccess : () -> Unit = {}, onFail : () -> Unit = {}) : User {
fun getUserById(id: String, onSuccess : (User) -> Unit = {User()}, onFail : () -> Unit = {}) : User {
Log.d("DataService", "getUserById: $id")

var user = User()
database.getReference("users").child(id).get().addOnSuccessListener {
user = it.getValue(User::class.java) ?: User.getUserFromFirebaseUser(Firebase.auth.currentUser)
Log.d("DataService", "Got value $loggedUser")
onSuccess()
onSuccess(user)
}.addOnFailureListener{
Log.e("DataService", "Error getting data", it)
user = User.getUserFromFirebaseUser(Firebase.auth.currentUser)
Expand Down
Original file line number Diff line number Diff line change
@@ -1,119 +1,88 @@
package pl.pawelosinski.skatefreak.ui.common

import android.annotation.SuppressLint
import android.net.Uri
import android.view.ViewGroup.LayoutParams.MATCH_PARENT
import android.widget.FrameLayout
import androidx.annotation.OptIn
import androidx.compose.foundation.clickable
import androidx.compose.foundation.interaction.MutableInteractionSource
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.runtime.Composable
import androidx.compose.runtime.DisposableEffect
import androidx.compose.runtime.remember
import androidx.compose.foundation.layout.*
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.PlayArrow
import androidx.compose.material3.Icon
import androidx.compose.material3.IconButton
import androidx.compose.runtime.*
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.unit.dp
import androidx.compose.ui.viewinterop.AndroidView
import androidx.media3.common.C
import androidx.media3.common.MediaItem
import androidx.media3.common.Player
import androidx.media3.common.util.UnstableApi
import androidx.media3.datasource.DataSource
import androidx.media3.datasource.DefaultDataSource
import androidx.media3.exoplayer.ExoPlayer
import androidx.media3.exoplayer.source.ProgressiveMediaSource
import androidx.media3.ui.AspectRatioFrameLayout
import androidx.media3.ui.PlayerView

@OptIn(UnstableApi::class) @SuppressLint("OpaqueUnitKey")
@OptIn(UnstableApi::class)
@SuppressLint("OpaqueUnitKey")
@Composable
fun VideoPlayer(videoUrl: String) {
val context = LocalContext.current
val exoPlayer = remember {
ExoPlayer.Builder(context).build().apply {
setMediaItem(MediaItem.fromUri(videoUrl))
prepare()
playWhenReady = false
repeatMode = Player.REPEAT_MODE_OFF
}
}

var isPlaying by remember { mutableStateOf(false) }
val interactionSource = remember { MutableInteractionSource() }

DisposableEffect(


Box(modifier = Modifier.fillMaxWidth()) {
AndroidView(
modifier = Modifier
.fillMaxWidth()
.clickable(
interactionSource = interactionSource,
indication = null, // brak wizualnego wskaźnika kliknięcia
onClick = {
exoPlayer.playWhenReady = !exoPlayer.playWhenReady
modifier = Modifier
.fillMaxWidth()
.clickable(
interactionSource = interactionSource,
indication = null, // brak wizualnego wskaźnika kliknięcia
onClick = {
isPlaying = !isPlaying
exoPlayer.playWhenReady = !exoPlayer.playWhenReady
}
),
factory = { context ->
PlayerView(context).apply {
player = exoPlayer
useController = false
}
),
factory = { context ->
PlayerView(context).apply {
player = exoPlayer
useController = false
},
update = { playerView ->
playerView.player = exoPlayer
}
},
update = { playerView ->
playerView.player = exoPlayer
}
)) {
onDispose {
exoPlayer.release()
}
}
exoPlayer.repeatMode = Player.REPEAT_MODE_OFF


}



/**
* Video player
*
* @param uri
*/
@SuppressLint("OpaqueUnitKey")
@Composable
@androidx.annotation.OptIn(androidx.media3.common.util.UnstableApi::class)
fun VideoPlayer2(uri: Uri) {
val context = LocalContext.current

val exoPlayer = remember {
ExoPlayer.Builder(context)
.build()
.apply {
val defaultDataSourceFactory = DefaultDataSource.Factory(context)
val dataSourceFactory: DataSource.Factory = DefaultDataSource.Factory(
context,
defaultDataSourceFactory
)
if (!isPlaying) {
IconButton(
onClick = {
isPlaying = true
exoPlayer.playWhenReady = true
},
modifier = Modifier.align(Alignment.Center)
) {
Icon(
imageVector = Icons.Default.PlayArrow,
contentDescription = "Play",
modifier = Modifier.size(48.dp),
tint = Color.White
)
val source = ProgressiveMediaSource.Factory(dataSourceFactory)
.createMediaSource(MediaItem.fromUri(uri))

setMediaSource(source)
prepare()
}
}

exoPlayer.playWhenReady = true
exoPlayer.videoScalingMode = C.VIDEO_SCALING_MODE_SCALE_TO_FIT_WITH_CROPPING
exoPlayer.repeatMode = Player.REPEAT_MODE_ONE

DisposableEffect(
AndroidView(factory = {
PlayerView(context).apply {
hideController()
useController = false
resizeMode = AspectRatioFrameLayout.RESIZE_MODE_ZOOM

player = exoPlayer
layoutParams = FrameLayout.LayoutParams(MATCH_PARENT, MATCH_PARENT)
}
DisposableEffect(Unit) {
onDispose {
exoPlayer.release()
}
})
) {
onDispose { exoPlayer.release() }
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -8,16 +8,13 @@ import androidx.compose.material.icons.filled.Favorite
import androidx.compose.material.icons.filled.FavoriteBorder
import androidx.compose.material.icons.filled.MoreVert
import androidx.compose.material.icons.filled.Send
import androidx.compose.material.icons.outlined.Favorite
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.graphics.Color
import androidx.compose.ui.graphics.vector.ImageVector
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.unit.dp
import pl.pawelosinski.skatefreak.model.TrickRecord
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,10 @@ import androidx.compose.material3.Icon
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text
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.graphics.Color
Expand All @@ -27,6 +31,7 @@ import coil.request.ImageRequest
import coil.transform.CircleCropTransformation
import pl.pawelosinski.skatefreak.R
import pl.pawelosinski.skatefreak.model.TrickRecord
import pl.pawelosinski.skatefreak.model.User
import pl.pawelosinski.skatefreak.service.databaseService

/**
Expand All @@ -37,11 +42,15 @@ import pl.pawelosinski.skatefreak.service.databaseService
*/
@Composable
fun FooterUserData(trickRecord: TrickRecord, modifier: Modifier) {
val horizontalPadding = 10.dp
Column(
modifier = modifier,
verticalArrangement = Arrangement.Center
) {
val trickCreator = databaseService.getUserById(trickRecord.userID)
var trickCreator by remember { mutableStateOf(User()) }
databaseService.getUserById(trickRecord.userID, onSuccess = {
trickCreator = it
})

Row(
verticalAlignment = Alignment.CenterVertically,
Expand Down Expand Up @@ -75,17 +84,17 @@ fun FooterUserData(trickRecord: TrickRecord, modifier: Modifier) {
style = MaterialTheme.typography.labelMedium
)

Spacer(modifier = Modifier.width(horizontalPadding))
Icon(
modifier = Modifier.size(20.dp),
imageVector = Icons.Outlined.Favorite,
contentDescription = "Bookmark"
)
Icon(
modifier = Modifier.size(15.dp),
imageVector = Icons.Outlined.Add,
contentDescription = ""
)
// Spacer(modifier = Modifier.width(horizontalPadding))
// Icon(
// modifier = Modifier.size(20.dp),
// imageVector = Icons.Outlined.Favorite,
// contentDescription = "Bookmark"
// )
// Icon(
// modifier = Modifier.size(15.dp),
// imageVector = Icons.Outlined.Add,
// contentDescription = ""
// )
}
Spacer(modifier = Modifier.height(horizontalPadding))

Expand Down
Original file line number Diff line number Diff line change
@@ -1,66 +1,72 @@
package pl.pawelosinski.skatefreak.ui.tricks.record

import androidx.annotation.OptIn
import androidx.compose.foundation.background
import androidx.compose.foundation.clickable
import androidx.compose.foundation.gestures.FlingBehavior
import androidx.compose.foundation.gestures.ScrollScope
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.foundation.lazy.LazyListState
import androidx.compose.foundation.lazy.items
import androidx.compose.foundation.lazy.rememberLazyListState
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.PlayArrow
import androidx.compose.material3.Divider
import androidx.compose.material3.Text
import androidx.compose.material3.Icon
import androidx.compose.runtime.Composable
import androidx.compose.runtime.derivedStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope
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.unit.dp
import androidx.media3.common.util.UnstableApi
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.isActive
import kotlinx.coroutines.launch
import pl.pawelosinski.skatefreak.local.allTrickRecords
import pl.pawelosinski.skatefreak.model.TrickRecord
import pl.pawelosinski.skatefreak.service.LocalDataService
import pl.pawelosinski.skatefreak.ui.common.VideoPlayer
import kotlin.math.abs

@OptIn(UnstableApi::class) @Composable
fun TrickRecordComposable(trickRecord: TrickRecord) {
val trickInfo = LocalDataService.getTrickInfo(trickRecord.trickID)


Column {
Text(text = "ID: ${trickRecord.id}")
Text(text = "Name: ${trickInfo.name}")
Text(text = "Description: ${trickRecord.userDescription}")
Text(text = "Difficulty: ${trickInfo.difficulty}")
VideoPlayer(videoUrl = trickRecord.videoUrl)
}
}



val horizontalPadding = 10.dp
@Composable
fun TrickRecordsScreen() {
val trickRecords = allTrickRecords
Box(
Modifier
.clip(RoundedCornerShape(bottomEnd = 10.dp, bottomStart = 10.dp))
.background(color = Color.Black)
val listState = rememberLazyListState()
val coroutineScope = rememberCoroutineScope()

LazyColumn(
state = listState,
flingBehavior = snapFlingBehavior(listState, coroutineScope),
modifier = Modifier.fillMaxSize()
) {
LazyColumn {
items(trickRecords.size) { index ->
Box(
modifier = Modifier
.fillParentMaxSize()
) {
VideoPlayer(videoUrl = trickRecords[index].videoUrl)
// VideoPlayer2(uri = Uri.parse(trickRecords[index].videoUrl))
Column(Modifier.align(Alignment.BottomStart)) {
TrickRecordsFooter(trickRecords[index])
Divider()
}
items(trickRecords) { trickRecord ->
Box(
modifier = Modifier
.fillParentMaxSize()
) {
VideoPlayer(videoUrl = trickRecord.videoUrl)
Column(Modifier.align(Alignment.BottomStart)) {
TrickRecordsFooter(trickRecord)
Divider()
}
}
}
}
}

fun snapFlingBehavior(listState: LazyListState, coroutineScope: CoroutineScope) = object : FlingBehavior {
override suspend fun ScrollScope.performFling(initialVelocity: Float): Float {
val layoutInfo = listState.layoutInfo
val visibleItemsInfo = layoutInfo.visibleItemsInfo

val visibleItemClosestToCenter = visibleItemsInfo.minByOrNull {
abs(it.offset + it.size / 2 - layoutInfo.viewportEndOffset / 2)
} ?: return initialVelocity

val targetIndex = visibleItemClosestToCenter.index
coroutineScope.launch {
listState.animateScrollToItem(index = targetIndex)
}

return initialVelocity
}
}

0 comments on commit 183d1fa

Please sign in to comment.