3 |
4 |
](https://f-droid.org/packages/cn.a10miaomiao.bilidown)
33 |
--------------------------------------------------------------------------------
/app/src/main/java/cn/a10miaomiao/bilidown/db/AppDatabase.kt:
--------------------------------------------------------------------------------
1 | package cn.a10miaomiao.bilidown.db
2 |
3 | import android.content.Context
4 | import androidx.room.Database
5 | import androidx.room.Room
6 | import androidx.room.RoomDatabase
7 | import androidx.room.migration.Migration
8 | import androidx.sqlite.db.SupportSQLiteDatabase
9 | import cn.a10miaomiao.bilidown.db.dao.OutRecord
10 | import cn.a10miaomiao.bilidown.db.model.OutRecordDao
11 |
12 | @Database(
13 | entities = [ OutRecord::class,],
14 | version = 2
15 | )
16 | abstract class AppDatabase : RoomDatabase() {
17 |
18 | abstract fun outRecordDao(): OutRecordDao
19 |
20 | companion object {
21 |
22 | fun initialize(context: Context): AppDatabase {
23 | return Room.databaseBuilder(
24 | context,
25 | AppDatabase::class.java,
26 | "bili-down-out"
27 | ).apply {
28 | fallbackToDestructiveMigration()
29 | addMigrations(
30 | MIGRATION_1_2
31 | )
32 | }.build()
33 | }
34 |
35 | private val MIGRATION_1_2 = object : Migration(1, 2) {
36 | override fun migrate(database: SupportSQLiteDatabase) {
37 | database.execSQL("ALTER TABLE out_record ADD COLUMN `message` TEXT")
38 | }
39 | }
40 | }
41 |
42 | }
--------------------------------------------------------------------------------
/app/src/main/java/cn/a10miaomiao/bilidown/common/scrollable/ScaffoldScrollableState.kt:
--------------------------------------------------------------------------------
1 | package cn.a10miaomiao.bilidown.common.scrollable
2 |
3 | import androidx.compose.runtime.Stable
4 | import androidx.compose.runtime.mutableStateOf
5 | import androidx.compose.ui.geometry.Offset
6 | import androidx.compose.ui.input.nestedscroll.NestedScrollConnection
7 | import androidx.compose.ui.input.nestedscroll.NestedScrollSource
8 |
9 | @Stable
10 | class ScaffoldScrollableState {
11 |
12 | private val _showBottomBar = mutableStateOf(true)
13 | val showBottomBar get() = _showBottomBar.value
14 |
15 | fun slideDown() {
16 | _showBottomBar.value = false
17 | }
18 |
19 | fun slideUp() {
20 | _showBottomBar.value = true
21 | }
22 | }
23 |
24 | class ScaffoldNestedScrollConnection(
25 | val state: ScaffoldScrollableState,
26 | ) : NestedScrollConnection {
27 | override fun onPreScroll(
28 | available: Offset,
29 | source: NestedScrollSource
30 | ): Offset {
31 | return Offset.Zero
32 | }
33 |
34 | override fun onPostScroll(
35 | consumed: Offset,
36 | available: Offset,
37 | source: NestedScrollSource
38 | ): Offset {
39 | if (consumed.y > 0) {
40 | state.slideUp()
41 | } else if (consumed.y < 0) {
42 | state.slideDown()
43 | }
44 | return Offset.Zero
45 | }
46 |
47 | }
--------------------------------------------------------------------------------
/app/src/main/java/cn/a10miaomiao/bilidown/ui/BiliDownScreen.kt:
--------------------------------------------------------------------------------
1 | package cn.a10miaomiao.bilidown.ui
2 |
3 | import androidx.compose.material.icons.Icons
4 | import androidx.compose.material.icons.automirrored.filled.List
5 | import androidx.compose.material.icons.filled.*
6 | import androidx.compose.ui.graphics.vector.ImageVector
7 |
8 | sealed class BiliDownScreen(
9 | val route: String,
10 | val name: String,
11 | val icon: ImageVector = Icons.Filled.Favorite,
12 | ) {
13 | object List : BiliDownScreen("list", "列表", Icons.Filled.Home)
14 | object More : BiliDownScreen("more", "设置", Icons.Filled.Settings)
15 | object Progress : BiliDownScreen("progress", "进度", Icons.Filled.DateRange)
16 | object OutList: BiliDownScreen("out_list", "已导出", Icons.Filled.CheckCircle)
17 | object Detail : BiliDownScreen("detail", "详情")
18 | object AddApp : BiliDownScreen("add_app", "添加APP信息")
19 | object About : BiliDownScreen("about", "关于")
20 |
21 | companion object {
22 | private val routeToNameMap = mapOf(
23 | "list" to "哔哩缓存导出",
24 | "progress" to "当前进度",
25 | "out_list" to "导出记录",
26 | "more" to "设置",
27 | "add_app" to "添加APP",
28 | "about" to "关于",
29 | "detail" to "哔哩缓存详情",
30 | )
31 |
32 | fun getRouteName(route: String?): String {
33 | val key = route?.split("?")?.get(0) ?: ""
34 | return routeToNameMap[key] ?: "BiliDownOut"
35 | }
36 | }
37 | }
--------------------------------------------------------------------------------
/app/src/main/java/cn/a10miaomiao/bilidown/service/MyRxFFmpegSubscriber.kt:
--------------------------------------------------------------------------------
1 | package cn.a10miaomiao.bilidown.service
2 |
3 | import android.util.Log
4 | import cn.a10miaomiao.bilidown.common.MiaoLog
5 | import cn.a10miaomiao.bilidown.state.AppState
6 | import cn.a10miaomiao.bilidown.state.TaskStatus
7 | import io.microshow.rxffmpeg.RxFFmpegSubscriber
8 | import kotlinx.coroutines.flow.MutableStateFlow
9 | import java.io.File
10 |
11 | open class MyRxFFmpegSubscriber(
12 | private val appState: AppState,
13 | // private val tempPath: String,
14 | ) : RxFFmpegSubscriber() {
15 | private val TAG = "MyRxFFmpegSubscriber"
16 |
17 | override fun onFinish() {
18 | appState.putTaskStatus(TaskStatus.InIdle)
19 | }
20 |
21 | override fun onProgress(progress: Int, progressTime: Long) {
22 | val taskStatus = appState.taskStatus.value
23 | if (taskStatus is TaskStatus.InProgress) {
24 | appState.putTaskStatus(
25 | taskStatus.copy(
26 | progress = progress.toFloat() / 100f
27 | )
28 | )
29 | }
30 | Log.d(TAG, "onProgress$progress $progressTime")
31 | }
32 |
33 | override fun onCancel() {
34 | appState.putTaskStatus(TaskStatus.InIdle)
35 | }
36 |
37 | override fun onError(message: String) {
38 | MiaoLog.info { message }
39 | appState.putTaskStatus(TaskStatus.Error(
40 | appState.taskStatus.value,
41 | message
42 | ))
43 | }
44 | }
--------------------------------------------------------------------------------
/app/src/main/java/cn/a10miaomiao/bilidown/ui/components/SwipeToRefresh.kt:
--------------------------------------------------------------------------------
1 | package cn.a10miaomiao.bilidown.ui.components
2 |
3 | import androidx.compose.foundation.layout.Box
4 | import androidx.compose.foundation.layout.BoxScope
5 | import androidx.compose.foundation.layout.fillMaxSize
6 | import androidx.compose.material.ExperimentalMaterialApi
7 | import androidx.compose.material.pullrefresh.PullRefreshIndicator
8 | import androidx.compose.material.pullrefresh.pullRefresh
9 | import androidx.compose.material.pullrefresh.rememberPullRefreshState
10 | import androidx.compose.material3.MaterialTheme
11 | import androidx.compose.runtime.Composable
12 | import androidx.compose.ui.Alignment
13 | import androidx.compose.ui.Modifier
14 |
15 | @OptIn(ExperimentalMaterialApi::class)
16 | @Composable
17 | fun SwipeToRefresh(
18 | modifier: Modifier = Modifier,
19 | refreshing: Boolean,
20 | onRefresh: () -> Unit,
21 | content: @Composable BoxScope.() -> Unit
22 | ) {
23 | val state = rememberPullRefreshState(
24 | refreshing = refreshing,
25 | onRefresh = onRefresh,
26 | )
27 | Box(modifier = modifier
28 | .fillMaxSize()
29 | .pullRefresh(state)
30 | ){
31 | content()
32 | PullRefreshIndicator(
33 | refreshing,
34 | state,
35 | Modifier.align(Alignment.TopCenter),
36 | contentColor = MaterialTheme.colorScheme.primary,
37 | backgroundColor = MaterialTheme.colorScheme.surface,
38 | )
39 | }
40 | }
--------------------------------------------------------------------------------
/gradle.properties:
--------------------------------------------------------------------------------
1 | # Project-wide Gradle settings.
2 | # IDE (e.g. Android Studio) users:
3 | # Gradle settings configured through the IDE *will override*
4 | # any settings specified in this file.
5 | # For more details on how to configure your build environment visit
6 | # http://www.gradle.org/docs/current/userguide/build_environment.html
7 | # Specifies the JVM arguments used for the daemon process.
8 | # The setting is particularly useful for tweaking memory settings.
9 | org.gradle.jvmargs=-Xmx2048m -Dfile.encoding=UTF-8
10 | # When configured, Gradle will run in incubating parallel mode.
11 | # This option should only be used with decoupled projects. More details, visit
12 | # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
13 | # org.gradle.parallel=true
14 | # AndroidX package structure to make it clearer which packages are bundled with the
15 | # Android operating system, and which are packaged with your app's APK
16 | # https://developer.android.com/topic/libraries/support-library/androidx-rn
17 | android.useAndroidX=true
18 | # Kotlin code style for this project: "official" or "obsolete":
19 | kotlin.code.style=official
20 | # Enables namespacing of each library's R class so that its R class includes only the
21 | # resources declared in the library itself and none from the library's dependencies,
22 | # thereby reducing the size of the R class for that library
23 | android.nonTransitiveRClass=true
24 | android.defaults.buildfeatures.buildconfig=true
25 | android.nonFinalResIds=false
--------------------------------------------------------------------------------
/.github/workflows/android.yml:
--------------------------------------------------------------------------------
1 | name: Android CI
2 |
3 | on:
4 | push:
5 | # Sequence of patterns matched against refs/tags
6 | tags:
7 | - "v*" # Push events to matching v*, i.e. v1.0, v20.15.10
8 |
9 | jobs:
10 | build:
11 |
12 | runs-on: ubuntu-latest
13 |
14 | steps:
15 | - uses: actions/checkout@v3
16 | - name: set up JDK 17
17 | uses: actions/setup-java@v3
18 | with:
19 | java-version: '17'
20 | distribution: 'temurin'
21 | cache: gradle
22 |
23 | - name: Generate signing.properties
24 | run: |
25 | rm -rf 10miaomiao.jks
26 | ${{ secrets.BASH_DOWNLOAD_JKS }}
27 | echo "KEYSTORE_FILE = ${{github.workspace}}/10miaomiao.jks" > app/signing.properties
28 | echo "KEYSTORE_PASSWORD = ${{ secrets.KEYSTORE_PASSWORD }}" >> app/signing.properties
29 | echo "KEY_ALIAS = ${{ secrets.KEY_ALIAS }}" >> app/signing.properties
30 | echo "KEY_PASSWORD = ${{ secrets.KEY_PASSWORD }}" >> app/signing.properties
31 | - name: Grant execute permission for gradlew
32 | run: chmod +x gradlew
33 | - name: Build with Gradle
34 | run: ./gradlew assembleRelease
35 |
36 | - name: Upload binaries to release
37 | uses: svenstaro/upload-release-action@v2
38 | with:
39 | repo_token: ${{ secrets.GITHUB_TOKEN }}
40 | file: app/build/outputs/apk/release/*.apk
41 | tag: ${{ github.ref }}
42 | release_name: ${{ github.ref }}
43 | overwrite: true
44 | file_glob: true
45 | body: ""
--------------------------------------------------------------------------------
/app/src/main/java/cn/a10miaomiao/bilidown/ui/animation/MaterialFade.kt:
--------------------------------------------------------------------------------
1 | package cn.a10miaomiao.bilimiao.compose.animation
2 |
3 | import androidx.compose.animation.EnterTransition
4 | import androidx.compose.animation.ExitTransition
5 | import androidx.compose.animation.core.FastOutSlowInEasing
6 | import androidx.compose.animation.core.LinearEasing
7 | import androidx.compose.animation.core.tween
8 | import androidx.compose.animation.fadeIn
9 | import androidx.compose.animation.fadeOut
10 | import androidx.compose.animation.scaleIn
11 |
12 | private const val DefaultFadeEndThresholdEnter = 0.3f
13 |
14 | private val Int.ForFade: Int
15 | get() = (this * DefaultFadeEndThresholdEnter).toInt()
16 |
17 | /**
18 | * [materialFadeIn] allows to switch a layout with a fade-in animation.
19 | */
20 | fun materialFadeIn(
21 | durationMillis: Int = DefaultFadeInDuration,
22 | ): EnterTransition = fadeIn(
23 | animationSpec = tween(
24 | durationMillis = durationMillis.ForFade,
25 | easing = LinearEasing,
26 | ),
27 | ) + scaleIn(
28 | animationSpec = tween(
29 | durationMillis = durationMillis,
30 | easing = FastOutSlowInEasing,
31 | ),
32 | initialScale = 0.8f,
33 | )
34 |
35 | /**
36 | * [materialFadeOut] allows to switch a layout with a fade-out animation.
37 | */
38 | fun materialFadeOut(
39 | durationMillis: Int = DefaultFadeOutDuration,
40 | ): ExitTransition = fadeOut(
41 | animationSpec = tween(
42 | durationMillis = durationMillis,
43 | easing = LinearEasing,
44 | ),
45 | )
--------------------------------------------------------------------------------
/app/src/main/java/cn/a10miaomiao/bilidown/state/TaskStatus.kt:
--------------------------------------------------------------------------------
1 | package cn.a10miaomiao.bilidown.state
2 |
3 | sealed class TaskStatus {
4 | open val entryDirPath: String = ""
5 | open val name: String = ""
6 | open val cover: String = ""
7 | open val progress = 0f
8 |
9 | object InIdle : TaskStatus()
10 |
11 | data class CopyingToTemp(
12 | override val entryDirPath: String,
13 | override val name: String,
14 | override val cover: String,
15 | override val progress: Float,
16 | // val videoFile: MiaoDocumentFile,
17 | // val audioFile: MiaoDocumentFile,
18 | ) : TaskStatus()
19 |
20 | data class Copying(
21 | override val entryDirPath: String,
22 | override val name: String,
23 | override val cover: String,
24 | override val progress: Float,
25 | ) : TaskStatus()
26 |
27 | data class InProgress(
28 | override val entryDirPath: String,
29 | override val name: String,
30 | override val cover: String,
31 | override val progress: Float,
32 | ) : TaskStatus()
33 |
34 | data class Error(
35 | override val entryDirPath: String,
36 | override val name: String,
37 | override val cover: String,
38 | override val progress: Float,
39 | val message: String,
40 | ) : TaskStatus() {
41 | constructor(
42 | status: TaskStatus,
43 | message: String
44 | ) : this(
45 | status.entryDirPath,
46 | status.name,
47 | status.cover,
48 | status.progress,
49 | message
50 | )
51 | }
52 | }
--------------------------------------------------------------------------------
/app/src/main/res/drawable-v24/ic_launcher_foreground.xml:
--------------------------------------------------------------------------------
1 |