Compare commits
8 Commits
b360724dca
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
7bd92f91f4 | ||
|
|
78d6564964 | ||
|
|
512da7be1c | ||
|
|
9efbcdfe8a | ||
|
|
c3e0a23ed1 | ||
|
|
7be18dd517 | ||
|
|
a13ddbdd87 | ||
|
|
390094b8b0 |
@@ -56,6 +56,7 @@ import com.acitelight.aether.view.pages.ComicGridView
|
|||||||
import com.acitelight.aether.view.pages.ComicPageView
|
import com.acitelight.aether.view.pages.ComicPageView
|
||||||
import com.acitelight.aether.view.pages.ComicScreen
|
import com.acitelight.aether.view.pages.ComicScreen
|
||||||
import com.acitelight.aether.view.pages.HomeScreen
|
import com.acitelight.aether.view.pages.HomeScreen
|
||||||
|
import com.acitelight.aether.view.pages.LiveScreen
|
||||||
import com.acitelight.aether.view.pages.MeScreen
|
import com.acitelight.aether.view.pages.MeScreen
|
||||||
import com.acitelight.aether.view.pages.TransmissionScreen
|
import com.acitelight.aether.view.pages.TransmissionScreen
|
||||||
import com.acitelight.aether.view.pages.VideoPlayer
|
import com.acitelight.aether.view.pages.VideoPlayer
|
||||||
@@ -179,12 +180,21 @@ fun AppNavigation() {
|
|||||||
TransmissionScreen(navigator = navController)
|
TransmissionScreen(navigator = navController)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
composable(Screen.Live.route,
|
||||||
|
enterTransition = { slideIntoContainer(AnimatedContentTransitionScope.SlideDirection.Start, animationSpec = tween(200)) },
|
||||||
|
exitTransition = { slideOutOfContainer(AnimatedContentTransitionScope.SlideDirection.Start, animationSpec = tween(200)) },
|
||||||
|
popEnterTransition = { slideIntoContainer(AnimatedContentTransitionScope.SlideDirection.End, animationSpec = tween(200)) },
|
||||||
|
popExitTransition = { slideOutOfContainer(AnimatedContentTransitionScope.SlideDirection.End, animationSpec = tween(200)) }) {
|
||||||
|
LiveScreen()
|
||||||
|
}
|
||||||
|
|
||||||
composable(Screen.Me.route,
|
composable(Screen.Me.route,
|
||||||
enterTransition = { slideIntoContainer(AnimatedContentTransitionScope.SlideDirection.Start, animationSpec = tween(200)) },
|
enterTransition = { slideIntoContainer(AnimatedContentTransitionScope.SlideDirection.Start, animationSpec = tween(200)) },
|
||||||
exitTransition = { slideOutOfContainer(AnimatedContentTransitionScope.SlideDirection.Start, animationSpec = tween(200)) },
|
exitTransition = { slideOutOfContainer(AnimatedContentTransitionScope.SlideDirection.Start, animationSpec = tween(200)) },
|
||||||
popEnterTransition = { slideIntoContainer(AnimatedContentTransitionScope.SlideDirection.End, animationSpec = tween(200)) },
|
popEnterTransition = { slideIntoContainer(AnimatedContentTransitionScope.SlideDirection.End, animationSpec = tween(200)) },
|
||||||
popExitTransition = { slideOutOfContainer(AnimatedContentTransitionScope.SlideDirection.End, animationSpec = tween(200)) }) {
|
popExitTransition = { slideOutOfContainer(AnimatedContentTransitionScope.SlideDirection.End, animationSpec = tween(200)) }) {
|
||||||
MeScreen();
|
MeScreen()
|
||||||
}
|
}
|
||||||
|
|
||||||
composable(
|
composable(
|
||||||
@@ -246,6 +256,7 @@ fun BottomNavigationBar(navController: NavController) {
|
|||||||
Screen.Video,
|
Screen.Video,
|
||||||
Screen.Comic,
|
Screen.Comic,
|
||||||
Screen.Transmission,
|
Screen.Transmission,
|
||||||
|
Screen.Live,
|
||||||
Screen.Me
|
Screen.Me
|
||||||
) else listOf(
|
) else listOf(
|
||||||
Screen.Video,
|
Screen.Video,
|
||||||
@@ -310,6 +321,8 @@ sealed class Screen(val route: String, val icon: ImageVector, val title: String)
|
|||||||
data object Comic : Screen("comic_route", Icons.Filled.Image, "Comic")
|
data object Comic : Screen("comic_route", Icons.Filled.Image, "Comic")
|
||||||
data object Transmission : Screen("transmission_route",
|
data object Transmission : Screen("transmission_route",
|
||||||
Icons.AutoMirrored.Filled.CompareArrows, "Transmission")
|
Icons.AutoMirrored.Filled.CompareArrows, "Transmission")
|
||||||
|
data object Live : Screen("live_route",
|
||||||
|
Icons.Filled.LiveTv, "Live")
|
||||||
data object Me : Screen("me_route", Icons.Filled.AccountCircle, "me")
|
data object Me : Screen("me_route", Icons.Filled.AccountCircle, "me")
|
||||||
data object VideoPlayer : Screen("video_player_route/{videoId}", Icons.Filled.PlayArrow, "VideoPlayer")
|
data object VideoPlayer : Screen("video_player_route/{videoId}", Icons.Filled.PlayArrow, "VideoPlayer")
|
||||||
data object ComicGrid : Screen("comic_grid_route/{comicId}", Icons.Filled.PlayArrow, "ComicGrid")
|
data object ComicGrid : Screen("comic_grid_route/{comicId}", Icons.Filled.PlayArrow, "ComicGrid")
|
||||||
|
|||||||
@@ -13,6 +13,12 @@ class Video(
|
|||||||
val id: String,
|
val id: String,
|
||||||
val video: VideoResponse
|
val video: VideoResponse
|
||||||
) {
|
) {
|
||||||
|
companion object {
|
||||||
|
fun getCoverStatic(api: ApiClient, klass: String, id: String): String
|
||||||
|
{
|
||||||
|
return "${api.getBase()}api/video/$klass/$id/cover"
|
||||||
|
}
|
||||||
|
}
|
||||||
fun getCover(api: ApiClient): String {
|
fun getCover(api: ApiClient): String {
|
||||||
return if (isLocal)
|
return if (isLocal)
|
||||||
"$localBase/videos/$klass/$id/cover.jpg"
|
"$localBase/videos/$klass/$id/cover.jpg"
|
||||||
|
|||||||
@@ -16,7 +16,8 @@ class VideoDownloadItemState(
|
|||||||
totalBytes: Long,
|
totalBytes: Long,
|
||||||
klass: String,
|
klass: String,
|
||||||
vid: String,
|
vid: String,
|
||||||
val type: String
|
val type: String,
|
||||||
|
val group: String
|
||||||
) {
|
) {
|
||||||
var fileName by mutableStateOf(fileName)
|
var fileName by mutableStateOf(fileName)
|
||||||
var filePath by mutableStateOf(filePath)
|
var filePath by mutableStateOf(filePath)
|
||||||
|
|||||||
@@ -46,6 +46,9 @@ class ApiClient @Inject constructor(
|
|||||||
fun getBase(): String{
|
fun getBase(): String{
|
||||||
return replaceAbyssProtocol(base)
|
return replaceAbyssProtocol(base)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun getDomain(): String = domain
|
||||||
|
|
||||||
private var base: String = ""
|
private var base: String = ""
|
||||||
private var domain: String = ""
|
private var domain: String = ""
|
||||||
private var cert: String = ""
|
private var cert: String = ""
|
||||||
@@ -236,7 +239,7 @@ class ApiClient @Inject constructor(
|
|||||||
throw Exception("No reachable URL found")
|
throw Exception("No reachable URL found")
|
||||||
}
|
}
|
||||||
|
|
||||||
domain = selectedUrl.toHttpUrlOrNull()?.host ?: ""
|
domain = replaceAbyssProtocol(selectedUrl).toHttpUrlOrNull()?.host ?: ""
|
||||||
cert = crt
|
cert = crt
|
||||||
base = selectedUrl
|
base = selectedUrl
|
||||||
withContext(Dispatchers.IO)
|
withContext(Dispatchers.IO)
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import com.tonyodev.fetch2.Download
|
|||||||
import com.tonyodev.fetch2.Fetch
|
import com.tonyodev.fetch2.Fetch
|
||||||
import com.tonyodev.fetch2.FetchConfiguration
|
import com.tonyodev.fetch2.FetchConfiguration
|
||||||
import com.tonyodev.fetch2.FetchListener
|
import com.tonyodev.fetch2.FetchListener
|
||||||
|
import com.tonyodev.fetch2.Priority
|
||||||
import com.tonyodev.fetch2.Request
|
import com.tonyodev.fetch2.Request
|
||||||
import com.tonyodev.fetch2core.Extras
|
import com.tonyodev.fetch2core.Extras
|
||||||
import com.tonyodev.fetch2okhttp.OkHttpDownloader
|
import com.tonyodev.fetch2okhttp.OkHttpDownloader
|
||||||
@@ -128,21 +129,25 @@ class FetchManager @Inject constructor(
|
|||||||
|
|
||||||
val requests = mutableListOf(
|
val requests = mutableListOf(
|
||||||
Request(video.getVideo(apiClient), videoPath.path).apply {
|
Request(video.getVideo(apiClient), videoPath.path).apply {
|
||||||
|
priority = Priority.LOW
|
||||||
extras = Extras(
|
extras = Extras(
|
||||||
mapOf(
|
mapOf(
|
||||||
"name" to video.video.name,
|
"name" to video.video.name,
|
||||||
"id" to video.id,
|
"id" to video.id,
|
||||||
"class" to video.klass,
|
"class" to video.klass,
|
||||||
|
"group" to (video.video.group ?: ""),
|
||||||
"type" to "main"
|
"type" to "main"
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
Request(video.getCover(apiClient), coverPath.path).apply {
|
Request(video.getCover(apiClient), coverPath.path).apply {
|
||||||
|
priority = Priority.HIGH
|
||||||
extras = Extras(
|
extras = Extras(
|
||||||
mapOf(
|
mapOf(
|
||||||
"name" to video.video.name,
|
"name" to video.video.name,
|
||||||
"id" to video.id,
|
"id" to video.id,
|
||||||
"class" to video.klass,
|
"class" to video.klass,
|
||||||
|
"group" to (video.video.group ?: ""),
|
||||||
"type" to "cover"
|
"type" to "cover"
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
@@ -153,6 +158,7 @@ class FetchManager @Inject constructor(
|
|||||||
"name" to video.video.name,
|
"name" to video.video.name,
|
||||||
"id" to video.id,
|
"id" to video.id,
|
||||||
"class" to video.klass,
|
"class" to video.klass,
|
||||||
|
"group" to (video.video.group ?: ""),
|
||||||
"type" to "subtitle"
|
"type" to "subtitle"
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
@@ -169,6 +175,7 @@ class FetchManager @Inject constructor(
|
|||||||
"name" to video.video.name,
|
"name" to video.video.name,
|
||||||
"id" to video.id,
|
"id" to video.id,
|
||||||
"class" to video.klass,
|
"class" to video.klass,
|
||||||
|
"group" to (video.video.group ?: ""),
|
||||||
"type" to "gallery"
|
"type" to "gallery"
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -156,7 +156,7 @@ class MediaManager @Inject constructor(
|
|||||||
{
|
{
|
||||||
try{
|
try{
|
||||||
val j = apiClient.api!!.getComics()
|
val j = apiClient.api!!.getComics()
|
||||||
return j
|
return j.sorted()
|
||||||
}catch (_: Exception)
|
}catch (_: Exception)
|
||||||
{
|
{
|
||||||
return listOf()
|
return listOf()
|
||||||
|
|||||||
@@ -64,11 +64,11 @@ fun ComicCard(
|
|||||||
.diskCacheKey("${comic.id}/cover")
|
.diskCacheKey("${comic.id}/cover")
|
||||||
.build(),
|
.build(),
|
||||||
contentDescription = null,
|
contentDescription = null,
|
||||||
imageLoader = comicScreenViewModel.imageLoader!!,
|
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.fillMaxSize()
|
.fillMaxSize()
|
||||||
.clip(RoundedCornerShape(8.dp)),
|
.clip(RoundedCornerShape(8.dp)),
|
||||||
contentScale = ContentScale.Crop,
|
contentScale = ContentScale.Fit,
|
||||||
|
imageLoader = comicScreenViewModel.imageLoader!!,
|
||||||
)
|
)
|
||||||
|
|
||||||
Box(
|
Box(
|
||||||
@@ -100,22 +100,21 @@ fun ComicCard(
|
|||||||
}
|
}
|
||||||
Text(
|
Text(
|
||||||
text = comic.comic.comic_name,
|
text = comic.comic.comic_name,
|
||||||
fontSize = 14.sp,
|
|
||||||
lineHeight = 17.sp,
|
|
||||||
fontWeight = FontWeight.Bold,
|
|
||||||
maxLines = 2,
|
|
||||||
modifier = Modifier.padding(4.dp)
|
|
||||||
)
|
|
||||||
|
|
||||||
Box(Modifier.padding(4.dp).fillMaxWidth()){
|
|
||||||
Text(
|
|
||||||
text = "Id: ${comic.id}",
|
|
||||||
fontSize = 12.sp,
|
fontSize = 12.sp,
|
||||||
lineHeight = 14.sp,
|
lineHeight = 14.sp,
|
||||||
maxLines = 1,
|
fontWeight = FontWeight.Bold,
|
||||||
modifier = Modifier.align(Alignment.CenterStart)
|
maxLines = 2,
|
||||||
|
modifier = Modifier
|
||||||
|
.padding(4.dp)
|
||||||
|
.heightIn(min = 14.dp)
|
||||||
|
)
|
||||||
|
Text(
|
||||||
|
text = "Id: ${comic.id}",
|
||||||
|
fontSize = 10.sp,
|
||||||
|
lineHeight = 12.sp,
|
||||||
|
maxLines = 1,
|
||||||
|
modifier = Modifier.padding(4.dp)
|
||||||
)
|
)
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,271 +1,293 @@
|
|||||||
package com.acitelight.aether.view.components
|
package com.acitelight.aether.view.components
|
||||||
|
|
||||||
|
import androidx.compose.animation.AnimatedVisibility
|
||||||
|
import androidx.compose.animation.expandVertically
|
||||||
|
import androidx.compose.animation.fadeIn
|
||||||
|
import androidx.compose.animation.fadeOut
|
||||||
|
import androidx.compose.animation.shrinkVertically
|
||||||
import androidx.compose.foundation.background
|
import androidx.compose.foundation.background
|
||||||
import androidx.compose.foundation.clickable
|
import androidx.compose.foundation.combinedClickable
|
||||||
import androidx.compose.foundation.layout.Arrangement
|
|
||||||
import androidx.compose.foundation.layout.Box
|
import androidx.compose.foundation.layout.Box
|
||||||
import androidx.compose.foundation.layout.Column
|
import androidx.compose.foundation.layout.Column
|
||||||
import androidx.compose.foundation.layout.Row
|
import androidx.compose.foundation.layout.Row
|
||||||
|
import androidx.compose.foundation.layout.Spacer
|
||||||
|
import androidx.compose.foundation.layout.fillMaxHeight
|
||||||
|
import androidx.compose.foundation.layout.fillMaxSize
|
||||||
import androidx.compose.foundation.layout.fillMaxWidth
|
import androidx.compose.foundation.layout.fillMaxWidth
|
||||||
import androidx.compose.foundation.layout.height
|
import androidx.compose.foundation.layout.height
|
||||||
|
import androidx.compose.foundation.layout.heightIn
|
||||||
import androidx.compose.foundation.layout.padding
|
import androidx.compose.foundation.layout.padding
|
||||||
|
import androidx.compose.foundation.layout.width
|
||||||
|
import androidx.compose.foundation.layout.widthIn
|
||||||
|
import androidx.compose.foundation.lazy.LazyColumn
|
||||||
|
import androidx.compose.foundation.lazy.items
|
||||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||||
import androidx.compose.material.icons.Icons
|
|
||||||
import androidx.compose.material.icons.filled.Delete
|
|
||||||
import androidx.compose.material.icons.filled.Pause
|
|
||||||
import androidx.compose.material.icons.filled.PlayArrow
|
|
||||||
import androidx.compose.material.icons.filled.Stop
|
|
||||||
import androidx.compose.material3.Button
|
|
||||||
import androidx.compose.material3.Card
|
import androidx.compose.material3.Card
|
||||||
import androidx.compose.material3.CardDefaults
|
import androidx.compose.material3.CardDefaults
|
||||||
import androidx.compose.material3.Icon
|
import androidx.compose.material3.DividerDefaults
|
||||||
import androidx.compose.material3.LinearProgressIndicator
|
import androidx.compose.material3.HorizontalDivider
|
||||||
import androidx.compose.material3.MaterialTheme
|
import androidx.compose.material3.MaterialTheme
|
||||||
import androidx.compose.material3.ProgressIndicatorDefaults
|
import androidx.compose.material3.SliderDefaults
|
||||||
import androidx.compose.material3.Text
|
import androidx.compose.material3.Text
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.runtime.getValue
|
||||||
|
import androidx.compose.runtime.setValue
|
||||||
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.clip
|
||||||
import androidx.compose.ui.graphics.Color
|
import androidx.compose.ui.graphics.Color
|
||||||
import androidx.compose.ui.layout.ContentScale
|
import androidx.compose.ui.layout.ContentScale
|
||||||
import androidx.compose.ui.platform.LocalContext
|
import androidx.compose.ui.platform.LocalContext
|
||||||
import androidx.compose.ui.text.font.FontWeight
|
import androidx.compose.ui.text.font.FontWeight
|
||||||
|
import androidx.compose.ui.unit.Dp
|
||||||
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.viewModelScope
|
|
||||||
import androidx.navigation.NavHostController
|
import androidx.navigation.NavHostController
|
||||||
import coil3.compose.AsyncImage
|
import coil3.compose.AsyncImage
|
||||||
import coil3.request.ImageRequest
|
import coil3.request.ImageRequest
|
||||||
import com.acitelight.aether.Global.updateRelate
|
|
||||||
import com.acitelight.aether.model.Video
|
import com.acitelight.aether.model.Video
|
||||||
import com.acitelight.aether.model.VideoDownloadItemState
|
import com.acitelight.aether.model.VideoDownloadItemState
|
||||||
import com.acitelight.aether.view.pages.toHex
|
import com.acitelight.aether.view.pages.downloadToGroup
|
||||||
import com.acitelight.aether.viewModel.TransmissionScreenViewModel
|
import com.acitelight.aether.viewModel.TransmissionScreenViewModel
|
||||||
import com.tonyodev.fetch2.Status
|
import com.tonyodev.fetch2.Status
|
||||||
import kotlinx.coroutines.Dispatchers
|
|
||||||
import kotlinx.coroutines.launch
|
|
||||||
import kotlinx.coroutines.withContext
|
|
||||||
import kotlinx.serialization.json.Json
|
|
||||||
import java.io.File
|
import java.io.File
|
||||||
import kotlin.math.abs
|
import kotlin.math.abs
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
private fun VideoDownloadCardMiniPack(
|
||||||
|
navigator: NavHostController,
|
||||||
|
viewModel: TransmissionScreenViewModel,
|
||||||
|
model: VideoDownloadItemState,
|
||||||
|
imageHeight: Dp = 85.dp,
|
||||||
|
imageMaxWidth: Dp = 140.dp
|
||||||
|
) {
|
||||||
|
val downloads = viewModel.downloads
|
||||||
|
VideoDownloadCardMini(
|
||||||
|
navigator = navigator,
|
||||||
|
viewModel = viewModel,
|
||||||
|
model = model,
|
||||||
|
onPause = {
|
||||||
|
for (i in downloadToGroup(
|
||||||
|
model,
|
||||||
|
downloads
|
||||||
|
)) viewModel.pause(i.id)
|
||||||
|
},
|
||||||
|
onResume = {
|
||||||
|
for (i in downloadToGroup(
|
||||||
|
model,
|
||||||
|
downloads
|
||||||
|
)) viewModel.resume(i.id)
|
||||||
|
},
|
||||||
|
onRetry = {
|
||||||
|
for (i in downloadToGroup(
|
||||||
|
model,
|
||||||
|
downloads
|
||||||
|
)) viewModel.retry(i.id)
|
||||||
|
},
|
||||||
|
imageHeight,
|
||||||
|
imageMaxWidth
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun VideoDownloadCard(
|
fun VideoDownloadCard(
|
||||||
navigator: NavHostController,
|
navigator: NavHostController,
|
||||||
viewModel: TransmissionScreenViewModel,
|
viewModel: TransmissionScreenViewModel,
|
||||||
model: VideoDownloadItemState,
|
models: List<VideoDownloadItemState>
|
||||||
onPause: () -> Unit,
|
|
||||||
onResume: () -> Unit,
|
|
||||||
onCancel: () -> Unit,
|
|
||||||
onDelete: () -> Unit,
|
|
||||||
onRetry: () -> Unit
|
|
||||||
) {
|
) {
|
||||||
Card(
|
val item = models.first()
|
||||||
shape = RoundedCornerShape(8.dp),
|
var mutiSelection by viewModel.mutiSelection
|
||||||
elevation = CardDefaults.cardElevation(4.dp),
|
val colorScheme = MaterialTheme.colorScheme
|
||||||
modifier = Modifier
|
|
||||||
.fillMaxWidth()
|
|
||||||
.padding(8.dp)
|
|
||||||
.background(Color.Transparent)
|
|
||||||
.clickable(onClick = {
|
|
||||||
if (model.status == Status.COMPLETED) {
|
|
||||||
viewModel.viewModelScope.launch(Dispatchers.IO)
|
|
||||||
{
|
|
||||||
val downloaded = viewModel.fetchManager.getAllDownloadsAsync().filter {
|
|
||||||
it.status == Status.COMPLETED && it.extras.getString(
|
|
||||||
"class",
|
|
||||||
""
|
|
||||||
) != "comic" && it.extras.getString(
|
|
||||||
"type",
|
|
||||||
""
|
|
||||||
) == "main"
|
|
||||||
}
|
|
||||||
|
|
||||||
val jsonQuery = downloaded.map {
|
if (models.size == 1) {
|
||||||
|
VideoDownloadCardMiniPack(
|
||||||
|
navigator = navigator,
|
||||||
|
viewModel = viewModel,
|
||||||
|
model = item
|
||||||
|
)
|
||||||
|
} else if (models.size > 1) {
|
||||||
|
val imageModel = if (item.status == Status.COMPLETED) {
|
||||||
|
ImageRequest.Builder(LocalContext.current)
|
||||||
|
.data(
|
||||||
File(
|
File(
|
||||||
viewModel.context.getExternalFilesDir(null),
|
viewModel.context.getExternalFilesDir(null),
|
||||||
"videos/${
|
"videos/${item.klass}/${item.vid}/cover.jpg"
|
||||||
it.extras.getString(
|
|
||||||
"class",
|
|
||||||
""
|
|
||||||
)
|
)
|
||||||
}/${it.extras.getString("id", "")}/summary.json"
|
|
||||||
).readText()
|
|
||||||
}
|
|
||||||
.map {
|
|
||||||
Json.decodeFromString<Video>(it)
|
|
||||||
.toLocal(viewModel.context.getExternalFilesDir(null)!!.path)
|
|
||||||
}
|
|
||||||
|
|
||||||
updateRelate(
|
|
||||||
jsonQuery,
|
|
||||||
jsonQuery.first { it.id == model.vid && it.klass == model.klass }
|
|
||||||
)
|
)
|
||||||
|
.memoryCacheKey("${item.klass}/${item.vid}/cover")
|
||||||
val playList = mutableListOf<String>()
|
.diskCacheKey("${item.klass}/${item.vid}/cover")
|
||||||
val fv = viewModel.videoLibrary.classesMap.map { it.value }.flatten()
|
.build()
|
||||||
val video = fv.firstOrNull { it.klass == model.klass && it.id == model.vid }
|
} else {
|
||||||
|
ImageRequest.Builder(LocalContext.current)
|
||||||
if (video != null) {
|
.data(Video.getCoverStatic(viewModel.apiClient, item.klass, item.vid))
|
||||||
val group = fv.filter { it.klass == video.klass && it.video.group == video.video.group }
|
.memoryCacheKey("${item.klass}/${item.vid}/cover")
|
||||||
for (i in group.sortedWith(compareBy(naturalOrder()) { it.video.name })) {
|
.diskCacheKey("${item.klass}/${item.vid}/cover")
|
||||||
playList.add("${i.klass}/${i.id}")
|
.build()
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
val route = "video_player_route/${(playList.joinToString(",") + "|${model.vid}").toHex()}"
|
Card(
|
||||||
withContext(Dispatchers.Main) {
|
colors = CardDefaults.cardColors(containerColor = Color.Transparent),
|
||||||
navigator.navigate(route)
|
shape = RoundedCornerShape(8.dp),
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
) {
|
|
||||||
Column(
|
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.fillMaxWidth()
|
.fillMaxWidth()
|
||||||
.padding(12.dp)
|
.padding(horizontal = 4.dp)
|
||||||
|
.background(Color.Transparent)
|
||||||
|
.combinedClickable(
|
||||||
|
onClick = {
|
||||||
|
if (!mutiSelection)
|
||||||
|
viewModel.groupExpandMap[item.group] = !viewModel.groupExpandMap.getOrDefault(item.group, false)
|
||||||
|
else {
|
||||||
|
if(!models.all { "${it.klass}/${it.vid}" in viewModel.mutiSelectionList })
|
||||||
|
viewModel.mutiSelectionList.addAll(
|
||||||
|
models.map { "${it.klass}/${it.vid}" }.filter { it !in viewModel.mutiSelectionList }
|
||||||
|
)
|
||||||
|
else
|
||||||
|
viewModel.mutiSelectionList.removeAll(models.map { "${it.klass}/${it.vid}" })
|
||||||
|
}
|
||||||
|
},
|
||||||
|
onLongClick = {
|
||||||
|
mutiSelection = !mutiSelection
|
||||||
|
}
|
||||||
|
)
|
||||||
|
.height(85.dp)
|
||||||
) {
|
) {
|
||||||
Row(
|
Row(
|
||||||
verticalAlignment = Alignment.CenterVertically,
|
modifier = Modifier.fillMaxSize()
|
||||||
modifier = Modifier.fillMaxWidth()
|
)
|
||||||
) {
|
{
|
||||||
Column(modifier = Modifier.weight(1f)) {
|
Box(
|
||||||
Text(text = model.fileName, style = MaterialTheme.typography.titleMedium, maxLines = 2)
|
Modifier
|
||||||
// Text(text = model.filePath, style = MaterialTheme.typography.titleSmall)
|
.fillMaxHeight()
|
||||||
}
|
)
|
||||||
|
{
|
||||||
|
AsyncImage(
|
||||||
|
model = imageModel,
|
||||||
|
contentDescription = null,
|
||||||
|
modifier = Modifier
|
||||||
|
.height(85.dp)
|
||||||
|
.clip(RoundedCornerShape(8.dp))
|
||||||
|
.widthIn(max = 140.dp)
|
||||||
|
.background(Color.Black),
|
||||||
|
contentScale = ContentScale.Crop
|
||||||
|
)
|
||||||
|
|
||||||
|
Box(Modifier
|
||||||
|
.padding(4.dp)
|
||||||
|
.background(Color.Black.copy(0.4f), shape = RoundedCornerShape(6.dp))
|
||||||
|
.align(Alignment.BottomEnd)
|
||||||
|
.padding(2.dp))
|
||||||
|
{
|
||||||
|
Text(text = "${models.size} Videos",
|
||||||
|
fontWeight = FontWeight.Bold,
|
||||||
|
fontSize = 12.sp,
|
||||||
|
lineHeight = 13.5.sp)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Box(
|
Box(
|
||||||
Modifier
|
Modifier
|
||||||
.fillMaxWidth()
|
.fillMaxSize()
|
||||||
.padding(top = 5.dp)
|
.padding(all = 4.dp)
|
||||||
|
.padding(end = 4.dp)
|
||||||
)
|
)
|
||||||
{
|
{
|
||||||
Card(
|
Text(
|
||||||
shape = RoundedCornerShape(8.dp),
|
text = models.first().group,
|
||||||
modifier = Modifier.align(Alignment.CenterStart)
|
lineHeight = 14.sp,
|
||||||
) {
|
fontSize = 12.sp,
|
||||||
val video = viewModel.modelToVideo(model)
|
fontWeight = FontWeight.Bold,
|
||||||
|
maxLines = 2,
|
||||||
if (video == null)
|
modifier = Modifier.align(Alignment.TopEnd)
|
||||||
AsyncImage(
|
|
||||||
model = ImageRequest.Builder(LocalContext.current)
|
|
||||||
.data(
|
|
||||||
File(
|
|
||||||
viewModel.context.getExternalFilesDir(null),
|
|
||||||
"videos/${model.klass}/${model.vid}/cover.jpg"
|
|
||||||
)
|
)
|
||||||
)
|
|
||||||
.memoryCacheKey("${model.klass}/${model.vid}/cover")
|
|
||||||
.diskCacheKey("${model.klass}/${model.vid}/cover")
|
|
||||||
.build(),
|
|
||||||
contentDescription = null,
|
|
||||||
modifier = Modifier.height(100.dp),
|
|
||||||
contentScale = ContentScale.Fit
|
|
||||||
)
|
|
||||||
else {
|
|
||||||
AsyncImage(
|
|
||||||
model = ImageRequest.Builder(LocalContext.current)
|
|
||||||
.data(video.getCover(viewModel.apiClient))
|
|
||||||
.memoryCacheKey("${model.klass}/${model.vid}/cover")
|
|
||||||
.diskCacheKey("${model.klass}/${model.vid}/cover")
|
|
||||||
.build(),
|
|
||||||
contentDescription = null,
|
|
||||||
modifier = Modifier.height(100.dp),
|
|
||||||
contentScale = ContentScale.Fit,
|
|
||||||
imageLoader = viewModel.imageLoader!!
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Column(Modifier.align(Alignment.BottomEnd)) {
|
Column(Modifier.align(Alignment.BottomEnd)) {
|
||||||
Text(
|
|
||||||
text = "${model.progress.coerceIn(0, 100)}%",
|
|
||||||
modifier = Modifier
|
|
||||||
.padding(start = 8.dp)
|
|
||||||
.align(Alignment.End)
|
|
||||||
)
|
|
||||||
|
|
||||||
|
Row(
|
||||||
|
Modifier
|
||||||
|
.align(Alignment.End)
|
||||||
|
.padding(vertical = 2.dp)
|
||||||
|
) {
|
||||||
Text(
|
Text(
|
||||||
modifier = Modifier
|
modifier = Modifier,
|
||||||
.padding(start = 8.dp)
|
|
||||||
.align(Alignment.End),
|
|
||||||
text = "%.2f MB/%.2f MB".format(
|
text = "%.2f MB/%.2f MB".format(
|
||||||
model.downloadedBytes / (1024.0 * 1024.0),
|
models.sumOf { it.downloadedBytes } / (1024.0 * 1024.0),
|
||||||
model.totalBytes / (1024.0 * 1024.0)
|
models.sumOf { it.totalBytes } / (1024.0 * 1024.0)
|
||||||
),
|
),
|
||||||
fontSize = 10.sp,
|
fontSize = 10.sp,
|
||||||
|
lineHeight = 11.sp,
|
||||||
|
fontWeight = FontWeight.Bold,
|
||||||
|
maxLines = 1,
|
||||||
|
)
|
||||||
|
Spacer(Modifier.width(12.dp))
|
||||||
|
Text(
|
||||||
|
text = "${
|
||||||
|
models.map { it.progress }.average().toInt().coerceIn(0, 100)
|
||||||
|
}%",
|
||||||
|
modifier = Modifier,
|
||||||
|
fontSize = 10.sp,
|
||||||
|
lineHeight = 11.sp,
|
||||||
fontWeight = FontWeight.Bold,
|
fontWeight = FontWeight.Bold,
|
||||||
maxLines = 1,
|
maxLines = 1,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
BiliMiniSlider(
|
||||||
|
value = abs(models.map { it.progress }.average().toInt()).coerceIn(
|
||||||
|
0,
|
||||||
// progress bar
|
100
|
||||||
LinearProgressIndicator(
|
) / 100f,
|
||||||
progress = { abs(model.progress).coerceIn(0, 100) / 100f },
|
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
|
.height(6.dp)
|
||||||
|
.align(Alignment.End)
|
||||||
|
.fillMaxWidth(),
|
||||||
|
onValueChange = {
|
||||||
|
|
||||||
|
},
|
||||||
|
colors = SliderDefaults.colors(
|
||||||
|
thumbColor = Color(0xFFFFFFFF),
|
||||||
|
activeTrackColor = MaterialTheme.colorScheme.primary,
|
||||||
|
inactiveTrackColor = Color.LightGray.copy(alpha = 0.4f)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
AnimatedVisibility(
|
||||||
|
visible = viewModel.groupExpandMap.getOrDefault(item.group, false) || mutiSelection,
|
||||||
|
enter = expandVertically(expandFrom = Alignment.Top) + fadeIn(),
|
||||||
|
exit = shrinkVertically(shrinkTowards = Alignment.Top) + fadeOut()
|
||||||
|
)
|
||||||
|
{
|
||||||
|
Column(
|
||||||
|
Modifier
|
||||||
.fillMaxWidth()
|
.fillMaxWidth()
|
||||||
.padding(top = 8.dp, bottom = 8.dp),
|
.heightIn(max = 300.dp)
|
||||||
color = ProgressIndicatorDefaults.linearColor,
|
|
||||||
trackColor = ProgressIndicatorDefaults.linearTrackColor,
|
|
||||||
strokeCap = ProgressIndicatorDefaults.LinearStrokeCap,
|
|
||||||
)
|
|
||||||
|
|
||||||
// action buttons
|
|
||||||
Row(
|
|
||||||
horizontalArrangement = Arrangement.spacedBy(8.dp),
|
|
||||||
verticalAlignment = Alignment.CenterVertically,
|
|
||||||
modifier = Modifier.fillMaxWidth()
|
|
||||||
) {
|
) {
|
||||||
when (model.status) {
|
HorizontalDivider(
|
||||||
Status.DOWNLOADING -> {
|
Modifier.padding(horizontal = 16.dp, vertical = 3.dp),
|
||||||
Button(onClick = onPause) {
|
2.dp,
|
||||||
Icon(imageVector = Icons.Default.Pause, contentDescription = "Pause")
|
DividerDefaults.color
|
||||||
Text(text = " Pause", modifier = Modifier.padding(start = 6.dp))
|
|
||||||
}
|
|
||||||
Button(onClick = onCancel) {
|
|
||||||
Icon(imageVector = Icons.Default.Stop, contentDescription = "Cancel")
|
|
||||||
Text(text = " Cancel", modifier = Modifier.padding(start = 6.dp))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Status.PAUSED, Status.QUEUED -> {
|
|
||||||
Button(onClick = onResume) {
|
|
||||||
Icon(
|
|
||||||
imageVector = Icons.Default.PlayArrow,
|
|
||||||
contentDescription = "Resume"
|
|
||||||
)
|
)
|
||||||
Text(text = " Resume", modifier = Modifier.padding(start = 6.dp))
|
LazyColumn(
|
||||||
}
|
Modifier
|
||||||
Button(onClick = onCancel) {
|
.padding(horizontal = 16.dp)
|
||||||
Icon(imageVector = Icons.Default.Stop, contentDescription = "Cancel")
|
.background(colorScheme.surface, shape = RoundedCornerShape(6.dp))
|
||||||
Text(text = " Cancel", modifier = Modifier.padding(start = 6.dp))
|
)
|
||||||
}
|
{
|
||||||
}
|
items(
|
||||||
|
items = models,
|
||||||
Status.COMPLETED -> {
|
key = { it.id }
|
||||||
Button(onClick = onDelete) {
|
) { single ->
|
||||||
Icon(imageVector = Icons.Default.Delete, contentDescription = "Delete")
|
Box(Modifier.padding(vertical = 4.dp))
|
||||||
Text(text = " Delete", modifier = Modifier.padding(start = 6.dp))
|
{
|
||||||
}
|
VideoDownloadCardMiniPack(
|
||||||
}
|
navigator = navigator,
|
||||||
|
viewModel = viewModel,
|
||||||
else -> {
|
model = single,
|
||||||
// for FAILED, CANCELLED, REMOVED etc.
|
imageHeight = 75.dp,
|
||||||
Button(onClick = onRetry) {
|
imageMaxWidth = 120.dp
|
||||||
Icon(
|
|
||||||
imageVector = Icons.Default.PlayArrow,
|
|
||||||
contentDescription = "Retry"
|
|
||||||
)
|
)
|
||||||
Text(text = " Retry", modifier = Modifier.padding(start = 6.dp))
|
|
||||||
}
|
|
||||||
Button(onClick = onDelete) {
|
|
||||||
Icon(imageVector = Icons.Default.Delete, contentDescription = "Delete")
|
|
||||||
Text(text = " Delete", modifier = Modifier.padding(start = 6.dp))
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,12 @@
|
|||||||
package com.acitelight.aether.view.components
|
package com.acitelight.aether.view.components
|
||||||
|
|
||||||
|
import androidx.compose.animation.AnimatedVisibility
|
||||||
|
import androidx.compose.animation.expandHorizontally
|
||||||
|
import androidx.compose.animation.fadeIn
|
||||||
|
import androidx.compose.animation.fadeOut
|
||||||
|
import androidx.compose.animation.shrinkHorizontally
|
||||||
import androidx.compose.foundation.background
|
import androidx.compose.foundation.background
|
||||||
import androidx.compose.foundation.clickable
|
import androidx.compose.foundation.combinedClickable
|
||||||
import androidx.compose.foundation.layout.Box
|
import androidx.compose.foundation.layout.Box
|
||||||
import androidx.compose.foundation.layout.Column
|
import androidx.compose.foundation.layout.Column
|
||||||
import androidx.compose.foundation.layout.Row
|
import androidx.compose.foundation.layout.Row
|
||||||
@@ -11,20 +16,21 @@ import androidx.compose.foundation.layout.fillMaxSize
|
|||||||
import androidx.compose.foundation.layout.fillMaxWidth
|
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.layout.padding
|
||||||
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.layout.widthIn
|
||||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||||
import androidx.compose.material.icons.Icons
|
|
||||||
import androidx.compose.material.icons.filled.Delete
|
|
||||||
import androidx.compose.material3.Card
|
import androidx.compose.material3.Card
|
||||||
import androidx.compose.material3.CardDefaults
|
import androidx.compose.material3.CardDefaults
|
||||||
import androidx.compose.material3.Icon
|
import androidx.compose.material3.Checkbox
|
||||||
import androidx.compose.material3.IconButton
|
|
||||||
import androidx.compose.material3.MaterialTheme
|
import androidx.compose.material3.MaterialTheme
|
||||||
import androidx.compose.material3.SliderDefaults
|
import androidx.compose.material3.SliderDefaults
|
||||||
import androidx.compose.material3.Text
|
import androidx.compose.material3.Text
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.runtime.getValue
|
||||||
|
import androidx.compose.runtime.setValue
|
||||||
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.clip
|
import androidx.compose.ui.draw.clip
|
||||||
@@ -32,12 +38,14 @@ import androidx.compose.ui.graphics.Color
|
|||||||
import androidx.compose.ui.layout.ContentScale
|
import androidx.compose.ui.layout.ContentScale
|
||||||
import androidx.compose.ui.platform.LocalContext
|
import androidx.compose.ui.platform.LocalContext
|
||||||
import androidx.compose.ui.text.font.FontWeight
|
import androidx.compose.ui.text.font.FontWeight
|
||||||
|
import androidx.compose.ui.unit.Dp
|
||||||
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.viewModelScope
|
import androidx.lifecycle.viewModelScope
|
||||||
import androidx.navigation.NavHostController
|
import androidx.navigation.NavHostController
|
||||||
import coil3.compose.AsyncImage
|
import coil3.compose.AsyncImage
|
||||||
import coil3.request.ImageRequest
|
import coil3.request.ImageRequest
|
||||||
|
import com.acitelight.aether.model.Video
|
||||||
import com.acitelight.aether.model.VideoDownloadItemState
|
import com.acitelight.aether.model.VideoDownloadItemState
|
||||||
import com.acitelight.aether.viewModel.TransmissionScreenViewModel
|
import com.acitelight.aether.viewModel.TransmissionScreenViewModel
|
||||||
import com.tonyodev.fetch2.Status
|
import com.tonyodev.fetch2.Status
|
||||||
@@ -53,13 +61,13 @@ fun VideoDownloadCardMini(
|
|||||||
model: VideoDownloadItemState,
|
model: VideoDownloadItemState,
|
||||||
onPause: () -> Unit,
|
onPause: () -> Unit,
|
||||||
onResume: () -> Unit,
|
onResume: () -> Unit,
|
||||||
onCancel: () -> Unit,
|
onRetry: () -> Unit,
|
||||||
onDelete: () -> Unit,
|
imageHeight: Dp = 85.dp,
|
||||||
onRetry: () -> Unit
|
imageMaxWidth: Dp = 140.dp
|
||||||
) {
|
) {
|
||||||
val video = viewModel.modelToVideo(model)
|
var mutiSelection by viewModel.mutiSelection
|
||||||
val imageModel =
|
val imageModel = if (model.status == Status.COMPLETED)
|
||||||
if (video == null)
|
{
|
||||||
ImageRequest.Builder(LocalContext.current)
|
ImageRequest.Builder(LocalContext.current)
|
||||||
.data(
|
.data(
|
||||||
File(
|
File(
|
||||||
@@ -70,12 +78,14 @@ fun VideoDownloadCardMini(
|
|||||||
.memoryCacheKey("${model.klass}/${model.vid}/cover")
|
.memoryCacheKey("${model.klass}/${model.vid}/cover")
|
||||||
.diskCacheKey("${model.klass}/${model.vid}/cover")
|
.diskCacheKey("${model.klass}/${model.vid}/cover")
|
||||||
.build()
|
.build()
|
||||||
else
|
} else
|
||||||
|
{
|
||||||
ImageRequest.Builder(LocalContext.current)
|
ImageRequest.Builder(LocalContext.current)
|
||||||
.data(video.getCover(viewModel.apiClient))
|
.data(Video.getCoverStatic(viewModel.apiClient, model.klass, model.vid))
|
||||||
.memoryCacheKey("${model.klass}/${model.vid}/cover")
|
.memoryCacheKey("${model.klass}/${model.vid}/cover")
|
||||||
.diskCacheKey("${model.klass}/${model.vid}/cover")
|
.diskCacheKey("${model.klass}/${model.vid}/cover")
|
||||||
.build()
|
.build()
|
||||||
|
}
|
||||||
|
|
||||||
Card(
|
Card(
|
||||||
colors = CardDefaults.cardColors(containerColor = Color.Transparent),
|
colors = CardDefaults.cardColors(containerColor = Color.Transparent),
|
||||||
@@ -84,54 +94,70 @@ fun VideoDownloadCardMini(
|
|||||||
.fillMaxWidth()
|
.fillMaxWidth()
|
||||||
.padding(horizontal = 4.dp)
|
.padding(horizontal = 4.dp)
|
||||||
.background(Color.Transparent)
|
.background(Color.Transparent)
|
||||||
.clickable(onClick = {
|
.combinedClickable(
|
||||||
|
onClick = {
|
||||||
|
if (!mutiSelection)
|
||||||
when (model.status) {
|
when (model.status) {
|
||||||
Status.COMPLETED -> viewModel.viewModelScope.launch(Dispatchers.IO)
|
Status.COMPLETED -> viewModel.viewModelScope.launch(Dispatchers.IO)
|
||||||
{
|
{
|
||||||
viewModel.playStart(model, navigator)
|
viewModel.playStart(model, navigator)
|
||||||
}
|
}
|
||||||
|
|
||||||
Status.DOWNLOADING -> onPause()
|
Status.DOWNLOADING -> onPause()
|
||||||
Status.PAUSED -> onResume()
|
Status.PAUSED -> onResume()
|
||||||
Status.ADDED, Status.FAILED, Status.CANCELLED -> onRetry()
|
Status.ADDED, Status.FAILED, Status.CANCELLED -> onRetry()
|
||||||
else -> {}
|
else -> {}
|
||||||
}
|
}
|
||||||
})
|
else {
|
||||||
.height(100.dp)
|
if ("${model.klass}/${model.vid}" !in viewModel.mutiSelectionList)
|
||||||
|
viewModel.mutiSelectionList.add("${model.klass}/${model.vid}")
|
||||||
|
else
|
||||||
|
viewModel.mutiSelectionList.remove("${model.klass}/${model.vid}")
|
||||||
|
}
|
||||||
|
},
|
||||||
|
onLongClick = {
|
||||||
|
mutiSelection = !mutiSelection
|
||||||
|
}
|
||||||
|
)
|
||||||
|
.height(imageHeight)
|
||||||
) {
|
) {
|
||||||
Row(
|
Row(
|
||||||
modifier = Modifier.fillMaxSize()
|
modifier = Modifier.fillMaxSize()
|
||||||
)
|
)
|
||||||
{
|
{
|
||||||
Box(Modifier
|
Row(
|
||||||
.fillMaxHeight())
|
Modifier.fillMaxHeight()
|
||||||
|
)
|
||||||
{
|
{
|
||||||
|
AnimatedVisibility(
|
||||||
|
modifier = Modifier.align(Alignment.CenterVertically),
|
||||||
|
visible = mutiSelection,
|
||||||
|
enter = expandHorizontally(expandFrom = Alignment.Start) + fadeIn(),
|
||||||
|
exit = shrinkHorizontally(shrinkTowards = Alignment.Start) + fadeOut()
|
||||||
|
)
|
||||||
|
{
|
||||||
|
Checkbox(
|
||||||
|
checked = "${model.klass}/${model.vid}" in viewModel.mutiSelectionList,
|
||||||
|
onCheckedChange = { state ->
|
||||||
|
if (state)
|
||||||
|
viewModel.mutiSelectionList.add("${model.klass}/${model.vid}")
|
||||||
|
else
|
||||||
|
viewModel.mutiSelectionList.remove("${model.klass}/${model.vid}")
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
AsyncImage(
|
AsyncImage(
|
||||||
model = imageModel,
|
model = imageModel,
|
||||||
contentDescription = null,
|
contentDescription = null,
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.height(100.dp)
|
.height(imageHeight)
|
||||||
.clip(RoundedCornerShape(8.dp))
|
.clip(RoundedCornerShape(8.dp))
|
||||||
.widthIn(max = 150.dp)
|
.widthIn(max = imageMaxWidth)
|
||||||
.background(Color.Black),
|
.background(Color.Black),
|
||||||
contentScale = ContentScale.Crop
|
contentScale = ContentScale.Crop,
|
||||||
|
imageLoader = viewModel.imageLoader!!
|
||||||
)
|
)
|
||||||
|
|
||||||
IconButton(
|
|
||||||
onClick = onDelete,
|
|
||||||
Modifier
|
|
||||||
.padding(2.dp)
|
|
||||||
.size(24.dp)
|
|
||||||
.align(Alignment.TopStart)
|
|
||||||
.background(MaterialTheme.colorScheme.error, RoundedCornerShape(4.dp))
|
|
||||||
.clip(RoundedCornerShape(4.dp))
|
|
||||||
) {
|
|
||||||
Icon(
|
|
||||||
imageVector = Icons.Default.Delete,
|
|
||||||
contentDescription = "Delete",
|
|
||||||
tint = Color.White,
|
|
||||||
modifier = Modifier.size(20.dp)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Box(
|
Box(
|
||||||
@@ -165,9 +191,11 @@ fun VideoDownloadCardMini(
|
|||||||
maxLines = 1,
|
maxLines = 1,
|
||||||
)
|
)
|
||||||
|
|
||||||
Row(Modifier
|
Row(
|
||||||
|
Modifier
|
||||||
.align(Alignment.End)
|
.align(Alignment.End)
|
||||||
.padding(vertical = 2.dp)) {
|
.padding(vertical = 2.dp)
|
||||||
|
) {
|
||||||
Text(
|
Text(
|
||||||
modifier = Modifier,
|
modifier = Modifier,
|
||||||
text = "%.2f MB/%.2f MB".format(
|
text = "%.2f MB/%.2f MB".format(
|
||||||
@@ -198,23 +226,25 @@ fun VideoDownloadCardMini(
|
|||||||
onValueChange = {
|
onValueChange = {
|
||||||
|
|
||||||
},
|
},
|
||||||
colors = when(model.status)
|
colors = when (model.status) {
|
||||||
{
|
|
||||||
Status.DOWNLOADING, Status.QUEUED, Status.ADDED -> SliderDefaults.colors(
|
Status.DOWNLOADING, Status.QUEUED, Status.ADDED -> SliderDefaults.colors(
|
||||||
thumbColor = Color(0xFFFFFFFF),
|
thumbColor = Color(0xFFFFFFFF),
|
||||||
activeTrackColor = MaterialTheme.colorScheme.primary,
|
activeTrackColor = MaterialTheme.colorScheme.primary,
|
||||||
inactiveTrackColor = Color.LightGray.copy(alpha = 0.4f)
|
inactiveTrackColor = Color.LightGray.copy(alpha = 0.4f)
|
||||||
)
|
)
|
||||||
|
|
||||||
Status.PAUSED -> SliderDefaults.colors(
|
Status.PAUSED -> SliderDefaults.colors(
|
||||||
thumbColor = Color(0xFFFFFFFF),
|
thumbColor = Color(0xFFFFFFFF),
|
||||||
activeTrackColor = Color(0xFFFFA500),
|
activeTrackColor = Color(0xFFFFA500),
|
||||||
inactiveTrackColor = Color.LightGray.copy(alpha = 0.4f)
|
inactiveTrackColor = Color.LightGray.copy(alpha = 0.4f)
|
||||||
)
|
)
|
||||||
|
|
||||||
Status.COMPLETED -> SliderDefaults.colors(
|
Status.COMPLETED -> SliderDefaults.colors(
|
||||||
thumbColor = Color(0xFFFFFFFF),
|
thumbColor = Color(0xFFFFFFFF),
|
||||||
activeTrackColor = MaterialTheme.colorScheme.primary,
|
activeTrackColor = MaterialTheme.colorScheme.primary,
|
||||||
inactiveTrackColor = Color.LightGray.copy(alpha = 0.4f)
|
inactiveTrackColor = Color.LightGray.copy(alpha = 0.4f)
|
||||||
)
|
)
|
||||||
|
|
||||||
else -> SliderDefaults.colors(
|
else -> SliderDefaults.colors(
|
||||||
thumbColor = Color(0xFFFFFFFF),
|
thumbColor = Color(0xFFFFFFFF),
|
||||||
activeTrackColor = MaterialTheme.colorScheme.error,
|
activeTrackColor = MaterialTheme.colorScheme.error,
|
||||||
|
|||||||
@@ -31,6 +31,7 @@ import androidx.compose.foundation.layout.size
|
|||||||
import androidx.compose.foundation.layout.width
|
import androidx.compose.foundation.layout.width
|
||||||
import androidx.compose.foundation.lazy.LazyColumn
|
import androidx.compose.foundation.lazy.LazyColumn
|
||||||
import androidx.compose.foundation.lazy.items
|
import androidx.compose.foundation.lazy.items
|
||||||
|
import androidx.compose.foundation.lazy.rememberLazyListState
|
||||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||||
import androidx.compose.material.icons.Icons
|
import androidx.compose.material.icons.Icons
|
||||||
import androidx.compose.material.icons.automirrored.filled.ArrowBack
|
import androidx.compose.material.icons.automirrored.filled.ArrowBack
|
||||||
@@ -52,6 +53,7 @@ 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.DisposableEffect
|
import androidx.compose.runtime.DisposableEffect
|
||||||
|
import androidx.compose.runtime.LaunchedEffect
|
||||||
import androidx.compose.runtime.getValue
|
import androidx.compose.runtime.getValue
|
||||||
import androidx.compose.runtime.mutableFloatStateOf
|
import androidx.compose.runtime.mutableFloatStateOf
|
||||||
import androidx.compose.runtime.remember
|
import androidx.compose.runtime.remember
|
||||||
@@ -87,6 +89,18 @@ fun VideoPlayerLandscape(videoPlayerViewModel: VideoPlayerViewModel) {
|
|||||||
val activity = (context as? Activity)!!
|
val activity = (context as? Activity)!!
|
||||||
val exoPlayer: ExoPlayer = videoPlayerViewModel.player!!
|
val exoPlayer: ExoPlayer = videoPlayerViewModel.player!!
|
||||||
|
|
||||||
|
val name by videoPlayerViewModel.currentName
|
||||||
|
val id by videoPlayerViewModel.currentId
|
||||||
|
val listState = rememberLazyListState()
|
||||||
|
val videos = videoPlayerViewModel.videos
|
||||||
|
|
||||||
|
LaunchedEffect(id, videos) {
|
||||||
|
val targetIndex = videos.indexOfFirst { it.id == id }
|
||||||
|
if (targetIndex >= 0) {
|
||||||
|
listState.scrollToItem(targetIndex)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
val audioManager = remember { context.getSystemService(Context.AUDIO_SERVICE) as AudioManager }
|
val audioManager = remember { context.getSystemService(Context.AUDIO_SERVICE) as AudioManager }
|
||||||
val maxVolume = remember { audioManager.getStreamMaxVolume(AudioManager.STREAM_MUSIC) }
|
val maxVolume = remember { audioManager.getStreamMaxVolume(AudioManager.STREAM_MUSIC) }
|
||||||
var volFactor by remember {
|
var volFactor by remember {
|
||||||
@@ -95,9 +109,6 @@ fun VideoPlayerLandscape(videoPlayerViewModel: VideoPlayerViewModel) {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
val name by videoPlayerViewModel.currentName
|
|
||||||
val id by videoPlayerViewModel.currentId
|
|
||||||
|
|
||||||
fun setVolume(value: Int) {
|
fun setVolume(value: Int) {
|
||||||
audioManager.setStreamVolume(
|
audioManager.setStreamVolume(
|
||||||
AudioManager.STREAM_MUSIC,
|
AudioManager.STREAM_MUSIC,
|
||||||
@@ -229,7 +240,9 @@ fun VideoPlayerLandscape(videoPlayerViewModel: VideoPlayerViewModel) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
modifier = Modifier.fillMaxWidth()
|
modifier = Modifier
|
||||||
|
.align(Alignment.Center)
|
||||||
|
.fillMaxWidth()
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -588,7 +601,7 @@ fun VideoPlayerLandscape(videoPlayerViewModel: VideoPlayerViewModel) {
|
|||||||
colors = CardDefaults.cardColors(containerColor = colorScheme.surface.copy(0.75f))
|
colors = CardDefaults.cardColors(containerColor = colorScheme.surface.copy(0.75f))
|
||||||
)
|
)
|
||||||
{
|
{
|
||||||
LazyColumn(contentPadding = PaddingValues(vertical = 4.dp)) {
|
LazyColumn(state = listState, contentPadding = PaddingValues(vertical = 4.dp)) {
|
||||||
items(videoPlayerViewModel.videos) { item ->
|
items(videoPlayerViewModel.videos) { item ->
|
||||||
MiniPlaylistCard(Modifier.padding(4.dp), video = item, imageLoader = videoPlayerViewModel.imageLoader!!,
|
MiniPlaylistCard(Modifier.padding(4.dp), video = item, imageLoader = videoPlayerViewModel.imageLoader!!,
|
||||||
selected = id == item.id, apiClient = videoPlayerViewModel.apiClient)
|
selected = id == item.id, apiClient = videoPlayerViewModel.apiClient)
|
||||||
|
|||||||
@@ -260,7 +260,7 @@ fun VideoPlayerPortal(
|
|||||||
playList.add("${i.klass}/${i.id}")
|
playList.add("${i.klass}/${i.id}")
|
||||||
}
|
}
|
||||||
|
|
||||||
val route = "video_player_route/${playList.joinToString(",").toHex()}"
|
val route = "video_player_route/${(playList.joinToString(",") + "|${i.id}").toHex()}"
|
||||||
navController.navigate(route)
|
navController.navigate(route)
|
||||||
}
|
}
|
||||||
HorizontalDivider(
|
HorizontalDivider(
|
||||||
|
|||||||
@@ -96,7 +96,7 @@ fun ComicGridView(
|
|||||||
}
|
}
|
||||||
|
|
||||||
LaunchedEffect(comicGridViewModel) {
|
LaunchedEffect(comicGridViewModel) {
|
||||||
comicGridViewModel.coverHeight = screenHeight * 0.4f
|
comicGridViewModel.coverHeight = screenHeight * 0.3f
|
||||||
if(comicGridViewModel.maxHeight == 0.dp)
|
if(comicGridViewModel.maxHeight == 0.dp)
|
||||||
comicGridViewModel.maxHeight = screenHeight * 0.8f
|
comicGridViewModel.maxHeight = screenHeight * 0.8f
|
||||||
}
|
}
|
||||||
@@ -252,7 +252,7 @@ fun ComicGridView(
|
|||||||
fontSize = 11.sp,
|
fontSize = 11.sp,
|
||||||
lineHeight = 15.sp,
|
lineHeight = 15.sp,
|
||||||
maxLines = 3,
|
maxLines = 3,
|
||||||
modifier = Modifier.padding(horizontal = 16.dp).padding(bottom = 4.dp)
|
modifier = Modifier.padding(horizontal = 16.dp).padding(bottom = 4.dp).align(Alignment.CenterStart)
|
||||||
)
|
)
|
||||||
|
|
||||||
Button(onClick = {
|
Button(onClick = {
|
||||||
@@ -379,10 +379,10 @@ fun ChapterCard(
|
|||||||
{
|
{
|
||||||
Text(
|
Text(
|
||||||
text = chapter.name,
|
text = chapter.name,
|
||||||
fontSize = 14.sp,
|
fontSize = 16.sp,
|
||||||
fontWeight = FontWeight.Bold,
|
fontWeight = FontWeight.Bold,
|
||||||
maxLines = 2,
|
maxLines = 2,
|
||||||
lineHeight = 16.sp,
|
lineHeight = 18.sp,
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.padding(horizontal = 8.dp).padding(vertical = 4.dp)
|
.padding(horizontal = 8.dp).padding(vertical = 4.dp)
|
||||||
.background(Color.Transparent)
|
.background(Color.Transparent)
|
||||||
@@ -391,7 +391,6 @@ fun ChapterCard(
|
|||||||
text = "${comic.getChapterLength(chapter.page)} Pages",
|
text = "${comic.getChapterLength(chapter.page)} Pages",
|
||||||
fontSize = 14.sp,
|
fontSize = 14.sp,
|
||||||
lineHeight = 16.sp,
|
lineHeight = 16.sp,
|
||||||
fontWeight = FontWeight.Bold,
|
|
||||||
maxLines = 1,
|
maxLines = 1,
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.padding(horizontal = 8.dp)
|
.padding(horizontal = 8.dp)
|
||||||
|
|||||||
@@ -243,7 +243,15 @@ fun ComicPageView(
|
|||||||
{
|
{
|
||||||
val k = it.getPageChapterIndex(pagerState.currentPage)
|
val k = it.getPageChapterIndex(pagerState.currentPage)
|
||||||
Column(Modifier
|
Column(Modifier
|
||||||
.padding(bottom = 24.dp)) {
|
.background(
|
||||||
|
brush = Brush.verticalGradient(
|
||||||
|
colors = listOf(
|
||||||
|
Color.Transparent,
|
||||||
|
Color.Black.copy(alpha = 0.9f),
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)) {
|
||||||
|
Spacer(Modifier.height(42.dp))
|
||||||
LazyRow(
|
LazyRow(
|
||||||
horizontalArrangement = Arrangement.spacedBy(5.dp),
|
horizontalArrangement = Arrangement.spacedBy(5.dp),
|
||||||
state = comicPageViewModel.listState!!, modifier = Modifier
|
state = comicPageViewModel.listState!!, modifier = Modifier
|
||||||
@@ -266,7 +274,7 @@ fun ComicPageView(
|
|||||||
pagerState.requestScrollToPage(page = r)
|
pagerState.requestScrollToPage(page = r)
|
||||||
}
|
}
|
||||||
) {
|
) {
|
||||||
Box(Modifier.padding(1.dp))
|
Box(Modifier.padding(0.dp))
|
||||||
{
|
{
|
||||||
AsyncImage(
|
AsyncImage(
|
||||||
model = ImageRequest.Builder(LocalContext.current)
|
model = ImageRequest.Builder(LocalContext.current)
|
||||||
@@ -321,6 +329,9 @@ fun ComicPageView(
|
|||||||
|
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
Spacer(Modifier.height(24.dp))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,11 @@
|
|||||||
package com.acitelight.aether.view.pages
|
package com.acitelight.aether.view.pages
|
||||||
|
|
||||||
|
import androidx.compose.animation.AnimatedVisibility
|
||||||
|
import androidx.compose.animation.animateContentSize
|
||||||
|
import androidx.compose.animation.expandVertically
|
||||||
|
import androidx.compose.animation.fadeIn
|
||||||
|
import androidx.compose.animation.fadeOut
|
||||||
|
import androidx.compose.animation.shrinkVertically
|
||||||
import androidx.compose.foundation.background
|
import androidx.compose.foundation.background
|
||||||
import androidx.compose.foundation.clickable
|
import androidx.compose.foundation.clickable
|
||||||
import androidx.compose.foundation.layout.Arrangement
|
import androidx.compose.foundation.layout.Arrangement
|
||||||
@@ -27,13 +33,21 @@ import androidx.compose.foundation.text.BasicTextField
|
|||||||
import androidx.compose.foundation.verticalScroll
|
import androidx.compose.foundation.verticalScroll
|
||||||
import androidx.compose.material.icons.Icons
|
import androidx.compose.material.icons.Icons
|
||||||
import androidx.compose.material.icons.filled.Search
|
import androidx.compose.material.icons.filled.Search
|
||||||
|
import androidx.compose.material3.Card
|
||||||
|
import androidx.compose.material3.CardDefaults
|
||||||
|
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||||
|
import androidx.compose.material3.ExposedDropdownMenuDefaults
|
||||||
import androidx.compose.material3.HorizontalDivider
|
import androidx.compose.material3.HorizontalDivider
|
||||||
import androidx.compose.material3.Icon
|
import androidx.compose.material3.Icon
|
||||||
import androidx.compose.material3.LocalTextStyle
|
import androidx.compose.material3.LocalTextStyle
|
||||||
import androidx.compose.material3.MaterialTheme
|
import androidx.compose.material3.MaterialTheme
|
||||||
|
import androidx.compose.material3.RadioButton
|
||||||
import androidx.compose.material3.Text
|
import androidx.compose.material3.Text
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.getValue
|
import androidx.compose.runtime.getValue
|
||||||
|
import androidx.compose.runtime.mutableIntStateOf
|
||||||
|
import androidx.compose.runtime.mutableStateOf
|
||||||
|
import androidx.compose.runtime.remember
|
||||||
import androidx.compose.runtime.setValue
|
import androidx.compose.runtime.setValue
|
||||||
import androidx.compose.ui.Alignment
|
import androidx.compose.ui.Alignment
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
@@ -122,6 +136,7 @@ fun VariableGrid(
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@OptIn(ExperimentalMaterial3Api::class)
|
||||||
@Composable
|
@Composable
|
||||||
fun ComicScreen(
|
fun ComicScreen(
|
||||||
navController: NavHostController,
|
navController: NavHostController,
|
||||||
@@ -131,11 +146,17 @@ fun ComicScreen(
|
|||||||
val state = rememberLazyStaggeredGridState()
|
val state = rememberLazyStaggeredGridState()
|
||||||
val colorScheme = MaterialTheme.colorScheme
|
val colorScheme = MaterialTheme.colorScheme
|
||||||
var searchFilter by comicScreenViewModel.searchFilter
|
var searchFilter by comicScreenViewModel.searchFilter
|
||||||
|
var isTagsVisible by remember { mutableStateOf(false) }
|
||||||
|
var sortType by remember { mutableIntStateOf(0) }
|
||||||
|
|
||||||
Column {
|
Column(
|
||||||
|
modifier = Modifier.animateContentSize()
|
||||||
|
) {
|
||||||
Row(Modifier
|
Row(Modifier
|
||||||
.padding(4.dp)
|
.padding(horizontal = 8.dp).padding(top = 4.dp)
|
||||||
.align(Alignment.CenterHorizontally)) {
|
.align(Alignment.CenterHorizontally)
|
||||||
|
)
|
||||||
|
{
|
||||||
Text(
|
Text(
|
||||||
text = "Comics",
|
text = "Comics",
|
||||||
style = MaterialTheme.typography.headlineMedium,
|
style = MaterialTheme.typography.headlineMedium,
|
||||||
@@ -149,7 +170,7 @@ fun ComicScreen(
|
|||||||
.align(Alignment.CenterVertically)
|
.align(Alignment.CenterVertically)
|
||||||
.height(36.dp)
|
.height(36.dp)
|
||||||
.widthIn(max = 240.dp)
|
.widthIn(max = 240.dp)
|
||||||
.background(colorScheme.primary, RoundedCornerShape(8.dp))
|
.background(colorScheme.surface, RoundedCornerShape(8.dp))
|
||||||
.padding(horizontal = 6.dp)
|
.padding(horizontal = 6.dp)
|
||||||
) {
|
) {
|
||||||
Icon(
|
Icon(
|
||||||
@@ -174,22 +195,91 @@ fun ComicScreen(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Row(Modifier
|
||||||
|
.padding(horizontal = 8.dp)
|
||||||
|
.align(Alignment.CenterHorizontally)
|
||||||
|
)
|
||||||
|
{
|
||||||
|
Text(
|
||||||
|
text = "Sorted by: ",
|
||||||
|
fontWeight = FontWeight.Bold,
|
||||||
|
fontSize = 16.sp,
|
||||||
|
modifier = Modifier.padding(horizontal = 6.dp).align(Alignment.CenterVertically)
|
||||||
|
)
|
||||||
|
|
||||||
|
RadioButton(
|
||||||
|
selected = (sortType == 0),
|
||||||
|
onClick = { sortType = 0 },
|
||||||
|
modifier = Modifier.align(Alignment.CenterVertically).size(24.dp)
|
||||||
|
)
|
||||||
|
Text(
|
||||||
|
text = "Id",
|
||||||
|
fontWeight = FontWeight.Bold,
|
||||||
|
fontSize = 16.sp,
|
||||||
|
modifier = Modifier.align(Alignment.CenterVertically).padding(3.dp)
|
||||||
|
)
|
||||||
|
Spacer(modifier = Modifier.width(12.dp))
|
||||||
|
|
||||||
|
RadioButton(
|
||||||
|
selected = (sortType == 1),
|
||||||
|
onClick = { sortType = 1 },
|
||||||
|
modifier = Modifier.align(Alignment.CenterVertically).size(24.dp)
|
||||||
|
)
|
||||||
|
Text(
|
||||||
|
text = "Name",
|
||||||
|
fontWeight = FontWeight.Bold,
|
||||||
|
fontSize = 16.sp,
|
||||||
|
modifier = Modifier.align(Alignment.CenterVertically).padding(3.dp)
|
||||||
|
)
|
||||||
|
Spacer(modifier = Modifier.width(12.dp))
|
||||||
|
|
||||||
|
Spacer(Modifier.weight(1f))
|
||||||
|
|
||||||
|
Card(
|
||||||
|
shape = RoundedCornerShape(8.dp),
|
||||||
|
colors = CardDefaults.cardColors(containerColor = colorScheme.surface),
|
||||||
|
modifier = Modifier
|
||||||
|
.align(Alignment.CenterVertically)
|
||||||
|
.padding(horizontal = 4.dp)
|
||||||
|
.padding(vertical = 4.dp)
|
||||||
|
.height(32.dp)
|
||||||
|
.width(64.dp),
|
||||||
|
onClick = {
|
||||||
|
isTagsVisible = !isTagsVisible
|
||||||
|
})
|
||||||
|
{
|
||||||
|
Row(Modifier.fillMaxSize())
|
||||||
|
{
|
||||||
|
Text(text = "Tags", fontWeight = FontWeight.Bold, fontSize = 16.sp, modifier = Modifier.align(Alignment.CenterVertically).padding(start = 5.dp))
|
||||||
|
ExposedDropdownMenuDefaults.TrailingIcon(expanded = isTagsVisible, modifier = Modifier.align(Alignment.CenterVertically).padding(end = 5.dp))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
HorizontalDivider(Modifier.padding(1.dp), thickness = 1.5.dp)
|
||||||
|
|
||||||
|
AnimatedVisibility(
|
||||||
|
visible = isTagsVisible,
|
||||||
|
enter = expandVertically(expandFrom = Alignment.Top) + fadeIn(),
|
||||||
|
exit = shrinkVertically(shrinkTowards = Alignment.Top) + fadeOut()
|
||||||
|
) {
|
||||||
|
Column {
|
||||||
VariableGrid(
|
VariableGrid(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.heightIn(max = 88.dp)
|
.heightIn(max = 80.dp)
|
||||||
.padding(4.dp),
|
.padding(3.dp),
|
||||||
rowHeight = 32.dp
|
rowHeight = 30.dp
|
||||||
)
|
)
|
||||||
{
|
{
|
||||||
for (i in comicScreenViewModel.tags) {
|
for (i in comicScreenViewModel.tags) {
|
||||||
|
|
||||||
Box(
|
Box(
|
||||||
Modifier
|
Modifier
|
||||||
.background(
|
.background(
|
||||||
if (included.contains(i)) Color.Green.copy(alpha = 0.65f) else colorScheme.surface,
|
if (included.contains(i)) Color.Green.copy(alpha = 0.65f) else colorScheme.surface,
|
||||||
shape = RoundedCornerShape(4.dp)
|
shape = RoundedCornerShape(4.dp)
|
||||||
)
|
)
|
||||||
.height(32.dp).widthIn(max = 72.dp)
|
.height(32.dp)
|
||||||
|
.widthIn(max = 72.dp)
|
||||||
.clickable {
|
.clickable {
|
||||||
if (included.contains(i))
|
if (included.contains(i))
|
||||||
included.remove(i)
|
included.remove(i)
|
||||||
@@ -211,6 +301,8 @@ fun ComicScreen(
|
|||||||
}
|
}
|
||||||
|
|
||||||
HorizontalDivider(Modifier.padding(1.dp), thickness = 1.5.dp)
|
HorizontalDivider(Modifier.padding(1.dp), thickness = 1.5.dp)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
LazyVerticalStaggeredGrid(
|
LazyVerticalStaggeredGrid(
|
||||||
columns = StaggeredGridCells.Adaptive(120.dp),
|
columns = StaggeredGridCells.Adaptive(120.dp),
|
||||||
@@ -221,12 +313,23 @@ fun ComicScreen(
|
|||||||
modifier = Modifier.fillMaxSize()
|
modifier = Modifier.fillMaxSize()
|
||||||
) {
|
) {
|
||||||
items(
|
items(
|
||||||
items = comicScreenViewModel.comics.filter { searchFilter.isEmpty() || searchFilter in it.comic.comic_name }.filter { x ->
|
items = comicScreenViewModel.comics
|
||||||
|
.filter { searchFilter.isEmpty() || searchFilter in it.comic.comic_name }
|
||||||
|
.filter { x ->
|
||||||
included.all { y -> y in x.comic.tags } || included.isEmpty()
|
included.all { y -> y in x.comic.tags } || included.isEmpty()
|
||||||
|
}
|
||||||
|
.sortedByDescending {
|
||||||
|
when(sortType)
|
||||||
|
{
|
||||||
|
0 -> it.id.toInt().toString().padStart(10, '0')
|
||||||
|
1 -> it.comic.comic_name
|
||||||
|
else -> it.id
|
||||||
|
}
|
||||||
},
|
},
|
||||||
key = { it.id }
|
key = { it.id }
|
||||||
) { comic ->
|
) { comic ->
|
||||||
Box(modifier = Modifier
|
Box(
|
||||||
|
modifier = Modifier
|
||||||
.fillMaxWidth()
|
.fillMaxWidth()
|
||||||
.wrapContentHeight()
|
.wrapContentHeight()
|
||||||
) {
|
) {
|
||||||
|
|||||||
@@ -0,0 +1,13 @@
|
|||||||
|
package com.acitelight.aether.view.pages
|
||||||
|
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.hilt.lifecycle.viewmodel.compose.hiltViewModel
|
||||||
|
import com.acitelight.aether.viewModel.LiveScreenViewModel
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun LiveScreen(
|
||||||
|
liveScreenViewModel: LiveScreenViewModel = hiltViewModel<LiveScreenViewModel>()
|
||||||
|
)
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
@@ -15,6 +15,7 @@ import androidx.compose.material.icons.Icons
|
|||||||
import androidx.compose.material.icons.filled.Key
|
import androidx.compose.material.icons.filled.Key
|
||||||
import androidx.compose.material.icons.filled.Link
|
import androidx.compose.material.icons.filled.Link
|
||||||
import androidx.compose.material.icons.filled.Person
|
import androidx.compose.material.icons.filled.Person
|
||||||
|
import androidx.compose.material.icons.filled.Textsms
|
||||||
import androidx.compose.material3.Button
|
import androidx.compose.material3.Button
|
||||||
import androidx.compose.material3.Card
|
import androidx.compose.material3.Card
|
||||||
import androidx.compose.material3.Checkbox
|
import androidx.compose.material3.Checkbox
|
||||||
@@ -41,6 +42,7 @@ fun MeScreen(meScreenViewModel: MeScreenViewModel = hiltViewModel<MeScreenViewMo
|
|||||||
var privateKey by meScreenViewModel.privateKey
|
var privateKey by meScreenViewModel.privateKey
|
||||||
var url by meScreenViewModel.url
|
var url by meScreenViewModel.url
|
||||||
var cert by meScreenViewModel.cert
|
var cert by meScreenViewModel.cert
|
||||||
|
var pak by meScreenViewModel.pak
|
||||||
|
|
||||||
val uss by meScreenViewModel.uss.collectAsState(initial = false)
|
val uss by meScreenViewModel.uss.collectAsState(initial = false)
|
||||||
|
|
||||||
@@ -50,7 +52,8 @@ fun MeScreen(meScreenViewModel: MeScreenViewModel = hiltViewModel<MeScreenViewMo
|
|||||||
.padding(8.dp),
|
.padding(8.dp),
|
||||||
horizontalAlignment = Alignment.CenterHorizontally,
|
horizontalAlignment = Alignment.CenterHorizontally,
|
||||||
verticalArrangement = Arrangement.Top
|
verticalArrangement = Arrangement.Top
|
||||||
) {
|
)
|
||||||
|
{
|
||||||
// Card component for a clean, contained UI block
|
// Card component for a clean, contained UI block
|
||||||
item{
|
item{
|
||||||
Card(
|
Card(
|
||||||
@@ -196,6 +199,54 @@ fun MeScreen(meScreenViewModel: MeScreenViewModel = hiltViewModel<MeScreenViewMo
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Card(
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.padding(8.dp)
|
||||||
|
)
|
||||||
|
{
|
||||||
|
Column(
|
||||||
|
modifier = Modifier
|
||||||
|
.padding(16.dp)
|
||||||
|
.fillMaxWidth(),
|
||||||
|
horizontalAlignment = Alignment.CenterHorizontally
|
||||||
|
) {
|
||||||
|
Text(
|
||||||
|
text = "Toolbox",
|
||||||
|
style = MaterialTheme.typography.headlineMedium,
|
||||||
|
modifier = Modifier
|
||||||
|
.padding(bottom = 16.dp)
|
||||||
|
.align(Alignment.Start)
|
||||||
|
)
|
||||||
|
|
||||||
|
Spacer(modifier = Modifier.width(8.dp))
|
||||||
|
|
||||||
|
OutlinedTextField(
|
||||||
|
value = pak,
|
||||||
|
onValueChange = { pak = it },
|
||||||
|
label = { Text("Packet") },
|
||||||
|
leadingIcon = {
|
||||||
|
Icon(Icons.Default.Textsms, contentDescription = "Packet")
|
||||||
|
},
|
||||||
|
singleLine = true,
|
||||||
|
modifier = Modifier.fillMaxWidth()
|
||||||
|
)
|
||||||
|
|
||||||
|
Spacer(modifier = Modifier.height(8.dp))
|
||||||
|
|
||||||
|
Row{
|
||||||
|
Button(
|
||||||
|
onClick = {
|
||||||
|
meScreenViewModel.sendPacket(pak)
|
||||||
|
},
|
||||||
|
modifier = Modifier.weight(0.5f).padding(8.dp)
|
||||||
|
) {
|
||||||
|
Text("Send")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,36 +1,50 @@
|
|||||||
package com.acitelight.aether.view.pages
|
package com.acitelight.aether.view.pages
|
||||||
|
|
||||||
|
import androidx.compose.animation.AnimatedVisibility
|
||||||
|
import androidx.compose.animation.animateContentSize
|
||||||
|
import androidx.compose.animation.expandVertically
|
||||||
|
import androidx.compose.animation.fadeIn
|
||||||
|
import androidx.compose.animation.fadeOut
|
||||||
|
import androidx.compose.animation.shrinkVertically
|
||||||
import androidx.compose.foundation.layout.Column
|
import androidx.compose.foundation.layout.Column
|
||||||
import androidx.compose.foundation.layout.fillMaxWidth
|
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.layout.padding
|
||||||
import androidx.compose.foundation.lazy.LazyColumn
|
import androidx.compose.foundation.lazy.LazyColumn
|
||||||
import androidx.compose.foundation.lazy.items
|
import androidx.compose.foundation.lazy.items
|
||||||
|
import androidx.compose.material3.Button
|
||||||
import androidx.compose.material3.DividerDefaults
|
import androidx.compose.material3.DividerDefaults
|
||||||
import androidx.compose.material3.HorizontalDivider
|
import androidx.compose.material3.HorizontalDivider
|
||||||
import androidx.compose.material3.MaterialTheme
|
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.getValue
|
||||||
|
import androidx.compose.runtime.setValue
|
||||||
import androidx.compose.ui.Alignment
|
import androidx.compose.ui.Alignment
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.text.font.FontWeight
|
||||||
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.hilt.lifecycle.viewmodel.compose.hiltViewModel
|
import androidx.hilt.lifecycle.viewmodel.compose.hiltViewModel
|
||||||
import androidx.navigation.NavHostController
|
import androidx.navigation.NavHostController
|
||||||
import com.acitelight.aether.model.VideoDownloadItemState
|
import com.acitelight.aether.model.VideoDownloadItemState
|
||||||
import com.acitelight.aether.view.components.BiliMiniSlider
|
import com.acitelight.aether.view.components.BiliMiniSlider
|
||||||
import com.acitelight.aether.view.components.VideoDownloadCardMini
|
import com.acitelight.aether.view.components.VideoDownloadCard
|
||||||
import com.acitelight.aether.viewModel.TransmissionScreenViewModel
|
import com.acitelight.aether.viewModel.TransmissionScreenViewModel
|
||||||
import com.tonyodev.fetch2.Status
|
import com.tonyodev.fetch2.Status
|
||||||
import kotlin.collections.sortedWith
|
import java.io.File
|
||||||
|
import kotlin.comparisons.compareBy
|
||||||
|
import kotlin.comparisons.naturalOrder
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun TransmissionScreen(
|
fun TransmissionScreen(
|
||||||
navigator: NavHostController,
|
navigator: NavHostController,
|
||||||
transmissionScreenViewModel: TransmissionScreenViewModel = hiltViewModel<TransmissionScreenViewModel>()
|
transmissionScreenViewModel: TransmissionScreenViewModel = hiltViewModel<TransmissionScreenViewModel>()
|
||||||
) {
|
) {
|
||||||
|
var mutiSelection by transmissionScreenViewModel.mutiSelection
|
||||||
val downloads = transmissionScreenViewModel.downloads
|
val downloads = transmissionScreenViewModel.downloads
|
||||||
Column()
|
|
||||||
|
Column(modifier = Modifier.animateContentSize())
|
||||||
{
|
{
|
||||||
Text(
|
Text(
|
||||||
text = "Video Tasks",
|
text = "Video Tasks",
|
||||||
@@ -73,55 +87,57 @@ fun TransmissionScreen(
|
|||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
HorizontalDivider(Modifier.padding(8.dp), 2.dp, DividerDefaults.color)
|
AnimatedVisibility(
|
||||||
|
visible = mutiSelection,
|
||||||
|
enter = expandVertically(expandFrom = Alignment.Top) + fadeIn(),
|
||||||
|
exit = shrinkVertically(shrinkTowards = Alignment.Top) + fadeOut()
|
||||||
|
){
|
||||||
|
Column(Modifier.fillMaxWidth()) {
|
||||||
|
Button(onClick = {
|
||||||
|
mutiSelection = false
|
||||||
|
transmissionScreenViewModel.mutiSelectionList.forEach {
|
||||||
|
item ->
|
||||||
|
val klass = item.split("/").first()
|
||||||
|
val id = item.split("/").last()
|
||||||
|
for (i in downloadToGroup(
|
||||||
|
klass, id,
|
||||||
|
downloads
|
||||||
|
)) transmissionScreenViewModel.delete(i.id)
|
||||||
|
|
||||||
|
File(
|
||||||
|
transmissionScreenViewModel.context.getExternalFilesDir(null),
|
||||||
|
"videos/${klass}/${id}/summary.json"
|
||||||
|
).delete()
|
||||||
|
}
|
||||||
|
}, modifier = Modifier.align(Alignment.End).padding(horizontal = 8.dp))
|
||||||
|
{
|
||||||
|
Text(text = "Delete", fontWeight = FontWeight.Bold)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
HorizontalDivider(Modifier.padding(horizontal = 8.dp).padding(vertical = 4.dp), 2.dp, DividerDefaults.color)
|
||||||
|
|
||||||
LazyColumn(
|
LazyColumn(
|
||||||
modifier = Modifier.fillMaxWidth()
|
modifier = Modifier.fillMaxWidth()
|
||||||
)
|
)
|
||||||
{
|
{
|
||||||
items(
|
items(
|
||||||
downloads
|
items = downloads
|
||||||
.filter { it.type == "main" }
|
.filter { it.type == "main" }
|
||||||
.sortedWith(compareBy(naturalOrder()) { it.fileName })
|
.sortedBy { it.status == Status.COMPLETED }
|
||||||
.sortedBy { it.status == Status.COMPLETED }, key = { it.id })
|
.groupBy { it.group }.map { it.value }
|
||||||
|
, key = { it.first().id }
|
||||||
|
)
|
||||||
{ item ->
|
{ item ->
|
||||||
VideoDownloadCardMini(
|
VideoDownloadCard(
|
||||||
navigator = navigator,
|
navigator = navigator,
|
||||||
viewModel = transmissionScreenViewModel,
|
viewModel = transmissionScreenViewModel,
|
||||||
model = item,
|
models = item.sortedWith(compareBy(naturalOrder()) { it.fileName })
|
||||||
onPause = {
|
|
||||||
for (i in downloadToGroup(
|
|
||||||
item,
|
|
||||||
downloads
|
|
||||||
)) transmissionScreenViewModel.pause(i.id)
|
|
||||||
},
|
|
||||||
onResume = {
|
|
||||||
for (i in downloadToGroup(
|
|
||||||
item,
|
|
||||||
downloads
|
|
||||||
)) transmissionScreenViewModel.resume(i.id)
|
|
||||||
},
|
|
||||||
onCancel = {
|
|
||||||
for (i in downloadToGroup(
|
|
||||||
item,
|
|
||||||
downloads
|
|
||||||
)) transmissionScreenViewModel.delete(i.id)
|
|
||||||
},
|
|
||||||
onDelete = {
|
|
||||||
for (i in downloadToGroup(
|
|
||||||
item,
|
|
||||||
downloads
|
|
||||||
)) transmissionScreenViewModel.delete(i.id)
|
|
||||||
},
|
|
||||||
onRetry = {
|
|
||||||
for (i in downloadToGroup(
|
|
||||||
item,
|
|
||||||
downloads
|
|
||||||
)) transmissionScreenViewModel.retry(i.id)
|
|
||||||
}
|
|
||||||
)
|
)
|
||||||
HorizontalDivider(
|
HorizontalDivider(
|
||||||
Modifier.padding(horizontal = 16.dp, vertical = 6.dp),
|
Modifier.padding(horizontal = 16.dp, vertical = 3.dp),
|
||||||
2.dp,
|
2.dp,
|
||||||
DividerDefaults.color
|
DividerDefaults.color
|
||||||
)
|
)
|
||||||
@@ -136,3 +152,10 @@ fun downloadToGroup(
|
|||||||
): List<VideoDownloadItemState> {
|
): List<VideoDownloadItemState> {
|
||||||
return downloads.filter { it.vid == i.vid && it.klass == i.klass }
|
return downloads.filter { it.vid == i.vid && it.klass == i.klass }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun downloadToGroup(
|
||||||
|
klass: String, id: String,
|
||||||
|
downloads: List<VideoDownloadItemState>
|
||||||
|
): List<VideoDownloadItemState> {
|
||||||
|
return downloads.filter { it.vid == id && it.klass == klass }
|
||||||
|
}
|
||||||
@@ -1,19 +1,13 @@
|
|||||||
package com.acitelight.aether.view.pages
|
package com.acitelight.aether.view.pages
|
||||||
|
|
||||||
import android.widget.Toast
|
|
||||||
import androidx.compose.animation.AnimatedVisibility
|
|
||||||
import androidx.compose.animation.slideInHorizontally
|
|
||||||
import androidx.compose.animation.slideOutHorizontally
|
|
||||||
import androidx.compose.foundation.background
|
import androidx.compose.foundation.background
|
||||||
import androidx.compose.foundation.clickable
|
import androidx.compose.foundation.clickable
|
||||||
import androidx.compose.foundation.combinedClickable
|
|
||||||
import androidx.compose.foundation.layout.Arrangement
|
import androidx.compose.foundation.layout.Arrangement
|
||||||
import androidx.compose.foundation.layout.Box
|
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.fillMaxHeight
|
|
||||||
import androidx.compose.foundation.layout.fillMaxSize
|
import androidx.compose.foundation.layout.fillMaxSize
|
||||||
import androidx.compose.foundation.layout.fillMaxWidth
|
import androidx.compose.foundation.layout.fillMaxWidth
|
||||||
import androidx.compose.foundation.layout.height
|
import androidx.compose.foundation.layout.height
|
||||||
@@ -23,8 +17,6 @@ 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.layout.widthIn
|
||||||
import androidx.compose.foundation.layout.wrapContentHeight
|
import androidx.compose.foundation.layout.wrapContentHeight
|
||||||
import androidx.compose.foundation.lazy.LazyColumn
|
|
||||||
import androidx.compose.foundation.lazy.items
|
|
||||||
import androidx.compose.foundation.lazy.staggeredgrid.LazyVerticalStaggeredGrid
|
import androidx.compose.foundation.lazy.staggeredgrid.LazyVerticalStaggeredGrid
|
||||||
import androidx.compose.foundation.lazy.staggeredgrid.StaggeredGridCells
|
import androidx.compose.foundation.lazy.staggeredgrid.StaggeredGridCells
|
||||||
import androidx.compose.foundation.lazy.staggeredgrid.items
|
import androidx.compose.foundation.lazy.staggeredgrid.items
|
||||||
@@ -32,48 +24,41 @@ import androidx.compose.foundation.lazy.staggeredgrid.rememberLazyStaggeredGridS
|
|||||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||||
import androidx.compose.foundation.text.BasicTextField
|
import androidx.compose.foundation.text.BasicTextField
|
||||||
import androidx.compose.material.icons.Icons
|
import androidx.compose.material.icons.Icons
|
||||||
import androidx.compose.material.icons.filled.Menu
|
|
||||||
import androidx.compose.material.icons.filled.Search
|
import androidx.compose.material.icons.filled.Search
|
||||||
import androidx.compose.material3.Card
|
import androidx.compose.material3.Card
|
||||||
import androidx.compose.material3.CardDefaults
|
import androidx.compose.material3.CardDefaults
|
||||||
import androidx.compose.material3.DividerDefaults
|
|
||||||
import androidx.compose.material3.HorizontalDivider
|
|
||||||
import androidx.compose.material3.Icon
|
import androidx.compose.material3.Icon
|
||||||
import androidx.compose.material3.LocalTextStyle
|
import androidx.compose.material3.LocalTextStyle
|
||||||
import androidx.compose.material3.MaterialTheme
|
import androidx.compose.material3.MaterialTheme
|
||||||
|
import androidx.compose.material3.SecondaryScrollableTabRow
|
||||||
|
import androidx.compose.material3.Tab
|
||||||
import androidx.compose.material3.Text
|
import androidx.compose.material3.Text
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.getValue
|
import androidx.compose.runtime.getValue
|
||||||
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.layout.ContentScale
|
|
||||||
import androidx.compose.ui.text.font.FontWeight
|
import androidx.compose.ui.text.font.FontWeight
|
||||||
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 coil3.compose.AsyncImage
|
|
||||||
import com.acitelight.aether.model.Video
|
import com.acitelight.aether.model.Video
|
||||||
import com.acitelight.aether.viewModel.VideoScreenViewModel
|
import com.acitelight.aether.viewModel.VideoScreenViewModel
|
||||||
import androidx.compose.runtime.mutableStateListOf
|
|
||||||
import androidx.compose.runtime.setValue
|
import androidx.compose.runtime.setValue
|
||||||
import androidx.compose.ui.Alignment
|
import androidx.compose.ui.Alignment
|
||||||
import androidx.compose.ui.graphics.Brush
|
|
||||||
import androidx.compose.ui.platform.LocalContext
|
|
||||||
import androidx.compose.ui.text.style.TextAlign
|
import androidx.compose.ui.text.style.TextAlign
|
||||||
import androidx.hilt.lifecycle.viewmodel.compose.hiltViewModel
|
import androidx.hilt.lifecycle.viewmodel.compose.hiltViewModel
|
||||||
import androidx.lifecycle.viewModelScope
|
|
||||||
import androidx.navigation.NavHostController
|
import androidx.navigation.NavHostController
|
||||||
import coil3.request.ImageRequest
|
|
||||||
import com.acitelight.aether.CardPage
|
import com.acitelight.aether.CardPage
|
||||||
import com.acitelight.aether.Global.updateRelate
|
|
||||||
import com.acitelight.aether.view.components.VideoCard
|
import com.acitelight.aether.view.components.VideoCard
|
||||||
import kotlinx.coroutines.launch
|
|
||||||
import java.nio.charset.Charset
|
import java.nio.charset.Charset
|
||||||
import kotlin.collections.sortedWith
|
import kotlin.collections.sortedWith
|
||||||
|
|
||||||
fun videoToView(v: List<Video>): Map<String?, List<Video>>
|
fun videoToGroup(v: List<Video>): Map<String?, List<Video>> {
|
||||||
{
|
return v.map {
|
||||||
return v.map { if(it.video.group != null) it else Video(id=it.id, isLocal = it.isLocal, localBase = it.localBase,
|
if (it.video.group != null) it else Video(
|
||||||
klass = it.klass, video = it.video.copy(group = it.video.name)) }.groupBy { it.video.group }
|
id = it.id, isLocal = it.isLocal, localBase = it.localBase,
|
||||||
|
klass = it.klass, video = it.video.copy(group = it.video.name)
|
||||||
|
)
|
||||||
|
}.groupBy { it.video.group }
|
||||||
}
|
}
|
||||||
|
|
||||||
fun String.toHex(): String {
|
fun String.toHex(): String {
|
||||||
@@ -102,12 +87,13 @@ fun VideoScreen(
|
|||||||
var menuVisibility by videoScreenViewModel.menuVisibility
|
var menuVisibility by videoScreenViewModel.menuVisibility
|
||||||
var searchFilter by videoScreenViewModel.searchFilter
|
var searchFilter by videoScreenViewModel.searchFilter
|
||||||
var doneInit by videoScreenViewModel.doneInit
|
var doneInit by videoScreenViewModel.doneInit
|
||||||
val vb = videoToView(videoScreenViewModel.videoLibrary.classesMap.getOrDefault(
|
val vb = videoToGroup(
|
||||||
|
videoScreenViewModel.videoLibrary.classesMap.getOrDefault(
|
||||||
videoScreenViewModel.videoLibrary.classes.getOrNull(
|
videoScreenViewModel.videoLibrary.classes.getOrNull(
|
||||||
tabIndex
|
tabIndex
|
||||||
), listOf()
|
), listOf()
|
||||||
).filter { it.video.name.contains(searchFilter) }).filter { it.key != null }
|
).filter { it.video.name.contains(searchFilter) }).filter { it.key != null }
|
||||||
.map{ i -> Pair(i.key!!, i.value.sortedWith(compareBy(naturalOrder()) { it.video.name }) ) }
|
.map { i -> Pair(i.key!!, i.value.sortedWith(compareBy(naturalOrder()) { it.video.name })) }
|
||||||
.toList()
|
.toList()
|
||||||
|
|
||||||
if (doneInit)
|
if (doneInit)
|
||||||
@@ -117,75 +103,31 @@ fun VideoScreen(
|
|||||||
Column(
|
Column(
|
||||||
modifier = Modifier.fillMaxSize()
|
modifier = Modifier.fillMaxSize()
|
||||||
) {
|
) {
|
||||||
|
Row(
|
||||||
|
Modifier
|
||||||
|
.padding(horizontal = 8.dp)
|
||||||
|
.padding(top = 4.dp)
|
||||||
|
.align(Alignment.CenterHorizontally)
|
||||||
|
)
|
||||||
|
{
|
||||||
Text(
|
Text(
|
||||||
text = "Videos",
|
text = "Videos",
|
||||||
style = MaterialTheme.typography.headlineMedium,
|
style = MaterialTheme.typography.headlineMedium,
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.padding(horizontal = 8.dp)
|
.padding(horizontal = 8.dp)
|
||||||
.align(Alignment.Start)
|
.align(Alignment.CenterVertically)
|
||||||
)
|
)
|
||||||
|
|
||||||
// TopRow(videoScreenViewModel);
|
Spacer(Modifier.weight(1f))
|
||||||
Row(Modifier.padding(bottom = 4.dp).padding(start = 8.dp))
|
|
||||||
{
|
|
||||||
Card(
|
|
||||||
shape = RoundedCornerShape(8.dp),
|
|
||||||
colors = CardDefaults.cardColors(containerColor = colorScheme.primary),
|
|
||||||
modifier = Modifier
|
|
||||||
.align(Alignment.CenterVertically)
|
|
||||||
.padding(horizontal = 1.dp)
|
|
||||||
.size(36.dp),
|
|
||||||
onClick = {
|
|
||||||
menuVisibility = !menuVisibility
|
|
||||||
})
|
|
||||||
{
|
|
||||||
Box(Modifier.fillMaxSize())
|
|
||||||
{
|
|
||||||
Icon(
|
|
||||||
modifier = Modifier
|
|
||||||
.size(30.dp)
|
|
||||||
.align(Alignment.Center),
|
|
||||||
imageVector = Icons.Default.Menu,
|
|
||||||
contentDescription = "Catalogue"
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Card(
|
|
||||||
shape = RoundedCornerShape(8.dp),
|
|
||||||
colors = CardDefaults.cardColors(containerColor = colorScheme.primary),
|
|
||||||
modifier = Modifier
|
|
||||||
.align(Alignment.CenterVertically)
|
|
||||||
.padding(horizontal = 1.dp)
|
|
||||||
.height(36.dp),
|
|
||||||
onClick = {
|
|
||||||
menuVisibility = !menuVisibility
|
|
||||||
})
|
|
||||||
{
|
|
||||||
Box(Modifier.fillMaxHeight())
|
|
||||||
{
|
|
||||||
Text(
|
|
||||||
text = videoScreenViewModel.videoLibrary.classes.getOrNull(
|
|
||||||
tabIndex
|
|
||||||
)
|
|
||||||
?: "",
|
|
||||||
style = MaterialTheme.typography.bodyLarge,
|
|
||||||
fontWeight = FontWeight.Bold,
|
|
||||||
modifier = Modifier
|
|
||||||
.align(Alignment.CenterStart)
|
|
||||||
.padding(horizontal = 8.dp),
|
|
||||||
maxLines = 1
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Row(
|
Row(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.height(36.dp)
|
.height(36.dp)
|
||||||
.widthIn(max = 240.dp)
|
.widthIn(max = 240.dp)
|
||||||
.background(colorScheme.primary, RoundedCornerShape(8.dp))
|
.background(colorScheme.surface, RoundedCornerShape(8.dp))
|
||||||
.padding(horizontal = 6.dp)
|
.padding(horizontal = 6.dp)
|
||||||
) {
|
)
|
||||||
|
{
|
||||||
Icon(
|
Icon(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.size(30.dp)
|
.size(30.dp)
|
||||||
@@ -207,11 +149,9 @@ fun VideoScreen(
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
HorizontalDivider(
|
|
||||||
Modifier.padding(4.dp),
|
TopRow(videoScreenViewModel)
|
||||||
2.dp,
|
|
||||||
DividerDefaults.color
|
|
||||||
)
|
|
||||||
LazyVerticalStaggeredGrid(
|
LazyVerticalStaggeredGrid(
|
||||||
columns = StaggeredGridCells.Adaptive(160.dp),
|
columns = StaggeredGridCells.Adaptive(160.dp),
|
||||||
contentPadding = PaddingValues(8.dp),
|
contentPadding = PaddingValues(8.dp),
|
||||||
@@ -231,48 +171,43 @@ fun VideoScreen(
|
|||||||
.fillMaxWidth()
|
.fillMaxWidth()
|
||||||
.wrapContentHeight()
|
.wrapContentHeight()
|
||||||
) {
|
) {
|
||||||
if(video.second.isNotEmpty())
|
if (video.second.isNotEmpty())
|
||||||
VideoCard(video.second, navController, videoScreenViewModel)
|
VideoCard(video.second, navController, videoScreenViewModel)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
AnimatedVisibility(
|
@Composable
|
||||||
visible = menuVisibility,
|
fun TopRow(videoScreenViewModel: VideoScreenViewModel) {
|
||||||
enter = slideInHorizontally(initialOffsetX = { full -> full }),
|
val tabIndex by videoScreenViewModel.tabIndex;
|
||||||
exit = slideOutHorizontally(targetOffsetX = { full -> full }),
|
if (videoScreenViewModel.videoLibrary.classes.isEmpty()) return
|
||||||
modifier = Modifier.align(Alignment.CenterEnd)
|
val colorScheme = MaterialTheme.colorScheme
|
||||||
|
|
||||||
|
SecondaryScrollableTabRow(
|
||||||
|
selectedTabIndex = tabIndex,
|
||||||
|
modifier = Modifier
|
||||||
|
.background(Color.Transparent)
|
||||||
|
.padding(vertical = 4.dp)
|
||||||
) {
|
) {
|
||||||
Card(
|
videoScreenViewModel.videoLibrary.classes.forEachIndexed { index, title ->
|
||||||
Modifier
|
Tab(
|
||||||
.fillMaxHeight()
|
modifier = Modifier.height(42.dp),
|
||||||
.width(250.dp)
|
selected = tabIndex == index,
|
||||||
.align(Alignment.CenterEnd),
|
onClick = { videoScreenViewModel.setTabIndex(index) },
|
||||||
shape = RoundedCornerShape(8.dp),
|
text = {
|
||||||
colors = CardDefaults.cardColors(containerColor = colorScheme.surface)
|
Text(
|
||||||
|
text = title,
|
||||||
|
maxLines = 1,
|
||||||
|
fontWeight = FontWeight.Bold,
|
||||||
|
lineHeight = 16.sp,
|
||||||
|
fontSize = 14.sp
|
||||||
)
|
)
|
||||||
{
|
},
|
||||||
LazyColumn {
|
|
||||||
items(videoScreenViewModel.videoLibrary.classes) { item ->
|
|
||||||
CatalogueItemRow(
|
|
||||||
item = Pair(
|
|
||||||
videoScreenViewModel.videoLibrary.classes.indexOf(item),
|
|
||||||
item
|
|
||||||
),
|
|
||||||
onItemClick = {
|
|
||||||
menuVisibility = false
|
|
||||||
videoScreenViewModel.setTabIndex(
|
|
||||||
videoScreenViewModel.videoLibrary.classes.indexOf(
|
|
||||||
item
|
|
||||||
)
|
)
|
||||||
)
|
|
||||||
}
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -61,7 +61,7 @@ class ComicScreenViewModel @Inject constructor(
|
|||||||
val m = mediaManager.queryComicInfoBulk(l)
|
val m = mediaManager.queryComicInfoBulk(l)
|
||||||
|
|
||||||
if(m != null) {
|
if(m != null) {
|
||||||
comics.addAll(m.sortedWith(compareBy(naturalOrder()) { it.comic.comic_name }))
|
comics.addAll(m.sortedBy { it.id.toInt() }.reversed())
|
||||||
tags.addAll(m.flatMap { it.comic.tags }.groupingBy { it }.eachCount()
|
tags.addAll(m.flatMap { it.comic.tags }.groupingBy { it }.eachCount()
|
||||||
.entries.sortedByDescending { it.value }
|
.entries.sortedByDescending { it.value }
|
||||||
.map { it.key })
|
.map { it.key })
|
||||||
|
|||||||
@@ -0,0 +1,14 @@
|
|||||||
|
package com.acitelight.aether.viewModel
|
||||||
|
|
||||||
|
import androidx.lifecycle.ViewModel
|
||||||
|
import com.acitelight.aether.service.ApiClient
|
||||||
|
import dagger.hilt.android.lifecycle.HiltViewModel
|
||||||
|
import javax.inject.Inject
|
||||||
|
|
||||||
|
|
||||||
|
@HiltViewModel
|
||||||
|
class LiveScreenViewModel @Inject constructor(
|
||||||
|
val apiClient: ApiClient
|
||||||
|
) : ViewModel(){
|
||||||
|
|
||||||
|
}
|
||||||
@@ -18,6 +18,9 @@ import kotlinx.coroutines.Dispatchers
|
|||||||
import kotlinx.coroutines.flow.first
|
import kotlinx.coroutines.flow.first
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import kotlinx.coroutines.withContext
|
import kotlinx.coroutines.withContext
|
||||||
|
import java.net.DatagramPacket
|
||||||
|
import java.net.DatagramSocket
|
||||||
|
import java.net.InetAddress
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
@HiltViewModel
|
@HiltViewModel
|
||||||
@@ -33,6 +36,7 @@ class MeScreenViewModel @Inject constructor(
|
|||||||
val privateKey = mutableStateOf("")
|
val privateKey = mutableStateOf("")
|
||||||
val url = mutableStateOf("")
|
val url = mutableStateOf("")
|
||||||
val cert = mutableStateOf("")
|
val cert = mutableStateOf("")
|
||||||
|
val pak = mutableStateOf("")
|
||||||
|
|
||||||
val uss = settingsDataStoreManager.useSelfSignedFlow
|
val uss = settingsDataStoreManager.useSelfSignedFlow
|
||||||
|
|
||||||
@@ -108,7 +112,8 @@ class MeScreenViewModel @Inject constructor(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun updateAccount(u: String, p: String) {
|
fun updateAccount(u: String, p: String)
|
||||||
|
{
|
||||||
viewModelScope.launch {
|
viewModelScope.launch {
|
||||||
settingsDataStoreManager.saveUserName(u)
|
settingsDataStoreManager.saveUserName(u)
|
||||||
settingsDataStoreManager.savePrivateKey(p)
|
settingsDataStoreManager.savePrivateKey(p)
|
||||||
@@ -142,4 +147,22 @@ class MeScreenViewModel @Inject constructor(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun sendPacket(p: String)
|
||||||
|
{
|
||||||
|
val b = (p + "\r\n").toByteArray(Charsets.UTF_8)
|
||||||
|
|
||||||
|
viewModelScope.launch {
|
||||||
|
withContext(Dispatchers.IO) {
|
||||||
|
val addr = InetAddress.getByName(apiClient.getDomain())
|
||||||
|
|
||||||
|
val socket = DatagramSocket()
|
||||||
|
val packet = DatagramPacket(
|
||||||
|
b, b.size, addr, 4096
|
||||||
|
)
|
||||||
|
|
||||||
|
socket.send(packet)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -1,7 +1,10 @@
|
|||||||
package com.acitelight.aether.viewModel
|
package com.acitelight.aether.viewModel
|
||||||
|
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
|
import android.widget.Toast
|
||||||
import androidx.compose.runtime.mutableStateListOf
|
import androidx.compose.runtime.mutableStateListOf
|
||||||
|
import androidx.compose.runtime.mutableStateMapOf
|
||||||
|
import androidx.compose.runtime.mutableStateOf
|
||||||
import androidx.compose.runtime.snapshots.SnapshotStateList
|
import androidx.compose.runtime.snapshots.SnapshotStateList
|
||||||
import androidx.lifecycle.ViewModel
|
import androidx.lifecycle.ViewModel
|
||||||
import androidx.lifecycle.viewModelScope
|
import androidx.lifecycle.viewModelScope
|
||||||
@@ -42,11 +45,9 @@ class TransmissionScreenViewModel @Inject constructor(
|
|||||||
|
|
||||||
// map id -> state object reference (no index bookkeeping)
|
// map id -> state object reference (no index bookkeeping)
|
||||||
private val idToState: MutableMap<Int, VideoDownloadItemState> = mutableMapOf()
|
private val idToState: MutableMap<Int, VideoDownloadItemState> = mutableMapOf()
|
||||||
|
var mutiSelection = mutableStateOf(false)
|
||||||
fun modelToVideo(model: VideoDownloadItemState): Video? {
|
var mutiSelectionList = mutableStateListOf<String>()
|
||||||
val fv = videoLibrary.classesMap.map { it.value }.flatten()
|
var groupExpandMap = mutableStateMapOf<String, Boolean>()
|
||||||
return fv.firstOrNull { it.klass == model.klass && it.id == model.vid }
|
|
||||||
}
|
|
||||||
|
|
||||||
private val fetchListener = object : FetchListener {
|
private val fetchListener = object : FetchListener {
|
||||||
override fun onAdded(download: Download) {
|
override fun onAdded(download: Download) {
|
||||||
@@ -79,18 +80,18 @@ class TransmissionScreenViewModel @Inject constructor(
|
|||||||
handleUpsert(download)
|
handleUpsert(download)
|
||||||
|
|
||||||
if (download.extras.getString("type", "") == "main") {
|
if (download.extras.getString("type", "") == "main") {
|
||||||
val ii = videoLibrary.classesMap[download.extras.getString(
|
val klass = download.extras.getString("class", "")
|
||||||
"class",
|
val ii = videoLibrary.classesMap[klass]?.indexOfFirst {
|
||||||
|
it.id == download.extras.getString(
|
||||||
|
"id",
|
||||||
""
|
""
|
||||||
)]?.indexOfFirst { it.id == download.extras.getString("id", "") }
|
)
|
||||||
|
}
|
||||||
|
|
||||||
if (ii != null) {
|
if (ii != null) {
|
||||||
val newi =
|
val newi =
|
||||||
videoLibrary.classesMap[download.extras.getString("class", "")]?.get(ii)
|
videoLibrary.classesMap[klass]?.get(ii)
|
||||||
if (newi != null) videoLibrary.classesMap[download.extras.getString(
|
if (newi != null) videoLibrary.classesMap[klass]?.set(
|
||||||
"class",
|
|
||||||
""
|
|
||||||
)]?.set(
|
|
||||||
ii, newi.toLocal(context.getExternalFilesDir(null)!!.path)
|
ii, newi.toLocal(context.getExternalFilesDir(null)!!.path)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@@ -107,6 +108,28 @@ class TransmissionScreenViewModel @Inject constructor(
|
|||||||
|
|
||||||
override fun onDeleted(download: Download) {
|
override fun onDeleted(download: Download) {
|
||||||
handleRemove(download.id)
|
handleRemove(download.id)
|
||||||
|
|
||||||
|
if (download.extras.getString("type", "") == "main") {
|
||||||
|
viewModelScope.launch {
|
||||||
|
val klass = download.extras.getString("class", "")
|
||||||
|
val ii = videoLibrary.classesMap[klass]?.indexOfFirst {
|
||||||
|
it.id == download.extras.getString(
|
||||||
|
"id",
|
||||||
|
""
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ii != null) {
|
||||||
|
val v = mediaManager.queryVideo(klass, download.extras.getString("id", ""))
|
||||||
|
if (v != null) {
|
||||||
|
val newi = videoLibrary.classesMap[klass]?.get(ii)
|
||||||
|
if (newi != null) videoLibrary.classesMap[klass]?.set(
|
||||||
|
ii, v
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onDownloadBlockUpdated(
|
override fun onDownloadBlockUpdated(
|
||||||
@@ -203,7 +226,8 @@ class TransmissionScreenViewModel @Inject constructor(
|
|||||||
totalBytes = download.total,
|
totalBytes = download.total,
|
||||||
klass = download.extras.getString("class", ""),
|
klass = download.extras.getString("class", ""),
|
||||||
vid = download.extras.getString("id", ""),
|
vid = download.extras.getString("id", ""),
|
||||||
type = download.extras.getString("type", "")
|
type = download.extras.getString("type", ""),
|
||||||
|
group = download.extras.getString("group", "")
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -223,8 +247,7 @@ class TransmissionScreenViewModel @Inject constructor(
|
|||||||
fetchManager.removeListener()
|
fetchManager.removeListener()
|
||||||
}
|
}
|
||||||
|
|
||||||
suspend fun playStart(model: VideoDownloadItemState, navigator: NavHostController)
|
suspend fun playStart(model: VideoDownloadItemState, navigator: NavHostController) {
|
||||||
{
|
|
||||||
val downloaded = fetchManager.getAllDownloadsAsync().filter {
|
val downloaded = fetchManager.getAllDownloadsAsync().filter {
|
||||||
it.status == Status.COMPLETED && it.extras.getString(
|
it.status == Status.COMPLETED && it.extras.getString(
|
||||||
"class",
|
"class",
|
||||||
@@ -261,7 +284,8 @@ class TransmissionScreenViewModel @Inject constructor(
|
|||||||
val video = fv.firstOrNull { it.klass == model.klass && it.id == model.vid }
|
val video = fv.firstOrNull { it.klass == model.klass && it.id == model.vid }
|
||||||
|
|
||||||
if (video != null) {
|
if (video != null) {
|
||||||
val group = fv.filter { it.klass == video.klass && it.video.group == video.video.group && it.video.group != "null" }
|
val group =
|
||||||
|
fv.filter { it.klass == video.klass && it.video.group == video.video.group && it.video.group != "null" }
|
||||||
for (i in group.sortedWith(compareBy(naturalOrder()) { it.video.name })) {
|
for (i in group.sortedWith(compareBy(naturalOrder()) { it.video.name })) {
|
||||||
playList.add("${i.klass}/${i.id}")
|
playList.add("${i.klass}/${i.id}")
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user