[add] Implement Bilibili style
This commit is contained in:
@@ -2,6 +2,7 @@ plugins {
|
|||||||
alias(libs.plugins.android.application)
|
alias(libs.plugins.android.application)
|
||||||
alias(libs.plugins.kotlin.android)
|
alias(libs.plugins.kotlin.android)
|
||||||
alias(libs.plugins.kotlin.compose)
|
alias(libs.plugins.kotlin.compose)
|
||||||
|
kotlin("plugin.serialization") version "1.9.0"
|
||||||
}
|
}
|
||||||
|
|
||||||
android {
|
android {
|
||||||
|
|||||||
@@ -6,8 +6,6 @@ import androidx.compose.runtime.setValue
|
|||||||
import com.acitelight.aether.model.Video
|
import com.acitelight.aether.model.Video
|
||||||
|
|
||||||
object Global {
|
object Global {
|
||||||
var videoName: String = ""
|
|
||||||
var videoClass: String = ""
|
|
||||||
var loggedIn by mutableStateOf(false)
|
var loggedIn by mutableStateOf(false)
|
||||||
var video: Video? = null
|
var sameClassVideos: List<Video>? = null
|
||||||
}
|
}
|
||||||
@@ -116,7 +116,7 @@ fun AppNavigation() {
|
|||||||
modifier = if(shouldShowBottomBar)Modifier.padding(innerPadding) else Modifier.padding(0.dp)
|
modifier = if(shouldShowBottomBar)Modifier.padding(innerPadding) else Modifier.padding(0.dp)
|
||||||
) {
|
) {
|
||||||
composable(Screen.Home.route) {
|
composable(Screen.Home.route) {
|
||||||
HomeScreen()
|
HomeScreen(navController = navController)
|
||||||
}
|
}
|
||||||
composable(Screen.Video.route) {
|
composable(Screen.Video.route) {
|
||||||
VideoScreen(navController = navController)
|
VideoScreen(navController = navController)
|
||||||
@@ -159,8 +159,6 @@ fun BottomNavigationBar(navController: NavController) {
|
|||||||
Screen.Me
|
Screen.Me
|
||||||
) else listOf(
|
) else listOf(
|
||||||
Screen.Home,
|
Screen.Home,
|
||||||
Screen.Video,
|
|
||||||
Screen.Comic,
|
|
||||||
Screen.Transmission,
|
Screen.Transmission,
|
||||||
Screen.Me
|
Screen.Me
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -25,4 +25,5 @@ class Video constructor(
|
|||||||
KeyImage(url = "${ApiClient.base}api/video/$klass/$id/gallery/$it?token=$token", key = "$klass/$id/gallery/$it")
|
KeyImage(url = "${ApiClient.base}api/video/$klass/$id/gallery/$it?token=$token", key = "$klass/$id/gallery/$it")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
package com.acitelight.aether.model
|
||||||
|
|
||||||
|
import kotlinx.serialization.Serializable
|
||||||
|
|
||||||
|
@Serializable
|
||||||
|
data class VideoQueryIndex(
|
||||||
|
val klass: String,
|
||||||
|
val id: String
|
||||||
|
)
|
||||||
@@ -0,0 +1,94 @@
|
|||||||
|
package com.acitelight.aether.service
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
|
import com.acitelight.aether.model.Video
|
||||||
|
import com.acitelight.aether.model.VideoQueryIndex
|
||||||
|
import kotlinx.coroutines.Dispatchers
|
||||||
|
import kotlinx.coroutines.flow.MutableStateFlow
|
||||||
|
import kotlinx.coroutines.flow.StateFlow
|
||||||
|
import kotlinx.coroutines.sync.Mutex
|
||||||
|
import kotlinx.coroutines.sync.withLock
|
||||||
|
import kotlinx.coroutines.withContext
|
||||||
|
import kotlinx.serialization.json.*
|
||||||
|
import java.io.File
|
||||||
|
import java.io.FileNotFoundException
|
||||||
|
import java.io.IOException
|
||||||
|
|
||||||
|
object RecentManager
|
||||||
|
{
|
||||||
|
private val mutex = Mutex()
|
||||||
|
|
||||||
|
suspend fun readFile(context: Context, filename: String): String {
|
||||||
|
return withContext(Dispatchers.IO) {
|
||||||
|
try {
|
||||||
|
val file = File(context.filesDir, filename)
|
||||||
|
val content = file.readText()
|
||||||
|
content
|
||||||
|
} catch (e: FileNotFoundException) {
|
||||||
|
"[]"
|
||||||
|
} catch (e: IOException) {
|
||||||
|
"[]"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
suspend fun writeFile(context: Context, filename: String, content: String) {
|
||||||
|
withContext(Dispatchers.IO) {
|
||||||
|
try {
|
||||||
|
val file = File(context.filesDir, filename)
|
||||||
|
file.writeText(content)
|
||||||
|
|
||||||
|
} catch (e: IOException) {
|
||||||
|
e.printStackTrace()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
suspend fun Query(context: Context): List<VideoQueryIndex>
|
||||||
|
{
|
||||||
|
val content = readFile(context, "recent.json")
|
||||||
|
try{
|
||||||
|
val r = Json.decodeFromString<List<VideoQueryIndex>>(content)
|
||||||
|
|
||||||
|
_recent.value = r.map{
|
||||||
|
MediaManager.queryVideo(it.klass, it.id)
|
||||||
|
}
|
||||||
|
return r
|
||||||
|
}catch (e: Exception)
|
||||||
|
{
|
||||||
|
print(e.message)
|
||||||
|
}
|
||||||
|
|
||||||
|
return listOf()
|
||||||
|
}
|
||||||
|
|
||||||
|
suspend fun Push(context: Context, video: VideoQueryIndex)
|
||||||
|
{
|
||||||
|
mutex.withLock{
|
||||||
|
val content = readFile(context, "recent.json")
|
||||||
|
var o = Json.decodeFromString<List<VideoQueryIndex>>(content).toMutableList();
|
||||||
|
|
||||||
|
if(o.contains(video))
|
||||||
|
{
|
||||||
|
val temp = o[0]
|
||||||
|
val index = o.indexOf(video)
|
||||||
|
o[0] = o[index]
|
||||||
|
o[index] = temp
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
o.add(0, video)
|
||||||
|
}
|
||||||
|
|
||||||
|
if(o.size >= 21)
|
||||||
|
o.removeAt(o.size - 1)
|
||||||
|
_recent.value = o.map{
|
||||||
|
MediaManager.queryVideo(it.klass, it.id)
|
||||||
|
}
|
||||||
|
writeFile(context, "recent.json", Json.encodeToString(o))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private val _recent = MutableStateFlow<List<Video>>(emptyList())
|
||||||
|
val recent: StateFlow<List<Video>> = _recent
|
||||||
|
}
|
||||||
@@ -4,20 +4,64 @@ import androidx.compose.foundation.layout.Arrangement
|
|||||||
import androidx.compose.foundation.layout.Column
|
import androidx.compose.foundation.layout.Column
|
||||||
import androidx.compose.foundation.layout.Spacer
|
import androidx.compose.foundation.layout.Spacer
|
||||||
import androidx.compose.foundation.layout.fillMaxSize
|
import androidx.compose.foundation.layout.fillMaxSize
|
||||||
|
import androidx.compose.foundation.layout.fillMaxWidth
|
||||||
import androidx.compose.foundation.layout.height
|
import androidx.compose.foundation.layout.height
|
||||||
|
import androidx.compose.foundation.layout.padding
|
||||||
|
import androidx.compose.foundation.lazy.LazyColumn
|
||||||
import androidx.compose.material3.Button
|
import androidx.compose.material3.Button
|
||||||
|
import androidx.compose.material3.DividerDefaults
|
||||||
|
import androidx.compose.material3.HorizontalDivider
|
||||||
|
import androidx.compose.material3.MaterialTheme
|
||||||
import androidx.compose.material3.Text
|
import androidx.compose.material3.Text
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.runtime.collectAsState
|
||||||
import androidx.compose.runtime.getValue
|
import androidx.compose.runtime.getValue
|
||||||
import androidx.compose.ui.Alignment
|
import androidx.compose.ui.Alignment
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.draw.alpha
|
||||||
|
import androidx.compose.ui.tooling.preview.Preview
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import androidx.compose.ui.unit.sp
|
import androidx.compose.ui.unit.sp
|
||||||
import androidx.lifecycle.viewmodel.compose.viewModel
|
import androidx.lifecycle.viewmodel.compose.viewModel
|
||||||
|
import androidx.navigation.NavController
|
||||||
|
import com.acitelight.aether.Global
|
||||||
|
import com.acitelight.aether.service.RecentManager
|
||||||
import com.acitelight.aether.viewModel.HomeScreenViewModel
|
import com.acitelight.aether.viewModel.HomeScreenViewModel
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun HomeScreen(homeScreenViewModel: HomeScreenViewModel = viewModel())
|
fun HomeScreen(homeScreenViewModel: HomeScreenViewModel = viewModel(), navController: NavController)
|
||||||
{
|
{
|
||||||
|
if(Global.loggedIn)
|
||||||
|
homeScreenViewModel.Init()
|
||||||
|
val recent by RecentManager.recent.collectAsState()
|
||||||
|
|
||||||
|
LazyColumn(modifier = Modifier.fillMaxWidth())
|
||||||
|
{
|
||||||
|
item()
|
||||||
|
{
|
||||||
|
Column {
|
||||||
|
Text(
|
||||||
|
text = "Recent",
|
||||||
|
style = MaterialTheme.typography.headlineMedium,
|
||||||
|
modifier = Modifier.padding(16.dp).align(Alignment.Start)
|
||||||
|
)
|
||||||
|
|
||||||
|
HorizontalDivider(Modifier.padding(8.dp), 2.dp, DividerDefaults.color)
|
||||||
|
|
||||||
|
for(i in recent)
|
||||||
|
{
|
||||||
|
MiniVideoCard(
|
||||||
|
modifier = Modifier
|
||||||
|
.padding(horizontal = 12.dp),
|
||||||
|
i,
|
||||||
|
{
|
||||||
|
val route = "video_player_route/${ "${i.klass}/${i.id}".toHex() }"
|
||||||
|
navController.navigate(route)
|
||||||
|
})
|
||||||
|
HorizontalDivider(Modifier.padding(vertical = 8.dp).alpha(0.25f), 1.dp, DividerDefaults.color)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -1,91 +1,56 @@
|
|||||||
package com.acitelight.aether.view
|
package com.acitelight.aether.view
|
||||||
|
|
||||||
import android.R
|
|
||||||
import android.app.Activity
|
import android.app.Activity
|
||||||
import android.content.pm.ActivityInfo
|
import android.content.pm.ActivityInfo
|
||||||
import android.content.res.Configuration
|
import android.content.res.Configuration
|
||||||
import android.text.Layout
|
|
||||||
import android.widget.Toast
|
|
||||||
import androidx.activity.compose.BackHandler
|
import androidx.activity.compose.BackHandler
|
||||||
import androidx.compose.animation.AnimatedVisibility
|
import androidx.compose.animation.AnimatedVisibility
|
||||||
import androidx.compose.animation.fadeIn
|
import androidx.compose.animation.fadeIn
|
||||||
import androidx.compose.animation.fadeOut
|
import androidx.compose.animation.fadeOut
|
||||||
import androidx.compose.animation.scaleIn
|
|
||||||
import androidx.compose.animation.scaleOut
|
|
||||||
import androidx.compose.animation.slideInVertically
|
import androidx.compose.animation.slideInVertically
|
||||||
import androidx.compose.animation.slideOutVertically
|
import androidx.compose.animation.slideOutVertically
|
||||||
import androidx.compose.foundation.background
|
import androidx.compose.foundation.background
|
||||||
import androidx.compose.foundation.layout.Box
|
import androidx.compose.foundation.layout.Box
|
||||||
import androidx.compose.foundation.layout.WindowInsets
|
|
||||||
import androidx.compose.foundation.layout.asPaddingValues
|
|
||||||
import androidx.compose.foundation.layout.fillMaxSize
|
|
||||||
import androidx.compose.foundation.layout.fillMaxWidth
|
import androidx.compose.foundation.layout.fillMaxWidth
|
||||||
import androidx.compose.foundation.layout.safeDrawing
|
|
||||||
import androidx.compose.material.icons.Icons
|
import androidx.compose.material.icons.Icons
|
||||||
import androidx.compose.material.icons.filled.Pause
|
import androidx.compose.material.icons.filled.Pause
|
||||||
import androidx.compose.material.icons.filled.PlayArrow
|
import androidx.compose.material.icons.filled.PlayArrow
|
||||||
import androidx.compose.material3.Icon
|
import androidx.compose.material3.Icon
|
||||||
import androidx.compose.material3.IconButton
|
import androidx.compose.material3.IconButton
|
||||||
import androidx.compose.material3.SegmentedButtonDefaults.Icon
|
|
||||||
import androidx.compose.material3.Slider
|
import androidx.compose.material3.Slider
|
||||||
import androidx.compose.material3.Text
|
import androidx.compose.material3.Text
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.DisposableEffect
|
|
||||||
import androidx.compose.runtime.LaunchedEffect
|
|
||||||
import androidx.compose.runtime.getValue
|
|
||||||
import androidx.compose.runtime.mutableLongStateOf
|
|
||||||
import androidx.compose.runtime.mutableStateOf
|
|
||||||
import androidx.compose.runtime.remember
|
|
||||||
import androidx.compose.runtime.setValue
|
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.graphics.Color
|
import androidx.compose.ui.graphics.Color
|
||||||
import androidx.compose.ui.platform.LocalContext
|
import androidx.compose.ui.platform.LocalContext
|
||||||
import androidx.compose.ui.platform.LocalView
|
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import androidx.compose.ui.viewinterop.AndroidView
|
import androidx.compose.ui.viewinterop.AndroidView
|
||||||
import androidx.core.view.WindowCompat
|
|
||||||
import androidx.core.view.WindowInsetsCompat
|
|
||||||
import androidx.core.view.WindowInsetsControllerCompat
|
|
||||||
import androidx.lifecycle.viewmodel.compose.viewModel
|
import androidx.lifecycle.viewmodel.compose.viewModel
|
||||||
import androidx.media3.common.MediaItem
|
|
||||||
import androidx.media3.common.Player
|
|
||||||
import androidx.media3.exoplayer.ExoPlayer
|
import androidx.media3.exoplayer.ExoPlayer
|
||||||
import androidx.media3.ui.PlayerView
|
import androidx.media3.ui.PlayerView
|
||||||
import androidx.navigation.NavHostController
|
import androidx.navigation.NavHostController
|
||||||
import com.acitelight.aether.service.MediaManager
|
|
||||||
import com.acitelight.aether.viewModel.VideoPlayerViewModel
|
import com.acitelight.aether.viewModel.VideoPlayerViewModel
|
||||||
|
|
||||||
import androidx.compose.foundation.background
|
|
||||||
import androidx.compose.foundation.border
|
|
||||||
import androidx.compose.foundation.clickable
|
|
||||||
import androidx.compose.foundation.combinedClickable
|
|
||||||
import androidx.compose.foundation.gestures.detectDragGestures
|
import androidx.compose.foundation.gestures.detectDragGestures
|
||||||
import androidx.compose.foundation.gestures.detectTapGestures
|
import androidx.compose.foundation.gestures.detectTapGestures
|
||||||
import androidx.compose.foundation.interaction.MutableInteractionSource
|
|
||||||
import androidx.compose.foundation.layout.Arrangement
|
import androidx.compose.foundation.layout.Arrangement
|
||||||
import androidx.compose.foundation.layout.Box
|
|
||||||
import androidx.compose.foundation.layout.Column
|
import androidx.compose.foundation.layout.Column
|
||||||
import androidx.compose.foundation.layout.PaddingValues
|
import androidx.compose.foundation.layout.PaddingValues
|
||||||
import androidx.compose.foundation.layout.Row
|
import androidx.compose.foundation.layout.Row
|
||||||
import androidx.compose.foundation.layout.Spacer
|
import androidx.compose.foundation.layout.Spacer
|
||||||
import androidx.compose.foundation.layout.absoluteOffset
|
|
||||||
import androidx.compose.foundation.layout.fillMaxHeight
|
import androidx.compose.foundation.layout.fillMaxHeight
|
||||||
import androidx.compose.foundation.layout.fillMaxSize
|
import androidx.compose.foundation.layout.fillMaxSize
|
||||||
import androidx.compose.foundation.layout.height
|
import androidx.compose.foundation.layout.height
|
||||||
import androidx.compose.foundation.layout.heightIn
|
import androidx.compose.foundation.layout.heightIn
|
||||||
import androidx.compose.foundation.layout.offset
|
|
||||||
import androidx.compose.foundation.layout.padding
|
import androidx.compose.foundation.layout.padding
|
||||||
import androidx.compose.foundation.layout.requiredWidthIn
|
|
||||||
import androidx.compose.foundation.layout.size
|
import androidx.compose.foundation.layout.size
|
||||||
import androidx.compose.foundation.layout.width
|
import androidx.compose.foundation.layout.width
|
||||||
import androidx.compose.foundation.layout.widthIn
|
|
||||||
import androidx.compose.foundation.lazy.LazyColumn
|
import androidx.compose.foundation.lazy.LazyColumn
|
||||||
import androidx.compose.foundation.lazy.LazyRow
|
import androidx.compose.foundation.lazy.LazyRow
|
||||||
import androidx.compose.foundation.lazy.items
|
import androidx.compose.foundation.lazy.items
|
||||||
import androidx.compose.foundation.shape.CircleShape
|
import androidx.compose.foundation.lazy.rememberLazyListState
|
||||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||||
import androidx.compose.material.icons.automirrored.filled.ArrowBack
|
import androidx.compose.material.icons.automirrored.filled.ArrowBack
|
||||||
import androidx.compose.material.icons.filled.ArrowBack
|
|
||||||
import androidx.compose.material.icons.filled.FastForward
|
import androidx.compose.material.icons.filled.FastForward
|
||||||
import androidx.compose.material.icons.filled.Fullscreen
|
import androidx.compose.material.icons.filled.Fullscreen
|
||||||
import androidx.compose.material.icons.filled.Info
|
import androidx.compose.material.icons.filled.Info
|
||||||
@@ -93,44 +58,42 @@ import androidx.compose.material.icons.filled.Share
|
|||||||
import androidx.compose.material.icons.filled.Star
|
import androidx.compose.material.icons.filled.Star
|
||||||
import androidx.compose.material.icons.filled.ThumbDown
|
import androidx.compose.material.icons.filled.ThumbDown
|
||||||
import androidx.compose.material.icons.filled.ThumbUp
|
import androidx.compose.material.icons.filled.ThumbUp
|
||||||
import androidx.compose.material3.Divider
|
import androidx.compose.material3.Card
|
||||||
|
import androidx.compose.material3.CardColors
|
||||||
import androidx.compose.material3.DividerDefaults
|
import androidx.compose.material3.DividerDefaults
|
||||||
import androidx.compose.material3.ExperimentalMaterial3Api
|
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||||
import androidx.compose.material3.HorizontalDivider
|
import androidx.compose.material3.HorizontalDivider
|
||||||
|
import androidx.compose.material3.MaterialTheme
|
||||||
|
import androidx.compose.material3.VerticalDivider
|
||||||
import androidx.compose.material3.SliderDefaults
|
import androidx.compose.material3.SliderDefaults
|
||||||
import androidx.compose.material3.Tab
|
import androidx.compose.material3.Tab
|
||||||
import androidx.compose.material3.TabRow
|
import androidx.compose.material3.TabRow
|
||||||
|
import androidx.compose.runtime.getValue
|
||||||
import androidx.compose.runtime.mutableFloatStateOf
|
import androidx.compose.runtime.mutableFloatStateOf
|
||||||
|
import androidx.compose.runtime.mutableStateOf
|
||||||
|
import androidx.compose.runtime.remember
|
||||||
|
import androidx.compose.runtime.setValue
|
||||||
import androidx.compose.ui.Alignment
|
import androidx.compose.ui.Alignment
|
||||||
import androidx.compose.ui.draw.alpha
|
import androidx.compose.ui.draw.alpha
|
||||||
import androidx.compose.ui.draw.clip
|
import androidx.compose.ui.draw.clip
|
||||||
|
import androidx.compose.ui.geometry.Offset
|
||||||
import androidx.compose.ui.graphics.Brush
|
import androidx.compose.ui.graphics.Brush
|
||||||
import androidx.compose.ui.graphics.TransformOrigin
|
import androidx.compose.ui.input.nestedscroll.NestedScrollConnection
|
||||||
|
import androidx.compose.ui.input.nestedscroll.NestedScrollSource
|
||||||
|
import androidx.compose.ui.input.nestedscroll.nestedScroll
|
||||||
import androidx.compose.ui.input.pointer.pointerInput
|
import androidx.compose.ui.input.pointer.pointerInput
|
||||||
import androidx.compose.ui.layout.ContentScale
|
import androidx.compose.ui.layout.ContentScale
|
||||||
import androidx.compose.ui.layout.ModifierLocalBeyondBoundsLayout
|
import androidx.compose.ui.layout.onGloballyPositioned
|
||||||
import androidx.compose.ui.platform.LocalConfiguration
|
import androidx.compose.ui.platform.LocalConfiguration
|
||||||
|
import androidx.compose.ui.platform.LocalDensity
|
||||||
import androidx.compose.ui.text.font.FontWeight
|
import androidx.compose.ui.text.font.FontWeight
|
||||||
import androidx.compose.ui.text.style.LineHeightStyle
|
|
||||||
import androidx.compose.ui.tooling.preview.Preview
|
|
||||||
import androidx.compose.ui.tooling.preview.PreviewParameter
|
|
||||||
import androidx.compose.ui.unit.dp
|
|
||||||
import androidx.compose.ui.unit.sp
|
import androidx.compose.ui.unit.sp
|
||||||
import coil3.compose.AsyncImage
|
import coil3.compose.AsyncImage
|
||||||
import coil3.request.ImageRequest
|
import coil3.request.ImageRequest
|
||||||
import com.acitelight.aether.BottomNavigationBar
|
|
||||||
import com.acitelight.aether.Global
|
import com.acitelight.aether.Global
|
||||||
import com.acitelight.aether.ToggleFullScreen
|
import com.acitelight.aether.ToggleFullScreen
|
||||||
import com.acitelight.aether.model.KeyImage
|
import com.acitelight.aether.model.KeyImage
|
||||||
import com.acitelight.aether.ui.theme.AetherTheme
|
import com.acitelight.aether.model.Video
|
||||||
import kotlinx.coroutines.CoroutineScope
|
|
||||||
import kotlinx.coroutines.Dispatchers
|
|
||||||
import kotlinx.coroutines.TimeoutCancellationException
|
|
||||||
import kotlinx.coroutines.delay
|
|
||||||
import kotlinx.coroutines.launch
|
|
||||||
import kotlinx.coroutines.withTimeout
|
|
||||||
import org.jetbrains.annotations.Async
|
|
||||||
import java.nio.file.WatchEvent
|
|
||||||
|
|
||||||
fun formatTime(ms: Long): String {
|
fun formatTime(ms: Long): String {
|
||||||
if (ms <= 0) return "00:00:00"
|
if (ms <= 0) return "00:00:00"
|
||||||
@@ -238,9 +201,10 @@ fun VideoPlayer(
|
|||||||
videoId: String,
|
videoId: String,
|
||||||
navController: NavHostController
|
navController: NavHostController
|
||||||
) {
|
) {
|
||||||
videoPlayerViewModel.Init(videoId);
|
videoPlayerViewModel.Init(videoId)
|
||||||
videoPlayerViewModel.startListen()
|
|
||||||
|
|
||||||
|
if(videoPlayerViewModel.startPlaying)
|
||||||
|
{
|
||||||
if (isLandscape()) {
|
if (isLandscape()) {
|
||||||
VideoPlayerLandscape(videoPlayerViewModel)
|
VideoPlayerLandscape(videoPlayerViewModel)
|
||||||
}
|
}
|
||||||
@@ -249,19 +213,16 @@ fun VideoPlayer(
|
|||||||
VideoPlayerPortal(videoPlayerViewModel, navController)
|
VideoPlayerPortal(videoPlayerViewModel, navController)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun VideoPlayerPortal(videoPlayerViewModel: VideoPlayerViewModel, navController: NavHostController?) {
|
fun PortalCorePlayer(modifier: Modifier, videoPlayerViewModel: VideoPlayerViewModel, cover: Float)
|
||||||
|
{
|
||||||
|
val exoPlayer: ExoPlayer = videoPlayerViewModel._player!!;
|
||||||
val context = LocalContext.current
|
val context = LocalContext.current
|
||||||
val activity = context as? Activity
|
val activity = context as? Activity
|
||||||
val exoPlayer: ExoPlayer = videoPlayerViewModel._player!!;
|
|
||||||
val configuration = LocalConfiguration.current
|
|
||||||
val screenHeight = configuration.screenHeightDp.dp;
|
|
||||||
|
|
||||||
ToggleFullScreen(false)
|
Box(modifier)
|
||||||
Column()
|
|
||||||
{
|
|
||||||
Box(modifier = Modifier.padding(top = 42.dp).heightIn(max = screenHeight * 0.65f))
|
|
||||||
{
|
{
|
||||||
AndroidView(
|
AndroidView(
|
||||||
factory = {
|
factory = {
|
||||||
@@ -349,19 +310,6 @@ fun VideoPlayerPortal(videoPlayerViewModel: VideoPlayerViewModel, navController:
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
IconButton(
|
|
||||||
onClick = { navController?.popBackStack() },
|
|
||||||
modifier = Modifier
|
|
||||||
.padding(8.dp)
|
|
||||||
.align(Alignment.TopStart)
|
|
||||||
) {
|
|
||||||
Icon(
|
|
||||||
imageVector = Icons.AutoMirrored.Filled.ArrowBack,
|
|
||||||
contentDescription = "Back",
|
|
||||||
tint = Color.White
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
androidx.compose.animation.AnimatedVisibility(
|
androidx.compose.animation.AnimatedVisibility(
|
||||||
visible = videoPlayerViewModel.dragging,
|
visible = videoPlayerViewModel.dragging,
|
||||||
enter = fadeIn(
|
enter = fadeIn(
|
||||||
@@ -385,6 +333,9 @@ fun VideoPlayerPortal(videoPlayerViewModel: VideoPlayerViewModel, navController:
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if(cover > 0.0f)
|
||||||
|
Spacer(Modifier.background(Color(0x00FF6699 - 0x00222222 + ((0x000000FF * cover).toLong() shl 24) )).fillMaxSize())
|
||||||
|
|
||||||
androidx.compose.animation.AnimatedVisibility(
|
androidx.compose.animation.AnimatedVisibility(
|
||||||
visible = !videoPlayerViewModel.planeVisibility,
|
visible = !videoPlayerViewModel.planeVisibility,
|
||||||
enter = fadeIn(
|
enter = fadeIn(
|
||||||
@@ -412,7 +363,7 @@ fun VideoPlayerPortal(videoPlayerViewModel: VideoPlayerViewModel, navController:
|
|||||||
exit = fadeOut(
|
exit = fadeOut(
|
||||||
targetAlpha = 0f
|
targetAlpha = 0f
|
||||||
),
|
),
|
||||||
modifier = Modifier.fillMaxWidth().align(Alignment.BottomCenter)
|
modifier = Modifier.fillMaxWidth().align(Alignment.BottomCenter).height(42.dp)
|
||||||
)
|
)
|
||||||
{
|
{
|
||||||
Row(
|
Row(
|
||||||
@@ -480,6 +431,72 @@ fun VideoPlayerPortal(videoPlayerViewModel: VideoPlayerViewModel, navController:
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun VideoPlayerPortal(videoPlayerViewModel: VideoPlayerViewModel, navController: NavHostController)
|
||||||
|
{
|
||||||
|
val configuration = LocalConfiguration.current
|
||||||
|
val screenHeight = configuration.screenHeightDp.dp;
|
||||||
|
|
||||||
|
val minHeight = 42.dp
|
||||||
|
var coverAlpha by remember{ mutableFloatStateOf(0.0f) }
|
||||||
|
var maxHeight = remember { screenHeight * 0.65f }
|
||||||
|
var posed = remember { false }
|
||||||
|
val dens = LocalDensity.current
|
||||||
|
val listState = rememberLazyListState()
|
||||||
|
|
||||||
|
var playerHeight by remember { mutableStateOf(screenHeight * 0.65f) }
|
||||||
|
|
||||||
|
val nestedScrollConnection = remember {
|
||||||
|
object : NestedScrollConnection {
|
||||||
|
override fun onPreScroll(available: Offset, source: NestedScrollSource): Offset {
|
||||||
|
val deltaY = available.y // px
|
||||||
|
val deltaDp = with(dens) { deltaY.toDp() }
|
||||||
|
|
||||||
|
val r = if (deltaY < 0 && playerHeight > minHeight) {
|
||||||
|
val newHeight = (playerHeight + deltaDp).coerceIn(minHeight, maxHeight)
|
||||||
|
val consumedDp = newHeight - playerHeight
|
||||||
|
playerHeight = newHeight
|
||||||
|
val consumedPx = with(dens) { consumedDp.toPx() }
|
||||||
|
Offset(0f, consumedPx)
|
||||||
|
} else if(deltaY > 0 && playerHeight < maxHeight && listState.firstVisibleItemIndex == 0 && listState.firstVisibleItemScrollOffset == 0) {
|
||||||
|
val newHeight = (playerHeight + deltaDp).coerceIn(minHeight, maxHeight)
|
||||||
|
val consumedDp = newHeight - playerHeight
|
||||||
|
playerHeight = newHeight
|
||||||
|
val consumedPx = with(dens) { consumedDp.toPx() }
|
||||||
|
Offset(0f, consumedPx)
|
||||||
|
} else {
|
||||||
|
Offset.Zero
|
||||||
|
}
|
||||||
|
|
||||||
|
val dh = playerHeight - minHeight;
|
||||||
|
coverAlpha = (if(dh > 10.dp)
|
||||||
|
0f
|
||||||
|
else
|
||||||
|
(10.dp.value - dh.value) / 10.0f)
|
||||||
|
|
||||||
|
return r
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ToggleFullScreen(false)
|
||||||
|
Column(Modifier.nestedScroll(nestedScrollConnection).fillMaxHeight())
|
||||||
|
{
|
||||||
|
PortalCorePlayer(
|
||||||
|
Modifier
|
||||||
|
.padding(top = 42.dp)
|
||||||
|
.heightIn(max = playerHeight)
|
||||||
|
.onGloballyPositioned { layoutCoordinates ->
|
||||||
|
if(!posed && videoPlayerViewModel.renderedFirst)
|
||||||
|
{
|
||||||
|
maxHeight = with(dens) {layoutCoordinates.size.height.toDp()}
|
||||||
|
playerHeight = maxHeight
|
||||||
|
posed = true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
videoPlayerViewModel = videoPlayerViewModel, coverAlpha)
|
||||||
|
|
||||||
Row()
|
Row()
|
||||||
{
|
{
|
||||||
@@ -503,13 +520,13 @@ fun VideoPlayerPortal(videoPlayerViewModel: VideoPlayerViewModel, navController:
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
LazyColumn( modifier = Modifier.fillMaxWidth()) {
|
LazyColumn(state = listState, modifier = Modifier.fillMaxWidth()) {
|
||||||
item{
|
item{
|
||||||
HorizontalDivider(Modifier, 2.dp, DividerDefaults.color)
|
HorizontalDivider(Modifier, 2.dp, DividerDefaults.color)
|
||||||
|
|
||||||
Text(
|
Text(
|
||||||
modifier = Modifier.align(Alignment.Start).padding(horizontal = 12.dp).padding(top = 12.dp),
|
modifier = Modifier.align(Alignment.Start).padding(horizontal = 12.dp).padding(top = 12.dp),
|
||||||
text = Global.videoName,
|
text = videoPlayerViewModel.video?.video?.name ?: "",
|
||||||
fontSize = 16.sp,
|
fontSize = 16.sp,
|
||||||
maxLines = 2,
|
maxLines = 2,
|
||||||
fontWeight = FontWeight.Bold,
|
fontWeight = FontWeight.Bold,
|
||||||
@@ -518,7 +535,7 @@ fun VideoPlayerPortal(videoPlayerViewModel: VideoPlayerViewModel, navController:
|
|||||||
Row(Modifier.align(Alignment.Start).padding(horizontal = 4.dp).alpha(0.5f)) {
|
Row(Modifier.align(Alignment.Start).padding(horizontal = 4.dp).alpha(0.5f)) {
|
||||||
Text(
|
Text(
|
||||||
modifier = Modifier.padding(horizontal = 8.dp),
|
modifier = Modifier.padding(horizontal = 8.dp),
|
||||||
text = Global.videoClass,
|
text = videoPlayerViewModel.video?.klass ?: "",
|
||||||
fontSize = 14.sp,
|
fontSize = 14.sp,
|
||||||
maxLines = 1,
|
maxLines = 1,
|
||||||
fontWeight = FontWeight.Bold,
|
fontWeight = FontWeight.Bold,
|
||||||
@@ -526,7 +543,7 @@ fun VideoPlayerPortal(videoPlayerViewModel: VideoPlayerViewModel, navController:
|
|||||||
|
|
||||||
Text(
|
Text(
|
||||||
modifier = Modifier.padding(horizontal = 8.dp),
|
modifier = Modifier.padding(horizontal = 8.dp),
|
||||||
text = formatTime(Global.video?.video?.duration ?: 0),
|
text = formatTime(videoPlayerViewModel.video?.video?.duration ?: 0),
|
||||||
fontSize = 14.sp,
|
fontSize = 14.sp,
|
||||||
maxLines = 1,
|
maxLines = 1,
|
||||||
fontWeight = FontWeight.Bold,
|
fontWeight = FontWeight.Bold,
|
||||||
@@ -535,9 +552,40 @@ fun VideoPlayerPortal(videoPlayerViewModel: VideoPlayerViewModel, navController:
|
|||||||
|
|
||||||
HorizontalDivider(Modifier.padding(vertical = 8.dp), 1.dp, DividerDefaults.color)
|
HorizontalDivider(Modifier.padding(vertical = 8.dp), 1.dp, DividerDefaults.color)
|
||||||
|
|
||||||
|
SocialPanel(Modifier.align(Alignment.CenterHorizontally).fillMaxWidth(), videoPlayerViewModel = videoPlayerViewModel)
|
||||||
|
|
||||||
|
HorizontalDivider(Modifier.padding(vertical = 8.dp), 1.dp, DividerDefaults.color)
|
||||||
|
|
||||||
|
HorizontalGallery(videoPlayerViewModel)
|
||||||
|
HorizontalDivider(Modifier.padding(vertical = 8.dp), 1.dp, DividerDefaults.color)
|
||||||
|
|
||||||
|
for(i in Global.sameClassVideos ?: listOf())
|
||||||
|
{
|
||||||
|
if(i.id == videoPlayerViewModel.video?.id) continue
|
||||||
|
|
||||||
|
MiniVideoCard(
|
||||||
|
modifier = Modifier
|
||||||
|
.padding(horizontal = 12.dp),
|
||||||
|
i,
|
||||||
|
{
|
||||||
|
videoPlayerViewModel.isPlaying = false
|
||||||
|
videoPlayerViewModel._player?.pause()
|
||||||
|
val route = "video_player_route/${ "${i.klass}/${i.id}".toHex() }"
|
||||||
|
navController.navigate(route)
|
||||||
|
})
|
||||||
|
HorizontalDivider(Modifier.padding(vertical = 8.dp).alpha(0.25f), 1.dp, DividerDefaults.color)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun SocialPanel(modifier: Modifier, videoPlayerViewModel: VideoPlayerViewModel)
|
||||||
|
{
|
||||||
Row(
|
Row(
|
||||||
horizontalArrangement = Arrangement.Center,
|
modifier,
|
||||||
modifier = Modifier.align(Alignment.CenterHorizontally)
|
horizontalArrangement = Arrangement.Center
|
||||||
)
|
)
|
||||||
{
|
{
|
||||||
Column(modifier = Modifier.padding(horizontal = 12.dp)) {
|
Column(modifier = Modifier.padding(horizontal = 12.dp)) {
|
||||||
@@ -624,25 +672,17 @@ fun VideoPlayerPortal(videoPlayerViewModel: VideoPlayerViewModel, navController:
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Spacer(modifier = Modifier.height(16.dp))
|
|
||||||
|
|
||||||
HorizontalGallery()
|
|
||||||
HorizontalDivider(Modifier.padding(vertical = 8.dp), 1.dp, DividerDefaults.color)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun HorizontalGallery()
|
fun HorizontalGallery(videoPlayerViewModel: VideoPlayerViewModel)
|
||||||
{
|
{
|
||||||
LazyRow(
|
LazyRow(
|
||||||
modifier = Modifier.fillMaxWidth().height(100.dp),
|
modifier = Modifier.fillMaxWidth().height(120.dp),
|
||||||
horizontalArrangement = Arrangement.spacedBy(12.dp),
|
horizontalArrangement = Arrangement.spacedBy(12.dp),
|
||||||
contentPadding = PaddingValues(horizontal = 24.dp)
|
contentPadding = PaddingValues(horizontal = 24.dp)
|
||||||
) {
|
) {
|
||||||
items(Global.video?.getGallery() ?: listOf()) { it ->
|
items(videoPlayerViewModel.video?.getGallery() ?: listOf()) { it ->
|
||||||
SingleImageItem(img = it)
|
SingleImageItem(img = it)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -659,7 +699,7 @@ fun SingleImageItem(img: KeyImage) {
|
|||||||
contentDescription = null,
|
contentDescription = null,
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.fillMaxWidth()
|
.fillMaxWidth()
|
||||||
.clip(RoundedCornerShape(8.dp)),
|
.clip(RoundedCornerShape(12.dp)),
|
||||||
contentScale = ContentScale.Crop
|
contentScale = ContentScale.Crop
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@@ -867,3 +907,67 @@ fun VideoPlayerLandscape(videoPlayerViewModel: VideoPlayerViewModel)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun MiniVideoCard(modifier: Modifier, video: Video, onClick: () -> Unit)
|
||||||
|
{
|
||||||
|
Card(
|
||||||
|
modifier = modifier.height(80.dp).fillMaxWidth(),
|
||||||
|
colors = CardColors(
|
||||||
|
containerColor = Color.Transparent,
|
||||||
|
contentColor = MaterialTheme.colorScheme.onSurface,
|
||||||
|
disabledContentColor = Color.Transparent,
|
||||||
|
disabledContainerColor = Color.Transparent
|
||||||
|
),
|
||||||
|
onClick = onClick
|
||||||
|
)
|
||||||
|
{
|
||||||
|
Row()
|
||||||
|
{
|
||||||
|
AsyncImage(
|
||||||
|
model = ImageRequest.Builder(LocalContext.current)
|
||||||
|
.data(video.getCover())
|
||||||
|
.memoryCacheKey("${video.klass}/${video.id}/cover")
|
||||||
|
.diskCacheKey("${video.klass}/${video.id}/cover")
|
||||||
|
.build(),
|
||||||
|
contentDescription = null,
|
||||||
|
modifier = Modifier
|
||||||
|
.width(128.dp).fillMaxHeight()
|
||||||
|
.clip(RoundedCornerShape(8.dp)),
|
||||||
|
contentScale = ContentScale.Crop
|
||||||
|
)
|
||||||
|
|
||||||
|
Column (
|
||||||
|
modifier = Modifier.padding(horizontal = 8.dp).fillMaxHeight().fillMaxWidth().align(Alignment.CenterVertically),
|
||||||
|
verticalArrangement = Arrangement.Center
|
||||||
|
)
|
||||||
|
{
|
||||||
|
Text(
|
||||||
|
modifier = Modifier,
|
||||||
|
text = video.video.name,
|
||||||
|
fontSize = 14.sp,
|
||||||
|
maxLines = 2,
|
||||||
|
fontWeight = FontWeight.Bold,
|
||||||
|
)
|
||||||
|
|
||||||
|
Spacer(modifier.weight(1f))
|
||||||
|
|
||||||
|
Text(
|
||||||
|
modifier = Modifier.height(16.dp),
|
||||||
|
text = video.klass,
|
||||||
|
fontSize = 8.sp,
|
||||||
|
maxLines = 1,
|
||||||
|
fontWeight = FontWeight.Bold,
|
||||||
|
)
|
||||||
|
|
||||||
|
Text(
|
||||||
|
modifier = Modifier.height(16.dp),
|
||||||
|
text = formatTime(video.video.duration),
|
||||||
|
fontSize = 8.sp,
|
||||||
|
maxLines = 1,
|
||||||
|
fontWeight = FontWeight.Bold,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -3,6 +3,7 @@ package com.acitelight.aether.view
|
|||||||
import android.R.id.tabs
|
import android.R.id.tabs
|
||||||
import androidx.compose.foundation.background
|
import androidx.compose.foundation.background
|
||||||
import androidx.compose.foundation.layout.Arrangement
|
import androidx.compose.foundation.layout.Arrangement
|
||||||
|
import androidx.compose.foundation.layout.Box
|
||||||
import androidx.compose.foundation.layout.Column
|
import androidx.compose.foundation.layout.Column
|
||||||
import androidx.compose.foundation.layout.PaddingValues
|
import androidx.compose.foundation.layout.PaddingValues
|
||||||
import androidx.compose.foundation.layout.Row
|
import androidx.compose.foundation.layout.Row
|
||||||
@@ -43,11 +44,14 @@ import com.acitelight.aether.viewModel.VideoScreenViewModel
|
|||||||
import androidx.compose.material3.PrimaryTabRow
|
import androidx.compose.material3.PrimaryTabRow
|
||||||
import androidx.compose.material3.ScrollableTabRow
|
import androidx.compose.material3.ScrollableTabRow
|
||||||
import androidx.compose.runtime.LaunchedEffect
|
import androidx.compose.runtime.LaunchedEffect
|
||||||
|
import androidx.compose.ui.Alignment
|
||||||
|
import androidx.compose.ui.graphics.Brush
|
||||||
import androidx.compose.ui.platform.LocalContext
|
import androidx.compose.ui.platform.LocalContext
|
||||||
import androidx.compose.ui.text.style.TextOverflow
|
import androidx.compose.ui.text.style.TextOverflow
|
||||||
import androidx.navigation.NavHostController
|
import androidx.navigation.NavHostController
|
||||||
import coil3.request.ImageRequest
|
import coil3.request.ImageRequest
|
||||||
import com.acitelight.aether.Global
|
import com.acitelight.aether.Global
|
||||||
|
import kotlinx.coroutines.flow.first
|
||||||
import java.nio.charset.Charset
|
import java.nio.charset.Charset
|
||||||
|
|
||||||
fun String.toHex(): String {
|
fun String.toHex(): String {
|
||||||
@@ -83,7 +87,7 @@ fun VideoScreen(videoScreenViewModel: VideoScreenViewModel = viewModel(), navCon
|
|||||||
)
|
)
|
||||||
{
|
{
|
||||||
items(videoList) { video ->
|
items(videoList) { video ->
|
||||||
VideoCard(video, navController)
|
VideoCard(video, navController, videoScreenViewModel)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -110,17 +114,17 @@ fun TopRow(videoScreenViewModel: VideoScreenViewModel)
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun VideoCard(video: Video, navController: NavHostController) {
|
fun VideoCard(video: Video, navController: NavHostController, videoScreenViewModel: VideoScreenViewModel) {
|
||||||
|
val videoList by videoScreenViewModel.videos.collectAsState()
|
||||||
|
|
||||||
Card(
|
Card(
|
||||||
shape = RoundedCornerShape(6.dp),
|
shape = RoundedCornerShape(6.dp),
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.fillMaxWidth()
|
.fillMaxWidth()
|
||||||
.wrapContentHeight(),
|
.wrapContentHeight(),
|
||||||
onClick = {
|
onClick = {
|
||||||
Global.videoName = video.video.name
|
Global.sameClassVideos = videoList
|
||||||
Global.videoClass = video.klass
|
val route = "video_player_route/${ "${video.klass}/${video.id}".toHex() }"
|
||||||
Global.video = video
|
|
||||||
val route = "video_player_route/${ video.getVideo().toHex() }"
|
|
||||||
navController.navigate(route)
|
navController.navigate(route)
|
||||||
}
|
}
|
||||||
) {
|
) {
|
||||||
@@ -128,6 +132,7 @@ fun VideoCard(video: Video, navController: NavHostController) {
|
|||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.fillMaxWidth()
|
.fillMaxWidth()
|
||||||
) {
|
) {
|
||||||
|
Box(modifier = Modifier.fillMaxSize()){
|
||||||
AsyncImage(
|
AsyncImage(
|
||||||
model = ImageRequest.Builder(LocalContext.current)
|
model = ImageRequest.Builder(LocalContext.current)
|
||||||
.data(video.getCover())
|
.data(video.getCover())
|
||||||
@@ -139,6 +144,23 @@ fun VideoCard(video: Video, navController: NavHostController) {
|
|||||||
.fillMaxSize(),
|
.fillMaxSize(),
|
||||||
contentScale = ContentScale.Crop
|
contentScale = ContentScale.Crop
|
||||||
)
|
)
|
||||||
|
|
||||||
|
Text(
|
||||||
|
modifier = Modifier.align(Alignment.BottomEnd).padding(2.dp),
|
||||||
|
text = formatTime(video.video.duration), fontSize = 12.sp, color = Color.White, fontWeight = FontWeight.Bold)
|
||||||
|
|
||||||
|
Box(
|
||||||
|
Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.height(24.dp)
|
||||||
|
.background( brush = Brush.verticalGradient(
|
||||||
|
colors = listOf(
|
||||||
|
Color.Transparent,
|
||||||
|
Color.Black.copy(alpha = 0.45f)
|
||||||
|
)
|
||||||
|
))
|
||||||
|
.align(Alignment.BottomCenter))
|
||||||
|
}
|
||||||
Text(
|
Text(
|
||||||
text = video.video.name,
|
text = video.video.name,
|
||||||
fontSize = 14.sp,
|
fontSize = 14.sp,
|
||||||
@@ -151,7 +173,8 @@ fun VideoCard(video: Video, navController: NavHostController) {
|
|||||||
modifier = Modifier.padding(horizontal = 8.dp),
|
modifier = Modifier.padding(horizontal = 8.dp),
|
||||||
horizontalArrangement = Arrangement.SpaceBetween,
|
horizontalArrangement = Arrangement.SpaceBetween,
|
||||||
) {
|
) {
|
||||||
Text("class: ${video.klass}", fontSize = 12.sp)
|
Text("Class", fontSize = 12.sp)
|
||||||
|
Text("${video.klass}", fontSize = 12.sp)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,21 +1,81 @@
|
|||||||
package com.acitelight.aether.viewModel
|
package com.acitelight.aether.viewModel
|
||||||
|
|
||||||
import android.app.Application
|
import android.app.Application
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.mutableStateOf
|
import androidx.compose.runtime.mutableStateOf
|
||||||
import androidx.lifecycle.ViewModel
|
import androidx.lifecycle.ViewModel
|
||||||
import androidx.compose.runtime.State
|
import androidx.compose.runtime.State
|
||||||
import androidx.compose.runtime.mutableIntStateOf
|
import androidx.compose.runtime.mutableIntStateOf
|
||||||
|
import androidx.compose.runtime.remember
|
||||||
|
import androidx.compose.ui.platform.LocalContext
|
||||||
import androidx.datastore.preferences.core.stringPreferencesKey
|
import androidx.datastore.preferences.core.stringPreferencesKey
|
||||||
|
import androidx.lifecycle.AndroidViewModel
|
||||||
import androidx.lifecycle.viewModelScope
|
import androidx.lifecycle.viewModelScope
|
||||||
import com.acitelight.aether.Global
|
import com.acitelight.aether.Global
|
||||||
import com.acitelight.aether.dataStore
|
import com.acitelight.aether.dataStore
|
||||||
|
import com.acitelight.aether.model.Video
|
||||||
|
import com.acitelight.aether.model.VideoQueryIndex
|
||||||
import com.acitelight.aether.service.ApiClient
|
import com.acitelight.aether.service.ApiClient
|
||||||
import com.acitelight.aether.service.AuthManager
|
import com.acitelight.aether.service.AuthManager
|
||||||
import com.acitelight.aether.service.MediaManager
|
import com.acitelight.aether.service.MediaManager
|
||||||
import com.acitelight.aether.service.MediaManager.token
|
import com.acitelight.aether.service.MediaManager.token
|
||||||
|
import com.acitelight.aether.service.RecentManager
|
||||||
|
import kotlinx.coroutines.flow.Flow
|
||||||
|
import kotlinx.coroutines.flow.MutableStateFlow
|
||||||
|
import kotlinx.coroutines.flow.StateFlow
|
||||||
|
import kotlinx.coroutines.flow.first
|
||||||
|
import kotlinx.coroutines.flow.map
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
|
|
||||||
class HomeScreenViewModel() : ViewModel()
|
class HomeScreenViewModel(application: Application) : AndroidViewModel(application)
|
||||||
{
|
{
|
||||||
|
private val dataStore = application.dataStore
|
||||||
|
private val USER_NAME_KEY = stringPreferencesKey("user_name")
|
||||||
|
private val PRIVATE_KEY = stringPreferencesKey("private_key")
|
||||||
|
|
||||||
|
val userNameFlow: Flow<String> = dataStore.data.map { preferences ->
|
||||||
|
preferences[USER_NAME_KEY] ?: ""
|
||||||
|
}
|
||||||
|
|
||||||
|
val privateKeyFlow: Flow<String> = dataStore.data.map { preferences ->
|
||||||
|
preferences[PRIVATE_KEY] ?: ""
|
||||||
|
}
|
||||||
|
|
||||||
|
var _init = false
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun Init(){
|
||||||
|
if(_init) return
|
||||||
|
_init = true
|
||||||
|
|
||||||
|
val context = LocalContext.current
|
||||||
|
remember {
|
||||||
|
viewModelScope.launch {
|
||||||
|
RecentManager.Query(context)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
init {
|
||||||
|
viewModelScope.launch {
|
||||||
|
val u = userNameFlow.first()
|
||||||
|
val p = privateKeyFlow.first()
|
||||||
|
|
||||||
|
if(u=="" || p=="") return@launch
|
||||||
|
|
||||||
|
try{
|
||||||
|
if (MediaManager.token == "null")
|
||||||
|
MediaManager.token = AuthManager.fetchToken(
|
||||||
|
ApiClient.base,
|
||||||
|
u,
|
||||||
|
p
|
||||||
|
)!!
|
||||||
|
}catch(e: Exception)
|
||||||
|
{
|
||||||
|
print(e.message)
|
||||||
|
}finally {
|
||||||
|
Global.loggedIn = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -13,10 +13,15 @@ import androidx.compose.ui.platform.LocalContext
|
|||||||
import androidx.lifecycle.ViewModel
|
import androidx.lifecycle.ViewModel
|
||||||
import androidx.lifecycle.viewModelScope
|
import androidx.lifecycle.viewModelScope
|
||||||
import androidx.media3.common.MediaItem
|
import androidx.media3.common.MediaItem
|
||||||
|
import androidx.media3.common.Player
|
||||||
|
import androidx.media3.common.Player.STATE_READY
|
||||||
import androidx.media3.common.util.UnstableApi
|
import androidx.media3.common.util.UnstableApi
|
||||||
import androidx.media3.exoplayer.ExoPlayer
|
import androidx.media3.exoplayer.ExoPlayer
|
||||||
import com.acitelight.aether.Global
|
import com.acitelight.aether.Global
|
||||||
|
import com.acitelight.aether.model.Video
|
||||||
|
import com.acitelight.aether.model.VideoQueryIndex
|
||||||
import com.acitelight.aether.service.MediaManager
|
import com.acitelight.aether.service.MediaManager
|
||||||
|
import com.acitelight.aether.service.RecentManager
|
||||||
import com.acitelight.aether.view.hexToString
|
import com.acitelight.aether.view.hexToString
|
||||||
import kotlinx.coroutines.CoroutineScope
|
import kotlinx.coroutines.CoroutineScope
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
@@ -32,25 +37,47 @@ class VideoPlayerViewModel() : ViewModel()
|
|||||||
var isLongPressing by mutableStateOf(false)
|
var isLongPressing by mutableStateOf(false)
|
||||||
var dragging by mutableStateOf(false)
|
var dragging by mutableStateOf(false)
|
||||||
|
|
||||||
|
|
||||||
var thumbUp by mutableIntStateOf(0)
|
var thumbUp by mutableIntStateOf(0)
|
||||||
var thumbDown by mutableIntStateOf(0)
|
var thumbDown by mutableIntStateOf(0)
|
||||||
var star by mutableStateOf(false)
|
var star by mutableStateOf(false)
|
||||||
|
|
||||||
private var _init: Boolean = false;
|
private var _init: Boolean = false;
|
||||||
|
var startPlaying by mutableStateOf(false)
|
||||||
|
var renderedFirst = false
|
||||||
|
var video: Video? = null
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun Init(videoId: String)
|
fun Init(videoId: String)
|
||||||
{
|
{
|
||||||
if(_init) return;
|
if(_init) return;
|
||||||
val context = LocalContext.current
|
val context = LocalContext.current
|
||||||
_player = remember {
|
val v = videoId.hexToString()
|
||||||
ExoPlayer.Builder(context).build().apply {
|
|
||||||
val url = videoId.hexToString()
|
remember {
|
||||||
|
viewModelScope.launch {
|
||||||
|
video = MediaManager.queryVideo(v.split("/")[0], v.split("/")[1])
|
||||||
|
RecentManager.Push(context, VideoQueryIndex(v.split("/")[0], v.split("/")[1]))
|
||||||
|
_player = ExoPlayer.Builder(context).build().apply {
|
||||||
|
val url = video?.getVideo() ?: ""
|
||||||
val mediaItem = MediaItem.fromUri(url)
|
val mediaItem = MediaItem.fromUri(url)
|
||||||
setMediaItem(mediaItem)
|
setMediaItem(mediaItem)
|
||||||
prepare()
|
prepare()
|
||||||
playWhenReady = true
|
playWhenReady = true
|
||||||
|
|
||||||
|
addListener(object : Player.Listener {
|
||||||
|
override fun onPlaybackStateChanged(playbackState: Int) {
|
||||||
|
if (playbackState == STATE_READY) {
|
||||||
|
startPlaying = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onRenderedFirstFrame() {
|
||||||
|
super.onRenderedFirstFrame()
|
||||||
|
renderedFirst = true
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
startListen()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -62,24 +62,7 @@ class VideoScreenViewModel(application: Application) : AndroidViewModel(applicat
|
|||||||
|
|
||||||
init {
|
init {
|
||||||
viewModelScope.launch {
|
viewModelScope.launch {
|
||||||
val u = userNameFlow.first()
|
|
||||||
val p = privateKeyFlow.first()
|
|
||||||
|
|
||||||
if(u=="" || p=="") return@launch
|
|
||||||
|
|
||||||
try{
|
|
||||||
if (MediaManager.token == "null")
|
|
||||||
MediaManager.token = AuthManager.fetchToken(
|
|
||||||
ApiClient.base,
|
|
||||||
u,
|
|
||||||
p
|
|
||||||
)!!
|
|
||||||
|
|
||||||
init()
|
init()
|
||||||
}catch(e: Exception)
|
|
||||||
{
|
|
||||||
print(e.message)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Reference in New Issue
Block a user