[update] Better UI

This commit is contained in:
acite
2025-09-14 18:26:05 +08:00
parent 9c04d7679c
commit cc540903d3
21 changed files with 661 additions and 98 deletions

View File

@@ -46,6 +46,9 @@ android {
} }
dependencies { dependencies {
implementation(libs.fetch2)
implementation(libs.fetch2okhttp)
implementation(libs.hilt.android) implementation(libs.hilt.android)
implementation(libs.hilt.navigation.compose) implementation(libs.hilt.navigation.compose)
ksp(libs.hilt.android.compiler) ksp(libs.hilt.android.compiler)

View File

@@ -5,6 +5,7 @@ import android.content.Intent
import android.os.Binder import android.os.Binder
import android.os.IBinder import android.os.IBinder
import com.acitelight.aether.service.AbyssTunnelProxy import com.acitelight.aether.service.AbyssTunnelProxy
import com.acitelight.aether.service.FetchManager
import com.acitelight.aether.service.SettingsDataStoreManager import com.acitelight.aether.service.SettingsDataStoreManager
import dagger.hilt.android.AndroidEntryPoint import dagger.hilt.android.AndroidEntryPoint
import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.CoroutineScope
@@ -21,6 +22,8 @@ import javax.inject.Inject
class AbyssService: Service() { class AbyssService: Service() {
@Inject @Inject
lateinit var proxy: AbyssTunnelProxy lateinit var proxy: AbyssTunnelProxy
@Inject
lateinit var downloader: FetchManager
private val binder = AbyssServiceBinder() private val binder = AbyssServiceBinder()
private val _isInitialized = MutableStateFlow(false) private val _isInitialized = MutableStateFlow(false)

View File

@@ -8,4 +8,17 @@ import com.acitelight.aether.model.Video
object Global { object Global {
var loggedIn by mutableStateOf(false) var loggedIn by mutableStateOf(false)
var sameClassVideos: List<Video>? = null var sameClassVideos: List<Video>? = null
private set
fun updateRelate(v: List<Video>, s: Video)
{
sameClassVideos = if (v.contains(s)) {
val index = v.indexOf(s)
val afterS = v.subList(index, v.size)
val beforeS = v.subList(0, index)
afterS + beforeS
} else {
v
}
}
} }

View File

@@ -13,13 +13,22 @@ import androidx.activity.enableEdgeToEdge
import androidx.compose.animation.AnimatedVisibility import androidx.compose.animation.AnimatedVisibility
import androidx.compose.animation.slideInVertically import androidx.compose.animation.slideInVertically
import androidx.compose.animation.slideOutVertically import androidx.compose.animation.slideOutVertically
import androidx.compose.foundation.background
import androidx.compose.foundation.isSystemInDarkTheme
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.heightIn import androidx.compose.foundation.layout.heightIn
import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material.icons.automirrored.filled.CompareArrows import androidx.compose.material.icons.automirrored.filled.CompareArrows
import androidx.compose.material.icons.filled.* import androidx.compose.material.icons.filled.*
import androidx.compose.material3.Card
import androidx.compose.material3.CardColors
import androidx.compose.material3.CardDefaults
import androidx.compose.material3.Icon import androidx.compose.material3.Icon
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.NavigationBar import androidx.compose.material3.NavigationBar
import androidx.compose.material3.NavigationBarItem import androidx.compose.material3.NavigationBarItem
import androidx.compose.material3.Scaffold import androidx.compose.material3.Scaffold
@@ -32,6 +41,7 @@ import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.vector.ImageVector import androidx.compose.ui.graphics.vector.ImageVector
import androidx.compose.ui.platform.LocalView import androidx.compose.ui.platform.LocalView
import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.painterResource
@@ -55,6 +65,7 @@ import com.acitelight.aether.view.ComicPageView
import com.acitelight.aether.view.ComicScreen import com.acitelight.aether.view.ComicScreen
import com.acitelight.aether.view.HomeScreen import com.acitelight.aether.view.HomeScreen
import com.acitelight.aether.view.MeScreen import com.acitelight.aether.view.MeScreen
import com.acitelight.aether.view.TransmissionScreen
import com.acitelight.aether.view.VideoPlayer import com.acitelight.aether.view.VideoPlayer
import com.acitelight.aether.view.VideoScreen import com.acitelight.aether.view.VideoScreen
import dagger.hilt.android.AndroidEntryPoint import dagger.hilt.android.AndroidEntryPoint
@@ -147,21 +158,29 @@ fun AppNavigation() {
) { innerPadding -> ) { innerPadding ->
NavHost( NavHost(
navController = navController, navController = navController,
startDestination = Screen.Home.route, startDestination = Screen.Me.route,
modifier = if(shouldShowBottomBar)Modifier.padding(innerPadding) else Modifier.padding(0.dp) modifier = if(shouldShowBottomBar)Modifier.padding(innerPadding) else Modifier.padding(0.dp)
) { ) {
composable(Screen.Home.route) { composable(Screen.Home.route) {
HomeScreen(navController = navController) CardPage(title = "Home") {
HomeScreen(navController = navController)
}
} }
composable(Screen.Video.route) { composable(Screen.Video.route) {
VideoScreen(navController = navController) CardPage(title = "Videos") {
VideoScreen(navController = navController)
}
} }
composable(Screen.Comic.route) { composable(Screen.Comic.route) {
ComicScreen(navController = navController) CardPage(title = "Comic") {
ComicScreen(navController = navController)
}
} }
composable(Screen.Transmission.route) { composable(Screen.Transmission.route) {
// ComicScreen() CardPage(title = "Tasks") {
TransmissionScreen()
}
} }
composable(Screen.Me.route) { composable(Screen.Me.route) {
MeScreen(); MeScreen();
@@ -217,8 +236,6 @@ fun BottomNavigationBar(navController: NavController) {
Screen.Transmission, Screen.Transmission,
Screen.Me Screen.Me
) else listOf( ) else listOf(
Screen.Home,
Screen.Transmission,
Screen.Me Screen.Me
) )
@@ -242,6 +259,37 @@ fun BottomNavigationBar(navController: NavController) {
} }
} }
@Composable
fun CardPage(
title: String,
modifier: Modifier = Modifier,
content: @Composable () -> Unit
) {
Box(Modifier.background(if (isSystemInDarkTheme()) {
Color.Black
} else {
Color.White
}).fillMaxSize())
{
val colorScheme = MaterialTheme.colorScheme
Card(
modifier = Modifier
.fillMaxSize()
.padding(16.dp),
elevation = CardDefaults.cardElevation(4.dp),
shape = RoundedCornerShape(12.dp),
colors = CardDefaults.cardColors(containerColor = colorScheme.background)
) {
Box(
modifier = Modifier
.fillMaxSize()
) {
content()
}
}
}
}
sealed class Screen(val route: String, val icon: ImageVector, val title: String) { sealed class Screen(val route: String, val icon: ImageVector, val title: String) {
data object Home : Screen("home_route", Icons.Filled.Home, "Home") data object Home : Screen("home_route", Icons.Filled.Home, "Home")
data object Video : Screen("video_route", Icons.Filled.VideoLibrary, "Video") data object Video : Screen("video_route", Icons.Filled.VideoLibrary, "Video")

View File

@@ -0,0 +1,25 @@
package com.acitelight.aether.model
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.setValue
import com.tonyodev.fetch2.Status
class DownloadItemState(
val id: Int,
fileName: String,
filePath: String,
url: String,
progress: Int,
status: Status,
downloadedBytes: Long,
totalBytes: Long
) {
var fileName by mutableStateOf(fileName)
var filePath by mutableStateOf(filePath)
var url by mutableStateOf(url)
var progress by mutableStateOf(progress)
var status by mutableStateOf(status)
var downloadedBytes by mutableStateOf(downloadedBytes)
var totalBytes by mutableStateOf(totalBytes)
}

View File

@@ -12,8 +12,10 @@ import java.net.InetAddress
import java.net.ServerSocket import java.net.ServerSocket
import java.net.Socket import java.net.Socket
import javax.inject.Inject import javax.inject.Inject
import javax.inject.Singleton
import kotlin.coroutines.CoroutineContext import kotlin.coroutines.CoroutineContext
@Singleton
class AbyssTunnelProxy @Inject constructor( class AbyssTunnelProxy @Inject constructor(
private val settingsDataStoreManager: SettingsDataStoreManager private val settingsDataStoreManager: SettingsDataStoreManager
) { ) {

View File

@@ -221,6 +221,7 @@ object ApiClient {
withContext(Dispatchers.IO) withContext(Dispatchers.IO)
{ {
(context as AetherApp).abyssService?.proxy?.config(base.toUri().host!!, 4096) (context as AetherApp).abyssService?.proxy?.config(base.toUri().host!!, 4096)
context.abyssService?.downloader?.init()
} }
api = createRetrofit().create(ApiInterface::class.java) api = createRetrofit().create(ApiInterface::class.java)

View File

@@ -0,0 +1,85 @@
package com.acitelight.aether.service
import android.content.Context
import com.acitelight.aether.service.ApiClient.createOkHttp
import com.tonyodev.fetch2.Download
import com.tonyodev.fetch2.Fetch
import com.tonyodev.fetch2.FetchConfiguration
import com.tonyodev.fetch2.FetchListener
import com.tonyodev.fetch2.Request
import com.tonyodev.fetch2.Status
import com.tonyodev.fetch2okhttp.OkHttpDownloader
import dagger.hilt.android.qualifiers.ApplicationContext
import javax.inject.Inject
import javax.inject.Singleton
@Singleton
class FetchManager @Inject constructor(
@ApplicationContext private val context: Context
) {
private var fetch: Fetch? = null
private var listener: FetchListener? = null
fun init()
{
val fetchConfiguration = FetchConfiguration.Builder(context)
.setDownloadConcurrentLimit(8)
.setHttpDownloader(OkHttpDownloader(createOkHttp()))
.build()
fetch = Fetch.Impl.getInstance(fetchConfiguration)
}
// listener management
fun setListener(l: FetchListener) {
if (fetch == null)
return
listener?.let { fetch?.removeListener(it) }
listener = l
fetch?.addListener(l)
}
fun removeListener() {
listener?.let {
fetch?.removeListener(it)
}
listener = null
}
// query downloads
fun getAllDownloads(callback: (List<Download>) -> Unit) {
if (fetch == null) init()
fetch?.getDownloads { list -> callback(list) } ?: callback(emptyList())
}
fun getDownloadsByStatus(status: Status, callback: (List<Download>) -> Unit) {
if (fetch == null) init()
fetch?.getDownloadsWithStatus(status) { list -> callback(list) } ?: callback(emptyList())
}
// operations
fun pause(id: Int) {
fetch?.pause(id)
}
fun resume(id: Int) {
fetch?.resume(id)
}
fun cancel(id: Int) {
fetch?.cancel(id)
}
fun delete(id: Int, callback: (() -> Unit)? = null) {
fetch?.delete(id) {
callback?.invoke()
} ?: callback?.invoke()
}
// enqueue helper if needed
fun enqueue(request: Request, onEnqueued: ((Request) -> Unit)? = null, onError: ((com.tonyodev.fetch2.Error) -> Unit)? = null) {
if (fetch == null) init()
fetch?.enqueue(request, { r -> onEnqueued?.invoke(r) }, { e -> onError?.invoke(e) })
}
}

View File

@@ -3,35 +3,135 @@ package com.acitelight.aether.ui.theme
import android.app.Activity import android.app.Activity
import android.os.Build import android.os.Build
import androidx.compose.foundation.isSystemInDarkTheme import androidx.compose.foundation.isSystemInDarkTheme
import androidx.compose.material3.ColorScheme
import androidx.compose.material3.MaterialTheme import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.darkColorScheme import androidx.compose.material3.darkColorScheme
import androidx.compose.material3.dynamicDarkColorScheme import androidx.compose.material3.dynamicDarkColorScheme
import androidx.compose.material3.dynamicLightColorScheme import androidx.compose.material3.dynamicLightColorScheme
import androidx.compose.material3.lightColorScheme import androidx.compose.material3.lightColorScheme
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.lerp
import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.platform.LocalContext
private val DarkColorScheme = darkColorScheme( fun generateColorScheme(primaryColor: Color, isDarkMode: Boolean): ColorScheme {
primary = Purple80,
secondary = PurpleGrey80,
tertiary = Pink80
)
private val LightColorScheme = lightColorScheme( val background = if (isDarkMode) Color(0xFF121212) else Color(0xFFFFFFFF)
primary = Purple40, val surface = if (isDarkMode) Color(0xFF1E1E1E) else Color(0xFFFFFFFF)
secondary = PurpleGrey40,
tertiary = Pink40
/* Other default colors to override val surfaceContainer = if (isDarkMode) Color(0xFF232323) else Color(0xFFFDFDFD)
background = Color(0xFFFFFBFE), val surfaceContainerLow = if (isDarkMode) Color(0xFF1A1A1A) else Color(0xFFF5F5F5)
surface = Color(0xFFFFFBFE), val surfaceContainerHigh = if (isDarkMode) Color(0xFF2A2A2A) else Color(0xFFFAFAFA)
onPrimary = Color.White, val surfaceContainerHighest = if (isDarkMode) Color(0xFF333333) else Color(0xFFFFFFFF)
onSecondary = Color.White, val surfaceContainerLowest = if (isDarkMode) Color(0xFF0F0F0F) else Color(0xFFF0F0F0)
onTertiary = Color.White, val surfaceBright = if (isDarkMode) Color(0xFF2C2C2C) else Color(0xFFFFFFFF)
onBackground = Color(0xFF1C1B1F), val surfaceDim = if (isDarkMode) Color(0xFF141414) else Color(0xFFF8F8F8)
onSurface = Color(0xFF1C1B1F),
*/ fun tint(surface: Color, factor: Float) = lerp(surface, primaryColor, factor)
)
return if (isDarkMode) {
darkColorScheme(
primary = primaryColor,
onPrimary = Color.White,
primaryContainer = tint(primaryColor, 0.2f),
onPrimaryContainer = Color.White,
inversePrimary = tint(primaryColor, 0.6f),
secondary = tint(primaryColor, 0.4f),
onSecondary = Color.White,
secondaryContainer = tint(primaryColor, 0.2f),
onSecondaryContainer = Color.White,
tertiary = tint(primaryColor, 0.5f),
onTertiary = Color.White,
tertiaryContainer = tint(primaryColor, 0.2f),
onTertiaryContainer = Color.White,
background = background,
onBackground = Color.White,
surface = surface,
onSurface = Color.White,
surfaceVariant = tint(surface, 0.1f),
onSurfaceVariant = Color(0xFFE0E0E0),
surfaceTint = primaryColor,
inverseSurface = Color.White,
inverseOnSurface = Color(0xFF121212),
error = Color(0xFFCF6679),
onError = Color.Black,
errorContainer = Color(0xFFB00020),
onErrorContainer = Color.White,
outline = Color(0xFF757575),
outlineVariant = Color(0xFF494949),
scrim = Color.Black,
surfaceBright = tint(surfaceBright, 0.1f),
surfaceContainer = tint(surfaceContainer, 0.1f),
surfaceContainerHigh = tint(surfaceContainerHigh, 0.12f),
surfaceContainerHighest = tint(surfaceContainerHighest, 0.15f),
surfaceContainerLow = tint(surfaceContainerLow, 0.08f),
surfaceContainerLowest = tint(surfaceContainerLowest, 0.05f),
surfaceDim = tint(surfaceDim, 0.1f)
)
} else {
lightColorScheme(
primary = primaryColor,
onPrimary = Color.White,
primaryContainer = tint(primaryColor, 0.3f),
onPrimaryContainer = Color.Black,
inversePrimary = tint(primaryColor, 0.6f),
secondary = tint(primaryColor, 0.4f),
onSecondary = Color.Black,
secondaryContainer = tint(primaryColor, 0.2f),
onSecondaryContainer = Color.Black,
tertiary = tint(primaryColor, 0.5f),
onTertiary = Color.Black,
tertiaryContainer = tint(primaryColor, 0.3f),
onTertiaryContainer = Color.Black,
background = background,
onBackground = Color.Black,
surface = surface,
onSurface = Color.Black,
surfaceVariant = tint(surface, 0.1f),
onSurfaceVariant = Color(0xFF49454F),
surfaceTint = primaryColor,
inverseSurface = Color(0xFF121212),
inverseOnSurface = Color.White,
error = Color(0xFFB00020),
onError = Color.White,
errorContainer = Color(0xFFFFDAD6),
onErrorContainer = Color.Black,
outline = Color(0xFF737373),
outlineVariant = Color(0xFFD0C4C9),
scrim = Color.Black,
surfaceBright = tint(surfaceBright, 0.1f),
surfaceContainer = tint(surfaceContainer, 0.1f),
surfaceContainerHigh = tint(surfaceContainerHigh, 0.12f),
surfaceContainerHighest = tint(surfaceContainerHighest, 0.15f),
surfaceContainerLow = tint(surfaceContainerLow, 0.08f),
surfaceContainerLowest = tint(surfaceContainerLowest, 0.05f),
surfaceDim = tint(surfaceDim, 0.05f)
)
}
}
private val DarkColorScheme = generateColorScheme(Color(0xFFFF4081), isDarkMode = true)
private val LightColorScheme = generateColorScheme(Color(0xFFFF4081), isDarkMode = false)
@Composable @Composable
fun AetherTheme( fun AetherTheme(
@@ -41,11 +141,6 @@ fun AetherTheme(
content: @Composable () -> Unit content: @Composable () -> Unit
) { ) {
val colorScheme = when { val colorScheme = when {
dynamicColor && Build.VERSION.SDK_INT >= Build.VERSION_CODES.S -> {
val context = LocalContext.current
if (darkTheme) dynamicDarkColorScheme(context) else dynamicLightColorScheme(context)
}
darkTheme -> DarkColorScheme darkTheme -> DarkColorScheme
else -> LightColorScheme else -> LightColorScheme
} }

View File

@@ -16,6 +16,7 @@ import androidx.compose.foundation.lazy.LazyRow
import androidx.compose.foundation.lazy.items import androidx.compose.foundation.lazy.items
import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material3.Card import androidx.compose.material3.Card
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.getValue
@@ -43,6 +44,7 @@ fun ComicGridView(comicId: String, navController: NavHostController, comicGridVi
comicGridViewModel.resolve(comicId.hexToString()) comicGridViewModel.resolve(comicId.hexToString())
comicGridViewModel.updateProcess(comicId.hexToString()){} comicGridViewModel.updateProcess(comicId.hexToString()){}
ToggleFullScreen(false) ToggleFullScreen(false)
val colorScheme = MaterialTheme.colorScheme
val context = LocalContext.current val context = LocalContext.current
val comic by comicGridViewModel.comic val comic by comicGridViewModel.comic
@@ -53,28 +55,25 @@ fun ComicGridView(comicId: String, navController: NavHostController, comicGridVi
Box( Box(
Modifier Modifier
.padding(horizontal = 16.dp).padding(top = 36.dp) .padding(horizontal = 16.dp).padding(top = 36.dp)
.background(Color.White.copy(alpha = 0.65f), shape = RoundedCornerShape(12.dp)) .background(colorScheme.surfaceContainerHighest, shape = RoundedCornerShape(12.dp))
) )
{ {
Text( Text(
text = comic!!.comic.comic_name, text = comic!!.comic.comic_name,
fontSize = 18.sp, fontSize = 18.sp,
fontWeight = FontWeight.Bold, fontWeight = FontWeight.Bold,
color = Color.Black,
maxLines = 1, maxLines = 1,
modifier = Modifier.padding(4.dp) modifier = Modifier.padding(4.dp))
)
} }
Box( Box(
Modifier Modifier
.padding(horizontal = 16.dp).padding(top = 4.dp) .padding(horizontal = 16.dp).padding(top = 4.dp)
.background(Color.White.copy(alpha = 0.65f), shape = RoundedCornerShape(12.dp)) .background(colorScheme.surfaceContainerHighest, shape = RoundedCornerShape(12.dp))
) { ) {
Text( Text(
text = comic!!.comic.author, text = comic!!.comic.author,
fontSize = 16.sp, fontSize = 16.sp,
fontWeight = FontWeight.Bold, fontWeight = FontWeight.Bold,
color = Color.Black,
maxLines = 1, maxLines = 1,
modifier = Modifier.padding(4.dp) modifier = Modifier.padding(4.dp)
) )
@@ -83,13 +82,12 @@ fun ComicGridView(comicId: String, navController: NavHostController, comicGridVi
Box( Box(
Modifier Modifier
.padding(horizontal = 16.dp).padding(top = 4.dp) .padding(horizontal = 16.dp).padding(top = 4.dp)
.background(Color.White.copy(alpha = 0.65f), shape = RoundedCornerShape(12.dp)) .background(colorScheme.surfaceContainerHighest, shape = RoundedCornerShape(12.dp))
) { ) {
Text( Text(
text = "Tags : ${comic!!.comic.tags.joinToString(", ")}", text = "Tags : ${comic!!.comic.tags.joinToString(", ")}",
fontSize = 16.sp, fontSize = 16.sp,
fontWeight = FontWeight.Bold, fontWeight = FontWeight.Bold,
color = Color.Black,
maxLines = 5, maxLines = 5,
modifier = Modifier.padding(4.dp) modifier = Modifier.padding(4.dp)
) )
@@ -105,7 +103,7 @@ fun ComicGridView(comicId: String, navController: NavHostController, comicGridVi
Box( Box(
Modifier Modifier
.padding(horizontal = 16.dp).padding(top = 6.dp).padding(bottom = 20.dp).heightIn(min = 42.dp) .padding(horizontal = 16.dp).padding(top = 6.dp).padding(bottom = 20.dp).heightIn(min = 42.dp)
.background(Color.White.copy(alpha = 0.65f), shape = RoundedCornerShape(12.dp)) .background(colorScheme.surfaceContainerHighest, shape = RoundedCornerShape(12.dp))
.clickable{ .clickable{
comicGridViewModel.updateProcess(comicId.hexToString()) comicGridViewModel.updateProcess(comicId.hexToString())
{ {
@@ -133,7 +131,6 @@ fun ComicGridView(comicId: String, navController: NavHostController, comicGridVi
text = "Last Read Position: ${k.first.name} ${k.second}/${comic!!.getChapterLength(k.first.page)}", text = "Last Read Position: ${k.first.name} ${k.second}/${comic!!.getChapterLength(k.first.page)}",
fontSize = 20.sp, fontSize = 20.sp,
fontWeight = FontWeight.Bold, fontWeight = FontWeight.Bold,
color = Color.Black,
maxLines = 1, maxLines = 1,
modifier = Modifier.padding(4.dp).weight(1f) modifier = Modifier.padding(4.dp).weight(1f)
) )
@@ -142,7 +139,6 @@ fun ComicGridView(comicId: String, navController: NavHostController, comicGridVi
text = "Read from scratch", text = "Read from scratch",
fontSize = 20.sp, fontSize = 20.sp,
fontWeight = FontWeight.Bold, fontWeight = FontWeight.Bold,
color = Color.Black,
maxLines = 1, maxLines = 1,
modifier = Modifier.padding(4.dp).weight(1f) modifier = Modifier.padding(4.dp).weight(1f)
) )

View File

@@ -26,6 +26,7 @@ import androidx.compose.foundation.verticalScroll
import androidx.compose.material3.Card import androidx.compose.material3.Card
import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.HorizontalDivider import androidx.compose.material3.HorizontalDivider
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Tab 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
@@ -139,6 +140,7 @@ fun ComicScreen(
comicScreenViewModel.SetupClient() comicScreenViewModel.SetupClient()
val included = comicScreenViewModel.included val included = comicScreenViewModel.included
val state = rememberLazyGridState() val state = rememberLazyGridState()
val colorScheme = MaterialTheme.colorScheme
Column { Column {
@@ -154,9 +156,7 @@ fun ComicScreen(
Box( Box(
Modifier Modifier
.background( .background(
if (included.contains(i)) Color.Green.copy(alpha = 0.65f) else Color.White.copy( if (included.contains(i)) Color.Green.copy(alpha = 0.65f) else colorScheme.surfaceContainerHighest,
alpha = 0.65f
),
shape = RoundedCornerShape(4.dp) shape = RoundedCornerShape(4.dp)
) )
.height(32.dp).widthIn(max = 72.dp) .height(32.dp).widthIn(max = 72.dp)
@@ -174,8 +174,7 @@ fun ComicScreen(
maxLines = 1, maxLines = 1,
modifier = Modifier modifier = Modifier
.padding(2.dp) .padding(2.dp)
.align(Alignment.Center), .align(Alignment.Center)
color = Color.Black
) )
} }
} }

View File

@@ -26,12 +26,13 @@ import androidx.hilt.navigation.compose.hiltViewModel
import androidx.lifecycle.viewmodel.compose.viewModel import androidx.lifecycle.viewmodel.compose.viewModel
import androidx.navigation.NavController import androidx.navigation.NavController
import com.acitelight.aether.Global import com.acitelight.aether.Global
import com.acitelight.aether.Global.updateRelate
import com.acitelight.aether.service.MediaManager import com.acitelight.aether.service.MediaManager
import com.acitelight.aether.service.RecentManager import com.acitelight.aether.service.RecentManager
import com.acitelight.aether.viewModel.HomeScreenViewModel import com.acitelight.aether.viewModel.HomeScreenViewModel
@Composable @Composable
fun HomeScreen(homeScreenViewModel: HomeScreenViewModel = hiltViewModel(), navController: NavController) fun HomeScreen(homeScreenViewModel: HomeScreenViewModel = androidx.hilt.lifecycle.viewmodel.compose.hiltViewModel<HomeScreenViewModel>(), navController: NavController)
{ {
if(Global.loggedIn) if(Global.loggedIn)
homeScreenViewModel.Init() homeScreenViewModel.Init()
@@ -42,9 +43,9 @@ fun HomeScreen(homeScreenViewModel: HomeScreenViewModel = hiltViewModel(), navCo
{ {
Column { Column {
Text( Text(
text = "Recent", text = "Videos",
style = MaterialTheme.typography.headlineMedium, style = MaterialTheme.typography.headlineMedium,
modifier = Modifier.padding(16.dp).align(Alignment.Start) modifier = Modifier.padding(8.dp).align(Alignment.Start)
) )
HorizontalDivider(Modifier.padding(8.dp), 2.dp, DividerDefaults.color) HorizontalDivider(Modifier.padding(8.dp), 2.dp, DividerDefaults.color)
@@ -56,7 +57,7 @@ fun HomeScreen(homeScreenViewModel: HomeScreenViewModel = hiltViewModel(), navCo
.padding(horizontal = 12.dp), .padding(horizontal = 12.dp),
i, i,
{ {
Global.sameClassVideos = RecentManager.recent updateRelate(RecentManager.recent, i)
val route = "video_player_route/${ "${i.klass}/${i.id}".toHex() }" val route = "video_player_route/${ "${i.klass}/${i.id}".toHex() }"
navController.navigate(route) navController.navigate(route)
}, homeScreenViewModel.imageLoader!!) }, homeScreenViewModel.imageLoader!!)

View File

@@ -46,7 +46,7 @@ import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext import kotlinx.coroutines.withContext
@Composable @Composable
fun MeScreen(meScreenViewModel: MeScreenViewModel = hiltViewModel()) { fun MeScreen(meScreenViewModel: MeScreenViewModel = androidx.hilt.lifecycle.viewmodel.compose.hiltViewModel<MeScreenViewModel>()) {
val context = LocalContext.current val context = LocalContext.current
var username by meScreenViewModel.username; var username by meScreenViewModel.username;
var privateKey by meScreenViewModel.privateKey; var privateKey by meScreenViewModel.privateKey;
@@ -200,7 +200,7 @@ fun MeScreen(meScreenViewModel: MeScreenViewModel = hiltViewModel()) {
onClick = { onClick = {
meScreenViewModel.updateServer(url, cert, context) meScreenViewModel.updateServer(url, cert, context)
}, },
modifier = Modifier.fillMaxWidth(0.5f) modifier = Modifier.weight(0.5f)
) { ) {
Text("Save") Text("Save")
} }
@@ -213,7 +213,7 @@ fun MeScreen(meScreenViewModel: MeScreenViewModel = hiltViewModel()) {
Log.i("Delay Analyze", "Abyss Hello: ${h.string()}") Log.i("Delay Analyze", "Abyss Hello: ${h.string()}")
} }
}, },
modifier = Modifier.fillMaxWidth(0.5f) modifier = Modifier.weight(0.5f)
) { ) {
Text("Ping") Text("Ping")
} }

View File

@@ -0,0 +1,150 @@
package com.acitelight.aether.view
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.items
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.Pause
import androidx.compose.material.icons.filled.Stop
import androidx.compose.material.icons.*
import androidx.compose.material.icons.filled.Delete
import androidx.compose.material.icons.filled.PlayArrow
import androidx.compose.material3.Button
import androidx.compose.material3.Card
import androidx.compose.material3.CardDefaults
import androidx.compose.material3.CardElevation
import androidx.compose.material3.DividerDefaults
import androidx.compose.material3.HorizontalDivider
import androidx.compose.material3.Icon
import androidx.compose.material3.LinearProgressIndicator
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.ProgressIndicatorDefaults
import androidx.compose.material3.Surface
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.mutableStateListOf
import androidx.compose.runtime.snapshots.SnapshotStateList
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.dp
import androidx.hilt.lifecycle.viewmodel.compose.hiltViewModel
import com.acitelight.aether.model.DownloadItemState
import com.acitelight.aether.viewModel.TransmissionScreenViewModel
import com.tonyodev.fetch2.Download
import com.tonyodev.fetch2.FetchListener
import com.tonyodev.fetch2.Status
import com.tonyodev.fetch2core.DownloadBlock
@Composable
fun TransmissionScreen(transmissionScreenViewModel: TransmissionScreenViewModel = hiltViewModel<TransmissionScreenViewModel>())
{
val downloads = transmissionScreenViewModel.downloads
Surface(modifier = Modifier.fillMaxWidth()) {
LazyColumn(modifier = Modifier.fillMaxWidth(), verticalArrangement = Arrangement.spacedBy(8.dp)) {
items(downloads, key = { it.id }) { item ->
DownloadCard(
model = item,
onPause = { transmissionScreenViewModel.pause(item.id) },
onResume = { transmissionScreenViewModel.resume(item.id) },
onCancel = { transmissionScreenViewModel.cancel(item.id) },
onDelete = { transmissionScreenViewModel.delete(item.id, true) }
)
}
}
}
}
@Composable
private fun DownloadCard(
model: DownloadItemState,
onPause: () -> Unit,
onResume: () -> Unit,
onCancel: () -> Unit,
onDelete: () -> Unit
) {
Card(
elevation = CardDefaults.cardElevation(4.dp),
modifier = Modifier
.fillMaxWidth()
.padding(8.dp)
) {
Column(modifier = Modifier
.fillMaxWidth()
.padding(12.dp)
) {
Row(verticalAlignment = Alignment.CenterVertically, modifier = Modifier.fillMaxWidth()) {
Column(modifier = Modifier.weight(1f)) {
Text(text = model.fileName, style = MaterialTheme.typography.titleMedium)
Text(text = model.url, style = MaterialTheme.typography.displayMedium, modifier = Modifier.padding(top = 4.dp))
}
// progress percentage
Text(text = "${model.progress}%", modifier = Modifier.padding(start = 8.dp))
}
// progress bar
LinearProgressIndicator(
progress = { model.progress.coerceIn(0, 100) / 100f },
modifier = Modifier
.fillMaxWidth()
.padding(top = 8.dp, bottom = 8.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) {
Status.DOWNLOADING -> {
Button(onClick = onPause) {
Icon(imageVector = Icons.Default.Pause, contentDescription = "Pause")
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))
}
Button(onClick = onCancel) {
Icon(imageVector = Icons.Default.Stop, contentDescription = "Cancel")
Text(text = " Cancel", modifier = Modifier.padding(start = 6.dp))
}
}
Status.COMPLETED -> {
Button(onClick = onDelete) {
Icon(imageVector = Icons.Default.Delete, contentDescription = "Delete")
Text(text = " Delete", modifier = Modifier.padding(start = 6.dp))
}
}
else -> {
// for FAILED, CANCELLED, REMOVED etc.
Button(onClick = onResume) {
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))
}
}
}
}
}
}
}

View File

@@ -625,7 +625,7 @@ fun VideoPlayerPortal(videoPlayerViewModel: VideoPlayerViewModel, navController:
{ {
TabRow ( TabRow (
selectedTabIndex = videoPlayerViewModel.tabIndex, selectedTabIndex = videoPlayerViewModel.tabIndex,
modifier = Modifier.height(38.dp).fillMaxWidth(0.6f) modifier = Modifier.height(38.dp)
) { ) {
Tab( Tab(
selected = videoPlayerViewModel.tabIndex == 0, selected = videoPlayerViewModel.tabIndex == 0,
@@ -645,8 +645,6 @@ fun VideoPlayerPortal(videoPlayerViewModel: VideoPlayerViewModel, navController:
LazyColumn(state = listState, modifier = Modifier.fillMaxWidth()) { LazyColumn(state = listState, modifier = Modifier.fillMaxWidth()) {
item{ item{
HorizontalDivider(Modifier, 2.dp, DividerDefaults.color)
Text( Text(
modifier = Modifier.align(Alignment.Start).padding(horizontal = 12.dp).padding(top = 12.dp), modifier = Modifier.align(Alignment.Start).padding(horizontal = 12.dp).padding(top = 12.dp),
text = videoPlayerViewModel.video?.video?.name ?: "", text = videoPlayerViewModel.video?.video?.name ?: "",

View File

@@ -19,6 +19,7 @@ import androidx.compose.foundation.lazy.grid.items
import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material3.Card import androidx.compose.material3.Card
import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Tab 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
@@ -43,6 +44,7 @@ import androidx.compose.ui.platform.LocalContext
import androidx.navigation.NavHostController import androidx.navigation.NavHostController
import coil3.request.ImageRequest import coil3.request.ImageRequest
import com.acitelight.aether.Global import com.acitelight.aether.Global
import com.acitelight.aether.Global.updateRelate
import java.nio.charset.Charset import java.nio.charset.Charset
fun String.toHex(): String { fun String.toHex(): String {
@@ -93,10 +95,10 @@ fun VideoScreen(videoScreenViewModel: VideoScreenViewModel = viewModel(), navCon
fun TopRow(videoScreenViewModel: VideoScreenViewModel) fun TopRow(videoScreenViewModel: VideoScreenViewModel)
{ {
val tabIndex by videoScreenViewModel.tabIndex; val tabIndex by videoScreenViewModel.tabIndex;
if(videoScreenViewModel.classes.isEmpty()) return if(videoScreenViewModel.classes.isEmpty()) return
val colorScheme = MaterialTheme.colorScheme
ScrollableTabRow (selectedTabIndex = tabIndex) { ScrollableTabRow (selectedTabIndex = tabIndex, modifier = Modifier.background(colorScheme.surface)) {
videoScreenViewModel.classes.forEachIndexed { index, title -> videoScreenViewModel.classes.forEachIndexed { index, title ->
Tab( Tab(
selected = tabIndex == index, selected = tabIndex == index,
@@ -116,7 +118,7 @@ fun VideoCard(video: Video, navController: NavHostController, videoScreenViewMod
.fillMaxWidth() .fillMaxWidth()
.wrapContentHeight(), .wrapContentHeight(),
onClick = { onClick = {
Global.sameClassVideos = videoScreenViewModel.classesMap[videoScreenViewModel.classes[tabIndex]] ?: mutableStateListOf() updateRelate(videoScreenViewModel.classesMap[videoScreenViewModel.classes[tabIndex]] ?: mutableStateListOf(), video)
val route = "video_player_route/${ "${video.klass}/${video.id}".toHex() }" val route = "video_player_route/${ "${video.klass}/${video.id}".toHex() }"
navController.navigate(route) navController.navigate(route)
} }
@@ -168,7 +170,7 @@ fun VideoCard(video: Video, navController: NavHostController, videoScreenViewMod
horizontalArrangement = Arrangement.SpaceBetween, horizontalArrangement = Arrangement.SpaceBetween,
) { ) {
Text("Class", fontSize = 12.sp) Text("Class", fontSize = 12.sp)
Text("${video.klass}", fontSize = 12.sp) Text(video.klass, fontSize = 12.sp)
} }
} }
} }

View File

@@ -40,13 +40,11 @@ import dagger.hilt.android.qualifiers.ApplicationContext
@HiltViewModel @HiltViewModel
class HomeScreenViewModel @Inject constructor( class HomeScreenViewModel @Inject constructor(
private val settingsDataStoreManager: SettingsDataStoreManager,
@ApplicationContext private val context: Context
) : ViewModel() ) : ViewModel()
{ {
var _init = false var _init = false
var imageLoader: ImageLoader? = null; var imageLoader: ImageLoader? = null;
val uss = settingsDataStoreManager.useSelfSignedFlow
@Composable @Composable
fun Init(){ fun Init(){
@@ -65,31 +63,4 @@ class HomeScreenViewModel @Inject constructor(
} }
} }
} }
init {
viewModelScope.launch {
val u = settingsDataStoreManager.userNameFlow.first()
val p = settingsDataStoreManager.privateKeyFlow.first()
val ur = settingsDataStoreManager.urlFlow.first()
val c = settingsDataStoreManager.certFlow.first()
if(u=="" || p=="" || ur=="") return@launch
try{
val usedUrl = ApiClient.apply(context, ur, if(uss.first()) c else "")
if (MediaManager.token == "null")
MediaManager.token = AuthManager.fetchToken(
u,
p
)!!
Global.loggedIn = true
}catch(e: Exception)
{
Global.loggedIn = false
print(e.message)
}
}
}
} }

View File

@@ -45,6 +45,24 @@ class MeScreenViewModel @Inject constructor(
privateKey.value = if (settingsDataStoreManager.privateKeyFlow.first() == "") "" else "******" privateKey.value = if (settingsDataStoreManager.privateKeyFlow.first() == "") "" else "******"
url.value = settingsDataStoreManager.urlFlow.first() url.value = settingsDataStoreManager.urlFlow.first()
cert.value = settingsDataStoreManager.certFlow.first() cert.value = settingsDataStoreManager.certFlow.first()
if(username.value=="" || privateKey.value=="" || url.value=="") return@launch
try{
val usedUrl = ApiClient.apply(context, url.value, if(uss.first()) cert.value else "")
if (MediaManager.token == "null")
MediaManager.token = AuthManager.fetchToken(
username.value,
settingsDataStoreManager.privateKeyFlow.first()
)!!
Global.loggedIn = true
}catch(e: Exception)
{
Global.loggedIn = false
print(e.message)
}
} }
} }

View File

@@ -0,0 +1,144 @@
package com.acitelight.aether.viewModel
import androidx.compose.runtime.mutableStateListOf
import androidx.compose.runtime.snapshots.SnapshotStateList
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import com.acitelight.aether.model.DownloadItemState
import com.acitelight.aether.service.FetchManager
import com.tonyodev.fetch2.Download
import com.tonyodev.fetch2.FetchListener
import com.tonyodev.fetch2core.DownloadBlock
import dagger.hilt.android.lifecycle.HiltViewModel
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.launch
import java.io.File
import javax.inject.Inject
@HiltViewModel
class TransmissionScreenViewModel @Inject constructor(
private val fetchManager: FetchManager
) : ViewModel() {
private val _downloads: SnapshotStateList<DownloadItemState> = mutableStateListOf()
val downloads: SnapshotStateList<DownloadItemState> = _downloads
// map id -> state object reference (no index bookkeeping)
private val idToState: MutableMap<Int, DownloadItemState> = mutableMapOf()
private val fetchListener = object : FetchListener {
override fun onAdded(download: Download) { handleUpsert(download) }
override fun onQueued(download: Download, waitingOnNetwork: Boolean) { handleUpsert(download) }
override fun onWaitingNetwork(download: Download) {
}
override fun onProgress(download: Download, etaInMilliSeconds: Long, downloadedBytesPerSecond: Long) { handleUpsert(download) }
override fun onPaused(download: Download) { handleUpsert(download) }
override fun onResumed(download: Download) { handleUpsert(download) }
override fun onCompleted(download: Download) { handleUpsert(download) }
override fun onCancelled(download: Download) { handleUpsert(download) }
override fun onRemoved(download: Download) { handleRemove(download.id) }
override fun onDeleted(download: Download) { handleRemove(download.id) }
override fun onDownloadBlockUpdated(download: Download, downloadBlock: DownloadBlock, blockCount: Int) { handleUpsert(download) }
override fun onStarted(
download: Download,
downloadBlocks: List<DownloadBlock>,
totalBlocks: Int
) {
handleUpsert(download)
}
override fun onError(download: Download, error: com.tonyodev.fetch2.Error, throwable: Throwable?) { handleUpsert(download) }
}
private fun handleUpsert(download: Download) {
viewModelScope.launch(Dispatchers.Main) {
upsertOnMain(download)
}
}
private fun handleRemove(id: Int) {
viewModelScope.launch(Dispatchers.Main) {
removeOnMain(id)
}
}
private fun upsertOnMain(download: Download) {
val existing = idToState[download.id]
if (existing != null) {
// update fields in-place -> minimal recomposition
existing.filePath = download.file ?: existing.filePath
existing.fileName = try { File(existing.filePath).name } catch (_: Exception) { existing.fileName }
existing.url = download.url
existing.progress = download.progress
existing.status = download.status
existing.downloadedBytes = download.downloaded
existing.totalBytes = download.total
} else {
// new item: add to head (or tail depending on preference)
val newState = downloadToState(download)
_downloads.add(0, newState)
idToState[newState.id] = newState
}
}
private fun removeOnMain(id: Int) {
val state = idToState.remove(id)
if (state != null) {
_downloads.remove(state)
} else {
val idx = _downloads.indexOfFirst { it.id == id }
if (idx >= 0) {
val removed = _downloads.removeAt(idx)
idToState.remove(removed.id)
}
}
}
private fun downloadToState(download: Download): DownloadItemState {
val filePath = download.file ?: ""
val fileName = try { File(filePath).name } catch (_: Exception) { filePath }
return DownloadItemState(
id = download.id,
fileName = fileName,
filePath = filePath,
url = download.url,
progress = download.progress,
status = download.status,
downloadedBytes = download.downloaded,
totalBytes = download.total
)
}
// UI actions delegated to FetchManager
fun pause(id: Int) = fetchManager.pause(id)
fun resume(id: Int) = fetchManager.resume(id)
fun cancel(id: Int) = fetchManager.cancel(id)
fun delete(id: Int, deleteFile: Boolean = true) {
fetchManager.delete(id) {
viewModelScope.launch(Dispatchers.Main) { removeOnMain(id) }
}
}
override fun onCleared() {
super.onCleared()
fetchManager.removeListener()
}
init {
fetchManager.setListener(fetchListener)
viewModelScope.launch(Dispatchers.Main) {
fetchManager.getAllDownloads { list ->
_downloads.clear()
idToState.clear()
list.sortedByDescending { it.id }.forEach { d ->
val s = downloadToState(d)
_downloads.add(s)
idToState[s.id] = s
}
}
}
}
}

View File

@@ -1,5 +1,6 @@
[versions] [versions]
agp = "8.13.0" agp = "8.13.0"
ariaCompiler = "latest"
bcprovJdk15on = "1.70" bcprovJdk15on = "1.70"
bcprovJdk18on = "1.81" bcprovJdk18on = "1.81"
coilCompose = "3.3.0" coilCompose = "3.3.0"
@@ -7,6 +8,8 @@ coilNetworkOkhttp = "3.3.0"
converterGson = "3.0.0" converterGson = "3.0.0"
datastorePreferences = "1.1.7" datastorePreferences = "1.1.7"
exoplayerplus = "0.2.0" exoplayerplus = "0.2.0"
fetch2 = "3.4.1"
fetch2okhttp = "3.4.1"
gson = "2.13.1" gson = "2.13.1"
kotlin = "2.2.20" kotlin = "2.2.20"
coreKtx = "1.17.0" coreKtx = "1.17.0"
@@ -15,23 +18,23 @@ junitVersion = "1.3.0"
espressoCore = "3.7.0" espressoCore = "3.7.0"
kotlinxSerializationJson = "1.9.0" kotlinxSerializationJson = "1.9.0"
lifecycleRuntimeKtx = "2.9.3" lifecycleRuntimeKtx = "2.9.3"
activityCompose = "1.10.1" activityCompose = "1.11.0"
composeBom = "2025.08.01" composeBom = "2025.09.00"
media3Common = "1.8.0" media3Common = "1.8.0"
media3Exoplayer = "1.8.0" media3Exoplayer = "1.8.0"
media3Ui = "1.8.0" media3Ui = "1.8.0"
navigationCompose = "2.9.3" navigationCompose = "2.9.4"
okhttp = "5.1.0" okhttp = "5.1.0"
retrofit = "3.0.0" retrofit = "3.0.0"
retrofit2KotlinxSerializationConverter = "1.0.0" retrofit2KotlinxSerializationConverter = "1.0.0"
media3DatasourceOkhttp = "1.8.0" media3DatasourceOkhttp = "1.8.0"
roomCompiler = "2.7.2" roomCompiler = "2.8.0"
roomKtx = "2.7.2" roomKtx = "2.8.0"
roomRuntime = "2.7.2" roomRuntime = "2.8.0"
ksp = "2.1.21-2.0.2" ksp = "2.1.21-2.0.2"
hilt = "2.57.1" hilt = "2.57.1"
hilt-navigation-compose = "1.2.0" hilt-navigation-compose = "1.3.0"
[libraries] [libraries]
androidx-core-ktx = { group = "androidx.core", name = "core-ktx", version.ref = "coreKtx" } androidx-core-ktx = { group = "androidx.core", name = "core-ktx", version.ref = "coreKtx" }
@@ -44,10 +47,13 @@ androidx-navigation-compose = { module = "androidx.navigation:navigation-compose
androidx-room-compiler = { module = "androidx.room:room-compiler", version.ref = "roomCompiler" } androidx-room-compiler = { module = "androidx.room:room-compiler", version.ref = "roomCompiler" }
androidx-room-ktx = { module = "androidx.room:room-ktx", version.ref = "roomKtx" } androidx-room-ktx = { module = "androidx.room:room-ktx", version.ref = "roomKtx" }
androidx-room-runtime = { module = "androidx.room:room-runtime", version.ref = "roomRuntime" } androidx-room-runtime = { module = "androidx.room:room-runtime", version.ref = "roomRuntime" }
aria-compiler = { module = "com.arialyy.aria:aria-compiler", version.ref = "ariaCompiler" }
bcprov-jdk15on = { module = "org.bouncycastle:bcprov-jdk15on", version.ref = "bcprovJdk15on" } bcprov-jdk15on = { module = "org.bouncycastle:bcprov-jdk15on", version.ref = "bcprovJdk15on" }
coil-compose = { module = "io.coil-kt.coil3:coil-compose", version.ref = "coilCompose" } coil-compose = { module = "io.coil-kt.coil3:coil-compose", version.ref = "coilCompose" }
coil-network-okhttp = { module = "io.coil-kt.coil3:coil-network-okhttp", version.ref = "coilNetworkOkhttp" } coil-network-okhttp = { module = "io.coil-kt.coil3:coil-network-okhttp", version.ref = "coilNetworkOkhttp" }
converter-gson = { module = "com.squareup.retrofit2:converter-gson", version.ref = "converterGson" } converter-gson = { module = "com.squareup.retrofit2:converter-gson", version.ref = "converterGson" }
fetch2 = { module = "com.github.tonyofrancis.Fetch:fetch2", version.ref = "fetch2" }
fetch2okhttp = { module = "com.github.tonyofrancis.Fetch:fetch2okhttp", version.ref = "fetch2okhttp" }
gson = { module = "com.google.code.gson:gson", version.ref = "gson" } gson = { module = "com.google.code.gson:gson", version.ref = "gson" }
junit = { group = "junit", name = "junit", version.ref = "junit" } junit = { group = "junit", name = "junit", version.ref = "junit" }
androidx-junit = { group = "androidx.test.ext", name = "junit", version.ref = "junitVersion" } androidx-junit = { group = "androidx.test.ext", name = "junit", version.ref = "junitVersion" }

View File

@@ -16,6 +16,9 @@ dependencyResolutionManagement {
repositories { repositories {
google() google()
mavenCentral() mavenCentral()
maven {
url = uri("https://jitpack.io")
}
} }
} }