Compare commits
No commits in common. "de97b9b7e1c3c6f389bed99641431d7ce02436f0" and "d9218af8087a06f5a84c4c02adef58f617b25fe6" have entirely different histories.
de97b9b7e1
...
d9218af808
27
.idea/deploymentTargetSelector.xml
generated
Normal file
27
.idea/deploymentTargetSelector.xml
generated
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project version="4">
|
||||||
|
<component name="deploymentTargetSelector">
|
||||||
|
<selectionStates>
|
||||||
|
<SelectionState runConfigName="app">
|
||||||
|
<option name="selectionMode" value="DROPDOWN" />
|
||||||
|
</SelectionState>
|
||||||
|
<SelectionState runConfigName="GreetingPreview">
|
||||||
|
<option name="selectionMode" value="DROPDOWN" />
|
||||||
|
</SelectionState>
|
||||||
|
<SelectionState runConfigName="MainActivity">
|
||||||
|
<option name="selectionMode" value="DROPDOWN" />
|
||||||
|
<DropdownSelection timestamp="2025-08-10T19:19:09.581900351Z">
|
||||||
|
<Target type="DEFAULT_BOOT">
|
||||||
|
<handle>
|
||||||
|
<DeviceId pluginId="LocalEmulator" identifier="path=/home/kodi/.config/.android/avd/Pixel_3a_API_35.avd" />
|
||||||
|
</handle>
|
||||||
|
</Target>
|
||||||
|
</DropdownSelection>
|
||||||
|
<DialogSelection />
|
||||||
|
</SelectionState>
|
||||||
|
<SelectionState runConfigName="CardReaderPreview">
|
||||||
|
<option name="selectionMode" value="DROPDOWN" />
|
||||||
|
</SelectionState>
|
||||||
|
</selectionStates>
|
||||||
|
</component>
|
||||||
|
</project>
|
4
.idea/gradle.xml
generated
4
.idea/gradle.xml
generated
@ -9,8 +9,8 @@
|
|||||||
<option name="gradleJvm" value="#GRADLE_LOCAL_JAVA_HOME" />
|
<option name="gradleJvm" value="#GRADLE_LOCAL_JAVA_HOME" />
|
||||||
<option name="modules">
|
<option name="modules">
|
||||||
<set>
|
<set>
|
||||||
<option value="/data/src/aimereader" />
|
<option value="$PROJECT_DIR$" />
|
||||||
<option value="/data/src/aimereader/app" />
|
<option value="$PROJECT_DIR$/app" />
|
||||||
</set>
|
</set>
|
||||||
</option>
|
</option>
|
||||||
<option name="resolveExternalAnnotations" value="false" />
|
<option name="resolveExternalAnnotations" value="false" />
|
||||||
|
4
.idea/inspectionProfiles/Project_Default.xml
generated
4
.idea/inspectionProfiles/Project_Default.xml
generated
@ -49,10 +49,6 @@
|
|||||||
<option name="composableFile" value="true" />
|
<option name="composableFile" value="true" />
|
||||||
<option name="previewFile" value="true" />
|
<option name="previewFile" value="true" />
|
||||||
</inspection_tool>
|
</inspection_tool>
|
||||||
<inspection_tool class="PreviewParameterProviderOnFirstParameter" enabled="true" level="ERROR" enabled_by_default="true">
|
|
||||||
<option name="composableFile" value="true" />
|
|
||||||
<option name="previewFile" value="true" />
|
|
||||||
</inspection_tool>
|
|
||||||
<inspection_tool class="PreviewPickerAnnotation" enabled="true" level="ERROR" enabled_by_default="true">
|
<inspection_tool class="PreviewPickerAnnotation" enabled="true" level="ERROR" enabled_by_default="true">
|
||||||
<option name="composableFile" value="true" />
|
<option name="composableFile" value="true" />
|
||||||
<option name="previewFile" value="true" />
|
<option name="previewFile" value="true" />
|
||||||
|
4
.idea/runConfigurations.xml
generated
4
.idea/runConfigurations.xml
generated
@ -5,12 +5,8 @@
|
|||||||
<set>
|
<set>
|
||||||
<option value="com.intellij.execution.junit.AbstractAllInDirectoryConfigurationProducer" />
|
<option value="com.intellij.execution.junit.AbstractAllInDirectoryConfigurationProducer" />
|
||||||
<option value="com.intellij.execution.junit.AllInPackageConfigurationProducer" />
|
<option value="com.intellij.execution.junit.AllInPackageConfigurationProducer" />
|
||||||
<option value="com.intellij.execution.junit.PatternConfigurationProducer" />
|
|
||||||
<option value="com.intellij.execution.junit.TestInClassConfigurationProducer" />
|
<option value="com.intellij.execution.junit.TestInClassConfigurationProducer" />
|
||||||
<option value="com.intellij.execution.junit.UniqueIdConfigurationProducer" />
|
<option value="com.intellij.execution.junit.UniqueIdConfigurationProducer" />
|
||||||
<option value="com.intellij.execution.junit.testDiscovery.JUnitTestDiscoveryConfigurationProducer" />
|
|
||||||
<option value="org.jetbrains.kotlin.idea.junit.KotlinJUnitRunConfigurationProducer" />
|
|
||||||
<option value="org.jetbrains.kotlin.idea.junit.KotlinPatternConfigurationProducer" />
|
|
||||||
</set>
|
</set>
|
||||||
</option>
|
</option>
|
||||||
</component>
|
</component>
|
||||||
|
1
.idea/vcs.xml
generated
1
.idea/vcs.xml
generated
@ -1,7 +1,6 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
<project version="4">
|
<project version="4">
|
||||||
<component name="VcsDirectoryMappings">
|
<component name="VcsDirectoryMappings">
|
||||||
<mapping directory="/data/src/aimereader" vcs="Git" />
|
|
||||||
<mapping directory="$PROJECT_DIR$" vcs="Git" />
|
<mapping directory="$PROJECT_DIR$" vcs="Git" />
|
||||||
</component>
|
</component>
|
||||||
</project>
|
</project>
|
@ -2,19 +2,16 @@ plugins {
|
|||||||
alias(libs.plugins.android.application)
|
alias(libs.plugins.android.application)
|
||||||
alias(libs.plugins.kotlin.android)
|
alias(libs.plugins.kotlin.android)
|
||||||
alias(libs.plugins.kotlin.compose)
|
alias(libs.plugins.kotlin.compose)
|
||||||
alias(libs.plugins.kotlin.serialization)
|
|
||||||
alias(libs.plugins.kotlin.parcelize)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
android {
|
android {
|
||||||
namespace = "me.kdcf.aimereader"
|
namespace = "me.kdcf.aimereader"
|
||||||
compileSdk = 35
|
compileSdk = 34
|
||||||
|
|
||||||
defaultConfig {
|
defaultConfig {
|
||||||
applicationId = "me.kdcf.aimereader"
|
applicationId = "me.kdcf.aimereader"
|
||||||
minSdk = 33
|
minSdk = 24
|
||||||
targetSdk = 35
|
targetSdk = 34
|
||||||
compileSdk = 36
|
|
||||||
versionCode = 1
|
versionCode = 1
|
||||||
versionName = "1.0"
|
versionName = "1.0"
|
||||||
|
|
||||||
@ -23,8 +20,7 @@ android {
|
|||||||
|
|
||||||
buildTypes {
|
buildTypes {
|
||||||
release {
|
release {
|
||||||
isMinifyEnabled = true
|
isMinifyEnabled = false
|
||||||
isShrinkResources = true
|
|
||||||
proguardFiles(
|
proguardFiles(
|
||||||
getDefaultProguardFile("proguard-android-optimize.txt"),
|
getDefaultProguardFile("proguard-android-optimize.txt"),
|
||||||
"proguard-rules.pro"
|
"proguard-rules.pro"
|
||||||
@ -44,6 +40,7 @@ android {
|
|||||||
}
|
}
|
||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
|
|
||||||
implementation(libs.androidx.core.ktx)
|
implementation(libs.androidx.core.ktx)
|
||||||
implementation(libs.androidx.lifecycle.runtime.ktx)
|
implementation(libs.androidx.lifecycle.runtime.ktx)
|
||||||
implementation(libs.androidx.activity.compose)
|
implementation(libs.androidx.activity.compose)
|
||||||
@ -52,8 +49,6 @@ dependencies {
|
|||||||
implementation(libs.androidx.ui.graphics)
|
implementation(libs.androidx.ui.graphics)
|
||||||
implementation(libs.androidx.ui.tooling.preview)
|
implementation(libs.androidx.ui.tooling.preview)
|
||||||
implementation(libs.androidx.material3)
|
implementation(libs.androidx.material3)
|
||||||
implementation(libs.kotlinx.serialization.json)
|
|
||||||
implementation(libs.androidx.material.icons.extended)
|
|
||||||
testImplementation(libs.junit)
|
testImplementation(libs.junit)
|
||||||
androidTestImplementation(libs.androidx.junit)
|
androidTestImplementation(libs.androidx.junit)
|
||||||
androidTestImplementation(libs.androidx.espresso.core)
|
androidTestImplementation(libs.androidx.espresso.core)
|
||||||
|
@ -1,159 +0,0 @@
|
|||||||
package me.kdcf.aimereader
|
|
||||||
|
|
||||||
import androidx.compose.foundation.layout.Arrangement
|
|
||||||
import androidx.compose.foundation.layout.Column
|
|
||||||
import androidx.compose.foundation.layout.Row
|
|
||||||
import androidx.compose.foundation.layout.Spacer
|
|
||||||
import androidx.compose.foundation.layout.fillMaxWidth
|
|
||||||
import androidx.compose.foundation.layout.height
|
|
||||||
import androidx.compose.foundation.layout.padding
|
|
||||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
|
||||||
import androidx.compose.foundation.text.KeyboardOptions
|
|
||||||
import androidx.compose.foundation.text.input.InputTransformation
|
|
||||||
import androidx.compose.foundation.text.input.OutputTransformation
|
|
||||||
import androidx.compose.foundation.text.input.TextFieldLineLimits
|
|
||||||
import androidx.compose.foundation.text.input.insert
|
|
||||||
import androidx.compose.foundation.text.input.maxLength
|
|
||||||
import androidx.compose.foundation.text.input.rememberTextFieldState
|
|
||||||
import androidx.compose.foundation.text.input.setTextAndPlaceCursorAtEnd
|
|
||||||
import androidx.compose.foundation.text.input.then
|
|
||||||
import androidx.compose.material3.Button
|
|
||||||
import androidx.compose.material3.Card
|
|
||||||
import androidx.compose.material3.FilledTonalButton
|
|
||||||
import androidx.compose.material3.MaterialTheme
|
|
||||||
import androidx.compose.material3.OutlinedButton
|
|
||||||
import androidx.compose.material3.OutlinedTextField
|
|
||||||
import androidx.compose.material3.Text
|
|
||||||
import androidx.compose.runtime.Composable
|
|
||||||
import androidx.compose.ui.Modifier
|
|
||||||
import androidx.compose.ui.text.input.KeyboardType
|
|
||||||
import androidx.compose.ui.text.style.TextAlign
|
|
||||||
import androidx.compose.ui.tooling.preview.Preview
|
|
||||||
import androidx.compose.ui.unit.dp
|
|
||||||
import androidx.compose.ui.window.Dialog
|
|
||||||
import androidx.core.text.isDigitsOnly
|
|
||||||
import java.nio.ByteBuffer
|
|
||||||
import kotlin.random.Random
|
|
||||||
|
|
||||||
@Composable
|
|
||||||
fun CardDialog(
|
|
||||||
initialCard: SavedCard,
|
|
||||||
titleText: String,
|
|
||||||
confirmText: String,
|
|
||||||
onConfirm: (SavedCard) -> Unit,
|
|
||||||
onDismissRequest: () -> Unit
|
|
||||||
) {
|
|
||||||
val codeState = rememberTextFieldState(initialText = initialCard.getPadded())
|
|
||||||
val nameState = rememberTextFieldState(initialText = initialCard.getName())
|
|
||||||
|
|
||||||
Dialog(onDismissRequest = { onDismissRequest() }) {
|
|
||||||
Card (
|
|
||||||
modifier = Modifier
|
|
||||||
.fillMaxWidth()
|
|
||||||
.height(350.dp)
|
|
||||||
.padding(16.dp),
|
|
||||||
shape = RoundedCornerShape(16.dp)
|
|
||||||
) {
|
|
||||||
Column(
|
|
||||||
modifier = Modifier
|
|
||||||
.fillMaxWidth()
|
|
||||||
.padding(vertical = 8.dp, horizontal = 8.dp),
|
|
||||||
verticalArrangement = Arrangement.Top
|
|
||||||
) {
|
|
||||||
Text (
|
|
||||||
titleText,
|
|
||||||
style = MaterialTheme.typography.titleMedium,
|
|
||||||
modifier = Modifier
|
|
||||||
.fillMaxWidth()
|
|
||||||
.padding(vertical = 8.dp),
|
|
||||||
textAlign = TextAlign.Center
|
|
||||||
)
|
|
||||||
OutlinedTextField (
|
|
||||||
state = nameState,
|
|
||||||
label = { Text("Card Name") },
|
|
||||||
placeholder = {
|
|
||||||
// Re-do the spacing, as the code inside the codeState text isn't actually spaced
|
|
||||||
Text(codeState.text.toString().chunked(4).joinToString(" "))
|
|
||||||
},
|
|
||||||
lineLimits = TextFieldLineLimits.SingleLine,
|
|
||||||
)
|
|
||||||
OutlinedTextField (
|
|
||||||
state = codeState,
|
|
||||||
label = { Text("Access Code") },
|
|
||||||
lineLimits = TextFieldLineLimits.SingleLine,
|
|
||||||
keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Number),
|
|
||||||
inputTransformation = InputTransformation.maxLength(20).then {
|
|
||||||
if (!asCharSequence().isDigitsOnly()) {
|
|
||||||
revertAllChanges()
|
|
||||||
}
|
|
||||||
},
|
|
||||||
outputTransformation = OutputTransformation {
|
|
||||||
if (length > 4) insert(4, " ")
|
|
||||||
if (length > 9) insert(9, " ")
|
|
||||||
if (length > 14) insert(14, " ")
|
|
||||||
if (length > 19) insert(19, " ")
|
|
||||||
}
|
|
||||||
)
|
|
||||||
Spacer(modifier = Modifier.weight(0.1f))
|
|
||||||
// Display a warning if the code cannot be emulated, meaning that the first 2
|
|
||||||
// bytes are 0x02 0xF3
|
|
||||||
val codeLong = codeState.text.toString().toULong()
|
|
||||||
Row (horizontalArrangement = Arrangement.SpaceAround, modifier = Modifier.fillMaxWidth()) {
|
|
||||||
if (codeLong and 0xFFFF000000000000u == EMULATABLE_CODE_MATCH) {
|
|
||||||
Text(
|
|
||||||
"This Access Code can be emulated.",
|
|
||||||
style = MaterialTheme.typography.bodySmall
|
|
||||||
)
|
|
||||||
} else {
|
|
||||||
Text(
|
|
||||||
"This Access Code cannot be emulated.",
|
|
||||||
style = MaterialTheme.typography.bodySmall,
|
|
||||||
color = MaterialTheme.colorScheme.error
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Spacer(modifier = Modifier.weight(0.2f))
|
|
||||||
Row (horizontalArrangement = Arrangement.SpaceAround, modifier = Modifier.fillMaxWidth()) {
|
|
||||||
OutlinedButton (
|
|
||||||
onClick = {
|
|
||||||
val newCodeLong = Random.nextLong() and EMULATABLE_CODE_MASK.toLong() or EMULATABLE_CODE_MATCH.toLong()
|
|
||||||
codeState.setTextAndPlaceCursorAtEnd(newCodeLong.toString().padStart(20, '0'))
|
|
||||||
}
|
|
||||||
) {
|
|
||||||
Text("Random Code")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Row (horizontalArrangement = Arrangement.SpaceBetween, modifier = Modifier.fillMaxWidth()) {
|
|
||||||
FilledTonalButton(
|
|
||||||
onClick = onDismissRequest
|
|
||||||
) {
|
|
||||||
Text("Cancel")
|
|
||||||
}
|
|
||||||
|
|
||||||
Button (
|
|
||||||
enabled = codeState.text.length == 20,
|
|
||||||
|
|
||||||
onClick = {
|
|
||||||
val buffer = ByteBuffer.allocate(Long.SIZE_BYTES)
|
|
||||||
buffer.putLong(codeLong.toLong())
|
|
||||||
onConfirm(SavedCard(buffer.array(), nameState.text.toString()))
|
|
||||||
}
|
|
||||||
) {
|
|
||||||
Text(confirmText)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Preview
|
|
||||||
@Composable
|
|
||||||
fun CardDialogPreview() {
|
|
||||||
CardDialog(
|
|
||||||
SavedCard(byteArrayOf(0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07), ""),
|
|
||||||
titleText = "Add Card",
|
|
||||||
confirmText = "Add",
|
|
||||||
onConfirm = {},
|
|
||||||
onDismissRequest = {})
|
|
||||||
}
|
|
@ -3,7 +3,6 @@
|
|||||||
package me.kdcf.aimereader
|
package me.kdcf.aimereader
|
||||||
|
|
||||||
import android.app.PendingIntent
|
import android.app.PendingIntent
|
||||||
import android.content.ClipData
|
|
||||||
import android.content.ComponentName
|
import android.content.ComponentName
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
import android.content.IntentFilter
|
import android.content.IntentFilter
|
||||||
@ -13,81 +12,42 @@ import android.nfc.Tag
|
|||||||
import android.nfc.cardemulation.NfcFCardEmulation
|
import android.nfc.cardemulation.NfcFCardEmulation
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import android.util.Log
|
import android.util.Log
|
||||||
import android.widget.Toast
|
|
||||||
import androidx.activity.ComponentActivity
|
import androidx.activity.ComponentActivity
|
||||||
import androidx.activity.compose.setContent
|
import androidx.activity.compose.setContent
|
||||||
import androidx.activity.enableEdgeToEdge
|
import androidx.activity.enableEdgeToEdge
|
||||||
import androidx.compose.foundation.background
|
|
||||||
import androidx.compose.foundation.layout.Arrangement
|
import androidx.compose.foundation.layout.Arrangement
|
||||||
import androidx.compose.foundation.layout.Column
|
import androidx.compose.foundation.layout.Column
|
||||||
import androidx.compose.foundation.layout.PaddingValues
|
|
||||||
import androidx.compose.foundation.layout.Row
|
|
||||||
import androidx.compose.foundation.layout.Spacer
|
|
||||||
import androidx.compose.foundation.layout.fillMaxSize
|
import androidx.compose.foundation.layout.fillMaxSize
|
||||||
import androidx.compose.foundation.layout.fillMaxWidth
|
import androidx.compose.material3.Button
|
||||||
import androidx.compose.foundation.layout.padding
|
import androidx.compose.material3.FilledTonalButton
|
||||||
import androidx.compose.foundation.lazy.LazyColumn
|
import androidx.compose.material3.HorizontalDivider
|
||||||
import androidx.compose.foundation.lazy.itemsIndexed
|
|
||||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
|
||||||
import androidx.compose.material.icons.Icons
|
|
||||||
import androidx.compose.material.icons.filled.Add
|
|
||||||
import androidx.compose.material.icons.filled.Contactless
|
|
||||||
import androidx.compose.material.icons.filled.ContentCopy
|
|
||||||
import androidx.compose.material.icons.filled.Delete
|
|
||||||
import androidx.compose.material.icons.filled.Edit
|
|
||||||
import androidx.compose.material.icons.outlined.Contactless
|
|
||||||
import androidx.compose.material3.FilledIconButton
|
|
||||||
import androidx.compose.material3.FilledIconToggleButton
|
|
||||||
import androidx.compose.material3.FilledTonalIconButton
|
|
||||||
import androidx.compose.material3.FloatingActionButton
|
|
||||||
import androidx.compose.material3.Icon
|
|
||||||
import androidx.compose.material3.MaterialTheme
|
import androidx.compose.material3.MaterialTheme
|
||||||
import androidx.compose.material3.Scaffold
|
import androidx.compose.material3.Surface
|
||||||
import androidx.compose.material3.SnackbarDuration
|
|
||||||
import androidx.compose.material3.SnackbarHost
|
|
||||||
import androidx.compose.material3.SnackbarHostState
|
|
||||||
import androidx.compose.material3.SnackbarResult
|
|
||||||
import androidx.compose.material3.Text
|
import androidx.compose.material3.Text
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.DisposableEffect
|
import androidx.compose.runtime.DisposableEffect
|
||||||
import androidx.compose.runtime.LaunchedEffect
|
|
||||||
import androidx.compose.runtime.getValue
|
import androidx.compose.runtime.getValue
|
||||||
import androidx.compose.runtime.mutableIntStateOf
|
|
||||||
import androidx.compose.runtime.mutableStateOf
|
import androidx.compose.runtime.mutableStateOf
|
||||||
import androidx.compose.runtime.remember
|
import androidx.compose.runtime.remember
|
||||||
import androidx.compose.runtime.rememberCoroutineScope
|
|
||||||
import androidx.compose.runtime.saveable.rememberSaveable
|
|
||||||
import androidx.compose.runtime.setValue
|
import androidx.compose.runtime.setValue
|
||||||
import androidx.compose.ui.Alignment
|
import androidx.compose.ui.Alignment
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.draw.clip
|
import androidx.compose.ui.platform.LocalClipboardManager
|
||||||
import androidx.compose.ui.platform.ClipEntry
|
|
||||||
import androidx.compose.ui.platform.LocalClipboard
|
|
||||||
import androidx.compose.ui.res.stringResource
|
import androidx.compose.ui.res.stringResource
|
||||||
import androidx.compose.ui.text.style.TextOverflow
|
import androidx.compose.ui.text.AnnotatedString
|
||||||
import androidx.compose.ui.tooling.preview.Preview
|
import androidx.compose.ui.tooling.preview.Preview
|
||||||
import androidx.compose.ui.unit.dp
|
|
||||||
import androidx.core.util.Consumer
|
import androidx.core.util.Consumer
|
||||||
import kotlinx.coroutines.launch
|
|
||||||
import kotlinx.serialization.encodeToString
|
|
||||||
import kotlinx.serialization.json.Json
|
|
||||||
import me.kdcf.aimereader.ui.theme.AimeReaderTheme
|
import me.kdcf.aimereader.ui.theme.AimeReaderTheme
|
||||||
import java.io.File
|
import java.nio.ByteBuffer
|
||||||
|
|
||||||
const val EMULATABLE_CODE_MASK: ULong = 0x02FEFFFFFFFFFFFFu
|
|
||||||
const val EMULATABLE_CODE_MATCH: ULong = 0x02FE000000000000u
|
|
||||||
|
|
||||||
class MainActivity : ComponentActivity() {
|
class MainActivity : ComponentActivity() {
|
||||||
var intentFiltersArray: Array<IntentFilter>? = null
|
var intentFiltersArray: Array<IntentFilter>? = null
|
||||||
var techListsArray: Array<Array<String>>? = null
|
var techListsArray: Array<Array<String>>? = null
|
||||||
lateinit var pendingIntent: PendingIntent
|
lateinit var pendingIntent: PendingIntent
|
||||||
lateinit var adapter: NfcAdapter
|
lateinit var adapter: NfcAdapter
|
||||||
|
|
||||||
var canEmulateCard: Boolean = false
|
var canEmulateCard: Boolean = false
|
||||||
var emulationCardID: String? = null
|
|
||||||
|
|
||||||
private val TAG = "AimeReader"
|
val TAG = "AimeReader"
|
||||||
private val FILENAME = "cards.json"
|
|
||||||
|
|
||||||
override fun onCreate(savedInstanceState: Bundle?) {
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
super.onCreate(savedInstanceState)
|
super.onCreate(savedInstanceState)
|
||||||
@ -105,48 +65,20 @@ class MainActivity : ComponentActivity() {
|
|||||||
|
|
||||||
enableEdgeToEdge()
|
enableEdgeToEdge()
|
||||||
setContent {
|
setContent {
|
||||||
val cardsFile by rememberSaveable {
|
var lastCode by remember { mutableStateOf<ByteArray?>(null) }
|
||||||
val file = File(filesDir, FILENAME)
|
var scanStatus by remember { mutableStateOf(ScanStatus.WAITING) }
|
||||||
if (!file.exists()) {
|
|
||||||
file.createNewFile()
|
|
||||||
file.writeText("[]") // Empty JSON array
|
|
||||||
}
|
|
||||||
mutableStateOf(file)
|
|
||||||
}
|
|
||||||
var cards by remember {
|
|
||||||
mutableStateOf(Json.decodeFromString<List<SavedCard>>(cardsFile.readText()))
|
|
||||||
}
|
|
||||||
|
|
||||||
var openEditDialog by rememberSaveable { mutableStateOf(false) }
|
DisposableEffect(lastCode, scanStatus) {
|
||||||
var editCardIndex by rememberSaveable { mutableIntStateOf(-1) }
|
|
||||||
var openAddDialog by rememberSaveable { mutableStateOf(false) }
|
|
||||||
var dialogCard by rememberSaveable { mutableStateOf(SavedCard(
|
|
||||||
byteArrayOf(0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00)
|
|
||||||
)) }
|
|
||||||
|
|
||||||
DisposableEffect(cardsFile, cards) {
|
|
||||||
val listener = Consumer<Intent> { intent ->
|
val listener = Consumer<Intent> { intent ->
|
||||||
val tagFromIntent = intent.getParcelableExtra(NfcAdapter.EXTRA_TAG, Tag::class.java)
|
val tagFromIntent: Tag? = intent.getParcelableExtra(NfcAdapter.EXTRA_TAG)
|
||||||
Log.i(TAG, tagFromIntent.toString())
|
Log.i(TAG, tagFromIntent.toString())
|
||||||
if (tagFromIntent == null) {
|
if (tagFromIntent == null ) {
|
||||||
Log.d(TAG, "Got intent without tag")
|
Log.i(TAG, "No tag found.")
|
||||||
|
scanStatus = ScanStatus.FAILURE
|
||||||
} else {
|
} else {
|
||||||
Log.i(TAG, "Got intent with tag $tagFromIntent")
|
Log.i(TAG, "Found tag $tagFromIntent")
|
||||||
if (tagFromIntent.id.size == Long.SIZE_BYTES) {
|
lastCode = tagFromIntent.id
|
||||||
if (openAddDialog || openEditDialog) {
|
scanStatus = ScanStatus.SUCCESS
|
||||||
val card = SavedCard(tagFromIntent.id, dialogCard.getName())
|
|
||||||
dialogCard = card
|
|
||||||
} else {
|
|
||||||
val card = SavedCard(tagFromIntent.id)
|
|
||||||
cards = cards.plus(card)
|
|
||||||
cardsFile.writeText(Json.encodeToString(cards))
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
Toast.makeText(
|
|
||||||
this@MainActivity,
|
|
||||||
"Tag is not a valid Aime/e-Amusement card",
|
|
||||||
Toast.LENGTH_SHORT).show()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -156,112 +88,16 @@ class MainActivity : ComponentActivity() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
val scope = rememberCoroutineScope()
|
|
||||||
var snackbarHostState = remember { SnackbarHostState() }
|
|
||||||
|
|
||||||
var selectedCardIndexForEmulation by rememberSaveable { mutableIntStateOf(-1) }
|
|
||||||
|
|
||||||
LaunchedEffect(selectedCardIndexForEmulation) {
|
|
||||||
if (selectedCardIndexForEmulation == -1) {
|
|
||||||
this@MainActivity.emulationCardID = null
|
|
||||||
} else {
|
|
||||||
this@MainActivity.emulationCardID = cards[selectedCardIndexForEmulation].getHexID()
|
|
||||||
}
|
|
||||||
updateEmulation()
|
|
||||||
}
|
|
||||||
|
|
||||||
AimeReaderTheme {
|
AimeReaderTheme {
|
||||||
Scaffold (
|
Surface(modifier = Modifier.fillMaxSize()) {
|
||||||
modifier = Modifier.fillMaxSize(),
|
Column (
|
||||||
snackbarHost = {
|
horizontalAlignment = Alignment.CenterHorizontally,
|
||||||
SnackbarHost(hostState = snackbarHostState)
|
verticalArrangement = Arrangement.Center,
|
||||||
},
|
modifier = Modifier.fillMaxSize()
|
||||||
floatingActionButton = {
|
|
||||||
FloatingActionButton(
|
|
||||||
onClick = {
|
|
||||||
openAddDialog = true
|
|
||||||
}
|
|
||||||
) {
|
) {
|
||||||
Icon(Icons.Filled.Add, "Add card")
|
CardReader(scanStatus, lastCode)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
) { innerPadding ->
|
|
||||||
|
|
||||||
if (openEditDialog) {
|
|
||||||
CardDialog (
|
|
||||||
dialogCard,
|
|
||||||
titleText = "Edit Card",
|
|
||||||
confirmText = "Edit",
|
|
||||||
onConfirm = {
|
|
||||||
openEditDialog = false
|
|
||||||
cards = cards.mapIndexed { i, prev ->
|
|
||||||
return@mapIndexed if (i == editCardIndex) {
|
|
||||||
it
|
|
||||||
} else {
|
|
||||||
prev
|
|
||||||
}
|
|
||||||
}
|
|
||||||
cardsFile.writeText(Json.encodeToString(cards))
|
|
||||||
if (editCardIndex == selectedCardIndexForEmulation) {
|
|
||||||
selectedCardIndexForEmulation = -1
|
|
||||||
}
|
|
||||||
},
|
|
||||||
onDismissRequest = {
|
|
||||||
openEditDialog = false
|
|
||||||
}
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
if (openAddDialog) {
|
|
||||||
CardDialog(
|
|
||||||
titleText = "Add Card",
|
|
||||||
confirmText = "Add",
|
|
||||||
initialCard = SavedCard(
|
|
||||||
byteArrayOf(0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00)
|
|
||||||
),
|
|
||||||
onConfirm = {
|
|
||||||
openAddDialog = false
|
|
||||||
cards = cards.plus(it)
|
|
||||||
cardsFile.writeText(Json.encodeToString(cards))
|
|
||||||
},
|
|
||||||
onDismissRequest = {
|
|
||||||
openAddDialog = false
|
|
||||||
}
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
CardList(cards,
|
|
||||||
emulationAvailable = this@MainActivity.canEmulateCard,
|
|
||||||
deleteCard = {
|
|
||||||
val backedUpCard = cards[it]
|
|
||||||
cards = cards.filterIndexed { i ,_ -> i != it }
|
|
||||||
if (it == selectedCardIndexForEmulation) {
|
|
||||||
selectedCardIndexForEmulation = -1
|
|
||||||
}
|
|
||||||
cardsFile.writeText(Json.encodeToString(cards))
|
|
||||||
scope.launch {
|
|
||||||
val result = snackbarHostState
|
|
||||||
.showSnackbar(
|
|
||||||
message = "Removed card",
|
|
||||||
actionLabel = "Undo",
|
|
||||||
duration = SnackbarDuration.Long,
|
|
||||||
withDismissAction = true
|
|
||||||
)
|
|
||||||
if (result == SnackbarResult.ActionPerformed) {
|
|
||||||
cards = cards.plus(backedUpCard)
|
|
||||||
cardsFile.writeText(Json.encodeToString(cards))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}, editCard = {
|
|
||||||
dialogCard = cards[it]
|
|
||||||
editCardIndex = it
|
|
||||||
openEditDialog = true
|
|
||||||
}, selectForEmulation = {
|
|
||||||
selectedCardIndexForEmulation = it
|
|
||||||
},
|
|
||||||
selectedForEmulation = selectedCardIndexForEmulation,
|
|
||||||
innerPadding = innerPadding)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -271,149 +107,63 @@ class MainActivity : ComponentActivity() {
|
|||||||
adapter.disableForegroundDispatch(this)
|
adapter.disableForegroundDispatch(this)
|
||||||
val nfcfCardEmulation = NfcFCardEmulation.getInstance(adapter)
|
val nfcfCardEmulation = NfcFCardEmulation.getInstance(adapter)
|
||||||
nfcfCardEmulation.disableService(this)
|
nfcfCardEmulation.disableService(this)
|
||||||
Log.i(TAG, "Pausing activity, emulation disabled")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public override fun onResume() {
|
public override fun onResume() {
|
||||||
super.onResume()
|
super.onResume()
|
||||||
adapter.enableForegroundDispatch(this, pendingIntent, intentFiltersArray, techListsArray)
|
adapter.enableForegroundDispatch(this, pendingIntent, intentFiltersArray, techListsArray)
|
||||||
updateEmulation()
|
|
||||||
}
|
|
||||||
|
|
||||||
fun updateEmulation() {
|
|
||||||
val nfcfCardEmulation = NfcFCardEmulation.getInstance(adapter)
|
val nfcfCardEmulation = NfcFCardEmulation.getInstance(adapter)
|
||||||
if (emulationCardID != null) {
|
|
||||||
Log.i(TAG, "Enabling emulation of card $emulationCardID")
|
|
||||||
nfcfCardEmulation.registerSystemCodeForService(ComponentName(this, AimeHostApduService::class.java), "4000")
|
nfcfCardEmulation.registerSystemCodeForService(ComponentName(this, AimeHostApduService::class.java), "4000")
|
||||||
Log.d(TAG, nfcfCardEmulation.setNfcid2ForService(ComponentName(this, AimeHostApduService::class.java), emulationCardID).toString())
|
Log.d(TAG, nfcfCardEmulation.setNfcid2ForService(ComponentName(this, AimeHostApduService::class.java), "02FE000000000000").toString())
|
||||||
Log.d(TAG, nfcfCardEmulation.enableService(this, ComponentName(this, AimeHostApduService::class.java)).toString())
|
Log.d(TAG, nfcfCardEmulation.enableService(this, ComponentName(this, AimeHostApduService::class.java)).toString())
|
||||||
} else {
|
Log.i(TAG, "activity resumed")
|
||||||
Log.i(TAG, "Disabling card emulation")
|
|
||||||
nfcfCardEmulation.disableService(this)
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun CardList(
|
fun CardReader(status: ScanStatus, code: ByteArray?, modifier: Modifier = Modifier) {
|
||||||
cards: List<SavedCard>,
|
if (status == ScanStatus.WAITING) {
|
||||||
emulationAvailable: Boolean,
|
Text(stringResource(R.string.main_scan_title), style = MaterialTheme.typography.titleLarge)
|
||||||
selectedForEmulation: Int,
|
Text(stringResource(R.string.main_scan_subtitle), style = MaterialTheme.typography.bodyLarge)
|
||||||
deleteCard: (Int) -> Unit,
|
} else if (status == ScanStatus.FAILURE || code == null) {
|
||||||
editCard: (Int) -> Unit,
|
Text(stringResource(R.string.main_read_failure),
|
||||||
selectForEmulation: (Int) -> Unit,
|
style = MaterialTheme.typography.titleLarge,
|
||||||
innerPadding: PaddingValues,
|
color = MaterialTheme.colorScheme.error)
|
||||||
modifier: Modifier = Modifier) {
|
Text(stringResource(R.string.main_read_failture_sub), style = MaterialTheme.typography.bodyLarge)
|
||||||
if (cards.isEmpty()) {
|
} else {
|
||||||
Column (
|
CodeDisplay(code)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun CodeDisplay(code: ByteArray) {
|
||||||
|
val codeNumber = ByteBuffer.wrap(code).long
|
||||||
|
val padded = codeNumber.toString().padStart(20, '0')
|
||||||
|
val spaced = padded.chunked(4).joinToString(" ")
|
||||||
|
val clipboardManager = LocalClipboardManager.current
|
||||||
|
|
||||||
|
Text(spaced, style = MaterialTheme.typography.titleLarge)
|
||||||
|
Button(onClick = { clipboardManager.setText(AnnotatedString(spaced)) }) { Text(stringResource(R.string.main_copy_code)) }
|
||||||
|
FilledTonalButton(onClick = { clipboardManager.setText(AnnotatedString(padded)) }) { Text(
|
||||||
|
stringResource(R.string.main_copy_without_spaces)
|
||||||
|
) }
|
||||||
|
FilledTonalButton(onClick = { clipboardManager.setText(AnnotatedString(codeNumber.toString())) }) { Text(
|
||||||
|
stringResource(R.string.main_copy_without_zeroes)
|
||||||
|
) }
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
@Preview
|
||||||
|
fun CardReaderPreview(modifier: Modifier = Modifier) {
|
||||||
|
Column(
|
||||||
horizontalAlignment = Alignment.CenterHorizontally,
|
horizontalAlignment = Alignment.CenterHorizontally,
|
||||||
verticalArrangement = Arrangement.Center,
|
verticalArrangement = Arrangement.Center,
|
||||||
modifier = modifier
|
modifier = Modifier.fillMaxSize()
|
||||||
.fillMaxSize()
|
|
||||||
.padding(innerPadding)
|
|
||||||
) {
|
) {
|
||||||
Text(stringResource(R.string.list_no_cards), style = MaterialTheme.typography.titleLarge)
|
CardReader(ScanStatus.WAITING, null)
|
||||||
Text(stringResource(R.string.list_no_cards_body), style = MaterialTheme.typography.bodyLarge)
|
HorizontalDivider()
|
||||||
}
|
CardReader(ScanStatus.SUCCESS, ByteArray(8) { x -> x.toByte() })
|
||||||
} else {
|
HorizontalDivider()
|
||||||
LazyColumn (
|
CardReader(ScanStatus.FAILURE, null)
|
||||||
modifier = modifier
|
|
||||||
.fillMaxWidth()
|
|
||||||
.padding(innerPadding)
|
|
||||||
) {
|
|
||||||
itemsIndexed(cards) { i, card ->
|
|
||||||
CardDisplay(card,
|
|
||||||
onDelete = { deleteCard(i) },
|
|
||||||
onEdit = { editCard(i) },
|
|
||||||
selectedForEmulation = i == selectedForEmulation,
|
|
||||||
emulationAvailable = emulationAvailable,
|
|
||||||
onSelectForEmulation = { selectForEmulation(i) })
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Composable
|
|
||||||
fun CardDisplay(
|
|
||||||
card: SavedCard,
|
|
||||||
emulationAvailable: Boolean,
|
|
||||||
selectedForEmulation: Boolean,
|
|
||||||
onSelectForEmulation: () -> Unit,
|
|
||||||
onDelete: () -> Unit,
|
|
||||||
onEdit: () -> Unit,
|
|
||||||
modifier: Modifier = Modifier
|
|
||||||
) {
|
|
||||||
val clipboard = LocalClipboard.current
|
|
||||||
val coroutineScope = rememberCoroutineScope()
|
|
||||||
|
|
||||||
Row (
|
|
||||||
modifier = modifier
|
|
||||||
.padding(horizontal = 8.dp, vertical = 8.dp)
|
|
||||||
.fillMaxWidth()
|
|
||||||
.clip(shape = RoundedCornerShape(16.dp))
|
|
||||||
.background(MaterialTheme.colorScheme.surfaceVariant),
|
|
||||||
verticalAlignment = Alignment.CenterVertically
|
|
||||||
) {
|
|
||||||
Text(card.getDisplay(),
|
|
||||||
style = MaterialTheme.typography.bodyLarge,
|
|
||||||
modifier = Modifier.padding(horizontal = 10.dp).weight(100f),
|
|
||||||
maxLines = 1,
|
|
||||||
overflow = TextOverflow.Ellipsis)
|
|
||||||
Spacer(modifier = Modifier.weight(0.5f))
|
|
||||||
FilledIconButton (
|
|
||||||
onClick = {
|
|
||||||
coroutineScope.launch {
|
|
||||||
val text = card.getSpaced()
|
|
||||||
clipboard.setClipEntry(ClipEntry(ClipData.newPlainText(text, text)))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
) {
|
|
||||||
Icon(Icons.Filled.ContentCopy, "Copy")
|
|
||||||
}
|
|
||||||
if (emulationAvailable) {
|
|
||||||
FilledIconToggleButton(
|
|
||||||
enabled = card.getCode().toULong() and 0xFFFF000000000000u == EMULATABLE_CODE_MATCH,
|
|
||||||
checked = selectedForEmulation,
|
|
||||||
onCheckedChange = {
|
|
||||||
if (it) {
|
|
||||||
onSelectForEmulation()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
) {
|
|
||||||
if (selectedForEmulation) {
|
|
||||||
Icon(Icons.Filled.Contactless, "Emulating")
|
|
||||||
} else {
|
|
||||||
Icon(Icons.Outlined.Contactless, "Emulate")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
FilledTonalIconButton(
|
|
||||||
onClick = onEdit
|
|
||||||
) {
|
|
||||||
Icon(Icons.Filled.Edit, "Edit")
|
|
||||||
}
|
|
||||||
FilledTonalIconButton(
|
|
||||||
onClick = onDelete
|
|
||||||
) {
|
|
||||||
Icon(Icons.Filled.Delete, "Delete")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Preview
|
|
||||||
@Composable
|
|
||||||
fun CardDisplayPreview(modifier: Modifier = Modifier) {
|
|
||||||
Column(modifier = modifier.fillMaxSize()) {
|
|
||||||
CardDisplay(SavedCard(byteArrayOf(0x02, 0xFE.toByte(), 0x02, 0x03, 0x04, 0x05, 0x06, 0x07)), true, true, {}, {}, {})
|
|
||||||
CardDisplay(SavedCard(byteArrayOf(0x02, 0xFE.toByte(), 0x12, 0x34, 0x56, 0x78, 0x12, 0x34)), true, false, {}, {}, {})
|
|
||||||
CardDisplay(SavedCard(byteArrayOf(0x02, 0x00, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07)), true, false, {}, {}, {})
|
|
||||||
CardDisplay(SavedCard(byteArrayOf(0x00, 0x00, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07)), false, false, {}, {}, {})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Preview
|
|
||||||
@Composable
|
|
||||||
fun EmptyCardList(modifier: Modifier = Modifier) {
|
|
||||||
CardList(emptyList(), true, -1, {}, {}, {}, PaddingValues(0.dp), modifier)
|
|
||||||
}
|
|
||||||
|
@ -1,39 +0,0 @@
|
|||||||
package me.kdcf.aimereader
|
|
||||||
|
|
||||||
import android.os.Parcelable
|
|
||||||
import kotlinx.parcelize.Parcelize
|
|
||||||
import kotlinx.serialization.Serializable
|
|
||||||
import java.nio.ByteBuffer
|
|
||||||
|
|
||||||
@Parcelize
|
|
||||||
@Serializable
|
|
||||||
class SavedCard(private var code: ByteArray, private var friendlyName: String = "") : Parcelable {
|
|
||||||
fun getName(): String {
|
|
||||||
return friendlyName
|
|
||||||
}
|
|
||||||
|
|
||||||
fun getCode(): Long {
|
|
||||||
return ByteBuffer.wrap(code).getLong()
|
|
||||||
}
|
|
||||||
|
|
||||||
fun getPadded(): String {
|
|
||||||
return getCode().toString().padStart(20, '0')
|
|
||||||
}
|
|
||||||
|
|
||||||
fun getSpaced(): String {
|
|
||||||
return getPadded().chunked(4).joinToString(" ")
|
|
||||||
}
|
|
||||||
|
|
||||||
@OptIn(ExperimentalStdlibApi::class)
|
|
||||||
fun getHexID(): String {
|
|
||||||
return getCode().toHexString()
|
|
||||||
}
|
|
||||||
|
|
||||||
fun getDisplay(): String {
|
|
||||||
if (friendlyName.isEmpty()) {
|
|
||||||
return getSpaced()
|
|
||||||
} else {
|
|
||||||
return friendlyName
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
12
app/src/main/res/values-b+ang/strings.xml
Normal file
12
app/src/main/res/values-b+ang/strings.xml
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<resources>
|
||||||
|
<string name="app_name">Aimeth Readere</string>
|
||||||
|
<string name="emu_service_desc">Aimeth carde emulatorth servicere</string>
|
||||||
|
<string name="main_scan_title">Pleaseth Scaneth thy Carde</string>
|
||||||
|
<string name="main_scan_subtitle">Waitingeth…</string>
|
||||||
|
<string name="main_read_failure">Faileth to Reade the Carde</string>
|
||||||
|
<string name="main_read_failture_sub">Pleaseth trye againe or reporteth a bugue</string>
|
||||||
|
<string name="main_copy_code">Copyeth thy code</string>
|
||||||
|
<string name="main_copy_without_spaces">Copyeth thy code withoute Spacese</string>
|
||||||
|
<string name="main_copy_without_zeroes">Copyeth thy code withoute Zeroes</string>
|
||||||
|
</resources>
|
@ -8,6 +8,4 @@
|
|||||||
<string name="main_copy_code">Copy Code</string>
|
<string name="main_copy_code">Copy Code</string>
|
||||||
<string name="main_copy_without_spaces">Copy Without Spaces</string>
|
<string name="main_copy_without_spaces">Copy Without Spaces</string>
|
||||||
<string name="main_copy_without_zeroes">Copy Without Zeroes</string>
|
<string name="main_copy_without_zeroes">Copy Without Zeroes</string>
|
||||||
<string name="list_no_cards">No cards.</string>
|
|
||||||
<string name="list_no_cards_body">Scan a card or press the add button.</string>
|
|
||||||
</resources>
|
</resources>
|
@ -1,13 +1,13 @@
|
|||||||
[versions]
|
[versions]
|
||||||
agp = "8.10.1"
|
agp = "8.7.0"
|
||||||
kotlin = "2.0.0"
|
kotlin = "2.0.0"
|
||||||
coreKtx = "1.17.0"
|
coreKtx = "1.10.1"
|
||||||
junit = "4.13.2"
|
junit = "4.13.2"
|
||||||
junitVersion = "1.3.0"
|
junitVersion = "1.1.5"
|
||||||
espressoCore = "3.7.0"
|
espressoCore = "3.5.1"
|
||||||
lifecycleRuntimeKtx = "2.9.2"
|
lifecycleRuntimeKtx = "2.6.1"
|
||||||
activityCompose = "1.10.1"
|
activityCompose = "1.8.0"
|
||||||
composeBom = "2025.08.00"
|
composeBom = "2024.04.01"
|
||||||
|
|
||||||
[libraries]
|
[libraries]
|
||||||
androidx-core-ktx = { group = "androidx.core", name = "core-ktx", version.ref = "coreKtx" }
|
androidx-core-ktx = { group = "androidx.core", name = "core-ktx", version.ref = "coreKtx" }
|
||||||
@ -23,13 +23,10 @@ androidx-ui-tooling = { group = "androidx.compose.ui", name = "ui-tooling" }
|
|||||||
androidx-ui-tooling-preview = { group = "androidx.compose.ui", name = "ui-tooling-preview" }
|
androidx-ui-tooling-preview = { group = "androidx.compose.ui", name = "ui-tooling-preview" }
|
||||||
androidx-ui-test-manifest = { group = "androidx.compose.ui", name = "ui-test-manifest" }
|
androidx-ui-test-manifest = { group = "androidx.compose.ui", name = "ui-test-manifest" }
|
||||||
androidx-ui-test-junit4 = { group = "androidx.compose.ui", name = "ui-test-junit4" }
|
androidx-ui-test-junit4 = { group = "androidx.compose.ui", name = "ui-test-junit4" }
|
||||||
androidx-material3 = { group = "androidx.compose.material3", name = "material3", version = "1.4.0-beta02" }
|
androidx-material3 = { group = "androidx.compose.material3", name = "material3" }
|
||||||
androidx-material-icons-extended = { group = "androidx.compose.material", name = "material-icons-extended-android", version = "1.7.8" }
|
|
||||||
kotlinx-serialization-json = { group = "org.jetbrains.kotlinx", name = "kotlinx-serialization-json"}
|
|
||||||
|
|
||||||
[plugins]
|
[plugins]
|
||||||
android-application = { id = "com.android.application", version.ref = "agp" }
|
android-application = { id = "com.android.application", version.ref = "agp" }
|
||||||
kotlin-android = { id = "org.jetbrains.kotlin.android", version.ref = "kotlin" }
|
kotlin-android = { id = "org.jetbrains.kotlin.android", version.ref = "kotlin" }
|
||||||
kotlin-compose = { id = "org.jetbrains.kotlin.plugin.compose", version.ref = "kotlin" }
|
kotlin-compose = { id = "org.jetbrains.kotlin.plugin.compose", version.ref = "kotlin" }
|
||||||
kotlin-serialization = { id = "org.jetbrains.kotlin.plugin.serialization", version.ref = "kotlin" }
|
|
||||||
kotlin-parcelize = { id = "org.jetbrains.kotlin.plugin.parcelize" }
|
|
||||||
|
4
gradle/wrapper/gradle-wrapper.properties
vendored
4
gradle/wrapper/gradle-wrapper.properties
vendored
@ -1,6 +1,6 @@
|
|||||||
#Sun Aug 17 11:26:34 GMT 2025
|
#Mon Aug 04 17:25:24 GMT 2025
|
||||||
distributionBase=GRADLE_USER_HOME
|
distributionBase=GRADLE_USER_HOME
|
||||||
distributionPath=wrapper/dists
|
distributionPath=wrapper/dists
|
||||||
distributionUrl=https\://services.gradle.org/distributions/gradle-8.11.1-bin.zip
|
distributionUrl=https\://services.gradle.org/distributions/gradle-8.9-bin.zip
|
||||||
zipStoreBase=GRADLE_USER_HOME
|
zipStoreBase=GRADLE_USER_HOME
|
||||||
zipStorePath=wrapper/dists
|
zipStorePath=wrapper/dists
|
||||||
|
Loading…
x
Reference in New Issue
Block a user