diff --git a/README.md b/README.md index 98d5ba1..b981e23 100644 --- a/README.md +++ b/README.md @@ -26,7 +26,7 @@ _🚀This is the client of the multimedia server Abyss, which can also be extend - [x] Hide private key after user input - [x] Optimize API call logic, do not create crashes - [x] Fix the issue of freezing when entering the client without configuring the private key -- [ ] Replace Android robot icon with custom design +- [x] Replace Android robot icon with custom design - [ ] Configure server baseURL in client settings - [ ] Implement proper access control for directory queries diff --git a/aether.png b/aether.png new file mode 100644 index 0000000..e273429 Binary files /dev/null and b/aether.png differ diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 4723206..bdc41dc 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -12,9 +12,9 @@ android:usesCleartextTraffic="true" android:dataExtractionRules="@xml/data_extraction_rules" android:fullBackupContent="@xml/backup_rules" - android:icon="@mipmap/ic_launcher" + android:icon="@mipmap/aether" android:label="@string/app_name" - android:roundIcon="@mipmap/ic_launcher_round" + android:roundIcon="@mipmap/aether_round" android:supportsRtl="true" android:theme="@style/Theme.Aether" android:name=".AetherApp"> diff --git a/app/src/main/aether-playstore.png b/app/src/main/aether-playstore.png new file mode 100644 index 0000000..82693c5 Binary files /dev/null and b/app/src/main/aether-playstore.png differ diff --git a/app/src/main/java/com/acitelight/aether/MainActivity.kt b/app/src/main/java/com/acitelight/aether/MainActivity.kt index 3eabeb2..4434af3 100644 --- a/app/src/main/java/com/acitelight/aether/MainActivity.kt +++ b/app/src/main/java/com/acitelight/aether/MainActivity.kt @@ -275,7 +275,7 @@ fun CardPage( Card( modifier = Modifier .fillMaxSize() - .padding(16.dp), + .padding(6.dp), elevation = CardDefaults.cardElevation(4.dp), shape = RoundedCornerShape(12.dp), colors = CardDefaults.cardColors(containerColor = colorScheme.background) diff --git a/app/src/main/java/com/acitelight/aether/service/FetchManager.kt b/app/src/main/java/com/acitelight/aether/service/FetchManager.kt index 11b3011..d796f03 100644 --- a/app/src/main/java/com/acitelight/aether/service/FetchManager.kt +++ b/app/src/main/java/com/acitelight/aether/service/FetchManager.kt @@ -1,6 +1,8 @@ package com.acitelight.aether.service import android.content.Context +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 @@ -8,8 +10,10 @@ 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 java.io.File import javax.inject.Inject import javax.inject.Singleton @@ -76,10 +80,26 @@ class FetchManager @Inject constructor( } ?: callback?.invoke() } - - // enqueue helper if needed - fun enqueue(request: Request, onEnqueued: ((Request) -> Unit)? = null, onError: ((com.tonyodev.fetch2.Error) -> Unit)? = null) { + private fun enqueue(request: Request, onEnqueued: ((Request) -> Unit)? = null, onError: ((com.tonyodev.fetch2.Error) -> Unit)? = null) { if (fetch == null) init() fetch?.enqueue(request, { r -> onEnqueued?.invoke(r) }, { e -> onError?.invoke(e) }) } + + private fun getVideosDirectory() { + val appFilesDir = context.filesDir + val videosDir = File(appFilesDir, "videos") + + if (!videosDir.exists()) { + val created = videosDir.mkdirs() + } + } + + fun startVideoDownload(video: Video) + { + val path = File(context.filesDir, "videos/${video.klass}/${video.id}") + val request = Request(video.getVideo(), path.path).apply { + extras = Extras(mapOf("name" to video.video.name, "id" to video.id, "class" to video.klass)) + } + enqueue(request) + } } \ No newline at end of file diff --git a/app/src/main/java/com/acitelight/aether/ui/theme/Theme.kt b/app/src/main/java/com/acitelight/aether/ui/theme/Theme.kt index d5e6156..977739b 100644 --- a/app/src/main/java/com/acitelight/aether/ui/theme/Theme.kt +++ b/app/src/main/java/com/acitelight/aether/ui/theme/Theme.kt @@ -130,8 +130,8 @@ fun generateColorScheme(primaryColor: Color, isDarkMode: Boolean): ColorScheme { } } -private val DarkColorScheme = generateColorScheme(Color(0xFFFF4081), isDarkMode = true) -private val LightColorScheme = generateColorScheme(Color(0xFFFF4081), isDarkMode = false) +private val DarkColorScheme = generateColorScheme(Color(0xFF2F4F8F), isDarkMode = true) +private val LightColorScheme = generateColorScheme(Color(0xFF2F4F8F), isDarkMode = false) @Composable fun AetherTheme( diff --git a/app/src/main/java/com/acitelight/aether/view/TransmissionScreen.kt b/app/src/main/java/com/acitelight/aether/view/TransmissionScreen.kt index db5d137..d855060 100644 --- a/app/src/main/java/com/acitelight/aether/view/TransmissionScreen.kt +++ b/app/src/main/java/com/acitelight/aether/view/TransmissionScreen.kt @@ -81,7 +81,6 @@ private fun DownloadCard( Row(verticalAlignment = Alignment.CenterVertically, modifier = Modifier.fillMaxWidth()) { Column(modifier = Modifier.weight(1f)) { Text(text = model.fileName, style = MaterialTheme.typography.titleMedium) - Text(text = model.url, style = MaterialTheme.typography.displayMedium, modifier = Modifier.padding(top = 4.dp)) } // progress percentage diff --git a/app/src/main/java/com/acitelight/aether/view/VideoPlayer.kt b/app/src/main/java/com/acitelight/aether/view/VideoPlayer.kt index b5f7625..a5fd21e 100644 --- a/app/src/main/java/com/acitelight/aether/view/VideoPlayer.kt +++ b/app/src/main/java/com/acitelight/aether/view/VideoPlayer.kt @@ -128,6 +128,7 @@ fun BiliStyleSlider( onValueChange: (Float) -> Unit, valueRange: ClosedFloatingPointRange = 0f..1f ) { + val colorScheme = MaterialTheme.colorScheme val thumbRadius = 6.dp val trackHeight = 3.dp @@ -137,8 +138,8 @@ fun BiliStyleSlider( valueRange = valueRange, modifier = modifier, colors = SliderDefaults.colors( - thumbColor = Color(0xFFFFFFFF), // B站粉色 - activeTrackColor = Color(0xFFFF6699), + thumbColor = Color(0xFFFFFFFF), + activeTrackColor = colorScheme.primary, inactiveTrackColor = Color.LightGray.copy(alpha = 0.4f) ), @@ -154,7 +155,7 @@ fun BiliStyleSlider( .align(Alignment.CenterStart) .fillMaxWidth(value) .fillMaxHeight() - .background(Color(0xFFFF6699), RoundedCornerShape(50)) + .background(colorScheme.primary, RoundedCornerShape(50)) ) } } @@ -169,6 +170,7 @@ fun BiliMiniSlider( onValueChange: (Float) -> Unit, valueRange: ClosedFloatingPointRange = 0f..1f ) { + val colorScheme = MaterialTheme.colorScheme val thumbRadius = 6.dp val trackHeight = 3.dp @@ -178,8 +180,8 @@ fun BiliMiniSlider( valueRange = valueRange, modifier = modifier, colors = SliderDefaults.colors( - thumbColor = Color(0xFFFFFFFF), // B站粉色 - activeTrackColor = Color(0xFFFF6699), + thumbColor = Color(0xFFFFFFFF), + activeTrackColor = colorScheme.primary, inactiveTrackColor = Color.LightGray.copy(alpha = 0.4f) ), thumb = { @@ -197,7 +199,7 @@ fun BiliMiniSlider( .align(Alignment.CenterStart) .fillMaxWidth(value) .fillMaxHeight() - .background(Color(0xFFFF6699), RoundedCornerShape(50)) + .background(colorScheme.primary, RoundedCornerShape(50)) ) } } @@ -450,9 +452,8 @@ fun PortalCorePlayer(modifier: Modifier, videoPlayerViewModel: VideoPlayerViewMo } } - if(cover > 0.0f) - Spacer(Modifier.background(Color(0x00FF6699 - 0x00222222 + ((0x000000FF * cover).toLong() shl 24) )).fillMaxSize()) + Spacer(Modifier.background(MaterialTheme.colorScheme.primary.copy(cover)).fillMaxSize()) androidx.compose.animation.AnimatedVisibility( visible = !videoPlayerViewModel.planeVisibility, @@ -704,6 +705,7 @@ fun VideoPlayerPortal(videoPlayerViewModel: VideoPlayerViewModel, navController: @Composable fun SocialPanel(modifier: Modifier, videoPlayerViewModel: VideoPlayerViewModel) { + val colorScheme = MaterialTheme.colorScheme Row( modifier, horizontalArrangement = Arrangement.Center @@ -760,7 +762,7 @@ fun SocialPanel(modifier: Modifier, videoPlayerViewModel: VideoPlayerViewModel) modifier = Modifier.size(28.dp), imageVector = Icons.Filled.Star, contentDescription = "Star", - tint = if(videoPlayerViewModel.star) Color(0xFFFF6699) else Color.Gray + tint = if(videoPlayerViewModel.star) colorScheme.primary else Color.Gray ) } } diff --git a/app/src/main/java/com/acitelight/aether/view/VideoScreen.kt b/app/src/main/java/com/acitelight/aether/view/VideoScreen.kt index 3bfd2c0..347f1ec 100644 --- a/app/src/main/java/com/acitelight/aether/view/VideoScreen.kt +++ b/app/src/main/java/com/acitelight/aether/view/VideoScreen.kt @@ -1,6 +1,8 @@ package com.acitelight.aether.view import androidx.compose.foundation.background +import androidx.compose.foundation.clickable +import androidx.compose.foundation.combinedClickable import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column @@ -41,6 +43,7 @@ import androidx.compose.runtime.mutableStateOf import androidx.compose.ui.Alignment import androidx.compose.ui.graphics.Brush import androidx.compose.ui.platform.LocalContext +import androidx.hilt.lifecycle.viewmodel.compose.hiltViewModel import androidx.navigation.NavHostController import coil3.request.ImageRequest import com.acitelight.aether.Global @@ -63,7 +66,7 @@ fun String.hexToString(charset: Charset = Charsets.UTF_8): String { } @Composable -fun VideoScreen(videoScreenViewModel: VideoScreenViewModel = viewModel(), navController: NavHostController) +fun VideoScreen(videoScreenViewModel: VideoScreenViewModel = hiltViewModel(), navController: NavHostController) { val tabIndex by videoScreenViewModel.tabIndex; videoScreenViewModel.SetupClient() @@ -113,15 +116,20 @@ fun TopRow(videoScreenViewModel: VideoScreenViewModel) fun VideoCard(video: Video, navController: NavHostController, videoScreenViewModel: VideoScreenViewModel) { val tabIndex by videoScreenViewModel.tabIndex; Card( - shape = RoundedCornerShape(6.dp), modifier = Modifier .fillMaxWidth() - .wrapContentHeight(), - onClick = { - updateRelate(videoScreenViewModel.classesMap[videoScreenViewModel.classes[tabIndex]] ?: mutableStateListOf(), video) - val route = "video_player_route/${ "${video.klass}/${video.id}".toHex() }" - navController.navigate(route) - } + .wrapContentHeight() + .combinedClickable( + onClick = { + updateRelate(videoScreenViewModel.classesMap[videoScreenViewModel.classes[tabIndex]] ?: mutableStateListOf(), video) + val route = "video_player_route/${ "${video.klass}/${video.id}".toHex() }" + navController.navigate(route) + }, + onLongClick = { + videoScreenViewModel.download(video) + } + ), + shape = RoundedCornerShape(6.dp), ) { Column( modifier = Modifier diff --git a/app/src/main/java/com/acitelight/aether/viewModel/TransmissionScreenViewModel.kt b/app/src/main/java/com/acitelight/aether/viewModel/TransmissionScreenViewModel.kt index 8732d68..23d4c66 100644 --- a/app/src/main/java/com/acitelight/aether/viewModel/TransmissionScreenViewModel.kt +++ b/app/src/main/java/com/acitelight/aether/viewModel/TransmissionScreenViewModel.kt @@ -69,8 +69,8 @@ class TransmissionScreenViewModel @Inject constructor( val existing = idToState[download.id] if (existing != null) { // update fields in-place -> minimal recomposition - existing.filePath = download.file ?: existing.filePath - existing.fileName = try { File(existing.filePath).name } catch (_: Exception) { existing.fileName } + existing.filePath = download.file + existing.fileName = download.request.extras.getString("name", "") existing.url = download.url existing.progress = download.progress existing.status = download.status @@ -97,11 +97,11 @@ class TransmissionScreenViewModel @Inject constructor( } } private fun downloadToState(download: Download): DownloadItemState { - val filePath = download.file ?: "" - val fileName = try { File(filePath).name } catch (_: Exception) { filePath } + val filePath = download.file + return DownloadItemState( id = download.id, - fileName = fileName, + fileName = download.request.extras.getString("name", ""), filePath = filePath, url = download.url, progress = download.progress, diff --git a/app/src/main/java/com/acitelight/aether/viewModel/VideoScreenViewModel.kt b/app/src/main/java/com/acitelight/aether/viewModel/VideoScreenViewModel.kt index 5e82f32..af18337 100644 --- a/app/src/main/java/com/acitelight/aether/viewModel/VideoScreenViewModel.kt +++ b/app/src/main/java/com/acitelight/aether/viewModel/VideoScreenViewModel.kt @@ -11,6 +11,7 @@ import androidx.compose.runtime.snapshots.SnapshotStateList import androidx.compose.ui.platform.LocalContext import androidx.datastore.preferences.core.stringPreferencesKey import androidx.lifecycle.AndroidViewModel +import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope import coil3.ImageLoader import coil3.network.okhttp.OkHttpNetworkFetcherFactory @@ -18,14 +19,20 @@ import com.acitelight.aether.dataStore import com.acitelight.aether.helper.insertInNaturalOrder import com.acitelight.aether.model.Video import com.acitelight.aether.service.ApiClient.createOkHttp +import com.acitelight.aether.service.FetchManager import com.acitelight.aether.service.MediaManager import com.acitelight.aether.service.MediaManager.queryVideoKlasses +import dagger.hilt.android.lifecycle.HiltViewModel import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.first import kotlinx.coroutines.flow.map import kotlinx.coroutines.launch +import javax.inject.Inject -class VideoScreenViewModel(application: Application) : AndroidViewModel(application) +@HiltViewModel +class VideoScreenViewModel @Inject constructor( + private val fetchManager: FetchManager +) : ViewModel() { private val _tabIndex = mutableIntStateOf(0) val tabIndex: State = _tabIndex @@ -83,6 +90,11 @@ class VideoScreenViewModel(application: Application) : AndroidViewModel(applicat } } + fun download(video :Video) + { + fetchManager.startVideoDownload(video) + } + init { viewModelScope.launch { init() diff --git a/app/src/main/res/drawable/aether_background.xml b/app/src/main/res/drawable/aether_background.xml new file mode 100644 index 0000000..ca3826a --- /dev/null +++ b/app/src/main/res/drawable/aether_background.xml @@ -0,0 +1,74 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/mipmap-anydpi-v26/aether.xml b/app/src/main/res/mipmap-anydpi-v26/aether.xml new file mode 100644 index 0000000..ee2a581 --- /dev/null +++ b/app/src/main/res/mipmap-anydpi-v26/aether.xml @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/app/src/main/res/mipmap-anydpi-v26/aether_round.xml b/app/src/main/res/mipmap-anydpi-v26/aether_round.xml new file mode 100644 index 0000000..ee2a581 --- /dev/null +++ b/app/src/main/res/mipmap-anydpi-v26/aether_round.xml @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/app/src/main/res/mipmap-hdpi/aether.webp b/app/src/main/res/mipmap-hdpi/aether.webp new file mode 100644 index 0000000..1a75b93 Binary files /dev/null and b/app/src/main/res/mipmap-hdpi/aether.webp differ diff --git a/app/src/main/res/mipmap-hdpi/aether_foreground.webp b/app/src/main/res/mipmap-hdpi/aether_foreground.webp new file mode 100644 index 0000000..879b122 Binary files /dev/null and b/app/src/main/res/mipmap-hdpi/aether_foreground.webp differ diff --git a/app/src/main/res/mipmap-hdpi/aether_round.webp b/app/src/main/res/mipmap-hdpi/aether_round.webp new file mode 100644 index 0000000..87f7097 Binary files /dev/null and b/app/src/main/res/mipmap-hdpi/aether_round.webp differ diff --git a/app/src/main/res/mipmap-mdpi/aether.webp b/app/src/main/res/mipmap-mdpi/aether.webp new file mode 100644 index 0000000..b35390e Binary files /dev/null and b/app/src/main/res/mipmap-mdpi/aether.webp differ diff --git a/app/src/main/res/mipmap-mdpi/aether_foreground.webp b/app/src/main/res/mipmap-mdpi/aether_foreground.webp new file mode 100644 index 0000000..a1d0b44 Binary files /dev/null and b/app/src/main/res/mipmap-mdpi/aether_foreground.webp differ diff --git a/app/src/main/res/mipmap-mdpi/aether_round.webp b/app/src/main/res/mipmap-mdpi/aether_round.webp new file mode 100644 index 0000000..47e86e4 Binary files /dev/null and b/app/src/main/res/mipmap-mdpi/aether_round.webp differ diff --git a/app/src/main/res/mipmap-xhdpi/aether.webp b/app/src/main/res/mipmap-xhdpi/aether.webp new file mode 100644 index 0000000..731775b Binary files /dev/null and b/app/src/main/res/mipmap-xhdpi/aether.webp differ diff --git a/app/src/main/res/mipmap-xhdpi/aether_foreground.webp b/app/src/main/res/mipmap-xhdpi/aether_foreground.webp new file mode 100644 index 0000000..d450ab0 Binary files /dev/null and b/app/src/main/res/mipmap-xhdpi/aether_foreground.webp differ diff --git a/app/src/main/res/mipmap-xhdpi/aether_round.webp b/app/src/main/res/mipmap-xhdpi/aether_round.webp new file mode 100644 index 0000000..8395458 Binary files /dev/null and b/app/src/main/res/mipmap-xhdpi/aether_round.webp differ diff --git a/app/src/main/res/mipmap-xxhdpi/aether.webp b/app/src/main/res/mipmap-xxhdpi/aether.webp new file mode 100644 index 0000000..fdac0bd Binary files /dev/null and b/app/src/main/res/mipmap-xxhdpi/aether.webp differ diff --git a/app/src/main/res/mipmap-xxhdpi/aether_foreground.webp b/app/src/main/res/mipmap-xxhdpi/aether_foreground.webp new file mode 100644 index 0000000..edcf32b Binary files /dev/null and b/app/src/main/res/mipmap-xxhdpi/aether_foreground.webp differ diff --git a/app/src/main/res/mipmap-xxhdpi/aether_round.webp b/app/src/main/res/mipmap-xxhdpi/aether_round.webp new file mode 100644 index 0000000..6674bd9 Binary files /dev/null and b/app/src/main/res/mipmap-xxhdpi/aether_round.webp differ diff --git a/app/src/main/res/mipmap-xxxhdpi/aether.webp b/app/src/main/res/mipmap-xxxhdpi/aether.webp new file mode 100644 index 0000000..f810edf Binary files /dev/null and b/app/src/main/res/mipmap-xxxhdpi/aether.webp differ diff --git a/app/src/main/res/mipmap-xxxhdpi/aether_foreground.webp b/app/src/main/res/mipmap-xxxhdpi/aether_foreground.webp new file mode 100644 index 0000000..7754dfb Binary files /dev/null and b/app/src/main/res/mipmap-xxxhdpi/aether_foreground.webp differ diff --git a/app/src/main/res/mipmap-xxxhdpi/aether_round.webp b/app/src/main/res/mipmap-xxxhdpi/aether_round.webp new file mode 100644 index 0000000..8d1fb79 Binary files /dev/null and b/app/src/main/res/mipmap-xxxhdpi/aether_round.webp differ