[update] Better UI
This commit is contained in:
@@ -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)
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|||||||
@@ -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
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -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")
|
||||||
|
|||||||
@@ -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)
|
||||||
|
}
|
||||||
@@ -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
|
||||||
) {
|
) {
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|||||||
@@ -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) })
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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)
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -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
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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!!)
|
||||||
|
|||||||
@@ -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")
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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 ?: "",
|
||||||
|
|||||||
@@ -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)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
@@ -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)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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" }
|
||||||
|
|||||||
@@ -16,6 +16,9 @@ dependencyResolutionManagement {
|
|||||||
repositories {
|
repositories {
|
||||||
google()
|
google()
|
||||||
mavenCentral()
|
mavenCentral()
|
||||||
|
maven {
|
||||||
|
url = uri("https://jitpack.io")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user