5 Commits

Author SHA1 Message Date
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
acite
b360724dca [fix] Chapter progress 2025-10-02 01:34:09 +08:00
acite
db8d5ef4d5 [fix] Cover height meansure 2025-10-02 01:20:13 +08:00
10 changed files with 229 additions and 145 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

@@ -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,
fontWeight = FontWeight.Bold,
maxLines = 2,
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)
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)
)
}
}
}
}

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

@@ -95,8 +95,9 @@ fun ComicGridView(
}
}
LaunchedEffect(Unit) {
comicGridViewModel.coverHeight = screenHeight * 0.4f
LaunchedEffect(comicGridViewModel) {
comicGridViewModel.coverHeight = screenHeight * 0.3f
if(comicGridViewModel.maxHeight == 0.dp)
comicGridViewModel.maxHeight = screenHeight * 0.8f
}
@@ -251,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 = {
@@ -378,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)
@@ -390,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

@@ -5,15 +5,18 @@ import androidx.compose.animation.slideInVertically
import androidx.compose.animation.slideOutVertically
import androidx.compose.foundation.background
import androidx.compose.foundation.clickable
import androidx.compose.foundation.gestures.detectTapGestures
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
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
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.width
import androidx.compose.foundation.layout.widthIn
import androidx.compose.foundation.layout.wrapContentHeight
import androidx.compose.foundation.lazy.LazyRow
@@ -36,7 +39,9 @@ import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.graphics.Brush
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.input.pointer.pointerInput
import androidx.compose.ui.layout.ContentScale
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.platform.LocalView
@@ -50,6 +55,7 @@ import coil3.compose.AsyncImage
import coil3.request.ImageRequest
import com.acitelight.aether.model.BookMark
import com.acitelight.aether.setFullScreen
import com.acitelight.aether.view.components.BiliMiniSlider
import com.acitelight.aether.view.components.BookmarkPop
import com.acitelight.aether.viewModel.ComicPageViewModel
import kotlinx.coroutines.launch
@@ -92,7 +98,9 @@ fun ComicPageView(
.fillMaxSize()
.align(Alignment.Center)
.background(Color.Black)
.clickable {
.pointerInput(Unit) {
detectTapGestures(
onTap = {
showPlane = !showPlane
if (showPlane) {
comicPageViewModel.viewModelScope.launch {
@@ -100,6 +108,8 @@ fun ComicPageView(
}
}
}
)
}
) { page ->
AsyncImage(
model = ImageRequest.Builder(LocalContext.current)
@@ -123,29 +133,29 @@ fun ComicPageView(
modifier = Modifier
.align(Alignment.TopCenter)
) {
Box()
{
Column(Modifier
.align(Alignment.TopCenter)
.fillMaxWidth())
{
Card(
colors = CardDefaults.cardColors(containerColor = colorScheme.primary),
shape = RoundedCornerShape(12.dp),
modifier = Modifier
.fillMaxWidth()
.padding(top = 18.dp)
.padding(horizontal = 12.dp)
.height(42.dp)
.background(
brush = Brush.verticalGradient(
colors = listOf(
Color.Black.copy(alpha = 0.9f),
Color.Transparent,
)
)
))
{
Row(modifier = Modifier.fillMaxSize())
Row(modifier = Modifier
.fillMaxWidth()
.padding(horizontal = 16.dp).padding(top = 16.dp))
{
Text(
text = title,
fontSize = 16.sp,
lineHeight = 19.sp,
fontWeight = FontWeight.Bold,
maxLines = 1,
color = Color.White,
modifier = Modifier
.padding(8.dp)
.padding(horizontal = 10.dp)
@@ -155,27 +165,19 @@ fun ComicPageView(
Text(
text = "${pagerState.currentPage + 1}/${pagerState.pageCount}",
fontSize = 18.sp,
fontSize = 16.sp,
lineHeight = 19.sp,
fontWeight = FontWeight.Bold,
maxLines = 1,
color = Color.White,
modifier = Modifier
.padding(8.dp)
.widthIn(min = 60.dp)
.align(Alignment.CenterVertically)
)
}
}
Box(Modifier.fillMaxWidth()) {
Card(
modifier = Modifier
.align(Alignment.CenterStart)
.padding(top = 6.dp)
.padding(horizontal = 12.dp)
.height(42.dp),
colors = CardDefaults.cardColors(containerColor = colorScheme.primary),
shape = RoundedCornerShape(12.dp)
)
Box(Modifier.fillMaxWidth()
.padding(horizontal = 16.dp))
{
Row {
val k = it.getPageChapterIndex(pagerState.currentPage)
@@ -184,6 +186,7 @@ fun ComicPageView(
fontSize = 16.sp,
fontWeight = FontWeight.Bold,
maxLines = 1,
color = Color.White,
modifier = Modifier
.padding(8.dp)
.padding(horizontal = 10.dp)
@@ -195,13 +198,13 @@ fun ComicPageView(
fontSize = 18.sp,
fontWeight = FontWeight.Bold,
maxLines = 1,
color = Color.White,
modifier = Modifier
.padding(8.dp)
.widthIn(min = 60.dp)
.align(Alignment.CenterVertically)
)
}
}
Card(
@@ -210,7 +213,7 @@ fun ComicPageView(
.padding(top = 6.dp)
.padding(horizontal = 12.dp)
.height(42.dp),
colors = CardDefaults.cardColors(containerColor = colorScheme.primary),
colors = CardDefaults.cardColors(containerColor = colorScheme.surface),
shape = RoundedCornerShape(12.dp)
)
{
@@ -226,7 +229,7 @@ fun ComicPageView(
}
}
}
}
Spacer(Modifier.height(64.dp))
}
}
@@ -238,15 +241,24 @@ fun ComicPageView(
.align(Alignment.BottomCenter)
)
{
Box {
val k = it.getPageChapterIndex(pagerState.currentPage)
Column(Modifier
.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
.fillMaxWidth()
.padding(bottom = 18.dp)
.padding(bottom = 1.dp)
.padding(horizontal = 12.dp)
.height(180.dp)
.align(Alignment.BottomCenter)
)
{
items(comicPageViewModel.pageList.size)
@@ -262,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)
@@ -306,6 +318,20 @@ fun ComicPageView(
}
}
}
BiliMiniSlider(
value = (k.second.toInt()) / it.getChapterLength(k.first.page).toFloat(),
modifier = Modifier
.height(6.dp)
.fillMaxWidth().padding(horizontal = 24.dp)
.fillMaxWidth(),
onValueChange = {
}
)
Spacer(Modifier.height(24.dp))
}
}
}

View File

@@ -133,9 +133,11 @@ fun ComicScreen(
var searchFilter by comicScreenViewModel.searchFilter
Column {
Row(Modifier
Row(
Modifier
.padding(4.dp)
.align(Alignment.CenterHorizontally)) {
.align(Alignment.CenterHorizontally)
) {
Text(
text = "Comics",
style = MaterialTheme.typography.headlineMedium,
@@ -189,7 +191,8 @@ fun ComicScreen(
if (included.contains(i)) Color.Green.copy(alpha = 0.65f) else colorScheme.surface,
shape = RoundedCornerShape(4.dp)
)
.height(32.dp).widthIn(max = 72.dp)
.height(32.dp)
.widthIn(max = 72.dp)
.clickable {
if (included.contains(i))
included.remove(i)
@@ -221,12 +224,15 @@ fun ComicScreen(
modifier = Modifier.fillMaxSize()
) {
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()
},
key = { it.id }
) { comic ->
Box(modifier = Modifier
Box(
modifier = Modifier
.fillMaxWidth()
.wrapContentHeight()
) {

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

@@ -32,7 +32,7 @@ class ComicGridViewModel @Inject constructor(
) : ViewModel()
{
var coverHeight by mutableStateOf(220.dp)
var maxHeight = 220.dp
var maxHeight = 0.dp
var imageLoader: ImageLoader? = null
var comic = mutableStateOf<Comic?>(null)

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