Skip to content

Commit

Permalink
feat(player): Add option to create and use custom buttons (#132)
Browse files Browse the repository at this point in the history
* feat(player): Add option to create and use custom buttons

* move to singleOf and viewModelOf
  • Loading branch information
Secozzi authored Nov 2, 2024
1 parent 8c70892 commit 1dead52
Show file tree
Hide file tree
Showing 20 changed files with 873 additions and 3 deletions.
2 changes: 2 additions & 0 deletions app/src/main/java/live/mehiz/mpvkt/App.kt
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import android.app.Application
import live.mehiz.mpvkt.di.DatabaseModule
import live.mehiz.mpvkt.di.FileManagerModule
import live.mehiz.mpvkt.di.PreferencesModule
import live.mehiz.mpvkt.di.ViewModelModule
import live.mehiz.mpvkt.presentation.crash.CrashActivity
import live.mehiz.mpvkt.presentation.crash.GlobalExceptionHandler
import org.koin.android.ext.koin.androidContext
Expand All @@ -19,6 +20,7 @@ class App : Application() {
PreferencesModule,
DatabaseModule,
FileManagerModule,
ViewModelModule,
)
}
}
Expand Down
17 changes: 17 additions & 0 deletions app/src/main/java/live/mehiz/mpvkt/database/Migrations.kt
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import androidx.sqlite.db.SupportSQLiteDatabase
val Migrations: Array<Migration> = arrayOf(
MIGRATION1to2,
MIGRATION2to3,
MIGRATION3to4,
)

private object MIGRATION1to2 : Migration(1, 2) {
Expand All @@ -22,3 +23,19 @@ private object MIGRATION2to3 : Migration(2, 3) {
db.execSQL("ALTER TABLE PlaybackStateEntity ADD COLUMN playbackSpeed REAL NOT NULL DEFAULT 0")
}
}

private object MIGRATION3to4 : Migration(3, 4) {
override fun migrate(db: SupportSQLiteDatabase) {
db.execSQL(
"""
CREATE TABLE IF NOT EXISTS `CustomButtonEntity` (
`id` INTEGER NOT NULL,
`title` TEXT NOT NULL,
`content` TEXT NOT NULL,
`index` INTEGER NOT NULL,
PRIMARY KEY(`id`)
)
""".trimIndent()
)
}
}
5 changes: 4 additions & 1 deletion app/src/main/java/live/mehiz/mpvkt/database/MpvKtDatabase.kt
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,13 @@ package live.mehiz.mpvkt.database

import androidx.room.Database
import androidx.room.RoomDatabase
import live.mehiz.mpvkt.database.dao.CustomButtonDao
import live.mehiz.mpvkt.database.dao.PlaybackStateDao
import live.mehiz.mpvkt.database.entities.CustomButtonEntity
import live.mehiz.mpvkt.database.entities.PlaybackStateEntity

@Database(entities = [PlaybackStateEntity::class], version = 3)
@Database(entities = [PlaybackStateEntity::class, CustomButtonEntity::class], version = 4)
abstract class MpvKtDatabase : RoomDatabase() {
abstract fun videoDataDao(): PlaybackStateDao
abstract fun customButtonDao(): CustomButtonDao
}
68 changes: 68 additions & 0 deletions app/src/main/java/live/mehiz/mpvkt/database/dao/CustomButtonDao.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
package live.mehiz.mpvkt.database.dao

import androidx.room.Dao
import androidx.room.Delete
import androidx.room.Query
import androidx.room.Transaction
import androidx.room.Update
import androidx.room.Upsert
import kotlinx.coroutines.flow.Flow
import live.mehiz.mpvkt.database.entities.CustomButtonEntity

