cut devmenu

This commit is contained in:
2024-12-15 21:27:01 +03:00
parent b093674b6e
commit 540eb08e22
110 changed files with 4 additions and 2904 deletions
+1 -16
View File
@@ -50,21 +50,6 @@ android {
}
}
productFlavors {
flavorDimensions.add("nfsmw")
create("devmenu") {
dimension = "nfsmw"
minSdk = 21
}
create("no_devmenu") {
dimension = "nfsmw"
minSdk = 21
}
}
compileOptions {
sourceCompatibility = JavaVersion.VERSION_11
@@ -104,7 +89,7 @@ dependencies {
androidTestImplementation(libs.ui.test.junit4)
debugImplementation(libs.ui.tooling)
debugImplementation(libs.ui.test.manifest)
implementation(project(mapOf("path" to ":devmenu")))
//implementation(project(mapOf("path" to ":devmenu")))
implementation ("commons-codec:commons-codec:1.15")
-13
View File
@@ -1,13 +0,0 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
<application>
<activity android:name="com.ea.ironmonkey.GameActivity"
android:exported="true">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
</application>
</manifest>
-1
View File
@@ -1 +0,0 @@
/build
-88
View File
@@ -1,88 +0,0 @@
plugins {
alias(libs.plugins.androidLibrary)
alias(libs.plugins.kotlinAndroid)
kotlin("kapt")
//alias(libs.plugins.ksp)
}
android {
namespace = "com.megboyzz.devmenu"
compileSdk = 34
defaultConfig {
minSdk = 21
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
consumerProguardFiles("consumer-rules.pro")
vectorDrawables {
useSupportLibrary = true
}
}
buildTypes {
release {
isMinifyEnabled = false
proguardFiles(
getDefaultProguardFile("proguard-android-optimize.txt"),
"proguard-rules.pro"
)
}
}
compileOptions {
sourceCompatibility = JavaVersion.VERSION_1_8
targetCompatibility = JavaVersion.VERSION_1_8
isCoreLibraryDesugaringEnabled = true
}
kotlinOptions {
jvmTarget = "1.8"
}
buildFeatures {
compose = true
}
composeOptions {
kotlinCompilerExtensionVersion = "1.5.7"
}
packaging {
resources {
excludes += "/META-INF/{AL2.0,LGPL2.1}"
}
}
}
dependencies {
implementation(libs.core.ktx)
implementation(libs.appcompat)
implementation(libs.material)
implementation(libs.lifecycle.runtime.ktx)
implementation(libs.activity.compose)
implementation(platform(libs.compose.bom))
implementation(libs.ui)
implementation(libs.ui.graphics)
implementation(libs.ui.tooling.preview)
implementation(libs.material3)
implementation(project(mapOf("path" to ":devmenu:domain")))
testImplementation(libs.junit)
androidTestImplementation(libs.androidx.test.ext.junit)
androidTestImplementation(libs.espresso.core)
androidTestImplementation(platform(libs.compose.bom))
androidTestImplementation(libs.ui.test.junit4)
debugImplementation(libs.ui.tooling)
debugImplementation(libs.ui.test.manifest)
coreLibraryDesugaring("com.android.tools:desugar_jdk_libs:2.0.4")
implementation(libs.compose.settings.ui.m3)
implementation(libs.voyager.navigator)
implementation(libs.voyager.bottomSheetNavigator)
implementation(libs.voyager.transitions)
implementation(libs.voyager.screenModel)
implementation(libs.dagger)
kapt(libs.dagger.compiler)
implementation(libs.room)
kapt(libs.room.compiler)
}
View File
-1
View File
@@ -1 +0,0 @@
/build
-45
View File
@@ -1,45 +0,0 @@
@Suppress("DSL_SCOPE_VIOLATION") // TODO: Remove once KTIJ-19369 is fixed
plugins {
alias(libs.plugins.androidLibrary)
alias(libs.plugins.kotlinAndroid)
}
android {
namespace = "com.megboyzz.devmenu.data"
compileSdk = 34
defaultConfig {
minSdk = 21
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
consumerProguardFiles("consumer-rules.pro")
}
buildTypes {
release {
isMinifyEnabled = false
proguardFiles(
getDefaultProguardFile("proguard-android-optimize.txt"),
"proguard-rules.pro"
)
}
}
compileOptions {
sourceCompatibility = JavaVersion.VERSION_1_8
targetCompatibility = JavaVersion.VERSION_1_8
}
kotlinOptions {
jvmTarget = "1.8"
}
}
dependencies {
implementation(libs.core.ktx)
implementation(libs.appcompat)
implementation(libs.material)
implementation(project(mapOf("path" to ":devmenu:domain")))
testImplementation(libs.junit)
androidTestImplementation(libs.androidx.test.ext.junit)
androidTestImplementation(libs.espresso.core)
}
View File
-21
View File
@@ -1,21 +0,0 @@
# Add project specific ProGuard rules here.
# You can control the set of applied configuration files using the
# proguardFiles setting in build.gradle.
#
# For more details, see
# http://developer.android.com/guide/developing/tools/proguard.html
# If your project uses WebView with JS, uncomment the following
# and specify the fully qualified class name to the JavaScript interface
# class:
#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
# public *;
#}
# Uncomment this to preserve the line number information for
# debugging stack traces.
#-keepattributes SourceFile,LineNumberTable
# If you keep the line number information, uncomment this to
# hide the original source file name.
#-renamesourcefileattribute SourceFile
@@ -1,24 +0,0 @@
package com.megboyzz.devmenu.data
import androidx.test.platform.app.InstrumentationRegistry
import androidx.test.ext.junit.runners.AndroidJUnit4
import org.junit.Test
import org.junit.runner.RunWith
import org.junit.Assert.*
/**
* Instrumented test, which will execute on an Android device.
*
* See [testing documentation](http://d.android.com/tools/testing).
*/
@RunWith(AndroidJUnit4::class)
class ExampleInstrumentedTest {
@Test
fun useAppContext() {
// Context of the app under test.
val appContext = InstrumentationRegistry.getInstrumentation().targetContext
assertEquals("com.megboyzz.devmenu.data.test", appContext.packageName)
}
}
@@ -1,4 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
</manifest>
@@ -1,17 +0,0 @@
package com.megboyzz.devmenu.data
import org.junit.Test
import org.junit.Assert.*
/**
* Example local unit test, which will execute on the development machine (host).
*
* See [testing documentation](http://d.android.com/tools/testing).
*/
class ExampleUnitTest {
@Test
fun addition_isCorrect() {
assertEquals(4, 2 + 2)
}
}
-1
View File
@@ -1 +0,0 @@
/build
-22
View File
@@ -1,22 +0,0 @@
plugins {
id("java-library")
alias(libs.plugins.org.jetbrains.kotlin.jvm)
}
java {
sourceCompatibility = JavaVersion.VERSION_17
targetCompatibility = JavaVersion.VERSION_17
}
dependencies {
implementation("javax.inject:javax.inject:1")
testImplementation("junit:junit:4.13.2")
//testImplementation("org.mockito:mockito-core:5.7.0")
//testImplementation("org.mockito.kotlin:mockito-kotlin:5.7.0")
}
/*
sourceSets {
test {
java.srcDir("src/test/java") // Путь к исходным файлам тестов на Kotlin
}
}*/
@@ -1,3 +0,0 @@
package com.megboyzz.devmenu.domain.entities
val devmenuId = "DevMenu"
@@ -1,9 +0,0 @@
package com.megboyzz.devmenu.domain.entities
import java.util.Date
data class FileProps(
val fileName: String,
val size: Long,
val lastModify: Date
)
@@ -1,19 +0,0 @@
package com.megboyzz.devmenu.domain.entities
enum class GameLang(private val lang: String) {
System("sys"),
Chinese("cn"),
Dutch("nl"),
English("en"),
French("fr"),
Deutsch("de"),
Italian("it"),
Japanese("ja"),
Korean("kr"),
Portuguese("br"),
Russian("ru"),
Spanish("es");
override fun toString() = lang
}
@@ -1,8 +0,0 @@
package com.megboyzz.devmenu.domain.entities
import java.util.Date
data class SvmwProps(
val description: String,
val date: Date
)
@@ -1,6 +0,0 @@
package com.megboyzz.devmenu.domain.entities
data class TrackingData(
val enabled: Boolean,
val pathToSave: String
)
@@ -1,20 +0,0 @@
package com.megboyzz.devmenu.domain.repository
import com.megboyzz.devmenu.domain.entities.FileProps
import java.io.File
interface FilesRepository {
val internalRoot: File
val externalRoot: File
fun getFileProps(file: File): FileProps
suspend fun hideFile(file: File): Boolean
suspend fun unHideFile(file: File): Boolean
val allHidedFiles: List<File>
}
@@ -1,13 +0,0 @@
package com.megboyzz.devmenu.domain.repository
import java.io.File
interface ReplacementRepository {
suspend fun replaceFile(what: File, to: File): Result<Boolean>
suspend fun recoverReplacement(replacement: File): Result<Boolean>
suspend fun getAllReplacements(): List<File>
}
@@ -1,11 +0,0 @@
package com.megboyzz.devmenu.domain.repository
import java.io.File
interface SaveRepository {
suspend fun loadSaveFile(file: File): Boolean
suspend fun uploadCurrentSaveFile(fileToUpload: File): Boolean
}
@@ -1,9 +0,0 @@
package com.megboyzz.devmenu.domain.repository
import com.megboyzz.devmenu.domain.entities.GameLang
interface SettingsRepository {
var gameLang: GameLang
}
@@ -1,20 +0,0 @@
package com.megboyzz.devmenu.domain.repository
import com.megboyzz.devmenu.domain.entities.SvmwProps
import java.io.File
interface SvmwRepository {
suspend fun createSvmwFromCurrentSaveFileAndSetIt(): Boolean
suspend fun createSvmwFromCurrentSaveFile(fileToSave: File): Boolean
suspend fun createSvmw(fileToSave: File, saveFile: File): Boolean
suspend fun loadSvmw(svmw: File): Boolean
suspend fun getInfoAboutSvmw(svmw: File): SvmwProps?
suspend fun getInfoAboutLoadedSvmw(): SvmwProps?
}
@@ -1,13 +0,0 @@
package com.megboyzz.devmenu.domain.repository
import com.megboyzz.devmenu.domain.entities.TrackingData
interface TrackingRepository {
var trackingData: TrackingData
fun startTracking()
fun stopTracking()
}
@@ -1,12 +0,0 @@
package com.megboyzz.devmenu.domain.usecases
import com.megboyzz.devmenu.domain.entities.devmenuId
import com.megboyzz.devmenu.domain.repository.FilesRepository
import java.io.File
import javax.inject.Inject
class IsDevMenuEnabledUseCase @Inject constructor(
private val filesRepository: FilesRepository
) {
operator fun invoke() = File(filesRepository.externalRoot, devmenuId).exists()
}
@@ -1,17 +0,0 @@
package com.megboyzz.devmenu.domain.usecases
import com.megboyzz.devmenu.domain.repository.TrackingRepository
import javax.inject.Inject
class StartTrackingUseCase @Inject constructor(
private val trackingRepository: TrackingRepository
) {
operator fun invoke(){
if(trackingRepository.trackingData.enabled)
trackingRepository.startTracking()
}
}
@@ -1,11 +0,0 @@
package com.megboyzz.devmenu.domain.usecases
import com.megboyzz.devmenu.domain.repository.TrackingRepository
import javax.inject.Inject
class StopTrackingUseCase @Inject constructor(
private val trackingRepository: TrackingRepository
) {
operator fun invoke() = trackingRepository.stopTracking()
}
@@ -1,11 +0,0 @@
package com.megboyzz.devmenu.domain.usecases.files
import com.megboyzz.devmenu.domain.repository.FilesRepository
import java.io.File
import javax.inject.Inject
class GetExternalFilesUseCase @Inject constructor(
private val filesRepository: FilesRepository
) {
operator fun invoke(childPath: String = ""): File = File(filesRepository.externalRoot, childPath)
}
@@ -1,12 +0,0 @@
package com.megboyzz.devmenu.domain.usecases.files
import com.megboyzz.devmenu.domain.entities.FileProps
import com.megboyzz.devmenu.domain.repository.FilesRepository
import java.io.File
import javax.inject.Inject
class GetFilePropsUseCase @Inject constructor(
private val filesRepository: FilesRepository
) {
operator fun invoke(file: File): FileProps = filesRepository.getFileProps(file)
}
@@ -1,13 +0,0 @@
package com.megboyzz.devmenu.domain.usecases.files
import com.megboyzz.devmenu.domain.repository.FilesRepository
import java.io.File
import javax.inject.Inject
class GetInternalFilesUseCase @Inject constructor(
private val filesRepository: FilesRepository
) {
operator fun invoke(childPath: String = ""): File = File(filesRepository.internalRoot, childPath)
}
@@ -1,8 +0,0 @@
package com.megboyzz.devmenu.domain.usecases.files
import java.io.File
import javax.inject.Inject
class RemoveFileUseCase @Inject constructor() {
operator fun invoke(file: File): Boolean = file.delete()
}
@@ -1,12 +0,0 @@
package com.megboyzz.devmenu.domain.usecases.files
import java.io.File
import javax.inject.Inject
class RenameFileUseCase @Inject constructor() {
operator fun invoke(file: File, newName: String) =
if(file.exists())
file.renameTo(File(file.parent, newName))
else
false
}
@@ -1,12 +0,0 @@
package com.megboyzz.devmenu.domain.usecases.hidefiles
import com.megboyzz.devmenu.domain.repository.FilesRepository
import javax.inject.Inject
class GetAllHidedFilesUseCase @Inject constructor(
private val filesRepository: FilesRepository
) {
operator fun invoke() = filesRepository.allHidedFiles
}
@@ -1,13 +0,0 @@
package com.megboyzz.devmenu.domain.usecases.hidefiles
import com.megboyzz.devmenu.domain.repository.FilesRepository
import java.io.File
import javax.inject.Inject
class HideFileUseCase @Inject constructor(
private val filesRepository: FilesRepository
) {
suspend operator fun invoke(file: File) = filesRepository.hideFile(file)
}
@@ -1,14 +0,0 @@
package com.megboyzz.devmenu.domain.usecases.hidefiles
import javax.inject.Inject
class UnHideAllUseCase @Inject constructor(
private val unHideFileUseCase: UnHideFileUseCase,
private val getAllHidedFilesUseCase: GetAllHidedFilesUseCase
) {
suspend operator fun invoke() = getAllHidedFilesUseCase().forEach {
unHideFileUseCase(it)
}
}
@@ -1,13 +0,0 @@
package com.megboyzz.devmenu.domain.usecases.hidefiles
import com.megboyzz.devmenu.domain.repository.FilesRepository
import java.io.File
import javax.inject.Inject
class UnHideFileUseCase @Inject constructor(
private val filesRepository: FilesRepository
) {
suspend operator fun invoke(file: File) = filesRepository.unHideFile(file)
}
@@ -1,10 +0,0 @@
package com.megboyzz.devmenu.domain.usecases.replacement
import com.megboyzz.devmenu.domain.repository.ReplacementRepository
import javax.inject.Inject
class GetAllReplacedFilesUseCase @Inject constructor(
private val replacementRepository: ReplacementRepository
) {
suspend operator fun invoke() = replacementRepository.getAllReplacements()
}
@@ -1,13 +0,0 @@
package com.megboyzz.devmenu.domain.usecases.replacement
import javax.inject.Inject
class RecoverAllReplacementsUseCase @Inject constructor(
private val getAllReplacedFilesUseCase: GetAllReplacedFilesUseCase,
private val recoverReplacementUseCase: RecoverReplacementUseCase
) {
suspend operator fun invoke(){
val listReplacedFiles = getAllReplacedFilesUseCase()
listReplacedFiles.forEach { recoverReplacementUseCase(it) }
}
}
@@ -1,11 +0,0 @@
package com.megboyzz.devmenu.domain.usecases.replacement
import com.megboyzz.devmenu.domain.repository.ReplacementRepository
import java.io.File
import javax.inject.Inject
class RecoverReplacementUseCase @Inject constructor(
private val replacementRepository: ReplacementRepository
) {
suspend operator fun invoke(replacement: File) = replacementRepository.recoverReplacement(replacement)
}
@@ -1,14 +0,0 @@
package com.megboyzz.devmenu.domain.usecases.replacement
import com.megboyzz.devmenu.domain.repository.ReplacementRepository
import java.io.File
import javax.inject.Inject
class ReplaceFileUseCase @Inject constructor(
private val replacementRepository: ReplacementRepository
) {
suspend operator fun invoke(what: File, to: File): Result<Boolean>
= replacementRepository.replaceFile(what, to)
}
@@ -1,11 +0,0 @@
package com.megboyzz.devmenu.domain.usecases.save
import com.megboyzz.devmenu.domain.repository.SaveRepository
import java.io.File
import javax.inject.Inject
class LoadSaveFileUseCase @Inject constructor(
private val saveRepository: SaveRepository
) {
suspend operator fun invoke(file: File) = if(file.exists()) saveRepository.loadSaveFile(file) else false
}
@@ -1,15 +0,0 @@
package com.megboyzz.devmenu.domain.usecases.save
import com.megboyzz.devmenu.domain.repository.SaveRepository
import java.io.File
import javax.inject.Inject
class UploadSaveFileUseCase @Inject constructor(
private val saveRepository: SaveRepository
) {
suspend operator fun invoke(fileToUpload: File) =
if(fileToUpload.exists())
saveRepository.uploadCurrentSaveFile(fileToUpload)
else
false
}
@@ -1,10 +0,0 @@
package com.megboyzz.devmenu.domain.usecases.settings
import com.megboyzz.devmenu.domain.usecases.files.GetInternalFilesUseCase
import javax.inject.Inject
class RemoveGameDataUseCase @Inject constructor(
private val getInternalFilesUseCase: GetInternalFilesUseCase
) {
operator fun invoke() = getInternalFilesUseCase().listFiles()?.forEach { it.delete() }
}
@@ -1,15 +0,0 @@
package com.megboyzz.devmenu.domain.usecases.settings
import com.megboyzz.devmenu.domain.entities.GameLang
import com.megboyzz.devmenu.domain.repository.SettingsRepository
import javax.inject.Inject
class SetGameLangUseCase @Inject constructor(
private val settingsRepository: SettingsRepository
) {
operator fun invoke(gameLang: GameLang){
settingsRepository.gameLang = gameLang
}
}
@@ -1,15 +0,0 @@
package com.megboyzz.devmenu.domain.usecases.settings
import com.megboyzz.devmenu.domain.entities.TrackingData
import com.megboyzz.devmenu.domain.repository.TrackingRepository
import javax.inject.Inject
class SetTrackingSaveFileUseCase @Inject constructor(
private val trackingRepository: TrackingRepository
) {
operator fun invoke(enabled: Boolean, pathToSave: String) {
trackingRepository.trackingData = TrackingData(enabled, pathToSave)
}
}
@@ -1,12 +0,0 @@
package com.megboyzz.devmenu.domain.usecases.settings
import com.megboyzz.devmenu.domain.entities.devmenuId
import com.megboyzz.devmenu.domain.repository.FilesRepository
import java.io.File
import javax.inject.Inject
class TurnOffDevMenuUseCase @Inject constructor(
private val filesRepository: FilesRepository
) {
operator fun invoke() = File(filesRepository.externalRoot, devmenuId).delete()
}
@@ -1,27 +0,0 @@
package com.megboyzz.devmenu.domain.usecases.svmw
import com.megboyzz.devmenu.domain.repository.SvmwRepository
import java.io.File
import javax.inject.Inject
class CreateSvmwFileUseCase @Inject constructor(
private val svmwRepository: SvmwRepository
) {
suspend operator fun invoke(
fileToSave: File? = null,
saveFile: File? = null
){
val a = fileToSave != null
val b = saveFile != null
when {
a && b -> svmwRepository.createSvmw(fileToSave!!, saveFile!!)
a && !b -> svmwRepository.createSvmwFromCurrentSaveFile(fileToSave!!)
!a && !b -> svmwRepository.createSvmwFromCurrentSaveFileAndSetIt()
}
}
}
@@ -1,11 +0,0 @@
package com.megboyzz.devmenu.domain.usecases.svmw
import com.megboyzz.devmenu.domain.repository.SvmwRepository
import java.io.File
import javax.inject.Inject
class GetSvmwFilePropsUseCase @Inject constructor(
private val svmwRepository: SvmwRepository
) {
suspend operator fun invoke(file: File) = svmwRepository.getInfoAboutSvmw(file)
}
@@ -1,10 +0,0 @@
package com.megboyzz.devmenu.domain.usecases.svmw
import com.megboyzz.devmenu.domain.repository.SvmwRepository
import javax.inject.Inject
class GetSvmwPropsUseCase @Inject constructor(
private val svmwRepository: SvmwRepository
) {
suspend operator fun invoke() = svmwRepository.getInfoAboutLoadedSvmw()
}
@@ -1,11 +0,0 @@
package com.megboyzz.devmenu.domain.usecases.svmw
import com.megboyzz.devmenu.domain.repository.SvmwRepository
import java.io.File
import javax.inject.Inject
class LoadSvmwUseCase @Inject constructor(
private val svmwRepository: SvmwRepository
) {
suspend operator fun invoke(file: File) = if(file.exists()) svmwRepository.loadSvmw(file) else false
}
-21
View File
@@ -1,21 +0,0 @@
# Add project specific ProGuard rules here.
# You can control the set of applied configuration files using the
# proguardFiles setting in build.gradle.
#
# For more details, see
# http://developer.android.com/guide/developing/tools/proguard.html
# If your project uses WebView with JS, uncomment the following
# and specify the fully qualified class name to the JavaScript interface
# class:
#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
# public *;
#}
# Uncomment this to preserve the line number information for
# debugging stack traces.
#-keepattributes SourceFile,LineNumberTable
# If you keep the line number information, uncomment this to
# hide the original source file name.
#-renamesourcefileattribute SourceFile
@@ -1,24 +0,0 @@
package com.megboyzz.devmenu
import androidx.test.platform.app.InstrumentationRegistry
import androidx.test.ext.junit.runners.AndroidJUnit4
import org.junit.Test
import org.junit.runner.RunWith
import org.junit.Assert.*
/**
* Instrumented test, which will execute on an Android device.
*
* See [testing documentation](http://d.android.com/tools/testing).
*/
@RunWith(AndroidJUnit4::class)
class ExampleInstrumentedTest {
@Test
fun useAppContext() {
// Context of the app under test.
val appContext = InstrumentationRegistry.getInstrumentation().targetContext
assertEquals("com.megboyzz.devmenu.test", appContext.packageName)
}
}
-10
View File
@@ -1,10 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
<application android:name=".App">
<activity
android:name=".MainActivity"
android:exported="true"/>
</application>
</manifest>
@@ -1,35 +0,0 @@
package com.megboyzz.devmenu
import android.app.Application
import com.megboyzz.devmenu.di.components.DaggerFilesUseCaseComponent
import com.megboyzz.devmenu.di.components.DaggerHideFilesUseCaseComponent
import com.megboyzz.devmenu.di.components.DaggerReplacementUseCaseComponent
import com.megboyzz.devmenu.di.components.DaggerSettingsUseCaseComponent
import com.megboyzz.devmenu.di.components.DaggerTrackingHandlerUseCaseComponent
import com.megboyzz.devmenu.di.components.FilesUseCaseComponent
import com.megboyzz.devmenu.di.components.HideFilesUseCaseComponent
import com.megboyzz.devmenu.di.components.ReplacementUseCaseComponent
import com.megboyzz.devmenu.di.components.SettingsUseCaseComponent
import com.megboyzz.devmenu.di.components.TrackingHandlerUseCaseComponent
import kotlinx.coroutines.coroutineScope
import kotlin.concurrent.thread
class App: Application() {
lateinit var filesUseCaseComponent: FilesUseCaseComponent
lateinit var hideFilesUseCaseComponent: HideFilesUseCaseComponent
lateinit var replacementUseCaseComponent: ReplacementUseCaseComponent
lateinit var settingsUseCaseComponent: SettingsUseCaseComponent
lateinit var trackingHandlerUseCaseComponent: TrackingHandlerUseCaseComponent
override fun onCreate() {
super.onCreate()
filesUseCaseComponent = DaggerFilesUseCaseComponent.builder().context(this).build()
hideFilesUseCaseComponent = DaggerHideFilesUseCaseComponent.builder().context(this).build()
replacementUseCaseComponent = DaggerReplacementUseCaseComponent.builder().context(this).build()
settingsUseCaseComponent = DaggerSettingsUseCaseComponent.builder().context(this).build()
trackingHandlerUseCaseComponent = DaggerTrackingHandlerUseCaseComponent.builder().context(this).build()
}
}
@@ -1,46 +0,0 @@
package com.megboyzz.devmenu
import android.content.Intent
import android.os.Bundle
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
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 com.megboyzz.devmenu.ui.components.MainScaffold
import com.megboyzz.devmenu.ui.screens.HomeScreen
class MainActivity : ComponentActivity() {
private fun runGame(){
val gameActivityClass = classLoader.loadClass("com.ea.ironmonkey.GameActivity")
val intent = Intent(this, gameActivityClass)
startActivity(intent)
finish()
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
val isDevMenuEnabled = (application as App)
.trackingHandlerUseCaseComponent
.isDevMenuEnabledUseCase()
if(!isDevMenuEnabled){
runGame()
return
}
setContent {
BottomSheetNavigator(
sheetShape = RoundedCornerShape(topStart = 10.dp, topEnd = 10.dp)
) {
Navigator(HomeScreen(::runGame))
}
}
}
}
@@ -1,11 +0,0 @@
package com.megboyzz.devmenu.db
import androidx.room.Database
import androidx.room.RoomDatabase
import com.megboyzz.devmenu.db.dao.ReplacementDao
import com.megboyzz.devmenu.db.entites.Replacement
@Database(entities = [Replacement::class], version = 1, exportSchema = false)
abstract class AppDatabase: RoomDatabase() {
abstract val replacementDao: ReplacementDao
}
@@ -1,24 +0,0 @@
package com.megboyzz.devmenu.db.dao
import androidx.room.Dao
import androidx.room.Delete
import androidx.room.Insert
import androidx.room.Query
import com.megboyzz.devmenu.db.entites.Replacement
@Dao
interface ReplacementDao {
@get:Query("SELECT * from replacement")
val all: List<Replacement>
@Insert
fun addReplacement(replacement: Replacement)
@Query("SELECT * FROM replacement WHERE original_file_name=:originalFileName")
fun getReplacementByOriginalName(originalFileName: String): Replacement
@Delete
fun removeReplacement(replacement: Replacement)
}
@@ -1,18 +0,0 @@
package com.megboyzz.devmenu.db.entites
import androidx.room.ColumnInfo
import androidx.room.Entity
import androidx.room.PrimaryKey
@Entity
data class Replacement(
@PrimaryKey(autoGenerate = true)
val id: Int = 0,
@ColumnInfo(name = "original_file_name")
val originalFileName: String,
@ColumnInfo(name = "replacement_file_name")
val replacementFileName: String
)
@@ -1,42 +0,0 @@
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.files.GetExternalFilesUseCase
import com.megboyzz.devmenu.domain.usecases.files.GetFilePropsUseCase
import com.megboyzz.devmenu.domain.usecases.files.GetInternalFilesUseCase
import com.megboyzz.devmenu.domain.usecases.files.RemoveFileUseCase
import com.megboyzz.devmenu.domain.usecases.files.RenameFileUseCase
import dagger.BindsInstance
import dagger.Component
import javax.inject.Singleton
@Component(
modules = [
DatabaseModule::class,
RepositoryModule::class
]
)
@Singleton
interface FilesUseCaseComponent {
val getExternalFilesUseCase: GetExternalFilesUseCase
val getInternalFilesUseCase: GetInternalFilesUseCase
val getFilePropsUseCase: GetFilePropsUseCase
val removeFileUseCase: RemoveFileUseCase
val renameFileUseCase: RenameFileUseCase
@Component.Builder
interface Builder{
@BindsInstance
fun context(context: Context): Builder
fun build(): FilesUseCaseComponent
}
}
@@ -1,40 +0,0 @@
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.hidefiles.GetAllHidedFilesUseCase
import com.megboyzz.devmenu.domain.usecases.hidefiles.HideFileUseCase
import com.megboyzz.devmenu.domain.usecases.hidefiles.UnHideAllUseCase
import com.megboyzz.devmenu.domain.usecases.hidefiles.UnHideFileUseCase
import dagger.BindsInstance
import dagger.Component
import javax.inject.Singleton
@Component(
modules = [
DatabaseModule::class,
RepositoryModule::class
]
)
@Singleton
interface HideFilesUseCaseComponent {
val getAllHidedFilesUseCase: GetAllHidedFilesUseCase
val hideFileUseCase: HideFileUseCase
val unHideFileUseCase: UnHideFileUseCase
val unHideAllUseCase: UnHideAllUseCase
@Component.Builder
interface Builder{
@BindsInstance
fun context(context: Context): Builder
fun build(): HideFilesUseCaseComponent
}
}
@@ -1,39 +0,0 @@
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.replacement.GetAllReplacedFilesUseCase
import com.megboyzz.devmenu.domain.usecases.replacement.RecoverAllReplacementsUseCase
import com.megboyzz.devmenu.domain.usecases.replacement.RecoverReplacementUseCase
import com.megboyzz.devmenu.domain.usecases.replacement.ReplaceFileUseCase
import dagger.BindsInstance
import dagger.Component
import javax.inject.Singleton
@Component(
modules = [
DatabaseModule::class,
RepositoryModule::class
]
)
@Singleton
interface ReplacementUseCaseComponent {
val getAllReplacedFilesUseCase: GetAllReplacedFilesUseCase
val recoverAllReplacedFilesUseCase: RecoverAllReplacementsUseCase
val recoverReplacementUseCase: RecoverReplacementUseCase
val replaceFileUseCase: ReplaceFileUseCase
@Component.Builder
interface Builder{
@BindsInstance
fun context(context: Context): Builder
fun build(): ReplacementUseCaseComponent
}
}
@@ -1,39 +0,0 @@
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.RemoveGameDataUseCase
import com.megboyzz.devmenu.domain.usecases.settings.SetGameLangUseCase
import com.megboyzz.devmenu.domain.usecases.settings.SetTrackingSaveFileUseCase
import com.megboyzz.devmenu.domain.usecases.settings.TurnOffDevMenuUseCase
import dagger.BindsInstance
import dagger.Component
import javax.inject.Singleton
@Component(
modules = [
DatabaseModule::class,
RepositoryModule::class
]
)
@Singleton
interface SettingsUseCaseComponent {
val removeGameDataUseCase: RemoveGameDataUseCase
val setGameLangUseCase: SetGameLangUseCase
val setTrackingSaveFileUseCase: SetTrackingSaveFileUseCase
val turnOffDevMenuUseCase: TurnOffDevMenuUseCase
@Component.Builder
interface Builder{
@BindsInstance
fun context(context: Context): Builder
fun build(): SettingsUseCaseComponent
}
}
@@ -1,36 +0,0 @@
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.IsDevMenuEnabledUseCase
import com.megboyzz.devmenu.domain.usecases.StartTrackingUseCase
import com.megboyzz.devmenu.domain.usecases.StopTrackingUseCase
import dagger.BindsInstance
import dagger.Component
import javax.inject.Singleton
@Component(
modules = [
DatabaseModule::class,
RepositoryModule::class
]
)
@Singleton
interface TrackingHandlerUseCaseComponent {
val isDevMenuEnabledUseCase: IsDevMenuEnabledUseCase
val startTrackingUseCase: StartTrackingUseCase
val stopTrackingUseCase: StopTrackingUseCase
@Component.Builder
interface Builder{
@BindsInstance
fun context(context: Context): Builder
fun build(): TrackingHandlerUseCaseComponent
}
}
@@ -1,25 +0,0 @@
package com.megboyzz.devmenu.di.modules
import android.content.Context
import androidx.room.Room
import androidx.room.RoomDatabase
import com.megboyzz.devmenu.db.AppDatabase
import dagger.Module
import dagger.Provides
@Module
class DatabaseModule {
@Provides
fun providesDataBase(context: Context): AppDatabase =
Room.databaseBuilder(
context,
AppDatabase::class.java,
"replacement-db"
).build()
@Provides
fun providesReplacementDao(appDatabase: AppDatabase) = appDatabase.replacementDao
}
@@ -1,51 +0,0 @@
package com.megboyzz.devmenu.di.modules
import android.content.Context
import com.megboyzz.devmenu.db.dao.ReplacementDao
import com.megboyzz.devmenu.domain.repository.FilesRepository
import com.megboyzz.devmenu.domain.repository.ReplacementRepository
import com.megboyzz.devmenu.domain.repository.SaveRepository
import com.megboyzz.devmenu.domain.repository.SettingsRepository
import com.megboyzz.devmenu.domain.repository.TrackingRepository
import com.megboyzz.devmenu.repositories.FilesRepositoryImpl
import com.megboyzz.devmenu.repositories.ReplacementRepositoryImpl
import com.megboyzz.devmenu.repositories.SaveRepositoryImpl
import com.megboyzz.devmenu.repositories.SettingsRepositoryImpl
import com.megboyzz.devmenu.repositories.TrackingRepositoryImpl
import dagger.Module
import dagger.Provides
@Module
class RepositoryModule {
@Provides
fun providesFilesRepository(context: Context): FilesRepository =
FilesRepositoryImpl(context)
@Provides
fun providesReplacementRepository(
replacementDao: ReplacementDao,
filesRepository: FilesRepository
): ReplacementRepository =
ReplacementRepositoryImpl(replacementDao, filesRepository)
@Provides
fun providesSaveRepositoryImpl(
filesRepository: FilesRepository
): SaveRepository =
SaveRepositoryImpl(filesRepository)
@Provides
fun providesSettingsRepository(
filesRepository: FilesRepository
): SettingsRepository =
SettingsRepositoryImpl(filesRepository)
@Provides
fun providesTrackingRepository(
context: Context,
filesRepository: FilesRepository
): TrackingRepository =
TrackingRepositoryImpl(context, filesRepository)
}
@@ -1,32 +0,0 @@
package com.megboyzz.devmenu.repositories
import android.content.Context
import com.megboyzz.devmenu.domain.entities.FileProps
import com.megboyzz.devmenu.domain.repository.FilesRepository
import java.io.File
import java.util.Date
class FilesRepositoryImpl(
private val context: Context
): FilesRepository {
override val internalRoot = File(context.applicationInfo.dataDir)
override val externalRoot = context.getExternalFilesDir(null)!!
override fun getFileProps(file: File) = FileProps(
fileName = file.name,
size = file.length(),
lastModify = Date(file.lastModified())
)
override suspend fun hideFile(file: File): Boolean {
TODO("Not yet implemented")
}
override suspend fun unHideFile(file: File): Boolean {
TODO("Not yet implemented")
}
override val allHidedFiles: List<File>
get() = TODO("Not yet implemented")
}
@@ -1,73 +0,0 @@
package com.megboyzz.devmenu.repositories
import com.megboyzz.devmenu.db.dao.ReplacementDao
import com.megboyzz.devmenu.db.entites.Replacement
import com.megboyzz.devmenu.domain.repository.FilesRepository
import com.megboyzz.devmenu.domain.repository.ReplacementRepository
import java.io.File
import java.lang.Exception
import javax.inject.Inject
import kotlin.random.Random
class ReplacementRepositoryImpl @Inject constructor(
private val replacementDao: ReplacementDao,
private val filesRepository: FilesRepository
): ReplacementRepository {
private val folderToSaveReplacements = File("${filesRepository.internalRoot}/replacements")
private fun generateRandomPostfix(): String {
val length = 10
return (1..length)
.map { _ -> Random.nextInt(0, 36) }
.joinToString("") { charIndex -> charIndex.toString(radix = 36) }
}
override suspend fun replaceFile(what: File, to: File): Result<Boolean> {
return kotlin.runCatching {
val replacementFile =
File(folderToSaveReplacements, "replacement-${generateRandomPostfix()}")
replacementFile.createNewFile()
to.copyTo(replacementFile, overwrite = true)
what.copyTo(to, overwrite = true)
val replacement = Replacement(
originalFileName = what.absolutePath,
replacementFileName = replacementFile.absolutePath
)
replacementDao.addReplacement(replacement)
return Result.success(true)
}.onFailure {
return Result.failure(it)
}
}
override suspend fun recoverReplacement(replacement: File): Result<Boolean> {
return kotlin.runCatching {
val rep = replacementDao.getReplacementByOriginalName(replacement.absolutePath)
val repFile = File(rep.replacementFileName)
repFile.copyTo(File(rep.originalFileName), overwrite = true)
repFile.delete()
replacementDao.removeReplacement(rep)
return Result.success(true)
}.onFailure {
return Result.failure(it)
}
}
override suspend fun getAllReplacements(): List<File> {
return replacementDao.all.map {
File(it.originalFileName)
}
}
}
@@ -1,27 +0,0 @@
package com.megboyzz.devmenu.repositories
import com.megboyzz.devmenu.domain.repository.FilesRepository
import com.megboyzz.devmenu.domain.repository.SaveRepository
import java.io.File
class SaveRepositoryImpl(
private val filesRepository: FilesRepository
): SaveRepository {
private val saveFile = File(filesRepository.internalRoot, "/files/var/nfstr_save.sb")
override suspend fun loadSaveFile(file: File): Boolean {
file.copyTo(saveFile, overwrite = true)
return true
}
override suspend fun uploadCurrentSaveFile(fileToUpload: File): Boolean {
saveFile.copyTo(fileToUpload, overwrite = true)
return true
}
}
@@ -1,47 +0,0 @@
package com.megboyzz.devmenu.repositories
import android.content.res.Resources
import com.megboyzz.devmenu.domain.entities.GameLang
import com.megboyzz.devmenu.domain.repository.FilesRepository
import com.megboyzz.devmenu.domain.repository.SettingsRepository
import java.io.File
import java.nio.charset.Charset
import javax.inject.Inject
class SettingsRepositoryImpl @Inject constructor(
private val filesRepository: FilesRepository
): SettingsRepository {
private val localeFile = File(filesRepository.internalRoot, "files/var/locale")
private fun getSystemLangAsGameLang(): GameLang {
val configuration = Resources.getSystem().configuration
return GameLang.values().find { it.name == configuration.locale.language } ?: GameLang.Russian
}
override var gameLang: GameLang
get() {
val systemLang = getSystemLangAsGameLang()
if(!localeFile.exists()){
localeFile.createNewFile()
localeFile.writeBytes(byteArrayOf(0x02, 0x0) + systemLang.name.toByteArray())
return systemLang
}
val curLocale = String(localeFile.readBytes().takeLast(2).toByteArray())
return GameLang.values().find { it.name == curLocale } ?: systemLang
}
set(value) {
if(!localeFile.exists()) localeFile.createNewFile()
localeFile.writeBytes(byteArrayOf(0x02, 0x0) + value.name.toByteArray())
}
}
@@ -1,55 +0,0 @@
package com.megboyzz.devmenu.repositories
import android.content.Context
import com.megboyzz.devmenu.domain.entities.TrackingData
import com.megboyzz.devmenu.domain.repository.FilesRepository
import com.megboyzz.devmenu.domain.repository.TrackingRepository
import com.megboyzz.devmenu.util.SaveFileObserver
import javax.inject.Inject
class TrackingRepositoryImpl @Inject constructor(
private val context: Context,
private val filesRepository: FilesRepository
) : TrackingRepository {
private val TAG = this::class.simpleName
private val trackingStateKey = "tracking_enabled"
private val trackingSavePath = "tracking_save_path"
private val defaultPathToSave = "${filesRepository.externalRoot}/files/saveTracking"
private val saveFile = "${filesRepository.internalRoot}/files/var/nfstr_save.sb"
private val sharedPreferences = context.getSharedPreferences("tracking", Context.MODE_PRIVATE)
private val editor = sharedPreferences.edit()
private lateinit var saveFileObserver: SaveFileObserver
override var trackingData: TrackingData
get() {
val enabled = sharedPreferences.getBoolean(trackingStateKey, false)
val path = sharedPreferences.getString(trackingStateKey, defaultPathToSave)?: defaultPathToSave
return TrackingData(
enabled = enabled,
pathToSave = path
)
}
set(value) {
with(editor) {
putBoolean(trackingStateKey, value.enabled)
putString(trackingSavePath, value.pathToSave)
apply()
}
}
override fun startTracking() {
saveFileObserver = SaveFileObserver(saveFile, trackingData.pathToSave)
saveFileObserver.startWatching()
}
override fun stopTracking() {
saveFileObserver.stopWatching()
}
}
@@ -1,66 +0,0 @@
package com.megboyzz.devmenu.ui
import android.annotation.SuppressLint
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.ui.Modifier
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.Dp
import java.text.CharacterIterator
import java.text.SimpleDateFormat
import java.text.StringCharacterIterator
import java.util.Date
@Composable
fun Int.asPainter() = painterResource(this)
@Composable
fun Int.asString() = stringResource(this)
@Composable
fun SpacerWidth(width: Dp) = Spacer(Modifier.width(width))
@Composable
fun SpacerHeight(height: Dp) = Spacer(Modifier.height(height))
fun humanReadableByteCountSI(bytes: Long): String {
var b = bytes
if (-1000 < b && b < 1000) {
return "$b B"
}
val ci: CharacterIterator = StringCharacterIterator("kMGTPE")
while (b <= -999950 || b >= 999950) {
b /= 1000
ci.next()
}
return String.format("%.1f %cB", b / 1000.0, ci.current())
}
@SuppressLint("SimpleDateFormat")
fun formatDate(date: Date) = SimpleDateFormat("dd.MM.yyyy HH:mm:ss").format(date)
/**
* Возвращает лямбду того типа которая требуется в Composable контексте<br>
* например есть функция
*
* fun example(
* a: @Composable () -> SomeType
* )
*
* Чтобы предать туда параметр нужно обернуть это в скобки<br>
*
* example(a = { SomeType() })
*
* а можно не создавать еще один уровень вложения и использовать asComposableLambda
*
* example(a = SomeType().asComposableLambda)
*/
val <T> T.asComposableLambda: @Composable () -> T
get() = { this }
@@ -1,28 +0,0 @@
package com.megboyzz.devmenu.ui.components
import androidx.compose.animation.core.tween
import androidx.compose.animation.expandIn
import androidx.compose.animation.shrinkOut
import androidx.compose.animation.slideInVertically
import androidx.compose.animation.slideOutVertically
import androidx.compose.ui.Alignment
val enter = expandIn(
expandFrom = Alignment.CenterStart,
animationSpec = tween(250)
)
val exit = shrinkOut(
shrinkTowards = Alignment.CenterStart,
animationSpec = tween(250)
)
fun searchFieldEnter(duration: Int) = slideInVertically(animationSpec = tween(durationMillis = duration)) {
-it / 3
}
fun searchFieldExit(duration: Int) = slideOutVertically(animationSpec = tween(durationMillis = duration)) {
-it / 3
}
@@ -1,290 +0,0 @@
package com.megboyzz.devmenu.ui.components
import androidx.compose.animation.AnimatedVisibility
import androidx.compose.foundation.Image
import androidx.compose.foundation.background
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.Row
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.layout.wrapContentHeight
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material3.Button
import androidx.compose.material3.Divider
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
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.draw.shadow
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.painter.Painter
import androidx.compose.ui.unit.dp
import com.megboyzz.devmenu.R
import com.megboyzz.devmenu.ui.SpacerHeight
import com.megboyzz.devmenu.ui.asPainter
import com.megboyzz.devmenu.ui.models.FsElementModel
import com.megboyzz.devmenu.ui.formatDate
import com.megboyzz.devmenu.ui.humanReadableByteCountSI
@Composable
fun ClickableMenuPosition(
name: String,
icon: Painter,
onClick: () -> Unit = {},
withEnd: Boolean = true
) {
Row(
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.SpaceBetween,
modifier = Modifier
.clickableWithRipple(onClick = onClick, enabled = withEnd)
.padding(4.dp)
.fillMaxWidth()
) {
CoreMenuPosition(name = name, icon = icon)
if (withEnd) Image(painter = R.drawable.enter.asPainter(), contentDescription = null)
}
}
@Composable
fun CoreMenuPosition(
name: String,
icon: Painter
) {
Row(
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.spacedBy(20.dp),
) {
Image(
painter = icon,
contentDescription = null,
Modifier.size(24.dp)
)
Text(text = name)
}
}
@Composable
fun FsElementInContextMenu(
name: String,
type: ElementType,
) {
Row(
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.spacedBy(20.dp)
) {
val modifier = Modifier
.size(36.dp)
.clip(RoundedCornerShape(10.dp))
.shadow(elevation = 6.dp)
FsElementIcon(type, modifier)
Text(
text = name
)
}
}
enum class ContextMenuState {
Main,
Delete,
Props
}
@Composable
fun FileContextMenu(
fsElementEntity: FsElementModel,
onRemoveElement: () -> Unit,
onUpdateType: (ElementType) -> ElementType?
) {
var innerType by remember { mutableStateOf(fsElementEntity.type) }
var state by remember { mutableStateOf(ContextMenuState.Main) }
CoreFileContextMenu(fsElementEntity = fsElementEntity) {
AnimatedBoxForContextMenuContent(isVisible = state == ContextMenuState.Main){
AnimatedBoxForContextMenuContent(isVisible = innerType == ElementType.isFile) {
ClickableMenuPosition(
name = "Заменить",
icon = R.drawable.star.asPainter(),
onClick = {
innerType = onUpdateType(ElementType.isReplaced) ?: ElementType.isReplaced
}
)
ClickableMenuPosition(
name = "Скрыть для игры",
icon = R.drawable.hide.asPainter(),
onClick = {
innerType = onUpdateType(ElementType.isHidden) ?: ElementType.isHidden
}
)
}
AnimatedBoxForContextMenuContent(isVisible = innerType == ElementType.isHidden) {
ClickableMenuPosition(
name = "Показать игре",
icon = R.drawable.unhide.asPainter(),
onClick = {
innerType = onUpdateType(ElementType.isFile) ?: ElementType.isFile
}
)
}
AnimatedBoxForContextMenuContent(isVisible = innerType == ElementType.isReplaced) {
ClickableMenuPosition(
name = "Воостановить",
icon = R.drawable.star.asPainter(),
onClick = {
innerType = onUpdateType(ElementType.isFile) ?: ElementType.isFile
}
)
}
ClickableMenuPosition(
name = "Свойства",
icon = R.drawable.info.asPainter(),
onClick = { state = ContextMenuState.Props }
)
ClickableMenuPosition(
name = "Удалить",
icon = R.drawable.trash.asPainter(),
onClick = { state = ContextMenuState.Delete }
)
}
AnimatedBoxForContextMenuContent(isVisible = state == ContextMenuState.Delete) {
ClickableMenuPosition(
name = "Удалить",
icon = R.drawable.trash.asPainter(),
withEnd = false
)
RemoveFileButtonsGroup(
onDelete = onRemoveElement,
onCancel = { state = ContextMenuState.Main }
)
}
AnimatedBoxForContextMenuContent(isVisible = state == ContextMenuState.Props) {
ClickableMenuPosition(
name = "Свойства",
icon = R.drawable.info.asPainter(),
withEnd = false,
)
PropsView(fsElementEntity = fsElementEntity)
RemoveButton(text = "Назад") {
state = ContextMenuState.Main
}
}
}
}
@Composable
fun PropsView(
fsElementEntity: FsElementModel
) {
Column(verticalArrangement = Arrangement.spacedBy(12.dp)) {
Text(text = "Изменен: ${formatDate(fsElementEntity.date)}")
Text(text = "Размер: ${humanReadableByteCountSI(fsElementEntity.size)} (${fsElementEntity.size} B)")
}
}
@Composable
fun AnimatedBoxForContextMenuContent(
isVisible: Boolean,
content: @Composable ColumnScope.() -> Unit
) {
AnimatedVisibility(
visible = isVisible,
enter = enter,
exit = exit
) {
Column(
verticalArrangement = Arrangement.spacedBy(12.dp),
content = content
)
}
}
@Composable
fun RemoveFileButtonsGroup(
onDelete: () -> Unit,
onCancel: () -> Unit
) {
Row(
horizontalArrangement = Arrangement.spacedBy(12.dp),
) {
RemoveButton(text = "Удалить", onClick = onDelete, modifier = Modifier.weight(1f))
RemoveButton(text = "Отмена", onClick = onCancel, modifier = Modifier.weight(1f))
}
}
@Composable
fun RemoveButton(
text: String,
modifier: Modifier = Modifier,
onClick: () -> Unit
) {
Button(
onClick = onClick,
shape = RoundedCornerShape(10.dp),
modifier = modifier
) {
Text(text = text)
}
}
@Composable
fun CoreFileContextMenu(
fsElementEntity: FsElementModel,
content: @Composable () -> Unit
) {
val shape = RoundedCornerShape(topStart = 10.dp, topEnd = 10.dp)
Box(
modifier = Modifier
.wrapContentHeight()
.fillMaxWidth()
//TODO отвязать от цвета
.clip(shape)
.background(Color.White),
contentAlignment = Alignment.TopCenter
) {
Image(
painter = R.drawable.clip.asPainter(),
contentDescription = null,
modifier = Modifier.padding(top = 8.dp)
)
Box(
modifier = Modifier.padding(
horizontal = 28.dp,
vertical = 32.dp
),
){
Column {
Column(verticalArrangement = Arrangement.spacedBy(16.dp)) {
FsElementInContextMenu(name = fsElementEntity.name, type = fsElementEntity.type)
Divider()
SpacerHeight(height = 0.dp)
}
content()
}
}
}
}
@@ -1,30 +0,0 @@
package com.megboyzz.devmenu.ui.components
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.dp
import com.megboyzz.devmenu.ui.models.FsElementModel
@Composable
fun FsElement(
fsElementEntity: FsElementModel,
onClick: () -> Unit,
onLongPressClick: () -> Unit
) {
Column(
verticalArrangement = Arrangement.spacedBy(10.dp),
horizontalAlignment = Alignment.CenterHorizontally,
modifier = Modifier.clickableWithRipple(
onClick = onClick,
onLongClick = onLongPressClick
)
) {
FsElementIcon(type = fsElementEntity.type)
Text(text = fsElementEntity.name)
}
}
@@ -1,55 +0,0 @@
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
import androidx.compose.foundation.lazy.items
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import com.megboyzz.devmenu.R
import com.megboyzz.devmenu.ui.asPainter
@Composable
fun CoreBar(folders: List<String>) {
LazyRow(verticalAlignment = Alignment.CenterVertically) {
items(folders){
Text(
text = it,
style = MaterialTheme.typography.labelSmall
)
Image(
painter = R.drawable.enter.asPainter(),
modifier = Modifier
.padding(4.dp)
.size(8.dp),
contentDescription = null
)
}
}
}
@Composable
fun StatusBarInCurFolder(
path: String
) {
if(path.isEmpty()) return
val pathAsArray = path.split("/").filter { it.isNotEmpty() }
CoreBar(folders = pathAsArray)
}
@Preview
@Composable
fun CoreBarPrev() {
val path = "/data/data/com.ea.games.nfs13_na/files/var/".split("/").filter { it.isNotEmpty() }
CoreBar(folders = path)
}
@@ -1,172 +0,0 @@
package com.megboyzz.devmenu.ui.components
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.PaddingValues
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.shape.CircleShape
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.foundation.text.BasicTextField
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.PlayArrow
import androidx.compose.material.icons.outlined.Search
import androidx.compose.material.icons.outlined.Settings
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.FloatingActionButton
import androidx.compose.material3.Icon
import androidx.compose.material3.IconButton
import androidx.compose.material3.Scaffold
import androidx.compose.material3.Text
import androidx.compose.material3.TopAppBar
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
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.theme.DevMenuTheme
@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun MainScaffold(
onSettingsClick: () -> Unit,
searchValue: String,
onSearchValueChanged: (String) -> Unit,
onFloatingButtonClick: () -> Unit,
content: @Composable (PaddingValues) -> Unit
) {
var searchValue by remember { mutableStateOf(searchValue) }
Scaffold(
topBar = {
TopBar(
searchValue = searchValue,
onSearchValueChanged = {
searchValue = it
onSearchValueChanged(it)
},
onSettingsClick = onSettingsClick
)
},
floatingActionButton = {
FloatingActionButton(
onClick = onFloatingButtonClick,
shape = CircleShape,
content = {Icon(imageVector = Icons.Default.PlayArrow, contentDescription = null)}
)
},
content = content
)
}
@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun TopBar(
searchValue: String,
onSearchValueChanged: (String) -> Unit,
onSettingsClick: () -> Unit
) {
var isSearching by remember { mutableStateOf(false) }
val context = LocalContext.current
TopAppBar(
title = {
if(isSearching){
SearchFileTextField(
value = searchValue,
onValueChange = onSearchValueChanged
)
}else Text("NFSMW DevMenu")
},
actions = {
SearchButton {
isSearching = !isSearching
}
SettingsButton(onClick = onSettingsClick)
},
)
}
@Composable
fun SettingsButton(
onClick: () -> Unit
) {
IconButton(onClick) {
Icon(
imageVector = Icons.Outlined.Settings,
contentDescription = null
)
}
}
@Composable
fun SearchButton(
onClick: () -> Unit
) {
IconButton(onClick) {
Icon(
imageVector = Icons.Outlined.Search,
contentDescription = null
)
}
}
@Preview
@Composable
fun ScaffoldPreview() {
DevMenuTheme {
MainScaffold(
onSettingsClick = { /*TODO*/ },
onSearchValueChanged = {},
searchValue = "",
onFloatingButtonClick = { /*TODO*/ }
) {
}
}
}
@Composable
fun SearchFileTextField(
value: String,
onValueChange: (String) -> Unit
) {
BasicTextField(
value = value,
onValueChange = onValueChange,
decorationBox = { innerTextField ->
Box(Modifier.padding(end = 12.dp)) {
Row(
Modifier
.fillMaxWidth()
.background(Color.White, RoundedCornerShape(percent = 30))
.border(1.dp, Color.Black, RoundedCornerShape(percent = 30))
.padding(start = 12.dp),
horizontalArrangement = Arrangement.SpaceBetween,
verticalAlignment = Alignment.CenterVertically
) {
Box(Modifier.padding(vertical = 4.dp)) {
innerTextField()
}
}
}
},
)
}
@@ -1,211 +0,0 @@
package com.megboyzz.devmenu.ui.components
import android.media.audiofx.Equalizer.Settings
import androidx.compose.foundation.layout.Arrangement
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.material.icons.Icons
import androidx.compose.material.icons.filled.ArrowBack
import androidx.compose.material.icons.filled.Settings
import androidx.compose.material.icons.sharp.Home
import androidx.compose.material.icons.sharp.List
import androidx.compose.material.icons.sharp.Notifications
import androidx.compose.material3.AlertDialog
import androidx.compose.material3.Divider
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.Icon
import androidx.compose.material3.IconButton
import androidx.compose.material3.Scaffold
import androidx.compose.material3.Text
import androidx.compose.material3.TextButton
import androidx.compose.material3.TopAppBar
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
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.alorma.compose.settings.storage.base.getValue
import com.alorma.compose.settings.storage.base.rememberBooleanSettingState
import com.alorma.compose.settings.storage.base.rememberIntSettingState
import com.alorma.compose.settings.ui.SettingsCheckbox
import com.alorma.compose.settings.ui.SettingsList
import com.alorma.compose.settings.ui.SettingsMenuLink
import com.alorma.compose.settings.ui.SettingsSwitch
import com.megboyzz.devmenu.R
import com.megboyzz.devmenu.ui.SpacerHeight
import com.megboyzz.devmenu.ui.asPainter
import com.megboyzz.devmenu.ui.models.GameLangModel
@Composable
fun SettingsScaffold(
onBackClick: () -> Unit,
content: @Composable ColumnScope.() -> Unit
) {
Scaffold(
topBar = { SettingsTopBar(onBackClick = onBackClick) },
){
Column {
SpacerHeight(height = it.calculateTopPadding())
content(this)
}
}
}
@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun SettingsTopBar(
onBackClick: () -> Unit
) {
TopAppBar(
navigationIcon = {
IconButton(onClick = onBackClick) {
Icon(imageVector = Icons.Filled.ArrowBack, contentDescription = null)
}
},
title = { Text("Настройки") }
)
}
@Composable
fun LangDialog(
langModel: GameLangModel,
onUpdateLanguage: (GameLangModel) -> Unit
) {
val items = GameLangModel.values().map { it.name }
val curItem = items.indexOf(langModel.name)
val lang = rememberIntSettingState(curItem)
SettingsList(
icon = { Icon(painter = R.drawable.lang.asPainter(), contentDescription = null) },
title = { Text(text = "Сменить язык") },
items = items,
state = lang,
onItemSelected = { index, _ ->
onUpdateLanguage(GameLangModel.values()[index])
}
)
}
@Composable
fun DevMenuOffDialog(
onDevMenuOff: () -> Unit,
onDismiss: () -> Unit,
) {
AlertDialog(
title = {
Text(text = "Выключить DevMenu")
},
text = {
Text(text = "Уверен что хочешь выключить DevMenu? Чтобы его включить нужно будет создать пустой файл c именем DevMenu в корне кэша игры")
},
onDismissRequest = onDismiss,
confirmButton = {
TextButton(
onClick = onDevMenuOff
) {
Text("OK")
}
},
dismissButton = {
TextButton(
onClick = onDismiss
) {
Text("Отмена")
}
}
)
}
@Composable
fun SettingsScreenContent(
onBackClick: () -> Unit,
langModel: GameLangModel,
onUpdateLanguage: (GameLangModel) -> Unit,
isTrackingSaveFile: Boolean,
onUpdateTrackingSaveFileState: (Boolean) -> Unit,
onChooseTrackingPathToSave: () -> Unit,
onUploadSaveFile: () -> Unit,
onDownloadSaveFile: () -> Unit,
onDevMenuOff: () -> Unit
) {
var isDevMenuOffDialog by remember { mutableStateOf(false) }
if(isDevMenuOffDialog){
DevMenuOffDialog(
onDevMenuOff = {
onDevMenuOff()
isDevMenuOffDialog =! isDevMenuOffDialog
},
onDismiss = { isDevMenuOffDialog =! isDevMenuOffDialog }
)
}
SettingsScaffold(onBackClick = onBackClick) {
LangDialog(langModel = langModel, onUpdateLanguage = onUpdateLanguage)
Divider()
SettingsSwitch(
icon = {
Icon(painter = R.drawable.tracking.asPainter(), contentDescription = null)
},
title = {
Text(text = "Отслеживание файла сохранения")
},
state = rememberBooleanSettingState(isTrackingSaveFile),
onCheckedChange = onUpdateTrackingSaveFileState
)
SettingsMenuLink(
icon = { Icon(painter = R.drawable.folder.asPainter(), contentDescription = null) },
title = { Text(text = "Выбрать путь сохранения отслеживаемого сохранения") },
onClick = onChooseTrackingPathToSave,
)
Divider()
SettingsMenuLink(
icon = { Icon(painter = R.drawable.download.asPainter(), contentDescription = null) },
title = { Text(text = "Выгрузить файл сохранения") },
subtitle = { Text(text = "Получить текущий nfstr_save.sb") },
onClick = onDownloadSaveFile,
)
SettingsMenuLink(
icon = { Icon(painter = R.drawable.upload.asPainter(), contentDescription = null) },
title = { Text(text = "Загрузить файл сохранения") },
subtitle = { Text(text = "Загрузить в игру nfstr_save.sb") },
onClick = onUploadSaveFile,
)
Divider()
SettingsMenuLink(
title = {
Text(
text = "Выключить DevMenu",
color = Color.Red
)
},
onClick = { isDevMenuOffDialog =! isDevMenuOffDialog },
)
}
}
@Preview
@Composable
fun SettingsTopBarPreview() {
SettingsScreenContent(
onBackClick = { /*TODO*/ },
langModel = GameLangModel.System,
onUpdateLanguage = {},
isTrackingSaveFile = false,
onUpdateTrackingSaveFileState = {},
onChooseTrackingPathToSave = { /*TODO*/ },
onUploadSaveFile = { /*TODO*/ },
onDownloadSaveFile = { /*TODO*/ },
onDevMenuOff = {})
}
@@ -1,55 +0,0 @@
package com.megboyzz.devmenu.ui.components
import android.annotation.SuppressLint
import androidx.compose.foundation.ExperimentalFoundationApi
import androidx.compose.foundation.Image
import androidx.compose.foundation.combinedClickable
import androidx.compose.foundation.interaction.MutableInteractionSource
import androidx.compose.material.ripple.rememberRipple
import androidx.compose.runtime.Composable
import androidx.compose.runtime.remember
import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.Dp
import com.megboyzz.devmenu.R
import com.megboyzz.devmenu.ui.asPainter
enum class ElementType {
isHidden,
isReplaced,
isFile,
isDir
}
@SuppressLint("ModifierFactoryUnreferencedReceiver")
@OptIn(ExperimentalFoundationApi::class)
@Composable
fun Modifier.clickableWithRipple(
onClick: () -> Unit,
onLongClick: () -> Unit = {},
enabled: Boolean = true
): Modifier {
return Modifier.combinedClickable(
enabled = enabled,
interactionSource = remember { MutableInteractionSource() },
indication = rememberRipple(),
onClick = onClick,
onLongClick = onLongClick
)
}
@Composable
fun FsElementIcon(
type: ElementType,
modifier: Modifier = Modifier
) {
val painter = when(type){
ElementType.isHidden -> R.drawable.icon_hidden_file.asPainter()
ElementType.isFile -> R.drawable.icon_file.asPainter()
ElementType.isDir -> R.drawable.icon_folder.asPainter()
ElementType.isReplaced -> R.drawable.icon_replaced_file.asPainter()
}
Image(painter = painter, contentDescription = null, modifier)
}
@@ -1,11 +0,0 @@
package com.megboyzz.devmenu.ui.models
import com.megboyzz.devmenu.ui.components.ElementType
import java.util.Date
data class FsElementModel(
val name: String,
val type: ElementType,
val size: Long,
val date: Date
)
@@ -1,19 +0,0 @@
package com.megboyzz.devmenu.ui.models
enum class GameLangModel(private val lang: String) {
System("sys"),
Chinese("cn"),
Dutch("nl"),
English("en"),
French("fr"),
Deutsch("de"),
Italian("it"),
Japanese("ja"),
Korean("kr"),
Portuguese("br"),
Russian("ru"),
Spanish("es");
override fun toString() = lang
}
@@ -1,18 +0,0 @@
package com.megboyzz.devmenu.ui.screens
import androidx.compose.runtime.Composable
import cafe.adriel.voyager.core.screen.Screen
import com.megboyzz.devmenu.ui.components.FileContextMenu
import com.megboyzz.devmenu.ui.models.FsElementModel
class FileContextMenuScreen(
private val fsElementModel: FsElementModel,
private val onRemoveFile: () -> Unit,
) : Screen {
@Composable
override fun Content() = FileContextMenu(
fsElementEntity = fsElementModel,
onRemoveElement = onRemoveFile,
onUpdateType = {null}
)
}
@@ -1,63 +0,0 @@
package com.megboyzz.devmenu.ui.screens
import androidx.compose.foundation.layout.Column
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import cafe.adriel.voyager.core.screen.Screen
import cafe.adriel.voyager.navigator.bottomSheet.LocalBottomSheetNavigator
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.models.FsElementModel
import java.util.Date
class HomeScreen(
private val onFloatingButtonClick: () -> Unit
): Screen {
@Composable
override fun Content() {
val bottomSheetNavigator = LocalBottomSheetNavigator.current
var searchValue by remember{ mutableStateOf("") }
val fsElementModel = FsElementModel(
name = "my_txt.txt",
type = ElementType.isHidden,
size = 123,
date = Date()
)
MainScaffold(
onSettingsClick = { /*TODO Переход к настройкам */ },
onSearchValueChanged = { searchValue = it },
searchValue = searchValue,
onFloatingButtonClick = onFloatingButtonClick
) {
Column {
SpacerHeight(height = it.calculateTopPadding())
FsElement(
fsElementEntity = fsElementModel,
onClick = { /*TODO*/ },
onLongPressClick = {
bottomSheetNavigator.show(
FileContextMenuScreen(
fsElementModel = fsElementModel,
onRemoveFile = {}
)
)
}
)
}
}
}
}
@@ -1,11 +0,0 @@
package com.megboyzz.devmenu.ui.screens
import androidx.compose.runtime.Composable
import cafe.adriel.voyager.core.screen.Screen
class SettingsScreen : Screen {
@Composable
override fun Content() {
TODO("Not yet implemented")
}
}
@@ -1,11 +0,0 @@
package com.megboyzz.devmenu.ui.theme
import androidx.compose.ui.graphics.Color
val Purple80 = Color(0xFFD0BCFF)
val PurpleGrey80 = Color(0xFFCCC2DC)
val Pink80 = Color(0xFFEFB8C8)
val Purple40 = Color(0xFF6650a4)
val PurpleGrey40 = Color(0xFF625b71)
val Pink40 = Color(0xFF7D5260)
@@ -1,68 +0,0 @@
package com.megboyzz.devmenu.ui.theme
import android.app.Activity
import android.os.Build
import androidx.compose.foundation.isSystemInDarkTheme
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.darkColorScheme
import androidx.compose.material3.dynamicDarkColorScheme
import androidx.compose.material3.dynamicLightColorScheme
import androidx.compose.material3.lightColorScheme
import androidx.compose.runtime.Composable
import androidx.compose.runtime.SideEffect
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.toArgb
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.platform.LocalView
import androidx.core.view.WindowCompat
private val DarkColorScheme = darkColorScheme(
primary = Purple80,
secondary = PurpleGrey80,
tertiary = Pink80
)
private val LightColorScheme = lightColorScheme(
primary = Color(0xFF36A0EA),
secondary = PurpleGrey40,
tertiary = Pink40,
background = Color(0xFFF2F7FA),
surface = Color(0xFFFFFBFE),
onPrimary = Color.White,
onSecondary = Color.White,
onTertiary = Color.White,
onBackground = Color(0xFF1B3D54),
onSurface = Color(0xFF1C1B1F),
)
@Composable
fun DevMenuTheme(
darkTheme: Boolean = isSystemInDarkTheme(),
// Dynamic color is available on Android 12+
dynamicColor: Boolean = true,
content: @Composable () -> Unit
) {
val colorScheme = when {
dynamicColor && Build.VERSION.SDK_INT >= Build.VERSION_CODES.S -> {
val context = LocalContext.current
if (darkTheme) dynamicDarkColorScheme(context) else dynamicLightColorScheme(context)
}
darkTheme -> DarkColorScheme
else -> LightColorScheme
}
val view = LocalView.current
if (!view.isInEditMode) {
SideEffect {
val window = (view.context as Activity).window
window.statusBarColor = colorScheme.primary.toArgb()
WindowCompat.getInsetsController(window, view).isAppearanceLightStatusBars = darkTheme
}
}
MaterialTheme(
colorScheme = colorScheme,
typography = Typography,
content = content
)
}
@@ -1,32 +0,0 @@
package com.megboyzz.devmenu.ui.theme
import androidx.compose.material3.Typography
import androidx.compose.ui.text.TextStyle
import androidx.compose.ui.text.font.FontFamily
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.unit.sp
// Set of Material typography styles to start with
val Typography = Typography(
bodyLarge = TextStyle(
fontFamily = FontFamily.Default,
fontWeight = FontWeight.Normal,
fontSize = 16.sp,
lineHeight = 24.sp,
letterSpacing = 0.5.sp
),
titleLarge = TextStyle(
fontFamily = FontFamily.Default,
fontWeight = FontWeight.Normal,
fontSize = 22.sp,
lineHeight = 28.sp,
letterSpacing = 0.sp
),
labelSmall = TextStyle(
fontFamily = FontFamily.Default,
fontWeight = FontWeight.Medium,
fontSize = 11.sp,
lineHeight = 16.sp,
letterSpacing = 0.5.sp
)
)
@@ -1,90 +0,0 @@
package com.megboyzz.devmenu.util
import android.os.FileObserver
import android.util.Log
import java.io.File
import java.io.FileInputStream
import java.io.FileOutputStream
import java.util.LinkedList
import java.util.Queue
import java.util.zip.ZipEntry
import java.util.zip.ZipOutputStream
abstract class BaseObserver(source: String) : FileObserver(source) {
private val eventQueue : Queue<Int> = LinkedList()
@OptIn(ExperimentalStdlibApi::class)
final override fun onEvent(event: Int, path: String?) {
val e = FileEvent.entries.find { it.value == event }
val fileEvent = e ?: FileEvent.UNKNOWN
Log.i("BaseObserver", "event code = ${event.toHexString()}, FileEvent is $fileEvent")
onEvent(fileEvent, path)
}
private fun hasLastThreeEvents() = runCatching {
eventQueue.size == 3 && eventQueue.poll() == OPEN && eventQueue.poll() == ACCESS && eventQueue.poll() == CLOSE_NOWRITE
}.getOrNull() ?: false
abstract fun onEvent(event: FileEvent, path: String?)
protected fun File.list(regex: String = ".") =
this.list { dir, _ -> Regex(regex).matches(dir.absolutePath) }?.asList() ?: listOf()
protected infix fun File.copyToThe(destination: File){
this.copyTo(destination, overwrite = true)
}
protected fun zipAll(outputFolder: String, filesToCompress: List<String>) {
if(filesToCompress.isEmpty()){
Log.i("BaseObserver", "no files exit!")
return
}
try {
// Проверяем, существует ли выходная папка. Если нет, создаем её.
val outputDir = File(outputFolder)
if (!outputDir.exists()) outputDir.mkdirs()
// Генерируем имя ZIP-архива на основе времени
val timestamp = System.currentTimeMillis()
val zipFileName = "$outputFolder/saves_$timestamp.zip"
val zipOutputStream = ZipOutputStream(FileOutputStream(zipFileName))
for (fileToCompress in filesToCompress) {
// Проверяем, существует ли файл
val inputFile = File(fileToCompress)
if (inputFile.exists()) {
// Создаем объект ZipEntry для каждого файла
val zipEntry = ZipEntry(inputFile.name)
zipOutputStream.putNextEntry(zipEntry)
// Читаем содержимое файла и записываем его в ZIP-архив
val fileInputStream = FileInputStream(inputFile)
val buffer = ByteArray(1024)
var len: Int
while (fileInputStream.read(buffer).also { len = it } > 0) {
zipOutputStream.write(buffer, 0, len)
}
fileInputStream.close()
// Закрываем текущий ZipEntry
zipOutputStream.closeEntry()
} else {
Log.i("SaveFileObserver", "Файл не существует: $fileToCompress")
}
}
// Закрываем ZipOutputStream
zipOutputStream.close()
Log.i("SaveFileObserver", "Архивация завершена. Архив создан: $zipFileName")
} catch (e: Exception) {
e.printStackTrace()
}
}
}
@@ -1,22 +0,0 @@
package com.megboyzz.devmenu.util
import android.os.FileObserver
enum class FileEvent(val value: Int) {
ACCESS(FileObserver.ACCESS),
MODIFY(FileObserver.MODIFY),
ATTRIB(FileObserver.ATTRIB),
CLOSE_WRITE(FileObserver.CLOSE_WRITE),
CLOSE_NOWRITE(FileObserver.CLOSE_NOWRITE),
OPEN(FileObserver.OPEN),
MOVED_FROM(FileObserver.MOVED_FROM),
MOVED_TO(FileObserver.MOVED_TO),
CREATE(FileObserver.CREATE),
DELETE(FileObserver.DELETE),
DELETE_SELF(FileObserver.DELETE_SELF),
MOVE_SELF(FileObserver.MOVE_SELF),
UNKNOWN(-1);
override fun toString() = this.name
}
@@ -1,52 +0,0 @@
package com.megboyzz.devmenu.util
import android.util.Log
import java.io.File
import kotlin.concurrent.thread
class SaveFileObserver(
private val source: String,
private val destinationFolder: String
) : BaseObserver(source) {
private var countSnapshots: Int = 1
private val checkList = listOf(
//FileEvent.CLOSE_WRITE,
//FileEvent.MODIFY,
//FileEvent.DELETE,
//FileEvent.CREATE,
//FileEvent.ACCESS,
//FileEvent.OPEN,
FileEvent.CLOSE_NOWRITE
)
init {
val dest = File(destinationFolder)
if(dest.isFile) dest.delete()
if(!dest.exists()) dest.mkdir()
val list = dest.list{
dir, name -> name.endsWith(".sb")
}
list?.forEach { File(dest, it).delete() }
File(source) copyToThe File(destinationFolder, "BASE.sb")
}
override fun onEvent(event: FileEvent, path: String?) {
Log.i("SaveFileObserver", "onEvent $event")
if (event in checkList) {
Log.d("SaveFileObserver", "File created or modified: $source")
File(source) copyToThe File(destinationFolder, "$event-$countSnapshots.sb")
countSnapshots++
}
}
override fun stopWatching() {
super.stopWatching()
val filesList = File(destinationFolder).list{ _, name -> name.endsWith(".sb") }?.map { it }
if (filesList != null) {
zipAll(destinationFolder, filesList)
}
}
}
-9
View File
@@ -1,9 +0,0 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="30dp"
android:height="3dp"
android:viewportWidth="30"
android:viewportHeight="3">
<path
android:pathData="M1.5,0L28.5,0A1.5,1.5 0,0 1,30 1.5L30,1.5A1.5,1.5 0,0 1,28.5 3L1.5,3A1.5,1.5 0,0 1,0 1.5L0,1.5A1.5,1.5 0,0 1,1.5 0z"
android:fillColor="#C9C9C9"/>
</vector>
@@ -1,9 +0,0 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="960"
android:viewportHeight="960">
<path
android:fillColor="#FF000000"
android:pathData="M480,640 L280,440l56,-58 104,104v-326h80v326l104,-104 56,58 -200,200ZM240,800q-33,0 -56.5,-23.5T160,720v-120h80v120h480v-120h80v120q0,33 -23.5,56.5T720,800L240,800Z"/>
</vector>
-9
View File
@@ -1,9 +0,0 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="12dp"
android:height="12dp"
android:viewportWidth="12"
android:viewportHeight="12">
<path
android:pathData="M3.45,10.613C3.358,10.512 3.311,10.396 3.306,10.262C3.302,10.129 3.35,10.012 3.45,9.913L7.388,5.975L3.45,2.038C3.358,1.946 3.311,1.831 3.306,1.694C3.302,1.556 3.35,1.438 3.45,1.338C3.542,1.238 3.656,1.185 3.794,1.181C3.931,1.177 4.05,1.225 4.15,1.325L8.538,5.713C8.579,5.754 8.608,5.796 8.625,5.838C8.642,5.879 8.65,5.925 8.65,5.975C8.65,6.025 8.642,6.071 8.625,6.113C8.608,6.154 8.579,6.196 8.538,6.238L4.15,10.625C4.058,10.717 3.944,10.762 3.806,10.762C3.669,10.762 3.55,10.712 3.45,10.613Z"
android:fillColor="#1B3D54"/>
</vector>
-9
View File
@@ -1,9 +0,0 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="960"
android:viewportHeight="960">
<path
android:fillColor="#FF000000"
android:pathData="M160,800q-33,0 -56.5,-23.5T80,720v-480q0,-33 23.5,-56.5T160,160h240l80,80h320q33,0 56.5,23.5T880,320v400q0,33 -23.5,56.5T800,800L160,800ZM160,720h640v-400L447,320l-80,-80L160,240v480ZM160,720v-480,480Z"/>
</vector>
-9
View File
@@ -1,9 +0,0 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:pathData="M16.1,13.3L14.65,11.85C14.8,11.067 14.575,10.333 13.975,9.65C13.375,8.967 12.6,8.7 11.65,8.85L10.2,7.4C10.483,7.267 10.771,7.167 11.063,7.1C11.354,7.033 11.667,7 12,7C13.25,7 14.313,7.437 15.188,8.312C16.063,9.187 16.5,10.25 16.5,11.5C16.5,11.833 16.467,12.146 16.4,12.438C16.333,12.729 16.233,13.017 16.1,13.3ZM19.3,16.45L17.85,15.05C18.483,14.567 19.046,14.038 19.538,13.462C20.029,12.887 20.45,12.233 20.8,11.5C19.967,9.817 18.771,8.479 17.212,7.487C15.654,6.496 13.917,6 12,6C11.517,6 11.042,6.033 10.575,6.1C10.108,6.167 9.65,6.267 9.2,6.4L7.65,4.85C8.333,4.567 9.033,4.354 9.75,4.212C10.467,4.071 11.217,4 12,4C14.517,4 16.758,4.696 18.725,6.087C20.692,7.479 22.117,9.283 23,11.5C22.617,12.483 22.112,13.396 21.487,14.238C20.862,15.079 20.133,15.817 19.3,16.45ZM19.8,22.6L15.6,18.45C15.017,18.633 14.429,18.771 13.837,18.862C13.246,18.954 12.633,19 12,19C9.483,19 7.242,18.304 5.275,16.913C3.308,15.521 1.883,13.717 1,11.5C1.35,10.617 1.792,9.796 2.325,9.037C2.858,8.279 3.467,7.6 4.15,7L1.4,4.2L2.8,2.8L21.2,21.2L19.8,22.6ZM5.55,8.4C5.067,8.833 4.625,9.308 4.225,9.825C3.825,10.342 3.483,10.9 3.2,11.5C4.033,13.183 5.229,14.521 6.787,15.512C8.346,16.504 10.083,17 12,17C12.333,17 12.658,16.979 12.975,16.938C13.292,16.896 13.617,16.85 13.95,16.8L13.05,15.85C12.867,15.9 12.692,15.938 12.525,15.962C12.358,15.988 12.183,16 12,16C10.75,16 9.688,15.563 8.813,14.688C7.938,13.813 7.5,12.75 7.5,11.5C7.5,11.317 7.512,11.142 7.537,10.975C7.563,10.808 7.6,10.633 7.65,10.45L5.55,8.4Z"
android:fillColor="#1B3D54"/>
</vector>
@@ -1,12 +0,0 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="55dp"
android:height="54dp"
android:viewportWidth="55"
android:viewportHeight="54">
<path
android:pathData="M10,0L44.857,0A10,10 0,0 1,54.857 10L54.857,43.053A10,10 0,0 1,44.857 53.053L10,53.053A10,10 0,0 1,0 43.053L0,10A10,10 0,0 1,10 0z"
android:fillColor="#36A0EA"/>
<path
android:pathData="M31.629,23.289C30.292,23.289 29.01,22.824 28.065,21.995C27.12,21.166 26.589,20.041 26.589,18.868V12.974H19.869C18.978,12.974 18.123,13.284 17.493,13.837C16.863,14.39 16.509,15.139 16.509,15.921V35.079C16.509,35.861 16.863,36.61 17.493,37.163C18.123,37.716 18.978,38.026 19.869,38.026H34.989C35.88,38.026 36.734,37.716 37.364,37.163C37.995,36.61 38.349,35.861 38.349,35.079V23.289H31.629ZM28.269,18.868C28.269,19.65 28.623,20.4 29.253,20.952C29.883,21.505 30.737,21.816 31.629,21.816H37.66L28.269,13.578V18.868ZM19.869,11.5H28.269L40.029,21.816V35.079C40.029,36.251 39.498,37.376 38.552,38.205C37.607,39.034 36.325,39.5 34.989,39.5H19.869C18.532,39.5 17.25,39.034 16.305,38.205C15.36,37.376 14.829,36.251 14.829,35.079V15.921C14.829,14.748 15.36,13.624 16.305,12.795C17.25,11.966 18.532,11.5 19.869,11.5Z"
android:fillColor="#ffffff"/>
</vector>
@@ -1,12 +0,0 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="55dp"
android:height="54dp"
android:viewportWidth="55"
android:viewportHeight="54">
<path
android:pathData="M10,0L44.857,0A10,10 0,0 1,54.857 10L54.857,43.053A10,10 0,0 1,44.857 53.053L10,53.053A10,10 0,0 1,0 43.053L0,10A10,10 0,0 1,10 0z"
android:fillColor="#36A0EA"/>
<path
android:pathData="M17.165,14.092H23.481L28.218,19.066H37.692C38.948,19.066 40.153,19.59 41.041,20.522C41.93,21.455 42.429,22.72 42.429,24.039V33.987C42.429,35.306 41.93,36.571 41.041,37.504C40.153,38.437 38.948,38.96 37.692,38.96H17.165C15.909,38.96 14.704,38.437 13.816,37.504C12.928,36.571 12.429,35.306 12.429,33.987V19.066C12.429,17.747 12.928,16.482 13.816,15.549C14.704,14.616 15.909,14.092 17.165,14.092ZM17.165,15.75C16.328,15.75 15.525,16.099 14.932,16.721C14.34,17.343 14.007,18.186 14.007,19.066V33.987C14.007,34.866 14.34,35.71 14.932,36.332C15.525,36.953 16.328,37.303 17.165,37.303H37.692C38.529,37.303 39.333,36.953 39.925,36.332C40.517,35.71 40.85,34.866 40.85,33.987V24.039C40.85,23.16 40.517,22.317 39.925,21.695C39.333,21.073 38.529,20.724 37.692,20.724H27.571L22.834,15.75H17.165Z"
android:fillColor="#ffffff"/>
</vector>
@@ -1,19 +0,0 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="55dp"
android:height="54dp"
android:viewportWidth="55"
android:viewportHeight="54">
<path
android:pathData="M10,0L44.857,0A10,10 0,0 1,54.857 10L54.857,43.053A10,10 0,0 1,44.857 53.053L10,53.053A10,10 0,0 1,0 43.053L0,10A10,10 0,0 1,10 0z"
android:fillColor="#36A0EA"/>
<path
android:pathData="M31.629,23.289C30.292,23.289 29.01,22.824 28.065,21.995C27.12,21.166 26.589,20.041 26.589,18.868V12.974H19.869C18.978,12.974 18.123,13.284 17.493,13.837C16.863,14.39 16.509,15.139 16.509,15.921V35.079C16.509,35.861 16.863,36.61 17.493,37.163C18.123,37.716 18.978,38.026 19.869,38.026H34.989C35.88,38.026 36.734,37.716 37.364,37.163C37.995,36.61 38.349,35.861 38.349,35.079V23.289H31.629ZM28.269,18.868C28.269,19.65 28.623,20.4 29.253,20.952C29.883,21.505 30.737,21.816 31.629,21.816H37.66L28.269,13.578V18.868ZM19.869,11.5H28.269L40.029,21.816V35.079C40.029,36.251 39.498,37.376 38.552,38.205C37.607,39.034 36.325,39.5 34.989,39.5H19.869C18.532,39.5 17.25,39.034 16.305,38.205C15.36,37.376 14.829,36.251 14.829,35.079V15.921C14.829,14.748 15.36,13.624 16.305,12.795C17.25,11.966 18.532,11.5 19.869,11.5Z"
android:fillColor="#ffffff"/>
<group>
<clip-path
android:pathData="M21.429,23h12v12h-12z"/>
<path
android:pathData="M29.479,29.65L28.754,28.925C28.829,28.533 28.716,28.167 28.416,27.825C28.116,27.483 27.729,27.35 27.254,27.425L26.529,26.7C26.67,26.633 26.814,26.583 26.96,26.55C27.106,26.517 27.262,26.5 27.429,26.5C28.054,26.5 28.585,26.719 29.022,27.156C29.46,27.594 29.679,28.125 29.679,28.75C29.679,28.917 29.662,29.073 29.629,29.219C29.595,29.365 29.545,29.508 29.479,29.65ZM31.079,31.225L30.354,30.525C30.67,30.283 30.951,30.019 31.197,29.731C31.443,29.444 31.654,29.117 31.829,28.75C31.412,27.908 30.814,27.24 30.035,26.744C29.256,26.248 28.387,26 27.429,26C27.187,26 26.949,26.017 26.716,26.05C26.483,26.083 26.254,26.133 26.029,26.2L25.254,25.425C25.595,25.283 25.945,25.177 26.304,25.106C26.662,25.035 27.037,25 27.429,25C28.687,25 29.808,25.348 30.791,26.044C31.774,26.74 32.487,27.642 32.929,28.75C32.737,29.242 32.485,29.698 32.172,30.119C31.86,30.54 31.495,30.908 31.079,31.225ZM31.329,34.3L29.229,32.225C28.937,32.317 28.643,32.385 28.347,32.431C28.052,32.477 27.745,32.5 27.429,32.5C26.17,32.5 25.049,32.152 24.066,31.456C23.083,30.76 22.37,29.858 21.929,28.75C22.104,28.308 22.324,27.898 22.591,27.519C22.858,27.14 23.162,26.8 23.504,26.5L22.129,25.1L22.829,24.4L32.029,33.6L31.329,34.3ZM24.204,27.2C23.962,27.417 23.741,27.654 23.541,27.913C23.341,28.171 23.17,28.45 23.029,28.75C23.445,29.592 24.043,30.26 24.822,30.756C25.601,31.252 26.47,31.5 27.429,31.5C27.595,31.5 27.758,31.49 27.916,31.469C28.074,31.448 28.237,31.425 28.404,31.4L27.954,30.925C27.862,30.95 27.774,30.969 27.691,30.981C27.608,30.994 27.52,31 27.429,31C26.804,31 26.272,30.781 25.835,30.344C25.397,29.906 25.179,29.375 25.179,28.75C25.179,28.658 25.185,28.571 25.197,28.487C25.21,28.404 25.229,28.317 25.254,28.225L24.204,27.2Z"
android:fillColor="#ffffff"/>
</group>
</vector>
@@ -1,15 +0,0 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="55dp"
android:height="54dp"
android:viewportWidth="55"
android:viewportHeight="54">
<path
android:pathData="M10,0L44.857,0A10,10 0,0 1,54.857 10L54.857,43.053A10,10 0,0 1,44.857 53.053L10,53.053A10,10 0,0 1,0 43.053L0,10A10,10 0,0 1,10 0z"
android:fillColor="#36A0EA"/>
<path
android:pathData="M31.629,23.289C30.292,23.289 29.01,22.824 28.065,21.995C27.12,21.166 26.589,20.041 26.589,18.868V12.974H19.869C18.978,12.974 18.123,13.284 17.493,13.837C16.863,14.39 16.509,15.139 16.509,15.921V35.079C16.509,35.861 16.863,36.61 17.493,37.163C18.123,37.716 18.978,38.026 19.869,38.026H34.989C35.88,38.026 36.734,37.716 37.364,37.163C37.995,36.61 38.349,35.861 38.349,35.079V23.289H31.629ZM28.269,18.868C28.269,19.65 28.623,20.4 29.253,20.952C29.883,21.505 30.737,21.816 31.629,21.816H37.66L28.269,13.578V18.868ZM19.869,11.5H28.269L40.029,21.816V35.079C40.029,36.251 39.498,37.376 38.552,38.205C37.607,39.034 36.325,39.5 34.989,39.5H19.869C18.532,39.5 17.25,39.034 16.305,38.205C15.36,37.376 14.829,36.251 14.829,35.079V15.921C14.829,14.748 15.36,13.624 16.305,12.795C17.25,11.966 18.532,11.5 19.869,11.5Z"
android:fillColor="#ffffff"/>
<path
android:pathData="M25.09,32.409L27.428,31.009L29.765,32.425L29.147,29.775L31.204,27.998L28.491,27.759L27.428,25.266L26.365,27.744L23.652,27.982L25.709,29.766L25.09,32.409ZM27.428,31.84L24.749,33.45C24.681,33.486 24.612,33.503 24.542,33.5C24.472,33.496 24.408,33.475 24.351,33.435C24.294,33.394 24.25,33.342 24.222,33.276C24.193,33.211 24.189,33.138 24.21,33.057L24.915,30.026L22.558,27.979C22.494,27.926 22.454,27.865 22.439,27.796C22.423,27.727 22.426,27.659 22.447,27.592C22.463,27.528 22.5,27.475 22.555,27.432C22.61,27.388 22.678,27.363 22.759,27.357L25.877,27.083L27.09,24.216C27.124,24.146 27.172,24.092 27.234,24.055C27.295,24.018 27.36,24 27.428,24C27.496,24 27.56,24.018 27.622,24.055C27.684,24.092 27.732,24.146 27.766,24.216L28.979,27.083L32.097,27.357C32.178,27.363 32.246,27.388 32.301,27.432C32.356,27.475 32.394,27.528 32.415,27.592C32.432,27.659 32.433,27.727 32.417,27.796C32.402,27.865 32.362,27.926 32.298,27.979L29.941,30.026L30.652,33.057C30.669,33.138 30.663,33.211 30.634,33.276C30.605,33.342 30.563,33.394 30.505,33.435C30.448,33.475 30.384,33.496 30.314,33.5C30.244,33.503 30.175,33.486 30.107,33.45L27.428,31.84Z"
android:fillColor="#ffffff"/>
</vector>
-9
View File
@@ -1,9 +0,0 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:pathData="M11,17H13V11H11V17ZM12,9C12.283,9 12.521,8.904 12.712,8.712C12.904,8.521 13,8.283 13,8C13,7.717 12.904,7.479 12.712,7.287C12.521,7.096 12.283,7 12,7C11.717,7 11.479,7.096 11.288,7.287C11.096,7.479 11,7.717 11,8C11,8.283 11.096,8.521 11.288,8.712C11.479,8.904 11.717,9 12,9ZM12,22C10.617,22 9.317,21.737 8.1,21.212C6.883,20.688 5.825,19.975 4.925,19.075C4.025,18.175 3.313,17.117 2.787,15.9C2.263,14.683 2,13.383 2,12C2,10.617 2.263,9.317 2.787,8.1C3.313,6.883 4.025,5.825 4.925,4.925C5.825,4.025 6.883,3.313 8.1,2.787C9.317,2.263 10.617,2 12,2C13.383,2 14.683,2.263 15.9,2.787C17.117,3.313 18.175,4.025 19.075,4.925C19.975,5.825 20.688,6.883 21.212,8.1C21.737,9.317 22,10.617 22,12C22,13.383 21.737,14.683 21.212,15.9C20.688,17.117 19.975,18.175 19.075,19.075C18.175,19.975 17.117,20.688 15.9,21.212C14.683,21.737 13.383,22 12,22ZM12,20C14.233,20 16.125,19.225 17.675,17.675C19.225,16.125 20,14.233 20,12C20,9.767 19.225,7.875 17.675,6.325C16.125,4.775 14.233,4 12,4C9.767,4 7.875,4.775 6.325,6.325C4.775,7.875 4,9.767 4,12C4,14.233 4.775,16.125 6.325,17.675C7.875,19.225 9.767,20 12,20Z"
android:fillColor="#1B3D54"/>
</vector>

Some files were not shown because too many files have changed in this diff Show More