6 Commits

Author SHA1 Message Date
rootacite
512da7be1c [update] ui patch 2025-11-01 03:28:13 +08:00
rootacite
9efbcdfe8a [feat] Optional sort, tags folder 2025-10-29 23:53:14 +08:00
rootacite
c3e0a23ed1 [update] Comic sort policy 2025-10-29 20:14:07 +08:00
acite
7be18dd517 [feat] Playlist remember 2025-10-09 11:33:34 +08:00
acite
a13ddbdd87 [feat] Live framework. 2025-10-06 22:54:08 +08:00
acite
390094b8b0 [fix&optimize] Fix the issue of element teleportation in ComicScreen pages 2025-10-02 12:24:26 +08:00
19 changed files with 393 additions and 205 deletions

View File

@@ -56,6 +56,7 @@ import com.acitelight.aether.view.pages.ComicGridView
import com.acitelight.aether.view.pages.ComicPageView
import com.acitelight.aether.view.pages.ComicScreen
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.TransmissionScreen
import com.acitelight.aether.view.pages.VideoPlayer
@@ -179,12 +180,21 @@ fun AppNavigation() {
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,
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)) }) {
MeScreen();
MeScreen()
}
composable(
@@ -246,6 +256,7 @@ fun BottomNavigationBar(navController: NavController) {
Screen.Video,
Screen.Comic,
Screen.Transmission,
Screen.Live,
Screen.Me
) else listOf(
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 Transmission : Screen("transmission_route",
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 VideoPlayer : Screen("video_player_route/{videoId}", Icons.Filled.PlayArrow, "VideoPlayer")
data object ComicGrid : Screen("comic_grid_route/{comicId}", Icons.Filled.PlayArrow, "ComicGrid")

View File

@@ -16,7 +16,8 @@ class VideoDownloadItemState(
totalBytes: Long,
klass: String,
vid: String,
val type: String
val type: String,
val group: String
) {
var fileName by mutableStateOf(fileName)
var filePath by mutableStateOf(filePath)

View File

@@ -46,6 +46,9 @@ class ApiClient @Inject constructor(
fun getBase(): String{
return replaceAbyssProtocol(base)
}
fun getDomain(): String = domain
private var base: String = ""
private var domain: String = ""
private var cert: String = ""
@@ -236,7 +239,7 @@ class ApiClient @Inject constructor(
throw Exception("No reachable URL found")
}
domain = selectedUrl.toHttpUrlOrNull()?.host ?: ""
domain = replaceAbyssProtocol(selectedUrl).toHttpUrlOrNull()?.host ?: ""
cert = crt
base = selectedUrl
withContext(Dispatchers.IO)

View File

@@ -133,6 +133,7 @@ class FetchManager @Inject constructor(
"name" to video.video.name,
"id" to video.id,
"class" to video.klass,
"group" to (video.video.group ?: ""),
"type" to "main"
)
)
@@ -143,6 +144,7 @@ class FetchManager @Inject constructor(
"name" to video.video.name,
"id" to video.id,
"class" to video.klass,
"group" to (video.video.group ?: ""),
"type" to "cover"
)
)
@@ -153,6 +155,7 @@ class FetchManager @Inject constructor(
"name" to video.video.name,
"id" to video.id,
"class" to video.klass,
"group" to (video.video.group ?: ""),
"type" to "subtitle"
)
)
@@ -169,6 +172,7 @@ class FetchManager @Inject constructor(
"name" to video.video.name,
"id" to video.id,
"class" to video.klass,
"group" to (video.video.group ?: ""),
"type" to "gallery"
)
)

View File

@@ -156,7 +156,7 @@ class MediaManager @Inject constructor(
{
try{
val j = apiClient.api!!.getComics()
return j
return j.sorted()
}catch (_: Exception)
{
return listOf()

View File

@@ -64,11 +64,11 @@ fun ComicCard(
.diskCacheKey("${comic.id}/cover")
.build(),
contentDescription = null,
imageLoader = comicScreenViewModel.imageLoader!!,
modifier = Modifier
.fillMaxSize()
.clip(RoundedCornerShape(8.dp)),
contentScale = ContentScale.Crop,
contentScale = ContentScale.Fit,
imageLoader = comicScreenViewModel.imageLoader!!,
)
Box(
@@ -100,22 +100,21 @@ fun ComicCard(
}
Text(
text = comic.comic.comic_name,
fontSize = 14.sp,
lineHeight = 17.sp,
fontSize = 12.sp,
lineHeight = 14.sp,
fontWeight = FontWeight.Bold,
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)
)
Box(Modifier.padding(4.dp).fillMaxWidth()){
Text(
text = "Id: ${comic.id}",
fontSize = 12.sp,
lineHeight = 14.sp,
maxLines = 1,
modifier = Modifier.align(Alignment.CenterStart)
)
}
}
}
}

View File

@@ -31,6 +31,7 @@ import androidx.compose.foundation.layout.size
import androidx.compose.foundation.layout.width
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.items
import androidx.compose.foundation.lazy.rememberLazyListState
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material.icons.Icons
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.runtime.Composable
import androidx.compose.runtime.DisposableEffect
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableFloatStateOf
import androidx.compose.runtime.remember
@@ -87,6 +89,18 @@ fun VideoPlayerLandscape(videoPlayerViewModel: VideoPlayerViewModel) {
val activity = (context as? Activity)!!
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 maxVolume = remember { audioManager.getStreamMaxVolume(AudioManager.STREAM_MUSIC) }
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) {
audioManager.setStreamVolume(
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))
)
{
LazyColumn(contentPadding = PaddingValues(vertical = 4.dp)) {
LazyColumn(state = listState, contentPadding = PaddingValues(vertical = 4.dp)) {
items(videoPlayerViewModel.videos) { item ->
MiniPlaylistCard(Modifier.padding(4.dp), video = item, imageLoader = videoPlayerViewModel.imageLoader!!,
selected = id == item.id, apiClient = videoPlayerViewModel.apiClient)

View File

@@ -260,7 +260,7 @@ fun VideoPlayerPortal(
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)
}
HorizontalDivider(

View File

@@ -96,7 +96,7 @@ fun ComicGridView(
}
LaunchedEffect(comicGridViewModel) {
comicGridViewModel.coverHeight = screenHeight * 0.4f
comicGridViewModel.coverHeight = screenHeight * 0.3f
if(comicGridViewModel.maxHeight == 0.dp)
comicGridViewModel.maxHeight = screenHeight * 0.8f
}
@@ -252,7 +252,7 @@ fun ComicGridView(
fontSize = 11.sp,
lineHeight = 15.sp,
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 = {
@@ -379,10 +379,10 @@ fun ChapterCard(
{
Text(
text = chapter.name,
fontSize = 14.sp,
fontSize = 16.sp,
fontWeight = FontWeight.Bold,
maxLines = 2,
lineHeight = 16.sp,
lineHeight = 18.sp,
modifier = Modifier
.padding(horizontal = 8.dp).padding(vertical = 4.dp)
.background(Color.Transparent)
@@ -391,7 +391,6 @@ fun ChapterCard(
text = "${comic.getChapterLength(chapter.page)} Pages",
fontSize = 14.sp,
lineHeight = 16.sp,
fontWeight = FontWeight.Bold,
maxLines = 1,
modifier = Modifier
.padding(horizontal = 8.dp)

View File

@@ -243,7 +243,15 @@ fun ComicPageView(
{
val k = it.getPageChapterIndex(pagerState.currentPage)
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(
horizontalArrangement = Arrangement.spacedBy(5.dp),
state = comicPageViewModel.listState!!, modifier = Modifier
@@ -266,7 +274,7 @@ fun ComicPageView(
pagerState.requestScrollToPage(page = r)
}
) {
Box(Modifier.padding(1.dp))
Box(Modifier.padding(0.dp))
{
AsyncImage(
model = ImageRequest.Builder(LocalContext.current)
@@ -321,6 +329,9 @@ fun ComicPageView(
}
)
Spacer(Modifier.height(24.dp))
}
}
}

View File

@@ -1,5 +1,11 @@
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.clickable
import androidx.compose.foundation.layout.Arrangement
@@ -27,13 +33,21 @@ import androidx.compose.foundation.text.BasicTextField
import androidx.compose.foundation.verticalScroll
import androidx.compose.material.icons.Icons
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.Icon
import androidx.compose.material3.LocalTextStyle
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.RadioButton
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
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.ui.Alignment
import androidx.compose.ui.Modifier
@@ -122,6 +136,7 @@ fun VariableGrid(
}
@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun ComicScreen(
navController: NavHostController,
@@ -131,11 +146,17 @@ fun ComicScreen(
val state = rememberLazyStaggeredGridState()
val colorScheme = MaterialTheme.colorScheme
var searchFilter by comicScreenViewModel.searchFilter
var isTagsVisible by remember { mutableStateOf(false) }
var sortType by remember { mutableIntStateOf(0) }
Column {
Column(
modifier = Modifier.animateContentSize()
) {
Row(Modifier
.padding(4.dp)
.align(Alignment.CenterHorizontally)) {
.padding(horizontal = 8.dp).padding(top = 4.dp)
.align(Alignment.CenterHorizontally)
)
{
Text(
text = "Comics",
style = MaterialTheme.typography.headlineMedium,
@@ -149,7 +170,7 @@ fun ComicScreen(
.align(Alignment.CenterVertically)
.height(36.dp)
.widthIn(max = 240.dp)
.background(colorScheme.primary, RoundedCornerShape(8.dp))
.background(colorScheme.surface, RoundedCornerShape(8.dp))
.padding(horizontal = 6.dp)
) {
Icon(
@@ -174,44 +195,115 @@ fun ComicScreen(
}
}
VariableGrid(
modifier = Modifier
.heightIn(max = 88.dp)
.padding(4.dp),
rowHeight = 32.dp
Row(Modifier
.padding(horizontal = 8.dp)
.align(Alignment.CenterHorizontally)
)
{
for (i in comicScreenViewModel.tags) {
Text(
text = "Sorted by: ",
fontWeight = FontWeight.Bold,
fontSize = 16.sp,
modifier = Modifier.padding(horizontal = 6.dp).align(Alignment.CenterVertically)
)
Box(
Modifier
.background(
if (included.contains(i)) Color.Green.copy(alpha = 0.65f) else colorScheme.surface,
shape = RoundedCornerShape(4.dp)
)
.height(32.dp).widthIn(max = 72.dp)
.clickable {
if (included.contains(i))
included.remove(i)
else
included.add(i)
}
) {
Text(
text = i,
fontWeight = FontWeight.Bold,
fontSize = 16.sp,
maxLines = 1,
modifier = Modifier
.padding(2.dp)
.align(Alignment.Center)
)
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(
modifier = Modifier
.heightIn(max = 80.dp)
.padding(3.dp),
rowHeight = 30.dp
)
{
for (i in comicScreenViewModel.tags) {
Box(
Modifier
.background(
if (included.contains(i)) Color.Green.copy(alpha = 0.65f) else colorScheme.surface,
shape = RoundedCornerShape(4.dp)
)
.height(32.dp)
.widthIn(max = 72.dp)
.clickable {
if (included.contains(i))
included.remove(i)
else
included.add(i)
}
) {
Text(
text = i,
fontWeight = FontWeight.Bold,
fontSize = 16.sp,
maxLines = 1,
modifier = Modifier
.padding(2.dp)
.align(Alignment.Center)
)
}
}
}
HorizontalDivider(Modifier.padding(1.dp), thickness = 1.5.dp)
}
}
LazyVerticalStaggeredGrid(
columns = StaggeredGridCells.Adaptive(120.dp),
contentPadding = PaddingValues(4.dp),
@@ -221,14 +313,25 @@ fun ComicScreen(
modifier = Modifier.fillMaxSize()
) {
items(
items = comicScreenViewModel.comics.filter { searchFilter.isEmpty() || searchFilter in it.comic.comic_name }.filter { x ->
included.all { y -> y in x.comic.tags } || included.isEmpty()
},
items = comicScreenViewModel.comics
.filter { searchFilter.isEmpty() || searchFilter in it.comic.comic_name }
.filter { x ->
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 }
) { comic ->
Box(modifier = Modifier
.fillMaxWidth()
.wrapContentHeight()
Box(
modifier = Modifier
.fillMaxWidth()
.wrapContentHeight()
) {
ComicCard(comic, navController, comicScreenViewModel)
}

View File

@@ -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>()
)
{
}

View File

@@ -15,6 +15,7 @@ import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.Key
import androidx.compose.material.icons.filled.Link
import androidx.compose.material.icons.filled.Person
import androidx.compose.material.icons.filled.Textsms
import androidx.compose.material3.Button
import androidx.compose.material3.Card
import androidx.compose.material3.Checkbox
@@ -41,6 +42,7 @@ fun MeScreen(meScreenViewModel: MeScreenViewModel = hiltViewModel<MeScreenViewMo
var privateKey by meScreenViewModel.privateKey
var url by meScreenViewModel.url
var cert by meScreenViewModel.cert
var pak by meScreenViewModel.pak
val uss by meScreenViewModel.uss.collectAsState(initial = false)
@@ -50,7 +52,8 @@ fun MeScreen(meScreenViewModel: MeScreenViewModel = hiltViewModel<MeScreenViewMo
.padding(8.dp),
horizontalAlignment = Alignment.CenterHorizontally,
verticalArrangement = Arrangement.Top
) {
)
{
// Card component for a clean, contained UI block
item{
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")
}
}
}
}
}
}
}

View File

@@ -22,6 +22,7 @@ import com.acitelight.aether.view.components.BiliMiniSlider
import com.acitelight.aether.view.components.VideoDownloadCardMini
import com.acitelight.aether.viewModel.TransmissionScreenViewModel
import com.tonyodev.fetch2.Status
import java.io.File
import kotlin.collections.sortedWith
@Composable
@@ -82,7 +83,6 @@ fun TransmissionScreen(
items(
downloads
.filter { it.type == "main" }
.sortedWith(compareBy(naturalOrder()) { it.fileName })
.sortedBy { it.status == Status.COMPLETED }, key = { it.id })
{ item ->
VideoDownloadCardMini(
@@ -112,6 +112,11 @@ fun TransmissionScreen(
item,
downloads
)) transmissionScreenViewModel.delete(i.id)
File(
transmissionScreenViewModel.context.getExternalFilesDir(null),
"videos/${item.klass}/${item.vid}/summary.json"
).delete()
},
onRetry = {
for (i in downloadToGroup(

View File

@@ -1,19 +1,13 @@
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.clickable
import androidx.compose.foundation.combinedClickable
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.PaddingValues
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.height
@@ -23,8 +17,6 @@ import androidx.compose.foundation.layout.size
import androidx.compose.foundation.layout.width
import androidx.compose.foundation.layout.widthIn
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.StaggeredGridCells
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.text.BasicTextField
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.Menu
import androidx.compose.material.icons.filled.Search
import androidx.compose.material3.Card
import androidx.compose.material3.CardDefaults
import androidx.compose.material3.DividerDefaults
import androidx.compose.material3.HorizontalDivider
import androidx.compose.material3.Icon
import androidx.compose.material3.LocalTextStyle
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.SecondaryScrollableTabRow
import androidx.compose.material3.Tab
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.layout.ContentScale
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import coil3.compose.AsyncImage
import com.acitelight.aether.model.Video
import com.acitelight.aether.viewModel.VideoScreenViewModel
import androidx.compose.runtime.mutableStateListOf
import androidx.compose.runtime.setValue
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.hilt.lifecycle.viewmodel.compose.hiltViewModel
import androidx.lifecycle.viewModelScope
import androidx.navigation.NavHostController
import coil3.request.ImageRequest
import com.acitelight.aether.CardPage
import com.acitelight.aether.Global.updateRelate
import com.acitelight.aether.view.components.VideoCard
import kotlinx.coroutines.launch
import java.nio.charset.Charset
import kotlin.collections.sortedWith
fun videoToView(v: List<Video>): Map<String?, List<Video>>
{
return v.map { if(it.video.group != null) it else Video(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 videoToGroup(v: List<Video>): Map<String?, List<Video>> {
return v.map {
if (it.video.group != null) it else Video(
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 {
@@ -102,12 +87,13 @@ fun VideoScreen(
var menuVisibility by videoScreenViewModel.menuVisibility
var searchFilter by videoScreenViewModel.searchFilter
var doneInit by videoScreenViewModel.doneInit
val vb = videoToView(videoScreenViewModel.videoLibrary.classesMap.getOrDefault(
videoScreenViewModel.videoLibrary.classes.getOrNull(
tabIndex
), listOf()
).filter { it.video.name.contains(searchFilter) }).filter { it.key != null }
.map{ i -> Pair(i.key!!, i.value.sortedWith(compareBy(naturalOrder()) { it.video.name }) ) }
val vb = videoToGroup(
videoScreenViewModel.videoLibrary.classesMap.getOrDefault(
videoScreenViewModel.videoLibrary.classes.getOrNull(
tabIndex
), listOf()
).filter { it.video.name.contains(searchFilter) }).filter { it.key != null }
.map { i -> Pair(i.key!!, i.value.sortedWith(compareBy(naturalOrder()) { it.video.name })) }
.toList()
if (doneInit)
@@ -117,75 +103,31 @@ fun VideoScreen(
Column(
modifier = Modifier.fillMaxSize()
) {
Text(
text = "Videos",
style = MaterialTheme.typography.headlineMedium,
modifier = Modifier
.padding(horizontal = 8.dp)
.align(Alignment.Start)
Row(
Modifier
.padding(horizontal = 8.dp)
.padding(top = 4.dp)
.align(Alignment.CenterHorizontally)
)
// TopRow(videoScreenViewModel);
Row(Modifier.padding(bottom = 4.dp).padding(start = 8.dp))
{
Card(
shape = RoundedCornerShape(8.dp),
colors = CardDefaults.cardColors(containerColor = colorScheme.primary),
Text(
text = "Videos",
style = MaterialTheme.typography.headlineMedium,
modifier = Modifier
.padding(horizontal = 8.dp)
.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
)
}
}
Spacer(Modifier.weight(1f))
Row(
modifier = Modifier
.height(36.dp)
.widthIn(max = 240.dp)
.background(colorScheme.primary, RoundedCornerShape(8.dp))
.background(colorScheme.surface, RoundedCornerShape(8.dp))
.padding(horizontal = 6.dp)
) {
)
{
Icon(
modifier = Modifier
.size(30.dp)
@@ -207,11 +149,9 @@ fun VideoScreen(
)
}
}
HorizontalDivider(
Modifier.padding(4.dp),
2.dp,
DividerDefaults.color
)
TopRow(videoScreenViewModel)
LazyVerticalStaggeredGrid(
columns = StaggeredGridCells.Adaptive(160.dp),
contentPadding = PaddingValues(8.dp),
@@ -231,52 +171,47 @@ fun VideoScreen(
.fillMaxWidth()
.wrapContentHeight()
) {
if(video.second.isNotEmpty())
if (video.second.isNotEmpty())
VideoCard(video.second, navController, videoScreenViewModel)
}
}
}
}
AnimatedVisibility(
visible = menuVisibility,
enter = slideInHorizontally(initialOffsetX = { full -> full }),
exit = slideOutHorizontally(targetOffsetX = { full -> full }),
modifier = Modifier.align(Alignment.CenterEnd)
) {
Card(
Modifier
.fillMaxHeight()
.width(250.dp)
.align(Alignment.CenterEnd),
shape = RoundedCornerShape(8.dp),
colors = CardDefaults.cardColors(containerColor = colorScheme.surface)
)
{
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
)
)
}
)
}
}
}
}
}
}
}
@Composable
fun TopRow(videoScreenViewModel: VideoScreenViewModel) {
val tabIndex by videoScreenViewModel.tabIndex;
if (videoScreenViewModel.videoLibrary.classes.isEmpty()) return
val colorScheme = MaterialTheme.colorScheme
SecondaryScrollableTabRow(
selectedTabIndex = tabIndex,
modifier = Modifier
.background(Color.Transparent)
.padding(vertical = 4.dp)
) {
videoScreenViewModel.videoLibrary.classes.forEachIndexed { index, title ->
Tab(
modifier = Modifier.height(42.dp),
selected = tabIndex == index,
onClick = { videoScreenViewModel.setTabIndex(index) },
text = {
Text(
text = title,
maxLines = 1,
fontWeight = FontWeight.Bold,
lineHeight = 16.sp,
fontSize = 14.sp
)
},
)
}
}
}
@Composable
fun CatalogueItemRow(
item: Pair<Int, String>,

View File

@@ -61,7 +61,7 @@ class ComicScreenViewModel @Inject constructor(
val m = mediaManager.queryComicInfoBulk(l)
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()
.entries.sortedByDescending { it.value }
.map { it.key })

View File

@@ -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(){
}

View File

@@ -18,6 +18,9 @@ import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.first
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import java.net.DatagramPacket
import java.net.DatagramSocket
import java.net.InetAddress
import javax.inject.Inject
@HiltViewModel
@@ -33,6 +36,7 @@ class MeScreenViewModel @Inject constructor(
val privateKey = mutableStateOf("")
val url = mutableStateOf("")
val cert = mutableStateOf("")
val pak = mutableStateOf("")
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 {
settingsDataStoreManager.saveUserName(u)
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)
}
}
}
}

View File

@@ -203,7 +203,8 @@ class TransmissionScreenViewModel @Inject constructor(
totalBytes = download.total,
klass = download.extras.getString("class", ""),
vid = download.extras.getString("id", ""),
type = download.extras.getString("type", "")
type = download.extras.getString("type", ""),
group = download.extras.getString("group", "")
)
}