[optimize] Refactoring API client injection architecture

This commit is contained in:
acite
2025-09-28 14:31:03 +08:00
parent 88392444a4
commit 393419afd7
27 changed files with 231 additions and 272 deletions

View File

@@ -8,17 +8,17 @@ class Comic(
val token: String
)
{
fun getPage(pageNumber: Int): String
fun getPage(pageNumber: Int, api: ApiClient): String
{
return "${ApiClient.getBase()}api/image/$id/${comic.list[pageNumber]}?token=$token"
return "${api.getBase()}api/image/$id/${comic.list[pageNumber]}?token=$token"
}
fun getPage(pageName: String): String?
fun getPage(pageName: String, api: ApiClient): String?
{
val v = comic.list.indexOf(pageName)
if(v >= 0)
{
return getPage(v)
return getPage(v, api)
}
return null
}
@@ -33,7 +33,7 @@ class Comic(
var v = comic.list.indexOf(pageName)
if(v >= 0)
{
var r: Int = 1
var r = 1
v+=1
while(v < comic.list.size && !comic.bookmarks.any{
x -> x.page == comic.list[v]

View File

@@ -14,28 +14,28 @@ class Video(
val token: String,
val video: VideoResponse
) {
fun getCover(): String {
fun getCover(api: ApiClient): String {
return if (isLocal)
"$localBase/videos/$klass/$id/cover.jpg"
else
"${ApiClient.getBase()}api/video/$klass/$id/cover?token=$token"
"${api.getBase()}api/video/$klass/$id/cover?token=$token"
}
fun getVideo(): String {
fun getVideo(api: ApiClient): String {
return if (isLocal)
"$localBase/videos/$klass/$id/video.mp4"
else
"${ApiClient.getBase()}api/video/$klass/$id/av?token=$token"
"${api.getBase()}api/video/$klass/$id/av?token=$token"
}
fun getSubtitle(): String {
fun getSubtitle(api: ApiClient): String {
return if (isLocal)
"$localBase/videos/$klass/$id/subtitle.vtt"
else
"${ApiClient.getBase()}api/video/$klass/$id/subtitle?token=$token"
"${api.getBase()}api/video/$klass/$id/subtitle?token=$token"
}
fun getGallery(): List<KeyImage> {
fun getGallery(api: ApiClient): List<KeyImage> {
return if (isLocal)
video.gallery.map {
KeyImage(
@@ -46,7 +46,7 @@ class Video(
} else video.gallery.map {
KeyImage(
name = it,
url = "${ApiClient.getBase()}api/video/$klass/$id/gallery/$it?token=$token",
url = "${api.getBase()}api/video/$klass/$id/gallery/$it?token=$token",
key = "$klass/$id/gallery/$it"
)
}

View File

@@ -1,8 +1,5 @@
package com.acitelight.aether.service
import com.acitelight.aether.service.AuthManager.db64
import com.acitelight.aether.service.AuthManager.signChallenge
import com.acitelight.aether.service.AuthManager.signChallengeByte
import kotlinx.coroutines.*
import java.io.InputStream
import java.io.OutputStream
@@ -44,7 +41,7 @@ class AbyssStream private constructor(
* Create and perform handshake on an already-connected socket.
* If privateKeyRaw is provided, it must be 32 bytes.
*/
suspend fun create(socket: Socket, privateKeyRaw: ByteArray? = null): AbyssStream = withContext(Dispatchers.IO) {
suspend fun create(authManager: AuthManager, socket: Socket, privateKeyRaw: ByteArray? = null): AbyssStream = withContext(Dispatchers.IO) {
if (!socket.isConnected) throw IllegalArgumentException("socket is not connected")
val inStream = socket.getInputStream()
val outStream = socket.getOutputStream()
@@ -69,7 +66,7 @@ class AbyssStream private constructor(
val ch = ByteArray(32)
readExact(inStream, ch, 0, 32)
val signed = signChallengeByte(localPriv, ch)
val signed = authManager.signChallengeByte(localPriv, ch)
writeExact(outStream, signed, 0, signed.size)
readExact(inStream, ch, 0, 16)
@@ -222,7 +219,7 @@ class AbyssStream private constructor(
val header = ByteArray(4)
try {
readExact(input, header, 0, 4)
} catch (e: EOFException) {
} catch (_: EOFException) {
return null
}
val payloadLen = ByteBuffer.wrap(header).int and 0xffffffff.toInt()

View File

@@ -1,8 +1,5 @@
package com.acitelight.aether.service
import android.util.Log
import com.acitelight.aether.service.AuthManager.db64
import kotlinx.coroutines.*
import kotlinx.coroutines.flow.first
import kotlinx.coroutines.selects.select
@@ -17,7 +14,8 @@ import kotlin.coroutines.CoroutineContext
@Singleton
class AbyssTunnelProxy @Inject constructor(
private val settingsDataStoreManager: SettingsDataStoreManager
private val settingsDataStoreManager: SettingsDataStoreManager,
private val authManager: AuthManager
) {
private val coroutineContext: CoroutineContext = Dispatchers.IO
private var serverHost: String = ""
@@ -48,7 +46,7 @@ class AbyssTunnelProxy @Inject constructor(
launch {
try { handleLocalConnection(client) }
catch (ex: Exception) { /* ignore */ }
catch (_: Exception) { /* ignore */ }
}
}
} catch (ex: Exception) {
@@ -72,14 +70,14 @@ class AbyssTunnelProxy @Inject constructor(
var abyssStream: AbyssStream? = null
try {
abyssSocket = Socket(serverHost, serverPort)
abyssStream = AbyssStream.create(abyssSocket, db64(settingsDataStoreManager.privateKeyFlow.first()))
abyssStream = AbyssStream.create(authManager, abyssSocket, authManager.db64(settingsDataStoreManager.privateKeyFlow.first()))
// concurrently copy in both directions
val job1 = launch { copyExactSuspend(localIn, abyssStream) } // local -> abyss
val job2 = launch { copyFromAbyssToLocal(abyssStream, localOut) } // abyss -> local
// wait for either direction to finish
select<Unit> {
select {
job1.onJoin { /* completed */ }
job2.onJoin { /* completed */ }
}

View File

@@ -10,7 +10,10 @@ import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
import kotlinx.serialization.json.Json
import okhttp3.ConnectionSpec
import okhttp3.Cookie
import okhttp3.CookieJar
import okhttp3.EventListener
import okhttp3.HttpUrl
import okhttp3.HttpUrl.Companion.toHttpUrlOrNull
import okhttp3.MediaType.Companion.toMediaType
import okhttp3.OkHttpClient
@@ -24,23 +27,27 @@ import java.security.KeyStore
import java.security.cert.CertificateException
import java.security.cert.CertificateFactory
import java.security.cert.X509Certificate
import javax.inject.Inject
import javax.inject.Singleton
import javax.net.ssl.SSLContext
import javax.net.ssl.TrustManagerFactory
import javax.net.ssl.X509TrustManager
@Singleton
class ApiClient @Inject constructor(
object ApiClient {
) {
fun getBase(): String{
return replaceAbyssProtocol(base)
}
private var base: String = ""
var domain: String = ""
var cert: String = ""
private var domain: String = ""
private var cert: String = ""
private val json = Json {
ignoreUnknownKeys = true
}
fun replaceAbyssProtocol(uri: String): String {
private fun replaceAbyssProtocol(uri: String): String {
return uri.replaceFirst("^abyss://".toRegex(), "https://")
}
@@ -52,7 +59,7 @@ object ApiClient {
}
}
fun loadCertificateFromString(pemString: String): X509Certificate {
private fun loadCertificateFromString(pemString: String): X509Certificate {
val certificateFactory = CertificateFactory.getInstance("X.509")
val decodedPem = pemString
.replace("-----BEGIN CERTIFICATE-----", "")
@@ -66,7 +73,7 @@ object ApiClient {
}
}
fun createOkHttpClientWithDynamicCert(trustedCert: X509Certificate?): OkHttpClient {
private fun createOkHttpClientWithDynamicCert(trustedCert: X509Certificate?): OkHttpClient {
try {
val defaultTmFactory = TrustManagerFactory.getInstance(
TrustManagerFactory.getDefaultAlgorithm()
@@ -86,7 +93,7 @@ object ApiClient {
).apply {
init(keyStore)
}
tmf.trustManagers.first { it is X509TrustManager } as X509TrustManager
tmf.trustManagers.first { i -> i is X509TrustManager } as X509TrustManager
}
val combinedTm = object : X509TrustManager {
@@ -157,11 +164,22 @@ object ApiClient {
}
}
fun createOkHttp(): OkHttpClient {
private fun createOkHttp(): OkHttpClient {
return if (cert == "")
if (base.startsWith("abyss://"))
OkHttpClient
.Builder()
.cookieJar(object : CookieJar {
private val cookieStore = mutableMapOf<HttpUrl, List<Cookie>>()
override fun saveFromResponse(url: HttpUrl, cookies: List<Cookie>) {
cookieStore[url] = cookies
}
override fun loadForRequest(url: HttpUrl): List<Cookie> {
return cookieStore[url] ?: emptyList()
}
})
.proxy(
Proxy(
Proxy.Type.HTTP,
@@ -174,6 +192,17 @@ object ApiClient {
else
OkHttpClient
.Builder()
.cookieJar(object : CookieJar {
private val cookieStore = mutableMapOf<HttpUrl, List<Cookie>>()
override fun saveFromResponse(url: HttpUrl, cookies: List<Cookie>) {
cookieStore[url] = cookies
}
override fun loadForRequest(url: HttpUrl): List<Cookie> {
return cookieStore[url] ?: emptyList()
}
})
.connectionSpecs(listOf(ConnectionSpec.MODERN_TLS, ConnectionSpec.COMPATIBLE_TLS))
.eventListener(dnsEventListener)
.build()
@@ -183,20 +212,22 @@ object ApiClient {
}
private fun createRetrofit(): Retrofit {
val okHttpClient = createOkHttp()
client = createOkHttp()
val b = replaceAbyssProtocol(base)
return Retrofit.Builder()
.baseUrl(b)
.client(okHttpClient)
.client(client!!)
.addConverterFactory(GsonConverterFactory.create())
.addConverterFactory(json.asConverterFactory("application/json".toMediaType()))
.build()
}
private var client: OkHttpClient? = null
var api: ApiInterface? = null
fun getClient() = client!!
suspend fun apply(context: Context, urls: String, crt: String): String? {
try {
val urlList = urls.split(";").map { it.trim() }
@@ -219,7 +250,7 @@ object ApiClient {
base = selectedUrl
withContext(Dispatchers.IO)
{
(context as AetherApp).abyssService?.proxy?.config(ApiClient.getBase().toUri().host!!, 4096)
(context as AetherApp).abyssService?.proxy?.config(getBase().toUri().host!!, 4096)
}
api = createRetrofit().create(ApiInterface::class.java)
@@ -228,7 +259,7 @@ object ApiClient {
Log.i("Delay Analyze", "Abyss Hello: ${h.string()}")
return base
} catch (e: Exception) {
} catch (_: Exception) {
api = null
base = ""
domain = ""
@@ -241,7 +272,7 @@ object ApiClient {
return@withContext try {
val address = InetAddress.getByName(host)
address.isReachable(200)
} catch (e: Exception) {
} catch (_: Exception) {
false
}
}

View File

@@ -18,10 +18,15 @@ import java.lang.reflect.Proxy
import java.net.InetSocketAddress
import java.security.PrivateKey
import java.security.Signature
import javax.inject.Inject
import javax.inject.Singleton
object AuthManager {
@Singleton
class AuthManager @Inject constructor(
private val apiClient: ApiClient
) {
suspend fun fetchToken(username: String, privateKey: String): String? {
val api = ApiClient.api
val api = apiClient.api
var challengeBase64 = ""
try{

View File

@@ -1,48 +1,37 @@
package com.acitelight.aether.service
import android.content.Context
import androidx.compose.runtime.mutableStateOf
import com.acitelight.aether.Screen
import com.acitelight.aether.model.Video
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.fetch2core.Extras
import com.tonyodev.fetch2okhttp.OkHttpDownloader
import dagger.hilt.android.qualifiers.ApplicationContext
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.flow.filter
import kotlinx.coroutines.flow.first
import kotlinx.coroutines.flow.update
import kotlinx.coroutines.withContext
import kotlinx.serialization.json.Json
import okhttp3.OkHttpClient
import java.io.File
import java.io.IOException
import javax.inject.Inject
import javax.inject.Singleton
@Singleton
class FetchManager @Inject constructor(
@ApplicationContext private val context: Context
@ApplicationContext private val context: Context,
private val apiClient: ApiClient
) {
private var fetch: Fetch? = null
private var listener: FetchListener? = null
private var client: OkHttpClient? = null
val configured = MutableStateFlow(false)
fun init() {
client = createOkHttp()
val fetchConfiguration = FetchConfiguration.Builder(context)
.setDownloadConcurrentLimit(8)
.setHttpDownloader(OkHttpDownloader(client))
.setHttpDownloader(OkHttpDownloader(apiClient.getClient()))
.build()
fetch = Fetch.Impl.getInstance(fetchConfiguration)
@@ -65,12 +54,6 @@ class FetchManager @Inject constructor(
listener = null
}
// query downloads
suspend fun getAllDownloads(callback: (List<Download>) -> Unit) {
configured.filter { it }.first()
fetch?.getDownloads { list -> callback(list) } ?: callback(emptyList())
}
suspend fun getAllDownloadsAsync(): List<Download> {
configured.filter { it }.first()
val completed = MutableStateFlow(false)
@@ -140,7 +123,7 @@ class FetchManager @Inject constructor(
)
val requests = mutableListOf(
Request(video.getVideo(), videoPath.path).apply {
Request(video.getVideo(apiClient), videoPath.path).apply {
extras = Extras(
mapOf(
"name" to video.video.name,
@@ -150,7 +133,7 @@ class FetchManager @Inject constructor(
)
)
},
Request(video.getCover(), coverPath.path).apply {
Request(video.getCover(apiClient), coverPath.path).apply {
extras = Extras(
mapOf(
"name" to video.video.name,
@@ -160,7 +143,7 @@ class FetchManager @Inject constructor(
)
)
},
Request(video.getSubtitle(), subtitlePath.path).apply {
Request(video.getSubtitle(apiClient), subtitlePath.path).apply {
extras = Extras(
mapOf(
"name" to video.video.name,
@@ -171,7 +154,7 @@ class FetchManager @Inject constructor(
)
},
)
for (p in video.getGallery()) {
for (p in video.getGallery(apiClient)) {
requests.add(
Request(p.url, File(
context.getExternalFilesDir(null),

View File

@@ -16,7 +16,8 @@ import javax.inject.Singleton
@Singleton
class MediaManager @Inject constructor(
val fetchManager: FetchManager,
@ApplicationContext val context: Context
@ApplicationContext val context: Context,
private val apiClient: ApiClient
)
{
var token: String = "null"
@@ -25,7 +26,7 @@ class MediaManager @Inject constructor(
{
try
{
val j = ApiClient.api!!.getVideoClasses(token)
val j = apiClient.api!!.getVideoClasses(token)
return j.toList()
}catch(_: Exception)
{
@@ -37,7 +38,7 @@ class MediaManager @Inject constructor(
{
try
{
val j = ApiClient.api!!.queryVideoClasses(klass, token)
val j = apiClient.api!!.queryVideoClasses(klass, token)
return j.toList()
}catch(_: Exception)
{
@@ -57,7 +58,7 @@ class MediaManager @Inject constructor(
}
try {
val j = ApiClient.api!!.queryVideo(klass, id, token)
val j = apiClient.api!!.queryVideo(klass, id, token)
return Video(klass = klass, id = id, token=token, isLocal = false, localBase = "", video = j)
}catch (_: Exception)
{
@@ -83,7 +84,7 @@ class MediaManager @Inject constructor(
}
try {
val j = ApiClient.api!!.queryVideo(klass, id, token)
val j = apiClient.api!!.queryVideo(klass, id, token)
return Video(klass = klass, id = id, token=token, isLocal = false, localBase = "", video = j)
}catch (_: Exception)
{
@@ -133,7 +134,7 @@ class MediaManager @Inject constructor(
}
val remoteVideos = if (remoteIds.isNotEmpty()) {
val j = ApiClient.api!!.queryVideoBulk(klass, remoteIds, token)
val j = apiClient.api!!.queryVideoBulk(klass, remoteIds, token)
j.zip(remoteIds).map {
Video(
klass = klass,
@@ -157,7 +158,7 @@ class MediaManager @Inject constructor(
suspend fun listComics() : List<String>
{
try{
val j = ApiClient.api!!.getComics(token)
val j = apiClient.api!!.getComics(token)
return j
}catch (_: Exception)
{
@@ -168,7 +169,7 @@ class MediaManager @Inject constructor(
suspend fun queryComicInfoSingle(id: String) : Comic?
{
try{
val j = ApiClient.api!!.queryComicInfo(id, token)
val j = apiClient.api!!.queryComicInfo(id, token)
return Comic(id = id, comic = j, token = token)
}catch (_: Exception)
{
@@ -179,7 +180,7 @@ class MediaManager @Inject constructor(
suspend fun queryComicInfoBulk(id: List<String>) : List<Comic>?
{
try{
val j = ApiClient.api!!.queryComicInfoBulk(id, token)
val j = apiClient.api!!.queryComicInfoBulk(id, token)
return j.zip(id).map { Comic(id = it.second, comic = it.first, token = token) }
}catch (_: Exception)
{
@@ -190,7 +191,7 @@ class MediaManager @Inject constructor(
suspend fun postBookmark(id: String, bookMark: BookMark): Boolean
{
try{
ApiClient.api!!.postBookmark(id, token, bookMark)
apiClient.api!!.postBookmark(id, token, bookMark)
return true
}catch (_: Exception)
{

View File

@@ -32,7 +32,6 @@ import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import androidx.hilt.lifecycle.viewmodel.compose.hiltViewModel
import androidx.lifecycle.viewmodel.compose.viewModel
import androidx.navigation.NavHostController
import coil3.compose.AsyncImage
import coil3.request.ImageRequest
@@ -157,7 +156,6 @@ fun ComicGridView(
comicGridViewModel.updateProcess(comicId.hexToString())
{
if (record != null) {
val k = comic!!.getPageChapterIndex(record!!.position)
val route = "comic_page_route/${comic!!.id.toHex()}/${
record!!.position
}"
@@ -248,7 +246,7 @@ fun ChapterCard(
{
AsyncImage(
model = ImageRequest.Builder(LocalContext.current)
.data(comic.getPage(c.page))
.data(comic.getPage(c.page, comicGridViewModel.apiClient))
.memoryCacheKey("${comic.id}/${c.page}")
.diskCacheKey("${comic.id}/${c.page}")
.build(),
@@ -300,13 +298,13 @@ fun ChapterCard(
.padding(horizontal = 6.dp),
onClick = {
val route =
"comic_page_route/${"${comic.id}".toHex()}/${comic.getPageIndex(r)}"
"comic_page_route/${comic.id.toHex()}/${comic.getPageIndex(r)}"
navController.navigate(route)
}
) {
AsyncImage(
model = ImageRequest.Builder(LocalContext.current)
.data(comic.getPage(r))
.data(comic.getPage(r, comicGridViewModel.apiClient))
.memoryCacheKey("${comic.id}/${r}")
.diskCacheKey("${comic.id}/${r}")
.build(),

View File

@@ -89,7 +89,7 @@ fun ComicPageView(
) { page ->
AsyncImage(
model = ImageRequest.Builder(LocalContext.current)
.data(it.getPage(page))
.data(it.getPage(page, comicPageViewModel.apiClient))
.memoryCacheKey("${it.id}/${page}")
.diskCacheKey("${it.id}/${page}")
.build(),
@@ -252,7 +252,7 @@ fun ComicPageView(
{
AsyncImage(
model = ImageRequest.Builder(LocalContext.current)
.data(it.getPage(r))
.data(it.getPage(r, comicPageViewModel.apiClient))
.memoryCacheKey("${it.id}/${r}")
.diskCacheKey("${it.id}/${r}")
.build(),

View File

@@ -1,13 +1,11 @@
package com.acitelight.aether.view
import android.nfc.Tag
import androidx.compose.foundation.background
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
@@ -16,10 +14,6 @@ import androidx.compose.foundation.layout.heightIn
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.widthIn
import androidx.compose.foundation.layout.wrapContentHeight
import androidx.compose.foundation.lazy.grid.GridCells
import androidx.compose.foundation.lazy.grid.LazyVerticalGrid
import androidx.compose.foundation.lazy.grid.items
import androidx.compose.foundation.lazy.grid.rememberLazyGridState
import androidx.compose.foundation.lazy.staggeredgrid.LazyVerticalStaggeredGrid
import androidx.compose.foundation.lazy.staggeredgrid.StaggeredGridCells
import androidx.compose.foundation.lazy.staggeredgrid.items
@@ -28,42 +22,28 @@ import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.foundation.verticalScroll
import androidx.compose.material3.Card
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.HorizontalDivider
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Tab
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Brush
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.layout.ContentScale
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import androidx.lifecycle.viewmodel.compose.viewModel
import coil3.compose.AsyncImage
import com.acitelight.aether.model.Video
import com.acitelight.aether.viewModel.VideoScreenViewModel
import androidx.compose.material3.ScrollableTabRow
import androidx.compose.material3.TopAppBar
import androidx.compose.runtime.mutableStateListOf
import androidx.compose.runtime.mutableStateOf
import androidx.compose.ui.Alignment
import androidx.compose.ui.graphics.Brush
import androidx.compose.ui.layout.Layout
import androidx.compose.ui.layout.Placeable
import androidx.compose.ui.modifier.modifierLocalOf
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import androidx.hilt.lifecycle.viewmodel.compose.hiltViewModel
import androidx.navigation.NavHostController
import coil3.compose.AsyncImage
import coil3.request.ImageRequest
import com.acitelight.aether.Global
import com.acitelight.aether.model.Comic
import com.acitelight.aether.viewModel.ComicScreenViewModel
import java.nio.charset.Charset
@Composable
fun VariableGrid(
@@ -223,7 +203,7 @@ fun ComicCard(
.fillMaxWidth()
.wrapContentHeight(),
onClick = {
val route = "comic_grid_route/${"${comic.id}".toHex()}"
val route = "comic_grid_route/${comic.id.toHex()}"
navController.navigate(route)
}
) {
@@ -234,7 +214,7 @@ fun ComicCard(
Box(modifier = Modifier.fillMaxSize()) {
AsyncImage(
model = ImageRequest.Builder(LocalContext.current)
.data(comic.getPage(0))
.data(comic.getPage(0, comicScreenViewModel.apiClient))
.memoryCacheKey("${comic.id}/${0}")
.diskCacheKey("${comic.id}/${0}")
.build(),

View File

@@ -1,7 +1,6 @@
package com.acitelight.aether.view
import androidx.compose.foundation.background
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
@@ -37,35 +36,34 @@ import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import androidx.navigation.NavController
import androidx.navigation.NavHostController
import coil3.compose.AsyncImage
import coil3.request.ImageRequest
import com.acitelight.aether.Global.updateRelate
import com.acitelight.aether.model.Comic
import com.acitelight.aether.viewModel.ComicScreenViewModel
import com.acitelight.aether.viewModel.HomeScreenViewModel
import kotlinx.coroutines.launch
@Composable
fun HomeScreen(
homeScreenViewModel: HomeScreenViewModel = androidx.hilt.lifecycle.viewmodel.compose.hiltViewModel<HomeScreenViewModel>(),
navController: NavHostController)
{
navController: NavHostController
) {
val pagerState = rememberPagerState(initialPage = 0, pageCount = { 2 })
HorizontalPager(
state = pagerState,
modifier = Modifier.fillMaxSize().background(Color.Black)
){
p ->
if(p == 0)
{
modifier = Modifier
.fillMaxSize()
.background(Color.Black)
) { p ->
if (p == 0) {
Column(Modifier.fillMaxHeight()) {
Text(
text = "Videos",
style = MaterialTheme.typography.headlineMedium,
modifier = Modifier.padding(8.dp).align(Alignment.Start)
modifier = Modifier
.padding(8.dp)
.align(Alignment.Start)
)
HorizontalDivider(Modifier.padding(8.dp), 2.dp, DividerDefaults.color)
@@ -73,27 +71,38 @@ fun HomeScreen(
LazyColumn(modifier = Modifier.fillMaxWidth())
{
items(homeScreenViewModel.recentManager.recentVideo)
{
i ->
{ i ->
MiniVideoCard(
modifier = Modifier
.padding(horizontal = 12.dp),
i,
{
updateRelate(homeScreenViewModel.recentManager.recentVideo, i)
apiClient = homeScreenViewModel.apiClient,
imageLoader = homeScreenViewModel.imageLoader!!
)
{
updateRelate(homeScreenViewModel.recentManager.recentVideo, i)
val playList = mutableListOf<String>()
val fv = homeScreenViewModel.videoLibrary.classesMap.map { it.value }.flatten()
val playList = mutableListOf<String>()
val fv = homeScreenViewModel.videoLibrary.classesMap.map { it.value }
.flatten()
val group = fv.filter { it.klass == i.klass && it.video.group == i.video.group }
for (i in group) {
playList.add("${i.klass}/${i.id}")
}
val group =
fv.filter { it.klass == i.klass && it.video.group == i.video.group }
for (i in group) {
playList.add("${i.klass}/${i.id}")
}
val route = "video_player_route/${(playList.joinToString(",") + "|${i.id}").toHex()}"
navController.navigate(route)
}, homeScreenViewModel.imageLoader!!)
HorizontalDivider(Modifier.padding(vertical = 8.dp).alpha(0.4f), 1.dp, DividerDefaults.color)
val route =
"video_player_route/${(playList.joinToString(",") + "|${i.id}").toHex()}"
navController.navigate(route)
}
HorizontalDivider(
Modifier
.padding(vertical = 8.dp)
.alpha(0.4f),
1.dp,
DividerDefaults.color
)
}
}
}
@@ -102,7 +111,9 @@ fun HomeScreen(
Text(
text = "Comics",
style = MaterialTheme.typography.headlineMedium,
modifier = Modifier.padding(8.dp).align(Alignment.Start)
modifier = Modifier
.padding(8.dp)
.align(Alignment.Start)
)
HorizontalDivider(Modifier.padding(8.dp), 2.dp, DividerDefaults.color)
@@ -115,8 +126,7 @@ fun HomeScreen(
)
{
items(homeScreenViewModel.recentManager.recentComic)
{
comic ->
{ comic ->
ComicCardRecent(comic, navController, homeScreenViewModel)
}
}
@@ -138,7 +148,7 @@ fun ComicCardRecent(
.fillMaxWidth()
.wrapContentHeight(),
onClick = {
val route = "comic_grid_route/${"${comic.id}".toHex()}"
val route = "comic_grid_route/${comic.id.toHex()}"
navController.navigate(route)
}
) {
@@ -149,7 +159,7 @@ fun ComicCardRecent(
Box(modifier = Modifier.fillMaxSize()) {
AsyncImage(
model = ImageRequest.Builder(LocalContext.current)
.data(comic.getPage(0))
.data(comic.getPage(0, homeScreenViewModel.apiClient))
.memoryCacheKey("${comic.id}/${0}")
.diskCacheKey("${comic.id}/${0}")
.build(),

View File

@@ -1,7 +1,5 @@
package com.acitelight.aether.view
import android.util.Log
import android.widget.Toast
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
@@ -17,7 +15,6 @@ import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.Key
import androidx.compose.material.icons.filled.Link
import androidx.compose.material.icons.filled.Person
import androidx.compose.material.icons.filled.Security
import androidx.compose.material3.Button
import androidx.compose.material3.Card
import androidx.compose.material3.Checkbox
@@ -31,25 +28,16 @@ import androidx.compose.runtime.getValue
import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.text.TextStyle
import androidx.compose.ui.text.input.KeyboardType
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import androidx.hilt.navigation.compose.hiltViewModel
import androidx.lifecycle.viewModelScope
import androidx.lifecycle.viewmodel.compose.viewModel
import com.acitelight.aether.service.ApiClient.api
import com.acitelight.aether.viewModel.MeScreenViewModel
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
@Composable
fun MeScreen(meScreenViewModel: MeScreenViewModel = androidx.hilt.lifecycle.viewmodel.compose.hiltViewModel<MeScreenViewModel>()) {
val context = LocalContext.current
var username by meScreenViewModel.username;
var privateKey by meScreenViewModel.privateKey;
var username by meScreenViewModel.username
var privateKey by meScreenViewModel.privateKey
var url by meScreenViewModel.url
var cert by meScreenViewModel.cert
@@ -204,19 +192,6 @@ fun MeScreen(meScreenViewModel: MeScreenViewModel = androidx.hilt.lifecycle.view
) {
Text("Save")
}
Button(
onClick = {
meScreenViewModel.viewModelScope.launch {
Log.i("Delay Analyze", "Start Abyss Hello")
val h = api!!.hello()
Log.i("Delay Analyze", "Abyss Hello: ${h.string()}")
}
},
modifier = Modifier.weight(0.5f).padding(8.dp)
) {
Text("Ping")
}
}
}
}

View File

@@ -35,12 +35,12 @@ import coil3.ImageLoader
import coil3.compose.AsyncImage
import coil3.request.ImageRequest
import com.acitelight.aether.model.Video
import com.acitelight.aether.service.ApiClient
@Composable
fun MiniPlaylistCard(modifier: Modifier, video: Video, imageLoader: ImageLoader, selected: Boolean, onClick: () -> Unit) {
fun MiniPlaylistCard(modifier: Modifier, video: Video, imageLoader: ImageLoader, selected: Boolean, apiClient: ApiClient, onClick: () -> Unit) {
val colorScheme = MaterialTheme.colorScheme
Card(
modifier = modifier
.height(80.dp)
@@ -58,7 +58,7 @@ fun MiniPlaylistCard(modifier: Modifier, video: Video, imageLoader: ImageLoader,
{
AsyncImage(
model = ImageRequest.Builder(LocalContext.current)
.data(video.getCover())
.data(video.getCover(apiClient))
.memoryCacheKey("${video.klass}/${video.id}/cover")
.diskCacheKey("${video.klass}/${video.id}/cover")
.listener(

View File

@@ -28,10 +28,11 @@ import coil3.ImageLoader
import coil3.compose.AsyncImage
import coil3.request.ImageRequest
import com.acitelight.aether.model.Video
import com.acitelight.aether.service.ApiClient
@Composable
fun MiniVideoCard(modifier: Modifier, video: Video, onClick: () -> Unit, imageLoader: ImageLoader) {
fun MiniVideoCard(modifier: Modifier, video: Video, imageLoader: ImageLoader, apiClient: ApiClient, onClick: () -> Unit) {
Card(
modifier = modifier
.height(80.dp)
@@ -49,7 +50,7 @@ fun MiniVideoCard(modifier: Modifier, video: Video, onClick: () -> Unit, imageLo
{
AsyncImage(
model = ImageRequest.Builder(LocalContext.current)
.data(video.getCover())
.data(video.getCover(apiClient))
.memoryCacheKey("${video.klass}/${video.id}/cover")
.diskCacheKey("${video.klass}/${video.id}/cover")
.listener(

View File

@@ -8,7 +8,6 @@ import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.heightIn
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.items
@@ -40,7 +39,6 @@ import androidx.compose.ui.unit.sp
import androidx.hilt.lifecycle.viewmodel.compose.hiltViewModel
import androidx.lifecycle.viewModelScope
import androidx.navigation.NavHostController
import androidx.room.util.TableInfo
import coil3.compose.AsyncImage
import coil3.request.ImageRequest
import com.acitelight.aether.Global.updateRelate
@@ -120,7 +118,7 @@ fun TransmissionScreen(
for (i in downloadToGroup(
item,
downloads
)) transmissionScreenViewModel.delete(i.id, true)
)) transmissionScreenViewModel.delete(i.id)
}
)
}
@@ -247,7 +245,7 @@ private fun VideoDownloadCard(
else {
AsyncImage(
model = ImageRequest.Builder(LocalContext.current)
.data(video.getCover())
.data(video.getCover(viewModel.apiClient))
.memoryCacheKey("${model.klass}/${model.vid}/cover")
.diskCacheKey("${model.klass}/${model.vid}/cover")
.build(),

View File

@@ -557,7 +557,7 @@ fun VideoPlayerLandscape(videoPlayerViewModel: VideoPlayerViewModel) {
LazyColumn(contentPadding = PaddingValues(vertical = 4.dp)) {
items(videoPlayerViewModel.videos) { item ->
MiniPlaylistCard(Modifier.padding(4.dp), video = item, imageLoader = videoPlayerViewModel.imageLoader!!,
selected = id == item.id)
selected = id == item.id, apiClient = videoPlayerViewModel.apiClient)
{
if (name == item.video.name)
return@MiniPlaylistCard

View File

@@ -245,22 +245,25 @@ fun VideoPlayerPortal(
modifier = Modifier
.padding(horizontal = 12.dp),
i,
{
videoPlayerViewModel.isPlaying = false
videoPlayerViewModel.player?.pause()
apiClient = videoPlayerViewModel.apiClient,
imageLoader = videoPlayerViewModel.imageLoader!!
) {
videoPlayerViewModel.isPlaying = false
videoPlayerViewModel.player?.pause()
val playList = mutableListOf<String>()
val fv = videoPlayerViewModel.videoLibrary.classesMap.map { it.value }.flatten()
val playList = mutableListOf<String>()
val fv =
videoPlayerViewModel.videoLibrary.classesMap.map { it.value }.flatten()
val group = fv.filter { it.klass == i.klass && it.video.group == i.video.group }
for (i in group) {
playList.add("${i.klass}/${i.id}")
}
val group =
fv.filter { it.klass == i.klass && it.video.group == i.video.group }
for (i in group) {
playList.add("${i.klass}/${i.id}")
}
val route = "video_player_route/${playList.joinToString(",").toHex()}"
navController.navigate(route)
}, videoPlayerViewModel.imageLoader!!
)
val route = "video_player_route/${playList.joinToString(",").toHex()}"
navController.navigate(route)
}
HorizontalDivider(
Modifier
.padding(vertical = 8.dp)

View File

@@ -368,7 +368,7 @@ fun VideoCard(
AsyncImage(
model = ImageRequest.Builder(LocalContext.current)
.data(video.getCover())
.data(video.getCover(videoScreenViewModel.apiClient))
.memoryCacheKey("${video.klass}/${video.id}/cover")
.diskCacheKey("${video.klass}/${video.id}/cover")
.build(),

View File

@@ -1,11 +1,8 @@
package com.acitelight.aether.viewModel
import android.content.Context
import androidx.compose.runtime.Composable
import androidx.compose.runtime.mutableStateListOf
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.ui.platform.LocalContext
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import coil3.ImageLoader
@@ -14,7 +11,7 @@ import com.acitelight.aether.model.BookMark
import com.acitelight.aether.model.Comic
import com.acitelight.aether.model.ComicRecord
import com.acitelight.aether.model.ComicRecordDatabase
import com.acitelight.aether.service.ApiClient.createOkHttp
import com.acitelight.aether.service.ApiClient
import com.acitelight.aether.service.MediaManager
import com.acitelight.aether.service.RecentManager
import dagger.hilt.android.lifecycle.HiltViewModel
@@ -26,7 +23,8 @@ import javax.inject.Inject
class ComicGridViewModel @Inject constructor(
@ApplicationContext val context: Context,
val mediaManager: MediaManager,
val recentManager: RecentManager
val recentManager: RecentManager,
val apiClient: ApiClient
) : ViewModel()
{
var imageLoader: ImageLoader? = null
@@ -38,7 +36,7 @@ class ComicGridViewModel @Inject constructor(
init {
imageLoader = ImageLoader.Builder(context)
.components {
add(OkHttpNetworkFetcherFactory(createOkHttp()))
add(OkHttpNetworkFetcherFactory(apiClient.getClient()))
}
.build()
db = try{

View File

@@ -17,7 +17,7 @@ import coil3.network.okhttp.OkHttpNetworkFetcherFactory
import com.acitelight.aether.model.Comic
import com.acitelight.aether.model.ComicRecord
import com.acitelight.aether.model.ComicRecordDatabase
import com.acitelight.aether.service.ApiClient.createOkHttp
import com.acitelight.aether.service.ApiClient
import com.acitelight.aether.service.MediaManager
import dagger.hilt.android.lifecycle.HiltViewModel
import dagger.hilt.android.qualifiers.ApplicationContext
@@ -28,7 +28,8 @@ import javax.inject.Inject
@HiltViewModel
class ComicPageViewModel @Inject constructor(
val mediaManager: MediaManager,
@ApplicationContext private val context: Context
@ApplicationContext private val context: Context,
val apiClient: ApiClient
) : ViewModel()
{
var imageLoader: ImageLoader? = null
@@ -43,7 +44,7 @@ class ComicPageViewModel @Inject constructor(
init{
imageLoader = ImageLoader.Builder(context)
.components {
add(OkHttpNetworkFetcherFactory(createOkHttp()))
add(OkHttpNetworkFetcherFactory(apiClient.getClient()))
}
.build()
listState = LazyListState(0, 0)

View File

@@ -1,28 +1,24 @@
package com.acitelight.aether.viewModel
import android.content.Context
import androidx.compose.runtime.Composable
import androidx.compose.runtime.mutableStateListOf
import androidx.compose.ui.platform.LocalContext
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import coil3.ImageLoader
import coil3.network.okhttp.OkHttpNetworkFetcherFactory
import com.acitelight.aether.model.Comic
import com.acitelight.aether.model.ComicResponse
import com.acitelight.aether.service.ApiClient.createOkHttp
import com.acitelight.aether.service.ApiClient
import com.acitelight.aether.service.MediaManager
import dagger.hilt.android.lifecycle.HiltViewModel
import dagger.hilt.android.qualifiers.ApplicationContext
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.launch
import javax.inject.Inject
@HiltViewModel
class ComicScreenViewModel @Inject constructor(
@ApplicationContext private val context: Context,
val mediaManager: MediaManager
val mediaManager: MediaManager,
val apiClient: ApiClient
) : ViewModel() {
var imageLoader: ImageLoader? = null;
@@ -54,7 +50,7 @@ class ComicScreenViewModel @Inject constructor(
init {
imageLoader = ImageLoader.Builder(context)
.components {
add(OkHttpNetworkFetcherFactory(createOkHttp()))
add(OkHttpNetworkFetcherFactory(apiClient.getClient()))
}
.build()

View File

@@ -5,7 +5,7 @@ import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import coil3.ImageLoader
import coil3.network.okhttp.OkHttpNetworkFetcherFactory
import com.acitelight.aether.service.ApiClient.createOkHttp
import com.acitelight.aether.service.ApiClient
import com.acitelight.aether.service.RecentManager
import com.acitelight.aether.service.VideoLibrary
import kotlinx.coroutines.launch
@@ -19,6 +19,7 @@ class HomeScreenViewModel @Inject constructor(
val recentManager: RecentManager,
@ApplicationContext val context: Context,
val videoLibrary: VideoLibrary,
val apiClient: ApiClient
) : ViewModel()
{
var imageLoader: ImageLoader? = null
@@ -26,7 +27,7 @@ class HomeScreenViewModel @Inject constructor(
init{
imageLoader = ImageLoader.Builder(context)
.components {
add(OkHttpNetworkFetcherFactory(createOkHttp()))
add(OkHttpNetworkFetcherFactory(apiClient.getClient()))
}
.build()
viewModelScope.launch {

View File

@@ -1,45 +1,37 @@
package com.acitelight.aether.viewModel
import android.app.Application
import android.content.Context
import android.widget.Toast
import androidx.compose.runtime.mutableStateOf
import androidx.compose.ui.platform.LocalContext
import androidx.core.net.toUri
import androidx.datastore.preferences.core.edit
import androidx.datastore.preferences.core.stringPreferencesKey
import androidx.lifecycle.AndroidViewModel
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import com.acitelight.aether.AetherApp
import com.acitelight.aether.Global
import com.acitelight.aether.dataStore
import com.acitelight.aether.model.Video
import com.acitelight.aether.service.ApiClient
import com.acitelight.aether.service.AuthManager
import com.acitelight.aether.service.MediaManager
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.first
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.launch
import javax.inject.Inject
import com.acitelight.aether.service.*
import com.acitelight.aether.service.SettingsDataStoreManager
import dagger.hilt.android.lifecycle.HiltViewModel
import dagger.hilt.android.qualifiers.ApplicationContext
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.first
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import javax.inject.Inject
@HiltViewModel
class MeScreenViewModel @Inject constructor(
private val settingsDataStoreManager: SettingsDataStoreManager,
@ApplicationContext private val context: Context,
val mediaManager: MediaManager
val mediaManager: MediaManager,
private val apiClient: ApiClient,
private val authManager: AuthManager
) : ViewModel() {
val username = mutableStateOf("");
val username = mutableStateOf("")
val privateKey = mutableStateOf("")
val url = mutableStateOf("");
val url = mutableStateOf("")
val cert = mutableStateOf("")
val uss = settingsDataStoreManager.useSelfSignedFlow
@@ -54,10 +46,10 @@ class MeScreenViewModel @Inject constructor(
if(username.value=="" || privateKey.value=="" || url.value=="") return@launch
try{
val usedUrl = ApiClient.apply(context, url.value, if(uss.first()) cert.value else "")
apiClient.apply(context, url.value, if(uss.first()) cert.value else "")
if (mediaManager.token == "null")
mediaManager.token = AuthManager.fetchToken(
mediaManager.token = authManager.fetchToken(
username.value,
settingsDataStoreManager.privateKeyFlow.first()
)!!
@@ -65,7 +57,7 @@ class MeScreenViewModel @Inject constructor(
Global.loggedIn = true
withContext(Dispatchers.IO)
{
(context as AetherApp).abyssService?.proxy?.config(ApiClient.getBase().toUri().host!!, 4096)
(context as AetherApp).abyssService?.proxy?.config(apiClient.getBase().toUri().host!!, 4096)
context.abyssService?.downloader?.init()
}
}catch(e: Exception)
@@ -100,10 +92,10 @@ class MeScreenViewModel @Inject constructor(
if (u == "" || p == "" || us == "") return@launch
try {
val usedUrl = ApiClient.apply(context, u, if(uss.first()) c else "")
(context as AetherApp).abyssService?.proxy?.config(ApiClient.getBase().toUri().host!!, 4096)
val usedUrl = apiClient.apply(context, u, if(uss.first()) c else "")
(context as AetherApp).abyssService?.proxy?.config(apiClient.getBase().toUri().host!!, 4096)
context.abyssService?.downloader?.init()
mediaManager.token = AuthManager.fetchToken(
mediaManager.token = authManager.fetchToken(
us,
p
)!!
@@ -133,7 +125,7 @@ class MeScreenViewModel @Inject constructor(
if (u == "" || p == "" || ur == "") return@launch
try {
mediaManager.token = AuthManager.fetchToken(
mediaManager.token = authManager.fetchToken(
u,
p
)!!
@@ -141,7 +133,7 @@ class MeScreenViewModel @Inject constructor(
Global.loggedIn = true
withContext(Dispatchers.IO)
{
(context as AetherApp).abyssService?.proxy?.config(ApiClient.getBase().toUri().host!!, 4096)
(context as AetherApp).abyssService?.proxy?.config(apiClient.getBase().toUri().host!!, 4096)
context.abyssService?.downloader?.init()
}
Toast.makeText(context, "Account Updated", Toast.LENGTH_SHORT).show()

View File

@@ -9,7 +9,7 @@ import coil3.ImageLoader
import coil3.network.okhttp.OkHttpNetworkFetcherFactory
import com.acitelight.aether.model.Video
import com.acitelight.aether.model.VideoDownloadItemState
import com.acitelight.aether.service.ApiClient.createOkHttp
import com.acitelight.aether.service.ApiClient
import com.acitelight.aether.service.FetchManager
import com.acitelight.aether.service.MediaManager
import com.acitelight.aether.service.VideoLibrary
@@ -20,7 +20,6 @@ import dagger.hilt.android.lifecycle.HiltViewModel
import dagger.hilt.android.qualifiers.ApplicationContext
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import javax.inject.Inject
@HiltViewModel
@@ -29,6 +28,7 @@ class TransmissionScreenViewModel @Inject constructor(
@ApplicationContext val context: Context,
val videoLibrary: VideoLibrary,
val mediaManager: MediaManager,
val apiClient: ApiClient
) : ViewModel() {
var imageLoader: ImageLoader? = null
val downloads: SnapshotStateList<VideoDownloadItemState> = mutableStateListOf()
@@ -205,7 +205,7 @@ class TransmissionScreenViewModel @Inject constructor(
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) {
fun delete(id: Int) {
fetchManager.delete(id) {
viewModelScope.launch(Dispatchers.Main) { removeOnMain(id) }
}
@@ -218,7 +218,7 @@ class TransmissionScreenViewModel @Inject constructor(
init {
imageLoader = ImageLoader.Builder(context).components {
add(OkHttpNetworkFetcherFactory(createOkHttp()))
add(OkHttpNetworkFetcherFactory(apiClient.getClient()))
}.build()
viewModelScope.launch {

View File

@@ -8,29 +8,34 @@ import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableFloatStateOf
import androidx.compose.runtime.mutableIntStateOf
import androidx.compose.runtime.mutableLongStateOf
import androidx.compose.runtime.mutableStateListOf
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.setValue
import androidx.core.net.toUri
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import androidx.media3.common.MediaItem
import androidx.media3.common.PlaybackException
import androidx.media3.common.Player
import androidx.media3.common.Player.STATE_READY
import androidx.media3.common.Tracks
import androidx.media3.common.text.Cue
import androidx.media3.common.util.UnstableApi
import androidx.media3.datasource.DefaultDataSource
import androidx.media3.datasource.okhttp.OkHttpDataSource
import androidx.media3.exoplayer.ExoPlayer
import androidx.media3.exoplayer.source.DefaultMediaSourceFactory
import androidx.media3.exoplayer.trackselection.DefaultTrackSelector
import coil3.ImageLoader
import coil3.network.okhttp.OkHttpNetworkFetcherFactory
import com.acitelight.aether.model.KeyImage
import com.acitelight.aether.model.Video
import com.acitelight.aether.model.VideoQueryIndex
import com.acitelight.aether.model.VideoRecord
import com.acitelight.aether.model.VideoRecordDatabase
import com.acitelight.aether.service.ApiClient.createOkHttp
import com.acitelight.aether.service.ApiClient
import com.acitelight.aether.service.MediaManager
import com.acitelight.aether.service.RecentManager
import com.acitelight.aether.service.VideoLibrary
import com.acitelight.aether.view.formatTime
import com.acitelight.aether.view.hexToString
import dagger.hilt.android.lifecycle.HiltViewModel
@@ -38,20 +43,12 @@ import dagger.hilt.android.qualifiers.ApplicationContext
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.first
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import okhttp3.Request
import java.io.File
import javax.inject.Inject
import androidx.core.net.toUri
import androidx.media3.common.Tracks
import androidx.media3.datasource.DefaultDataSource
import androidx.media3.exoplayer.trackselection.DefaultTrackSelector
import com.acitelight.aether.Global
import com.acitelight.aether.model.KeyImage
import com.acitelight.aether.service.VideoLibrary
import kotlinx.coroutines.flow.filter
import kotlinx.coroutines.flow.first
@HiltViewModel
class VideoPlayerViewModel @Inject constructor(
@@ -59,6 +56,7 @@ class VideoPlayerViewModel @Inject constructor(
val mediaManager: MediaManager,
val recentManager: RecentManager,
val videoLibrary: VideoLibrary,
val apiClient: ApiClient
) : ViewModel() {
var showPlaylist by mutableStateOf(false)
var isLandscape by mutableStateOf(false)
@@ -79,7 +77,7 @@ class VideoPlayerViewModel @Inject constructor(
var renderedFirst = false
var videos: List<Video> = listOf()
private val httpDataSourceFactory = OkHttpDataSource.Factory(createOkHttp())
private val httpDataSourceFactory = OkHttpDataSource.Factory(apiClient.getClient())
private val defaultDataSourceFactory by lazy {
DefaultDataSource.Factory(
context,
@@ -117,7 +115,7 @@ class VideoPlayerViewModel @Inject constructor(
imageLoader = ImageLoader.Builder(context)
.components {
add(OkHttpNetworkFetcherFactory(createOkHttp()))
add(OkHttpNetworkFetcherFactory(apiClient.getClient()))
}
.build()
@@ -156,7 +154,7 @@ class VideoPlayerViewModel @Inject constructor(
)
) {
try {
val client = createOkHttp()
val client = apiClient.getClient()
val headReq = Request.Builder().url(trimmed).head().build()
val headResp = try {
@@ -240,7 +238,7 @@ class VideoPlayerViewModel @Inject constructor(
currentKlass.value = video.klass
currentName.value = video.video.name
currentDuration.longValue = video.video.duration
currentGallery.value = video.getGallery()
currentGallery.value = video.getGallery(apiClient)
player?.apply {
stop()
@@ -249,7 +247,7 @@ class VideoPlayerViewModel @Inject constructor(
recentManager.pushVideo(context, VideoQueryIndex(video.klass, video.id))
val subtitleCandidate = video.getSubtitle().trim()
val subtitleCandidate = video.getSubtitle(apiClient).trim()
val subtitleUri = tryResolveSubtitleUri(subtitleCandidate)
if (player == null) {
@@ -303,7 +301,7 @@ class VideoPlayerViewModel @Inject constructor(
}
}
val url = video.getVideo()
val url = video.getVideo(apiClient)
val videoUri = if (video.isLocal) Uri.fromFile(File(url)) else url.toUri()
val mediaItem: MediaItem = if (subtitleUri != null) {

View File

@@ -1,25 +1,19 @@
package com.acitelight.aether.viewModel
import android.app.Application
import android.content.Context
import androidx.compose.runtime.Composable
import androidx.compose.runtime.State
import androidx.compose.runtime.mutableIntStateOf
import androidx.compose.runtime.mutableStateListOf
import androidx.compose.runtime.mutableStateMapOf
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.snapshots.SnapshotStateList
import androidx.compose.ui.platform.LocalContext
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import coil3.ImageLoader
import coil3.network.okhttp.OkHttpNetworkFetcherFactory
import com.acitelight.aether.Global
import com.acitelight.aether.model.Video
import com.acitelight.aether.service.ApiClient.createOkHttp
import com.acitelight.aether.service.ApiClient
import com.acitelight.aether.service.FetchManager
import com.acitelight.aether.service.MediaManager
import com.acitelight.aether.service.RecentManager
import com.acitelight.aether.service.VideoLibrary
import com.tonyodev.fetch2.Status
import dagger.hilt.android.lifecycle.HiltViewModel
@@ -31,7 +25,6 @@ import kotlinx.coroutines.launch
import kotlinx.serialization.json.Json
import java.io.File
import javax.inject.Inject
import javax.inject.Singleton
@HiltViewModel
@@ -39,12 +32,12 @@ class VideoScreenViewModel @Inject constructor(
val fetchManager: FetchManager,
@ApplicationContext val context: Context,
val mediaManager: MediaManager,
val recentManager: RecentManager,
val videoLibrary: VideoLibrary
val videoLibrary: VideoLibrary,
val apiClient: ApiClient
) : ViewModel() {
private val _tabIndex = mutableIntStateOf(0)
val tabIndex: State<Int> = _tabIndex
var imageLoader: ImageLoader? = null;
var imageLoader: ImageLoader? = null
var menuVisibility = mutableStateOf(false)
var searchFilter = mutableStateOf("")
var doneInit = mutableStateOf(false)
@@ -79,7 +72,7 @@ class VideoScreenViewModel @Inject constructor(
else {
videoLibrary.classes.add("Offline")
videoLibrary.updatingMap[0] = true
videoLibrary.classesMap["Offline"] = mutableStateListOf<Video>()
videoLibrary.classesMap["Offline"] = mutableStateListOf()
val downloaded = fetchManager.getAllDownloadsAsync().filter {
it.status == Status.COMPLETED &&
@@ -101,7 +94,7 @@ class VideoScreenViewModel @Inject constructor(
fun setTabIndex(index: Int) {
viewModelScope.launch()
{
_tabIndex.intValue = index;
_tabIndex.intValue = index
if (videoLibrary.updatingMap[index] == true) return@launch
videoLibrary.updatingMap[index] = true
@@ -126,7 +119,7 @@ class VideoScreenViewModel @Inject constructor(
init {
imageLoader = ImageLoader.Builder(context)
.components {
add(OkHttpNetworkFetcherFactory(createOkHttp()))
add(OkHttpNetworkFetcherFactory(apiClient.getClient()))
}
.build()