@Dao
interface CustomButtonDao {
@Upsert
suspend fun upsert(customButtonEntity: CustomButtonEntity)

@Query("SELECT * FROM CustomButtonEntity ORDER BY `index`")
fun getCustomButtons(): Flow<List<CustomButtonEntity>>

@Query("SELECT * FROM CustomButtonEntity WHERE `index` > :currentIndex ORDER BY `index` ASC LIMIT 1")
suspend fun getNextCustomButton(currentIndex: Int): CustomButtonEntity?

@Query("SELECT * FROM CustomButtonEntity WHERE `index` < :currentIndex ORDER BY `index` DESC LIMIT 1")
suspend fun getPreviousCustomButton(currentIndex: Int): CustomButtonEntity?

@Transaction
suspend fun increaseIndex(customButton: CustomButtonEntity) {
val nextCustomButton = getNextCustomButton(customButton.index) ?: return

val current = customButton.copy(index = nextCustomButton.index)
val next = nextCustomButton.copy(index = customButton.index)

updateCustomButton(current)
updateCustomButton(next)
}

@Transaction
suspend fun decreaseIndex(customButton: CustomButtonEntity) {
val previousCustomButton = getPreviousCustomButton(customButton.index) ?: return

val current = customButton.copy(index = previousCustomButton.index)
val previous = previousCustomButton.copy(index = customButton.index)

updateCustomButton(current)
updateCustomButton(previous)
}

@Update
suspend fun updateCustomButton(customButton: CustomButtonEntity)

@Query("SELECT * FROM CustomButtonEntity WHERE `index` > :deletedIndex")
suspend fun getNotesAfterOrder(deletedIndex: Int): List<CustomButtonEntity>

@Transaction
suspend fun deleteAndReindex(customButton: CustomButtonEntity) {
val buttonsAfter = getNotesAfterOrder(customButton.index)

deleteCustomButton(customButton)

buttonsAfter.forEach { button ->
val updatedButton = button.copy(index = button.index - 1)
updateCustomButton(updatedButton)
}
}

@Delete
suspend fun deleteCustomButton(customButtonEntity: CustomButtonEntity)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package live.mehiz.mpvkt.database.entities

import androidx.room.Entity
import androidx.room.PrimaryKey

@Entity
data class CustomButtonEntity(
@PrimaryKey(autoGenerate = true)
val id: Int = 0,
val title: String,
val content: String,
val index: Int,
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
package live.mehiz.mpvkt.database.repository

import kotlinx.coroutines.flow.Flow
import live.mehiz.mpvkt.database.MpvKtDatabase
import live.mehiz.mpvkt.database.entities.CustomButtonEntity
import live.mehiz.mpvkt.domain.custombuttons.repository.CustomButtonRepository

class CustomButtonRepositoryImpl(
private val database: MpvKtDatabase,
) : CustomButtonRepository {
override fun getCustomButtons(): Flow<List<CustomButtonEntity>> {
return database.customButtonDao().getCustomButtons()
}

override suspend fun upsert(customButtonEntity: CustomButtonEntity) {
database.customButtonDao().upsert(customButtonEntity)
}

override suspend fun deleteAndReindex(customButtonEntity: CustomButtonEntity) {
database.customButtonDao().deleteAndReindex(customButtonEntity)
}

override suspend fun increaseIndex(customButtonEntity: CustomButtonEntity) {
database.customButtonDao().increaseIndex(customButtonEntity)
}

override suspend fun decreaseIndex(customButtonEntity: CustomButtonEntity) {
database.customButtonDao().decreaseIndex(customButtonEntity)
}

override suspend fun updateButton(customButtonEntity: CustomButtonEntity) {
database.customButtonDao().updateCustomButton(customButtonEntity)
}
}
4 changes: 4 additions & 0 deletions app/src/main/java/live/mehiz/mpvkt/di/DatabaseModule.kt
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,9 @@ package live.mehiz.mpvkt.di
import androidx.room.Room
import live.mehiz.mpvkt.database.Migrations
import live.mehiz.mpvkt.database.MpvKtDatabase
import live.mehiz.mpvkt.database.repository.CustomButtonRepositoryImpl
import org.koin.android.ext.koin.androidContext
import org.koin.core.module.dsl.singleOf
import org.koin.dsl.module

val DatabaseModule = module {
Expand All @@ -13,4 +15,6 @@ val DatabaseModule = module {
.addMigrations(migrations = Migrations)
.build()
}

singleOf(::CustomButtonRepositoryImpl)
}
9 changes: 9 additions & 0 deletions app/src/main/java/live/mehiz/mpvkt/di/ViewModel.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
package live.mehiz.mpvkt.di

import live.mehiz.mpvkt.ui.custombuttons.CustomButtonsScreenViewModel
import org.koin.core.module.dsl.viewModelOf
import org.koin.dsl.module

val ViewModelModule = module {
viewModelOf(::CustomButtonsScreenViewModel)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
package live.mehiz.mpvkt.domain.custombuttons.repository

import kotlinx.coroutines.flow.Flow
import live.mehiz.mpvkt.database.entities.CustomButtonEntity

interface CustomButtonRepository {
fun getCustomButtons(): Flow<List<CustomButtonEntity>>

suspend fun upsert(customButtonEntity: CustomButtonEntity)

suspend fun deleteAndReindex(customButtonEntity: CustomButtonEntity)

suspend fun increaseIndex(customButtonEntity: CustomButtonEntity)

suspend fun decreaseIndex(customButtonEntity: CustomButtonEntity)

suspend fun updateButton(customButtonEntity: CustomButtonEntity)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,140 @@
package live.mehiz.mpvkt.presentation.custombuttons

import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.layout.calculateEndPadding
import androidx.compose.foundation.layout.calculateStartPadding
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.LazyListState
import androidx.compose.foundation.lazy.itemsIndexed
import androidx.compose.foundation.lazy.rememberLazyListState
import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.verticalScroll
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.automirrored.filled.ArrowBack
import androidx.compose.material.icons.filled.Add
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.ExtendedFloatingActionButton
import androidx.compose.material3.Icon
import androidx.compose.material3.IconButton
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Scaffold
import androidx.compose.material3.Text
import androidx.compose.material3.TopAppBar
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalLayoutDirection
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.unit.dp
import live.mehiz.mpvkt.R
import live.mehiz.mpvkt.database.entities.CustomButtonEntity
import live.mehiz.mpvkt.presentation.custombuttons.components.CustomButtonListItem
import live.mehiz.mpvkt.ui.theme.spacing

@Suppress("ModifierNotUsedAtRoot", "ModifierMissing")
@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun CustomButtonsScreen(
buttons: List<CustomButtonEntity>,
onClickAdd: () -> Unit,
onClickRename: (CustomButtonEntity) -> Unit,
onClickDelete: (CustomButtonEntity) -> Unit,
onClickMoveUp: (CustomButtonEntity) -> Unit,
onClickMoveDown: (CustomButtonEntity) -> Unit,
onNavigateBack: () -> Unit,
) {
val lazyListState = rememberLazyListState()
Scaffold(
topBar = {
TopAppBar(
title = {
Text(stringResource(R.string.pref_custom_buttons_title))
},
navigationIcon = {
IconButton(onClick = { onNavigateBack() }) {
Icon(Icons.AutoMirrored.Default.ArrowBack, null)
}
},
)
},
floatingActionButton = {
ExtendedFloatingActionButton(
onClick = { onClickAdd() },
icon = { Icon(Icons.Filled.Add, null) },
text = { Text(text = stringResource(id = R.string.pref_custom_button_action_add)) },
)
}
) { padding ->
if (buttons.isEmpty()) {
Column(
modifier = Modifier
.padding(padding)
.fillMaxSize()
.verticalScroll(rememberScrollState())
.padding(24.dp),
horizontalAlignment = Alignment.CenterHorizontally,
verticalArrangement = Arrangement.Center,
) {
Text(
text = stringResource(id = R.string.pref_custom_button_empty),
textAlign = TextAlign.Center,
)
}
return@Scaffold
}

val layoutDirection = LocalLayoutDirection.current
CustomButtonsContent(
customButtons = buttons,
lazyListState = lazyListState,
paddingValues = PaddingValues(
top = MaterialTheme.spacing.small + padding.calculateTopPadding(),
start = MaterialTheme.spacing.medium + padding.calculateStartPadding(layoutDirection),
end = MaterialTheme.spacing.medium + padding.calculateEndPadding(layoutDirection),
bottom = padding.calculateBottomPadding(),
),
onClickRename = onClickRename,
onClickDelete = onClickDelete,
onMoveUp = onClickMoveUp,
onMoveDown = onClickMoveDown,
)
}
}

@Composable
private fun CustomButtonsContent(
customButtons: List<CustomButtonEntity>,
lazyListState: LazyListState,
paddingValues: PaddingValues,
onClickRename: (CustomButtonEntity) -> Unit,
onClickDelete: (CustomButtonEntity) -> Unit,
onMoveUp: (CustomButtonEntity) -> Unit,
onMoveDown: (CustomButtonEntity) -> Unit,
) {
LazyColumn(
state = lazyListState,
contentPadding = paddingValues,
verticalArrangement = Arrangement.spacedBy(MaterialTheme.spacing.small),
) {
itemsIndexed(
items = customButtons,
key = { _, button -> "button-${button.index}" }
) { index, button ->
CustomButtonListItem(
modifier = Modifier.animateItem(),
customButton = button,
canMoveUp = index != 0,
canMoveDown = index != customButtons.lastIndex,
onMoveUp = onMoveUp,
onMoveDown = onMoveDown,
onRename = { onClickRename(button) },
onDelete = { onClickDelete(button) },
)
}
}
}
Loading

0 comments on commit 1dead52

Please sign in to comment.