comeback commit!

This commit is contained in:
2024-12-15 21:21:44 +03:00
parent b093674b6e
commit 947ba44245
30 changed files with 793 additions and 57 deletions
@@ -8,6 +8,7 @@ import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.ui.unit.dp
import cafe.adriel.voyager.navigator.Navigator
import cafe.adriel.voyager.navigator.bottomSheet.BottomSheetNavigator
import cafe.adriel.voyager.transitions.SlideTransition
import com.megboyzz.devmenu.ui.components.MainScaffold
import com.megboyzz.devmenu.ui.screens.HomeScreen
@@ -39,7 +40,9 @@ class MainActivity : ComponentActivity() {
BottomSheetNavigator(
sheetShape = RoundedCornerShape(topStart = 10.dp, topEnd = 10.dp)
) {
Navigator(HomeScreen(::runGame))
Navigator(HomeScreen(::runGame)) {
SlideTransition(it)
}
}
}
}
@@ -3,6 +3,8 @@ package com.megboyzz.devmenu.di.components
import android.content.Context
import com.megboyzz.devmenu.di.modules.DatabaseModule
import com.megboyzz.devmenu.di.modules.RepositoryModule
import com.megboyzz.devmenu.domain.usecases.settings.GetGameLangUseCase
import com.megboyzz.devmenu.domain.usecases.settings.GetTrackingStateUseCase
import com.megboyzz.devmenu.domain.usecases.settings.RemoveGameDataUseCase
import com.megboyzz.devmenu.domain.usecases.settings.SetGameLangUseCase
import com.megboyzz.devmenu.domain.usecases.settings.SetTrackingSaveFileUseCase
@@ -24,8 +26,12 @@ interface SettingsUseCaseComponent {
val setGameLangUseCase: SetGameLangUseCase
val getGameLangUseCase: GetGameLangUseCase
val setTrackingSaveFileUseCase: SetTrackingSaveFileUseCase
val getTrackingStateUseCase: GetTrackingStateUseCase
val turnOffDevMenuUseCase: TurnOffDevMenuUseCase
@Component.Builder
@@ -29,7 +29,7 @@ class SettingsRepositoryImpl @Inject constructor(
if(!localeFile.exists()){
localeFile.createNewFile()
localeFile.writeBytes(byteArrayOf(0x02, 0x0) + systemLang.name.toByteArray())
localeFile.writeBytes(byteArrayOf(0x02, 0x0) + systemLang.lang.toByteArray())
return systemLang
}
@@ -40,7 +40,7 @@ class SettingsRepositoryImpl @Inject constructor(
if(!localeFile.exists()) localeFile.createNewFile()
localeFile.writeBytes(byteArrayOf(0x02, 0x0) + value.name.toByteArray())
localeFile.writeBytes(byteArrayOf(0x02, 0x0) + value.lang.toByteArray())
}
@@ -29,7 +29,7 @@ class TrackingRepositoryImpl @Inject constructor(
override var trackingData: TrackingData
get() {
val enabled = sharedPreferences.getBoolean(trackingStateKey, false)
val path = sharedPreferences.getString(trackingStateKey, defaultPathToSave)?: defaultPathToSave
val path = sharedPreferences.getString(trackingSavePath, defaultPathToSave)?: defaultPathToSave
return TrackingData(
enabled = enabled,
@@ -1,14 +1,19 @@
package com.megboyzz.devmenu.ui
import android.annotation.SuppressLint
import android.content.Context
import androidx.activity.ComponentActivity
import androidx.activity.OnBackPressedCallback
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.width
import androidx.compose.runtime.Composable
import androidx.compose.runtime.DisposableEffect
import androidx.compose.ui.Modifier
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.Dp
import com.megboyzz.devmenu.MainActivity
import java.text.CharacterIterator
import java.text.SimpleDateFormat
import java.text.StringCharacterIterator
@@ -46,7 +51,39 @@ fun humanReadableByteCountSI(bytes: Long): String {
fun formatDate(date: Date) = SimpleDateFormat("dd.MM.yyyy HH:mm:ss").format(date)
fun Context.getInternalAppDataDirectory() = "/data/data/${packageName}"
fun Context.getExternalAppDataDirectory(): String {
val externalDir = getExternalFilesDir(null)
return externalDir?.absolutePath ?: ""
}
@Composable
fun Context.registerOnBackPressedAction(
action: () -> Unit
) {
val context = this
DisposableEffect(this) {
val callback = object : OnBackPressedCallback(true) {
override fun handleOnBackPressed() {
action()
}
}
(context as ComponentActivity).onBackPressedDispatcher.addCallback(callback)
// Отменяем обратный вызов при уничтожении DisposableEffect
onDispose {
callback.remove()
}
}
}
/**
* ## Неработает((
*
* Возвращает лямбду того типа которая требуется в Composable контексте<br>
* например есть функция
*
@@ -62,5 +99,4 @@ fun formatDate(date: Date) = SimpleDateFormat("dd.MM.yyyy HH:mm:ss").format(date
*
* example(a = SomeType().asComposableLambda)
*/
val <T> T.asComposableLambda: @Composable () -> T
get() = { this }
val <T> T.asComposableLambda: @Composable () -> T get() = { this }
@@ -6,6 +6,7 @@ import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.unit.dp
import com.megboyzz.devmenu.ui.models.FsElementModel
@@ -13,7 +14,7 @@ import com.megboyzz.devmenu.ui.models.FsElementModel
fun FsElement(
fsElementEntity: FsElementModel,
onClick: () -> Unit,
onLongPressClick: () -> Unit
onLongPressClick: () -> Unit = {}
) {
Column(
@@ -25,6 +26,9 @@ fun FsElement(
)
) {
FsElementIcon(type = fsElementEntity.type)
Text(text = fsElementEntity.name)
Text(
text = fsElementEntity.name,
textAlign = TextAlign.Center
)
}
}
@@ -1,7 +1,6 @@
package com.megboyzz.devmenu.ui.components
import androidx.compose.foundation.Image
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.lazy.LazyRow
@@ -39,10 +38,20 @@ fun CoreBar(folders: List<String>) {
@Composable
fun StatusBarInCurFolder(
path: String
path: String,
internalPath: String = "",
externalPath: String = ""
) {
if(path.isEmpty()) return
val pathAsArray = path.split("/").filter { it.isNotEmpty() }
var pathProcess = path
if(internalPath.isNotEmpty() && pathProcess.contains(internalPath))
pathProcess = path.replace(internalPath, "Внутренее хранилище")
if(externalPath.isNotEmpty() && pathProcess.contains(externalPath))
pathProcess = path.replace(externalPath, "Внешнее хранилище")
val pathAsArray = pathProcess.split("/").filter { it.isNotEmpty() }
CoreBar(folders = pathAsArray)
}
@@ -4,6 +4,8 @@ import androidx.compose.foundation.background
import androidx.compose.foundation.border
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.ColumnScope
import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.fillMaxWidth
@@ -33,6 +35,7 @@ import androidx.compose.ui.graphics.Color
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import com.megboyzz.devmenu.ui.SpacerHeight
import com.megboyzz.devmenu.ui.theme.DevMenuTheme
@OptIn(ExperimentalMaterial3Api::class)
@@ -42,7 +45,7 @@ fun MainScaffold(
searchValue: String,
onSearchValueChanged: (String) -> Unit,
onFloatingButtonClick: () -> Unit,
content: @Composable (PaddingValues) -> Unit
content: @Composable ColumnScope.() -> Unit
) {
var searchValue by remember { mutableStateOf(searchValue) }
@@ -66,11 +69,13 @@ fun MainScaffold(
shape = CircleShape,
content = {Icon(imageVector = Icons.Default.PlayArrow, contentDescription = null)}
)
},
content = content
)
}
){
Column {
SpacerHeight(height = it.calculateTopPadding())
content()
}
}
}
@@ -1,5 +1,7 @@
package com.megboyzz.devmenu.ui.models
import com.megboyzz.devmenu.domain.entities.GameLang
enum class GameLangModel(private val lang: String) {
System("sys"),
Chinese("cn"),
@@ -16,4 +18,8 @@ enum class GameLangModel(private val lang: String) {
override fun toString() = lang
}
fun toDomain() = GameLang.values()[ordinal]
}
fun GameLang.toModel() = GameLangModel.values()[ordinal]
@@ -0,0 +1,125 @@
package com.megboyzz.devmenu.ui.screenmodels
import android.content.Context
import android.util.Log
import cafe.adriel.voyager.core.model.ScreenModel
import cafe.adriel.voyager.core.model.screenModelScope
import com.megboyzz.devmenu.ui.components.ElementType
import com.megboyzz.devmenu.ui.getExternalAppDataDirectory
import com.megboyzz.devmenu.ui.getInternalAppDataDirectory
import com.megboyzz.devmenu.ui.models.FsElementModel
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.launch
import java.io.File
import java.util.Date
class HomeScreenModel(
private val context: Context
): ScreenModel {
private val TAG = this::class.simpleName
private val internalRef = FsElementModel(
"Внутренне хранилище",
ElementType.isDir,
0,
Date()
)
private val externalRef = FsElementModel(
"Внешнее хранилище",
ElementType.isDir,
0,
Date()
)
private val rootFs = listOf(
internalRef,
externalRef
)
val currentFolder = MutableStateFlow("")
val folderFiles = MutableStateFlow(listOf(internalRef, externalRef))
private fun isRoot() = currentFolder.value.isEmpty()
private fun getElements(path: String): List<FsElementModel>{
val here = File(path)
return if(here.exists()){
here.listFiles()!!.map {
FsElementModel(
name = it.name,
type = if(it.isFile) ElementType.isFile else ElementType.isDir,
size = it.length(),
date = Date(it.lastModified())
)
}
}else listOf()
}
fun enterRoot(){
screenModelScope.launch(Dispatchers.IO) {
currentFolder.emit("")
folderFiles.emit(rootFs)
}
}
fun enterFolder(elementModel: FsElementModel) {
if(isRoot()){
when(elementModel){
externalRef -> enterTheExternal()
internalRef -> enterTheInternal()
else -> Log.e(TAG, "enterFolder: error path! ${elementModel.name}")
}
}else{
screenModelScope.launch(Dispatchers.IO) {
currentFolder.emit(currentFolder.value + "/" + elementModel.name)
folderFiles.emit(getElements(currentFolder.value))
}
}
}
fun backFolder(){
val path = File(currentFolder.value)
val internal = context.getInternalAppDataDirectory()
val external = context.getExternalAppDataDirectory()
if(path.exists()){
screenModelScope.launch(Dispatchers.IO) {
when(currentFolder.value){
context.getInternalAppDataDirectory(), context.getExternalAppDataDirectory() -> enterRoot()
else -> {
currentFolder.emit(path.parentFile.absolutePath)
folderFiles.emit(getElements(currentFolder.value))
}
}
}
}
}
private fun enterTheInternal(){
screenModelScope.launch(Dispatchers.IO) {
currentFolder.emit(context.getInternalAppDataDirectory())
folderFiles.emit(getElements(currentFolder.value))
}
}
private fun enterTheExternal(){
screenModelScope.launch(Dispatchers.IO) {
currentFolder.emit(context.getExternalAppDataDirectory())
folderFiles.emit(getElements(currentFolder.value))
}
}
}
@@ -0,0 +1,62 @@
package com.megboyzz.devmenu.ui.screenmodels
import android.content.Context
import cafe.adriel.voyager.core.model.ScreenModel
import cafe.adriel.voyager.core.model.screenModelScope
import com.megboyzz.devmenu.App
import com.megboyzz.devmenu.MainActivity
import com.megboyzz.devmenu.ui.models.GameLangModel
import com.megboyzz.devmenu.ui.models.toModel
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.launch
class SettingsScreenModel(
context: Context
): ScreenModel {
private val settingsUseCaseComponent = (((context as MainActivity).application) as App).settingsUseCaseComponent
val gameLangModel = MutableStateFlow(settingsUseCaseComponent.getGameLangUseCase().toModel())
val trackingIsEnabled = MutableStateFlow(false)
init {
screenModelScope.launch(Dispatchers.IO) {
update()
}
}
private suspend fun update() {
gameLangModel.emit(settingsUseCaseComponent.getGameLangUseCase().toModel())
trackingIsEnabled.emit(settingsUseCaseComponent.getTrackingStateUseCase().enabled)
}
fun changeLang(gameLangModel: GameLangModel){
screenModelScope.launch(Dispatchers.IO) {
settingsUseCaseComponent.setGameLangUseCase(gameLangModel.toDomain())
update()
}
}
fun changeTrackingState(state: Boolean) {
screenModelScope.launch(Dispatchers.IO) {
val trackingData = settingsUseCaseComponent.getTrackingStateUseCase()
trackingData.copy(enabled = state)
settingsUseCaseComponent.setTrackingSaveFileUseCase(trackingData.enabled, trackingData.pathToSave)
update()
}
}
fun changeTrackingPath(path: String) {
screenModelScope.launch(Dispatchers.IO) {
val state = settingsUseCaseComponent.getTrackingStateUseCase()
state.copy(pathToSave = path)
settingsUseCaseComponent.setTrackingSaveFileUseCase(state.enabled, state.pathToSave)
update()
}
}
}
@@ -2,17 +2,19 @@ package com.megboyzz.devmenu.ui.screens
import androidx.compose.runtime.Composable
import cafe.adriel.voyager.core.screen.Screen
import com.megboyzz.devmenu.ui.components.ElementType
import com.megboyzz.devmenu.ui.components.FileContextMenu
import com.megboyzz.devmenu.ui.models.FsElementModel
class FileContextMenuScreen(
private val fsElementModel: FsElementModel,
private val onRemoveFile: () -> Unit,
private val onUpdateType: (ElementType) -> ElementType?
) : Screen {
@Composable
override fun Content() = FileContextMenu(
fsElementEntity = fsElementModel,
onRemoveElement = onRemoveFile,
onUpdateType = {null}
onUpdateType = onUpdateType
)
}
@@ -1,61 +1,115 @@
package com.megboyzz.devmenu.ui.screens
import android.annotation.SuppressLint
import androidx.activity.OnBackPressedCallback
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.lazy.grid.GridCells
import androidx.compose.foundation.lazy.grid.LazyVerticalGrid
import androidx.compose.foundation.lazy.grid.items
import androidx.compose.material3.Button
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.DisposableEffect
import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.derivedStateOf
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.unit.dp
import cafe.adriel.voyager.core.model.rememberScreenModel
import cafe.adriel.voyager.core.screen.Screen
import cafe.adriel.voyager.navigator.LocalNavigator
import cafe.adriel.voyager.navigator.bottomSheet.LocalBottomSheetNavigator
import com.megboyzz.devmenu.MainActivity
import com.megboyzz.devmenu.ui.SpacerHeight
import com.megboyzz.devmenu.ui.components.ElementType
import com.megboyzz.devmenu.ui.components.FsElement
import com.megboyzz.devmenu.ui.components.MainScaffold
import com.megboyzz.devmenu.ui.components.StatusBarInCurFolder
import com.megboyzz.devmenu.ui.getExternalAppDataDirectory
import com.megboyzz.devmenu.ui.getInternalAppDataDirectory
import com.megboyzz.devmenu.ui.models.FsElementModel
import com.megboyzz.devmenu.ui.registerOnBackPressedAction
import com.megboyzz.devmenu.ui.screenmodels.HomeScreenModel
import java.util.Date
class HomeScreen(
private val onFloatingButtonClick: () -> Unit
): Screen {
@SuppressLint("StateFlowValueCalledInComposition")
@Composable
override fun Content() {
val context = LocalContext.current
val bottomSheetNavigator = LocalBottomSheetNavigator.current
var searchValue by remember{ mutableStateOf("") }
val navigator = LocalNavigator.current
val fsElementModel = FsElementModel(
name = "my_txt.txt",
type = ElementType.isHidden,
size = 123,
date = Date()
)
var searchValue by remember { mutableStateOf("") }
val homeScreenModel = rememberScreenModel { HomeScreenModel(context) }
val fsState by homeScreenModel.folderFiles.collectAsState()
val currentFolder by homeScreenModel.currentFolder.collectAsState()
context.registerOnBackPressedAction {
homeScreenModel.backFolder()
}
MainScaffold(
onSettingsClick = { /*TODO Переход к настройкам */ },
onSettingsClick = { navigator?.push(SettingsScreen()) },
onSearchValueChanged = { searchValue = it },
searchValue = searchValue,
onFloatingButtonClick = onFloatingButtonClick
) {
Column {
SpacerHeight(height = it.calculateTopPadding())
FsElement(
fsElementEntity = fsElementModel,
onClick = { /*TODO*/ },
onLongPressClick = {
bottomSheetNavigator.show(
FileContextMenuScreen(
fsElementModel = fsElementModel,
onRemoveFile = {}
)
)
}
Column(
modifier = Modifier.padding(horizontal = 20.dp)
) {
StatusBarInCurFolder(
path = currentFolder,
externalPath = context.getExternalAppDataDirectory(),
internalPath = context.getInternalAppDataDirectory()
)
}
val filteredList by remember(searchValue, fsState) {
derivedStateOf {
fsState.filter { it.name.contains(searchValue, ignoreCase = true) }
}
}
LazyVerticalGrid(
columns = GridCells.Adaptive(80.dp),
horizontalArrangement = Arrangement.spacedBy(40.dp),
verticalArrangement = Arrangement.spacedBy(40.dp),
contentPadding = PaddingValues(20.dp)
){
items(filteredList){
FsElement(
fsElementEntity = it,
onClick = { homeScreenModel.enterFolder(it) },
onLongPressClick = {
bottomSheetNavigator.show(FileContextMenuScreen(
fsElementModel = it,
onRemoveFile = {},
onUpdateType = {null}
))
}
)
}
}
}
}
@@ -1,11 +1,41 @@
package com.megboyzz.devmenu.ui.screens
import androidx.compose.runtime.Composable
import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue
import androidx.compose.ui.platform.LocalContext
import cafe.adriel.voyager.core.model.rememberScreenModel
import cafe.adriel.voyager.core.screen.Screen
import cafe.adriel.voyager.navigator.LocalNavigator
import com.megboyzz.devmenu.ui.components.SettingsScreenContent
import com.megboyzz.devmenu.ui.models.GameLangModel
import com.megboyzz.devmenu.ui.screenmodels.SettingsScreenModel
class SettingsScreen : Screen {
@Composable
override fun Content() {
TODO("Not yet implemented")
val navigator = LocalNavigator.current
val context = LocalContext.current
val settingsScreenModel = rememberScreenModel { SettingsScreenModel(context) }
val curLang by settingsScreenModel.gameLangModel.collectAsState()
val trackingEnabled by settingsScreenModel.trackingIsEnabled.collectAsState()
SettingsScreenContent(
onBackClick = { navigator?.pop() },
langModel = curLang,
onUpdateLanguage = { settingsScreenModel.changeLang(it) },
isTrackingSaveFile = trackingEnabled,
onUpdateTrackingSaveFileState = { settingsScreenModel.changeTrackingState(it) },
onChooseTrackingPathToSave = { },
onUploadSaveFile = { /*TODO*/ },
onDownloadSaveFile = { /*TODO*/ },
onDevMenuOff = {
})
}
}