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.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")

View File

@@ -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)

View File

@@ -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)

View File

@@ -133,6 +133,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 "main" "type" to "main"
) )
) )
@@ -143,6 +144,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 "cover" "type" to "cover"
) )
) )
@@ -153,6 +155,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 +172,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"
) )
) )

View File

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

View File

@@ -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, fontSize = 12.sp,
lineHeight = 17.sp, lineHeight = 14.sp,
fontWeight = FontWeight.Bold, fontWeight = FontWeight.Bold,
maxLines = 2, 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) 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.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)

View File

@@ -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(

View File

@@ -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)

View File

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

View File

@@ -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,44 +195,115 @@ fun ComicScreen(
} }
} }
VariableGrid( Row(Modifier
modifier = Modifier .padding(horizontal = 8.dp)
.heightIn(max = 88.dp) .align(Alignment.CenterHorizontally)
.padding(4.dp),
rowHeight = 32.dp
) )
{ {
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( RadioButton(
Modifier selected = (sortType == 0),
.background( onClick = { sortType = 0 },
if (included.contains(i)) Color.Green.copy(alpha = 0.65f) else colorScheme.surface, modifier = Modifier.align(Alignment.CenterVertically).size(24.dp)
shape = RoundedCornerShape(4.dp) )
) Text(
.height(32.dp).widthIn(max = 72.dp) text = "Id",
.clickable { fontWeight = FontWeight.Bold,
if (included.contains(i)) fontSize = 16.sp,
included.remove(i) modifier = Modifier.align(Alignment.CenterVertically).padding(3.dp)
else )
included.add(i) Spacer(modifier = Modifier.width(12.dp))
}
) { RadioButton(
Text( selected = (sortType == 1),
text = i, onClick = { sortType = 1 },
fontWeight = FontWeight.Bold, modifier = Modifier.align(Alignment.CenterVertically).size(24.dp)
fontSize = 16.sp, )
maxLines = 1, Text(
modifier = Modifier text = "Name",
.padding(2.dp) fontWeight = FontWeight.Bold,
.align(Alignment.Center) 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) 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( LazyVerticalStaggeredGrid(
columns = StaggeredGridCells.Adaptive(120.dp), columns = StaggeredGridCells.Adaptive(120.dp),
contentPadding = PaddingValues(4.dp), contentPadding = PaddingValues(4.dp),
@@ -221,14 +313,25 @@ 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
included.all { y -> y in x.comic.tags } || included.isEmpty() .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 } key = { it.id }
) { comic -> ) { comic ->
Box(modifier = Modifier Box(
.fillMaxWidth() modifier = Modifier
.wrapContentHeight() .fillMaxWidth()
.wrapContentHeight()
) { ) {
ComicCard(comic, navController, comicScreenViewModel) 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.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")
}
}
}
}
} }
} }
} }

View File

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

View File

@@ -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.classes.getOrNull( videoScreenViewModel.videoLibrary.classesMap.getOrDefault(
tabIndex videoScreenViewModel.videoLibrary.classes.getOrNull(
), listOf() tabIndex
).filter { it.video.name.contains(searchFilter) }).filter { it.key != null } ), listOf()
.map{ i -> Pair(i.key!!, i.value.sortedWith(compareBy(naturalOrder()) { it.video.name }) ) } ).filter { it.video.name.contains(searchFilter) }).filter { it.key != null }
.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()
) { ) {
Text( Row(
text = "Videos", Modifier
style = MaterialTheme.typography.headlineMedium, .padding(horizontal = 8.dp)
modifier = Modifier .padding(top = 4.dp)
.padding(horizontal = 8.dp) .align(Alignment.CenterHorizontally)
.align(Alignment.Start)
) )
// TopRow(videoScreenViewModel);
Row(Modifier.padding(bottom = 4.dp).padding(start = 8.dp))
{ {
Card( Text(
shape = RoundedCornerShape(8.dp), text = "Videos",
colors = CardDefaults.cardColors(containerColor = colorScheme.primary), style = MaterialTheme.typography.headlineMedium,
modifier = Modifier modifier = Modifier
.padding(horizontal = 8.dp)
.align(Alignment.CenterVertically) .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( Spacer(Modifier.weight(1f))
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,52 +171,47 @@ 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(
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 @Composable
fun CatalogueItemRow( fun CatalogueItemRow(
item: Pair<Int, String>, item: Pair<Int, String>,

View File

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

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.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)
}
}
}
} }

View File

@@ -203,7 +203,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", "")
) )
} }