diff --git a/aether.png b/aether.png old mode 100644 new mode 100755 index e273429..69dcd48 Binary files a/aether.png and b/aether.png differ diff --git a/aether_clip.png b/aether_clip.png index 8ebefd4..1ff79f4 100644 Binary files a/aether_clip.png and b/aether_clip.png differ diff --git a/app/src/main/aether-playstore.png b/app/src/main/aether-playstore.png index 82693c5..7e13177 100644 Binary files a/app/src/main/aether-playstore.png and b/app/src/main/aether-playstore.png differ diff --git a/app/src/main/java/com/acitelight/aether/model/VideoRecord.kt b/app/src/main/java/com/acitelight/aether/model/VideoRecord.kt new file mode 100644 index 0000000..bfafc8b --- /dev/null +++ b/app/src/main/java/com/acitelight/aether/model/VideoRecord.kt @@ -0,0 +1,12 @@ +package com.acitelight.aether.model + +import androidx.room.ColumnInfo +import androidx.room.Entity +import androidx.room.PrimaryKey + +@Entity +data class VideoRecord ( + @PrimaryKey(autoGenerate = false) val id: String = "", + @ColumnInfo(name = "name") val klass: String = "", + @ColumnInfo(name = "position") val position: Long +) \ No newline at end of file diff --git a/app/src/main/java/com/acitelight/aether/model/VideoRecordDao.kt b/app/src/main/java/com/acitelight/aether/model/VideoRecordDao.kt new file mode 100644 index 0000000..7bff4e3 --- /dev/null +++ b/app/src/main/java/com/acitelight/aether/model/VideoRecordDao.kt @@ -0,0 +1,27 @@ +package com.acitelight.aether.model + +import androidx.room.Dao +import androidx.room.Delete +import androidx.room.Insert +import androidx.room.OnConflictStrategy +import androidx.room.Query +import androidx.room.Update +import kotlinx.coroutines.flow.Flow + +@Dao +interface VideoRecordDao { + @Query("SELECT * FROM videorecord") + fun getAll(): Flow> + + @Insert(onConflict = OnConflictStrategy.REPLACE) + suspend fun insert(rec: VideoRecord) + + @Update + suspend fun update(rec: VideoRecord) + + @Delete + suspend fun delete(rec: VideoRecord) + + @Query("SELECT * FROM videorecord WHERE id = :id") + suspend fun getById(id: String): VideoRecord? +} \ No newline at end of file diff --git a/app/src/main/java/com/acitelight/aether/model/VideoRecordDatabase.kt b/app/src/main/java/com/acitelight/aether/model/VideoRecordDatabase.kt new file mode 100644 index 0000000..59c6329 --- /dev/null +++ b/app/src/main/java/com/acitelight/aether/model/VideoRecordDatabase.kt @@ -0,0 +1,28 @@ +package com.acitelight.aether.model + +import android.content.Context +import androidx.room.Database +import androidx.room.Room +import androidx.room.RoomDatabase + +@Database(entities = [VideoRecord::class], version = 1) +abstract class VideoRecordDatabase : RoomDatabase() { + abstract fun userDao(): VideoRecordDao + + companion object { + @Volatile + private var INSTANCE: VideoRecordDatabase? = null + + fun getDatabase(context: Context): VideoRecordDatabase { + return INSTANCE ?: synchronized(this) { + val instance = Room.databaseBuilder( + context.applicationContext, + VideoRecordDatabase::class.java, + "videorecord_database" + ).build() + INSTANCE = instance + instance + } + } + } +} \ 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 977739b..d87164e 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(0xFF2F4F8F), isDarkMode = true) -private val LightColorScheme = generateColorScheme(Color(0xFF2F4F8F), isDarkMode = false) +private val DarkColorScheme = generateColorScheme(Color(0xFF4A6F9F), isDarkMode = true) +private val LightColorScheme = generateColorScheme(Color(0xFF4A6F9F), isDarkMode = false) @Composable fun AetherTheme( diff --git a/app/src/main/java/com/acitelight/aether/view/ComicPageView.kt b/app/src/main/java/com/acitelight/aether/view/ComicPageView.kt index 6958a63..6c4e088 100644 --- a/app/src/main/java/com/acitelight/aether/view/ComicPageView.kt +++ b/app/src/main/java/com/acitelight/aether/view/ComicPageView.kt @@ -8,58 +8,44 @@ import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Row -import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.fillMaxHeight import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.height -import androidx.compose.foundation.layout.heightIn import androidx.compose.foundation.layout.padding -import androidx.compose.foundation.layout.width import androidx.compose.foundation.layout.widthIn import androidx.compose.foundation.layout.wrapContentHeight import androidx.compose.foundation.lazy.LazyRow -import androidx.compose.foundation.lazy.items -import androidx.compose.foundation.lazy.itemsIndexed -import androidx.compose.foundation.lazy.rememberLazyListState import androidx.compose.foundation.pager.HorizontalPager import androidx.compose.foundation.pager.rememberPagerState import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.material.icons.Icons import androidx.compose.material.icons.filled.Bookmarks -import androidx.compose.material.icons.filled.Key import androidx.compose.material3.Card import androidx.compose.material3.CardDefaults import androidx.compose.material3.Icon import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Text import androidx.compose.runtime.Composable -import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember -import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clip -import androidx.compose.ui.graphics.Brush import androidx.compose.ui.graphics.Color import androidx.compose.ui.layout.ContentScale import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.unit.dp -import androidx.compose.ui.unit.max import androidx.compose.ui.unit.sp import androidx.hilt.lifecycle.viewmodel.compose.hiltViewModel -import androidx.lifecycle.viewmodel.compose.viewModel +import androidx.lifecycle.viewModelScope import androidx.navigation.NavHostController import coil3.compose.AsyncImage import coil3.request.ImageRequest -import com.acitelight.aether.ToggleFullScreen import com.acitelight.aether.model.BookMark -import com.acitelight.aether.model.ComicRecord -import com.acitelight.aether.service.MediaManager import com.acitelight.aether.viewModel.ComicPageViewModel import kotlinx.coroutines.launch @@ -71,7 +57,6 @@ fun ComicPageView( comicPageViewModel: ComicPageViewModel = hiltViewModel() ) { val colorScheme = MaterialTheme.colorScheme - comicPageViewModel.SetupClient() comicPageViewModel.Resolve(comicId.hexToString(), page.toInt()) val title by comicPageViewModel.title @@ -81,7 +66,7 @@ fun ComicPageView( var showPlane by comicPageViewModel.showPlane var showBookMarkPop by remember { mutableStateOf(false) } - comicPageViewModel.UpdateProcess(pagerState.currentPage) + comicPageViewModel.updateProcess(pagerState.currentPage) val comic by comicPageViewModel.comic comic?.let { @@ -96,7 +81,7 @@ fun ComicPageView( .clickable { showPlane = !showPlane if (showPlane) { - comicPageViewModel.coroutineScope?.launch { + comicPageViewModel.viewModelScope.launch { comicPageViewModel.listState?.scrollToItem(index = pagerState.currentPage) } } @@ -329,7 +314,7 @@ fun ComicPageView( showBookMarkPop = false }, { s -> showBookMarkPop = false - comicPageViewModel.coroutineScope?.launch { + comicPageViewModel.viewModelScope.launch { comicPageViewModel.mediaManager.postBookmark( comicId.hexToString(), BookMark(name = s, page = comicPageViewModel.pageList[pagerState.currentPage]) 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 590c2fa..684d7a8 100644 --- a/app/src/main/java/com/acitelight/aether/view/VideoPlayer.kt +++ b/app/src/main/java/com/acitelight/aether/view/VideoPlayer.kt @@ -213,7 +213,7 @@ fun VideoPlayer( videoId: String, navController: NavHostController ) { - videoPlayerViewModel.Init(videoId) + videoPlayerViewModel.init(videoId) if(videoPlayerViewModel.startPlaying) { diff --git a/app/src/main/java/com/acitelight/aether/viewModel/ComicPageViewModel.kt b/app/src/main/java/com/acitelight/aether/viewModel/ComicPageViewModel.kt index b725ade..fc7dfaf 100644 --- a/app/src/main/java/com/acitelight/aether/viewModel/ComicPageViewModel.kt +++ b/app/src/main/java/com/acitelight/aether/viewModel/ComicPageViewModel.kt @@ -1,11 +1,8 @@ package com.acitelight.aether.viewModel import android.content.Context -import androidx.compose.foundation.ExperimentalFoundationApi import androidx.compose.foundation.lazy.LazyListState import androidx.compose.foundation.lazy.rememberLazyListState -import androidx.compose.foundation.pager.PagerState -import androidx.compose.foundation.pager.rememberPagerState import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.mutableStateListOf @@ -22,8 +19,6 @@ import com.acitelight.aether.model.ComicRecord import com.acitelight.aether.model.ComicRecordDatabase import com.acitelight.aether.service.ApiClient.createOkHttp import com.acitelight.aether.service.MediaManager -import com.acitelight.aether.service.SettingsDataStoreManager -import com.acitelight.aether.view.hexToString import dagger.hilt.android.lifecycle.HiltViewModel import dagger.hilt.android.qualifiers.ApplicationContext import kotlinx.coroutines.CoroutineScope @@ -32,7 +27,8 @@ import javax.inject.Inject @HiltViewModel class ComicPageViewModel @Inject constructor( - val mediaManager: MediaManager + val mediaManager: MediaManager, + @ApplicationContext private val context: Context ) : ViewModel() { var imageLoader: ImageLoader? = null @@ -40,29 +36,18 @@ class ComicPageViewModel @Inject constructor( var pageList = mutableStateListOf() var title = mutableStateOf("") var listState: LazyListState? = null - var coroutineScope: CoroutineScope? = null var showPlane = mutableStateOf(true) - var db: ComicRecordDatabase? = null + var db: ComicRecordDatabase - @Composable - fun SetupClient() - { - val context = LocalContext.current + + init{ imageLoader = ImageLoader.Builder(context) .components { add(OkHttpNetworkFetcherFactory(createOkHttp())) } .build() - listState = rememberLazyListState() - coroutineScope = rememberCoroutineScope() - - db = remember { - try{ - ComicRecordDatabase.getDatabase(context) - }catch (e: Exception) { - print(e.message) - } as ComicRecordDatabase? - } + listState = LazyListState(0, 0) + db = ComicRecordDatabase.getDatabase(context) } @Composable @@ -70,23 +55,23 @@ class ComicPageViewModel @Inject constructor( { if(comic.value != null) return LaunchedEffect(id, page) { - coroutineScope?.launch { + viewModelScope.launch { comic.value = mediaManager.queryComicInfoSingle(id) comic.value?.let { pageList.addAll(it.comic.list) title.value = it.comic.comic_name listState?.scrollToItem(index = page) - UpdateProcess(page) + updateProcess(page) } } } } - fun UpdateProcess(page: Int) + fun updateProcess(page: Int) { if(comic.value == null) return - coroutineScope?.launch { - db?.userDao()?.insert(ComicRecord(id = comic.value!!.id.toInt(), name = comic.value!!.comic.comic_name, position = page)) + viewModelScope.launch { + db.userDao().insert(ComicRecord(id = comic.value!!.id.toInt(), name = comic.value!!.comic.comic_name, position = page)) } } } \ No newline at end of file diff --git a/app/src/main/java/com/acitelight/aether/viewModel/VideoPlayerViewModel.kt b/app/src/main/java/com/acitelight/aether/viewModel/VideoPlayerViewModel.kt index ea25ff0..4dfc78c 100644 --- a/app/src/main/java/com/acitelight/aether/viewModel/VideoPlayerViewModel.kt +++ b/app/src/main/java/com/acitelight/aether/viewModel/VideoPlayerViewModel.kt @@ -1,6 +1,8 @@ package com.acitelight.aether.viewModel +import android.content.Context import android.net.Uri +import android.widget.Toast import androidx.annotation.OptIn import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue @@ -25,11 +27,15 @@ import coil3.ImageLoader import coil3.network.okhttp.OkHttpNetworkFetcherFactory import com.acitelight.aether.model.Video import com.acitelight.aether.model.VideoQueryIndex +import com.acitelight.aether.model.VideoRecord +import com.acitelight.aether.model.VideoRecordDatabase import com.acitelight.aether.service.ApiClient.createOkHttp import com.acitelight.aether.service.MediaManager import com.acitelight.aether.service.RecentManager +import com.acitelight.aether.view.formatTime import com.acitelight.aether.view.hexToString import dagger.hilt.android.lifecycle.HiltViewModel +import dagger.hilt.android.qualifiers.ApplicationContext import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.delay @@ -39,6 +45,7 @@ import javax.inject.Inject @HiltViewModel class VideoPlayerViewModel @Inject constructor( + @ApplicationContext private val context: Context, val mediaManager: MediaManager, val recentManager: RecentManager ) : ViewModel() { @@ -66,25 +73,24 @@ class VideoPlayerViewModel @Inject constructor( val dataSourceFactory = OkHttpDataSource.Factory(createOkHttp()) var imageLoader: ImageLoader? = null; var brit by mutableFloatStateOf(0.5f) + val database: VideoRecordDatabase = VideoRecordDatabase.getDatabase(context) @OptIn(UnstableApi::class) - @Composable - fun Init(videoId: String) { + fun init(videoId: String) { if (_init) return; - val context = LocalContext.current val v = videoId.hexToString() - imageLoader = ImageLoader.Builder(context) .components { add(OkHttpNetworkFetcherFactory(createOkHttp())) } .build() - remember { - viewModelScope.launch { - video = mediaManager.queryVideo(v.split("/")[0], v.split("/")[1])!! - recentManager.pushVideo(context, VideoQueryIndex(v.split("/")[0], v.split("/")[1])) - _player = (if(video!!.isLocal) ExoPlayer.Builder(context) else ExoPlayer.Builder(context).setMediaSourceFactory(DefaultMediaSourceFactory(dataSourceFactory))) + viewModelScope.launch { + video = mediaManager.queryVideo(v.split("/")[0], v.split("/")[1])!! + recentManager.pushVideo(context, VideoQueryIndex(v.split("/")[0], v.split("/")[1])) + _player = + (if (video!!.isLocal) ExoPlayer.Builder(context) else ExoPlayer.Builder(context) + .setMediaSourceFactory(DefaultMediaSourceFactory(dataSourceFactory))) .build().apply { val url = video?.getVideo() ?: "" val mediaItem = if (video!!.isLocal) @@ -105,18 +111,27 @@ class VideoPlayerViewModel @Inject constructor( override fun onRenderedFirstFrame() { super.onRenderedFirstFrame() + if(!renderedFirst) + { + viewModelScope.launch { + val ii = database.userDao().getById(video!!.id) + if(ii != null) + { + _player!!.seekTo(ii.position) + Toast.makeText(context, "Recover from ${formatTime(ii.position)} ", Toast.LENGTH_SHORT).show() + } + } + } renderedFirst = true } override fun onPlayerError(error: PlaybackException) { - Log.e("ExoPlayer", "Playback error: ", error) + } }) } - startListen() - } + startListen() } - _init = true; } @@ -135,6 +150,10 @@ class VideoPlayerViewModel @Inject constructor( override fun onCleared() { super.onCleared() + val p = _player!!.currentPosition _player?.release() + CoroutineScope(Dispatchers.IO).launch { + database.userDao().insert(VideoRecord(video!!.id, video!!.klass, p)) + } } } \ 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 index 1a75b93..3f1a384 100644 Binary files a/app/src/main/res/mipmap-hdpi/aether.webp 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 index 879b122..645d292 100644 Binary files a/app/src/main/res/mipmap-hdpi/aether_foreground.webp 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 index 87f7097..2f7578a 100644 Binary files a/app/src/main/res/mipmap-hdpi/aether_round.webp 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 index b35390e..37f8234 100644 Binary files a/app/src/main/res/mipmap-mdpi/aether.webp 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 index a1d0b44..cd6336e 100644 Binary files a/app/src/main/res/mipmap-mdpi/aether_foreground.webp 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 index 47e86e4..c9f642f 100644 Binary files a/app/src/main/res/mipmap-mdpi/aether_round.webp 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 index 731775b..cd08fb2 100644 Binary files a/app/src/main/res/mipmap-xhdpi/aether.webp 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 index d450ab0..47061df 100644 Binary files a/app/src/main/res/mipmap-xhdpi/aether_foreground.webp 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 index 8395458..a5ff750 100644 Binary files a/app/src/main/res/mipmap-xhdpi/aether_round.webp 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 index fdac0bd..3a7d669 100644 Binary files a/app/src/main/res/mipmap-xxhdpi/aether.webp 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 index edcf32b..3cf5cc4 100644 Binary files a/app/src/main/res/mipmap-xxhdpi/aether_foreground.webp 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 index 6674bd9..513636c 100644 Binary files a/app/src/main/res/mipmap-xxhdpi/aether_round.webp 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 index f810edf..213a502 100644 Binary files a/app/src/main/res/mipmap-xxxhdpi/aether.webp 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 index 7754dfb..f2a118a 100644 Binary files a/app/src/main/res/mipmap-xxxhdpi/aether_foreground.webp 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 index 8d1fb79..3ee1a51 100644 Binary files a/app/src/main/res/mipmap-xxxhdpi/aether_round.webp and b/app/src/main/res/mipmap-xxxhdpi/aether_round.webp differ