[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 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) val v = comic.list.indexOf(pageName)
if(v >= 0) if(v >= 0)
{ {
return getPage(v) return getPage(v, api)
} }
return null return null
} }
@@ -33,7 +33,7 @@ class Comic(
var v = comic.list.indexOf(pageName) var v = comic.list.indexOf(pageName)
if(v >= 0) if(v >= 0)
{ {
var r: Int = 1 var r = 1
v+=1 v+=1
while(v < comic.list.size && !comic.bookmarks.any{ while(v < comic.list.size && !comic.bookmarks.any{
x -> x.page == comic.list[v] x -> x.page == comic.list[v]

View File

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

View File

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

View File

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

View File

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

View File

@@ -18,10 +18,15 @@ import java.lang.reflect.Proxy
import java.net.InetSocketAddress import java.net.InetSocketAddress
import java.security.PrivateKey import java.security.PrivateKey
import java.security.Signature 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? { suspend fun fetchToken(username: String, privateKey: String): String? {
val api = ApiClient.api val api = apiClient.api
var challengeBase64 = "" var challengeBase64 = ""
try{ try{

View File

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

View File

@@ -16,7 +16,8 @@ import javax.inject.Singleton
@Singleton @Singleton
class MediaManager @Inject constructor( class MediaManager @Inject constructor(
val fetchManager: FetchManager, val fetchManager: FetchManager,
@ApplicationContext val context: Context @ApplicationContext val context: Context,
private val apiClient: ApiClient
) )
{ {
var token: String = "null" var token: String = "null"
@@ -25,7 +26,7 @@ class MediaManager @Inject constructor(
{ {
try try
{ {
val j = ApiClient.api!!.getVideoClasses(token) val j = apiClient.api!!.getVideoClasses(token)
return j.toList() return j.toList()
}catch(_: Exception) }catch(_: Exception)
{ {
@@ -37,7 +38,7 @@ class MediaManager @Inject constructor(
{ {
try try
{ {
val j = ApiClient.api!!.queryVideoClasses(klass, token) val j = apiClient.api!!.queryVideoClasses(klass, token)
return j.toList() return j.toList()
}catch(_: Exception) }catch(_: Exception)
{ {
@@ -57,7 +58,7 @@ class MediaManager @Inject constructor(
} }
try { 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) return Video(klass = klass, id = id, token=token, isLocal = false, localBase = "", video = j)
}catch (_: Exception) }catch (_: Exception)
{ {
@@ -83,7 +84,7 @@ class MediaManager @Inject constructor(
} }
try { 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) return Video(klass = klass, id = id, token=token, isLocal = false, localBase = "", video = j)
}catch (_: Exception) }catch (_: Exception)
{ {
@@ -133,7 +134,7 @@ class MediaManager @Inject constructor(
} }
val remoteVideos = if (remoteIds.isNotEmpty()) { 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 { j.zip(remoteIds).map {
Video( Video(
klass = klass, klass = klass,
@@ -157,7 +158,7 @@ class MediaManager @Inject constructor(
suspend fun listComics() : List<String> suspend fun listComics() : List<String>
{ {
try{ try{
val j = ApiClient.api!!.getComics(token) val j = apiClient.api!!.getComics(token)
return j return j
}catch (_: Exception) }catch (_: Exception)
{ {
@@ -168,7 +169,7 @@ class MediaManager @Inject constructor(
suspend fun queryComicInfoSingle(id: String) : Comic? suspend fun queryComicInfoSingle(id: String) : Comic?
{ {
try{ try{
val j = ApiClient.api!!.queryComicInfo(id, token) val j = apiClient.api!!.queryComicInfo(id, token)
return Comic(id = id, comic = j, token = token) return Comic(id = id, comic = j, token = token)
}catch (_: Exception) }catch (_: Exception)
{ {
@@ -179,7 +180,7 @@ class MediaManager @Inject constructor(
suspend fun queryComicInfoBulk(id: List<String>) : List<Comic>? suspend fun queryComicInfoBulk(id: List<String>) : List<Comic>?
{ {
try{ 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) } return j.zip(id).map { Comic(id = it.second, comic = it.first, token = token) }
}catch (_: Exception) }catch (_: Exception)
{ {
@@ -190,7 +191,7 @@ class MediaManager @Inject constructor(
suspend fun postBookmark(id: String, bookMark: BookMark): Boolean suspend fun postBookmark(id: String, bookMark: BookMark): Boolean
{ {
try{ try{
ApiClient.api!!.postBookmark(id, token, bookMark) apiClient.api!!.postBookmark(id, token, bookMark)
return true return true
}catch (_: Exception) }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.dp
import androidx.compose.ui.unit.sp import androidx.compose.ui.unit.sp
import androidx.hilt.lifecycle.viewmodel.compose.hiltViewModel import androidx.hilt.lifecycle.viewmodel.compose.hiltViewModel
import androidx.lifecycle.viewmodel.compose.viewModel
import androidx.navigation.NavHostController import androidx.navigation.NavHostController
import coil3.compose.AsyncImage import coil3.compose.AsyncImage
import coil3.request.ImageRequest import coil3.request.ImageRequest
@@ -157,7 +156,6 @@ fun ComicGridView(
comicGridViewModel.updateProcess(comicId.hexToString()) comicGridViewModel.updateProcess(comicId.hexToString())
{ {
if (record != null) { if (record != null) {
val k = comic!!.getPageChapterIndex(record!!.position)
val route = "comic_page_route/${comic!!.id.toHex()}/${ val route = "comic_page_route/${comic!!.id.toHex()}/${
record!!.position record!!.position
}" }"
@@ -248,7 +246,7 @@ fun ChapterCard(
{ {
AsyncImage( AsyncImage(
model = ImageRequest.Builder(LocalContext.current) model = ImageRequest.Builder(LocalContext.current)
.data(comic.getPage(c.page)) .data(comic.getPage(c.page, comicGridViewModel.apiClient))
.memoryCacheKey("${comic.id}/${c.page}") .memoryCacheKey("${comic.id}/${c.page}")
.diskCacheKey("${comic.id}/${c.page}") .diskCacheKey("${comic.id}/${c.page}")
.build(), .build(),
@@ -300,13 +298,13 @@ fun ChapterCard(
.padding(horizontal = 6.dp), .padding(horizontal = 6.dp),
onClick = { onClick = {
val route = val route =
"comic_page_route/${"${comic.id}".toHex()}/${comic.getPageIndex(r)}" "comic_page_route/${comic.id.toHex()}/${comic.getPageIndex(r)}"
navController.navigate(route) navController.navigate(route)
} }
) { ) {
AsyncImage( AsyncImage(
model = ImageRequest.Builder(LocalContext.current) model = ImageRequest.Builder(LocalContext.current)
.data(comic.getPage(r)) .data(comic.getPage(r, comicGridViewModel.apiClient))
.memoryCacheKey("${comic.id}/${r}") .memoryCacheKey("${comic.id}/${r}")
.diskCacheKey("${comic.id}/${r}") .diskCacheKey("${comic.id}/${r}")
.build(), .build(),

View File

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

View File

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

View File

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

View File

@@ -1,7 +1,5 @@
package com.acitelight.aether.view package com.acitelight.aether.view
import android.util.Log
import android.widget.Toast
import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row 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.Key
import androidx.compose.material.icons.filled.Link import androidx.compose.material.icons.filled.Link
import androidx.compose.material.icons.filled.Person import androidx.compose.material.icons.filled.Person
import androidx.compose.material.icons.filled.Security
import androidx.compose.material3.Button import androidx.compose.material3.Button
import androidx.compose.material3.Card import androidx.compose.material3.Card
import androidx.compose.material3.Checkbox import androidx.compose.material3.Checkbox
@@ -31,25 +28,16 @@ import androidx.compose.runtime.getValue
import androidx.compose.runtime.setValue import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.text.TextStyle import androidx.compose.ui.text.TextStyle
import androidx.compose.ui.text.input.KeyboardType import androidx.compose.ui.text.input.KeyboardType
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp import androidx.compose.ui.unit.sp
import 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 com.acitelight.aether.viewModel.MeScreenViewModel
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
@Composable @Composable
fun MeScreen(meScreenViewModel: MeScreenViewModel = androidx.hilt.lifecycle.viewmodel.compose.hiltViewModel<MeScreenViewModel>()) { fun MeScreen(meScreenViewModel: MeScreenViewModel = androidx.hilt.lifecycle.viewmodel.compose.hiltViewModel<MeScreenViewModel>()) {
val context = LocalContext.current var username by meScreenViewModel.username
var username by meScreenViewModel.username; var privateKey by meScreenViewModel.privateKey
var privateKey by meScreenViewModel.privateKey;
var url by meScreenViewModel.url var url by meScreenViewModel.url
var cert by meScreenViewModel.cert var cert by meScreenViewModel.cert
@@ -204,19 +192,6 @@ fun MeScreen(meScreenViewModel: MeScreenViewModel = androidx.hilt.lifecycle.view
) { ) {
Text("Save") 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.compose.AsyncImage
import coil3.request.ImageRequest import coil3.request.ImageRequest
import com.acitelight.aether.model.Video import com.acitelight.aether.model.Video
import com.acitelight.aether.service.ApiClient
@Composable @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 val colorScheme = MaterialTheme.colorScheme
Card( Card(
modifier = modifier modifier = modifier
.height(80.dp) .height(80.dp)
@@ -58,7 +58,7 @@ fun MiniPlaylistCard(modifier: Modifier, video: Video, imageLoader: ImageLoader,
{ {
AsyncImage( AsyncImage(
model = ImageRequest.Builder(LocalContext.current) model = ImageRequest.Builder(LocalContext.current)
.data(video.getCover()) .data(video.getCover(apiClient))
.memoryCacheKey("${video.klass}/${video.id}/cover") .memoryCacheKey("${video.klass}/${video.id}/cover")
.diskCacheKey("${video.klass}/${video.id}/cover") .diskCacheKey("${video.klass}/${video.id}/cover")
.listener( .listener(

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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