diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml new file mode 100644 index 0000000..469abaa --- /dev/null +++ b/.github/workflows/ci.yaml @@ -0,0 +1,41 @@ +name: CI +on: { pull_request: { } } +concurrency: + group: ${{ github.head_ref || github.ref }} + cancel-in-progress: true +jobs: + build: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v5 + + - name: Setup Java + uses: actions/setup-java@v5 + with: + distribution: temurin + java-version: 17 + + - name: Setup Gradle + uses: gradle/actions/setup-gradle@v4 + + - name: Build apk + run: | + ./gradlew assembleRelease + + - name: Build aab + run: | + ./gradlew bundleRelease + + - name: Upload apk + uses: actions/upload-artifact@v4 + with: + name: message-decoder.apk + path: ./app/build/outputs/apk/*/*.apk + retention-days: 7 + + - name: Upload bundle + uses: actions/upload-artifact@v4 + with: + name: message-decoder.aab + path: ./app/build/outputs/bundle/*/*.aab + retention-days: 7 diff --git a/.github/workflows/release.yaml b/.github/workflows/release.yaml new file mode 100644 index 0000000..816db66 --- /dev/null +++ b/.github/workflows/release.yaml @@ -0,0 +1,72 @@ +name: Release +on: { push: { branches: [ main ], tags: [ 'v*' ] } } +concurrency: + group: ${{ github.head_ref || github.ref }} + cancel-in-progress: true +permissions: + contents: write +jobs: + build: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v5 + + - name: Setup Java + uses: actions/setup-java@v5 + with: + distribution: temurin + java-version: 17 + + - name: Setup Gradle + uses: gradle/actions/setup-gradle@v4 + + - name: Setup android keystore + run: | + base64 -d <<<'${{ secrets.ANDROID_SIGNING_KEY_STORE }}' > android.jks + echo "ANDROID_KEYSTORE_PATH=$GITHUB_WORKSPACE/android.jks" >> $GITHUB_ENV + # Once the path is set, we need to be able to load it + echo "ANDROID_KEYSTORE_PASSWORD=${{ secrets.ANDROID_SIGNING_KEY_PASSWORD }}" >> $GITHUB_ENV + echo "ANDROID_KEYSTORE_KEY_NAME=Github CI" >> $GITHUB_ENV + echo "ANDROID_KEYSTORE_KEY_PASSWORD=${{ secrets.ANDROID_SIGNING_KEY_PASSWORD }}" >> $GITHUB_ENV + + - name: Update version code + run: | + ./gradlew increaseVersionCode + + git config user.email "ci-update-version-code[bot]@users.noreply.github.com" + git config user.name "ci-update-version-code[bot]" + + git add version.properties + git commit -m 'Increase version code' + + git push origin ${{ github.ref }} + if: "!startsWith(github.ref, 'refs/tags/')" + + - name: Update version.properties for release + run: | + sed -i 's/VERSION_NAME=.*/VERSION_NAME='${GITHUB_REF##*/}'/g' version.properties + sed -i 's/VERSION_CODE=.*/VERSION_CODE=1/g' version.properties + if: "startsWith(github.ref, 'refs/tags/')" + + + - name: Build apk + run: | + ./gradlew assembleRelease + + - name: Build aab + run: | + ./gradlew bundleRelease + + - name: Publish beta release + run: | + ./gradlew publishReleaseBundle --track 'internal' --release-name '${{ github.sha }}' + env: + ANDROID_PUBLISHER_CREDENTIALS: '${{ secrets.ANDROID_PUBLISHER_CREDENTIALS }}' + if: "!startsWith(github.ref, 'refs/tags/')" + + - name: Publish named release + run: | + ./gradlew publishReleaseBundle --track 'production' --release-name "${GITHUB_REF##*/}" + env: + ANDROID_PUBLISHER_CREDENTIALS: '${{ secrets.ANDROID_PUBLISHER_CREDENTIALS }}' + if: "startsWith(github.ref, 'refs/tags/')" diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..aa724b7 --- /dev/null +++ b/.gitignore @@ -0,0 +1,15 @@ +*.iml +.gradle +/local.properties +/.idea/caches +/.idea/libraries +/.idea/modules.xml +/.idea/workspace.xml +/.idea/navEditor.xml +/.idea/assetWizardSettings.xml +.DS_Store +/build +/captures +.externalNativeBuild +.cxx +local.properties diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..1524cdf --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2025 Sailing with Damian + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/README.md b/README.md index fab2e2b..021a63f 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,9 @@ -# message-decoder -Message decoder for InReach Proxy +# Message Decoder + +This project supports decoding of multi-part messages sent via inreach-proxy. + +Expected input format: +`m:grib:1:2:Awetwet==` +`m:grib:2:2:Awetwet==` + +Output: `GRIB` file saved to storage. diff --git a/app/.gitignore b/app/.gitignore new file mode 100644 index 0000000..42afabf --- /dev/null +++ b/app/.gitignore @@ -0,0 +1 @@ +/build \ No newline at end of file diff --git a/app/build.gradle.kts b/app/build.gradle.kts new file mode 100644 index 0000000..741f9a6 --- /dev/null +++ b/app/build.gradle.kts @@ -0,0 +1,99 @@ +import java.io.FileInputStream +import java.util.Properties + +plugins { + alias(libs.plugins.android.application) + alias(libs.plugins.kotlin.android) + alias(libs.plugins.kotlin.compose) + id("com.github.triplet.play") version "3.12.1" +} + +val versionProperties = Properties().apply { + load(FileInputStream(rootProject.file("version.properties"))) +} + +val signingKeystorePath = file(System.getenv("ANDROID_KEYSTORE_PATH") ?: "keystore.jks") + +android { + namespace = "eu.sailwithdamian.message_decoder" + compileSdk = 35 + + defaultConfig { + applicationId = "eu.sailwithdamian.MessageDecoder" + minSdk = 33 + targetSdk = 35 + versionCode = versionProperties["VERSION_CODE"].toString().toInt() + versionName = versionProperties["VERSION_NAME"].toString() + + testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner" + } + signingConfigs { + if (signingKeystorePath.exists()) { + create("release") { + storeFile = signingKeystorePath + storePassword = System.getenv("ANDROID_KEYSTORE_PASSWORD") + keyAlias = System.getenv("ANDROID_KEYSTORE_KEY_NAME") + keyPassword = System.getenv("ANDROID_KEYSTORE_KEY_PASSWORD") + } + } + } + buildTypes { + release { + isMinifyEnabled = false + proguardFiles( + getDefaultProguardFile("proguard-android-optimize.txt"), + "proguard-rules.pro" + ) + if (file(signingKeystorePath).exists()) { + signingConfig = signingConfigs.getByName("release") + } + } + } + compileOptions { + sourceCompatibility = JavaVersion.VERSION_11 + targetCompatibility = JavaVersion.VERSION_11 + } + kotlinOptions { + jvmTarget = "11" + } + buildFeatures { + compose = true + } + bundle { + storeArchive { + enable = true + } + } +} + +dependencies { + + implementation(libs.androidx.core.ktx) + implementation(libs.androidx.lifecycle.runtime.ktx) + implementation(libs.androidx.activity.compose) + implementation(platform(libs.androidx.compose.bom)) + implementation(libs.androidx.ui) + implementation(libs.androidx.ui.graphics) + implementation(libs.androidx.ui.tooling.preview) + implementation(libs.androidx.material3) + testImplementation(libs.junit) + androidTestImplementation(libs.androidx.junit) + androidTestImplementation(libs.androidx.espresso.core) + androidTestImplementation(platform(libs.androidx.compose.bom)) + androidTestImplementation(libs.androidx.ui.test.junit4) + debugImplementation(libs.androidx.ui.tooling) + debugImplementation(libs.androidx.ui.test.manifest) +} + +tasks.register("increaseVersionCode") { + doLast { + versionProperties["VERSION_CODE"] = + (versionProperties["VERSION_CODE"].toString().toInt() + 1).toString() + rootProject.file("version.properties").writer().use { writer -> + versionProperties.store(writer, null) + } + } +} + +play { +} diff --git a/app/proguard-rules.pro b/app/proguard-rules.pro new file mode 100644 index 0000000..481bb43 --- /dev/null +++ b/app/proguard-rules.pro @@ -0,0 +1,21 @@ +# 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 \ No newline at end of file diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml new file mode 100644 index 0000000..cbaaab9 --- /dev/null +++ b/app/src/main/AndroidManifest.xml @@ -0,0 +1,30 @@ + + + + + + + + + + + + + + + + diff --git a/app/src/main/ic_launcher-playstore.png b/app/src/main/ic_launcher-playstore.png new file mode 100644 index 0000000..819c7f7 Binary files /dev/null and b/app/src/main/ic_launcher-playstore.png differ diff --git a/app/src/main/java/eu/sailwithdamian/message_decoder/MainActivity.kt b/app/src/main/java/eu/sailwithdamian/message_decoder/MainActivity.kt new file mode 100644 index 0000000..31d8c81 --- /dev/null +++ b/app/src/main/java/eu/sailwithdamian/message_decoder/MainActivity.kt @@ -0,0 +1,21 @@ +package eu.sailwithdamian.message_decoder + +import android.os.Bundle +import androidx.activity.ComponentActivity +import androidx.activity.compose.setContent +import androidx.activity.enableEdgeToEdge +import eu.sailwithdamian.message_decoder.ui.DecoderScreen +import eu.sailwithdamian.message_decoder.ui.DecoderTheme + +class MainActivity : ComponentActivity() { + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + enableEdgeToEdge() + + setContent { + DecoderTheme { + DecoderScreen() + } + } + } +} diff --git a/app/src/main/java/eu/sailwithdamian/message_decoder/MessageProcessor.kt b/app/src/main/java/eu/sailwithdamian/message_decoder/MessageProcessor.kt new file mode 100644 index 0000000..d5c31bd --- /dev/null +++ b/app/src/main/java/eu/sailwithdamian/message_decoder/MessageProcessor.kt @@ -0,0 +1,62 @@ +package eu.sailwithdamian.message_decoder + +import android.os.Environment +import java.io.ByteArrayInputStream +import java.io.File +import java.io.FileOutputStream +import java.time.Instant +import java.time.format.DateTimeFormatter +import java.util.Base64 +import java.util.zip.InflaterInputStream + +data class Message( + val type: String, + val number: Int, + val total: Int, + val payload: String +) + +fun ExportMessages(messages: List): String? { + val downloadsDir = + Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS) + if (!downloadsDir.exists()) { + downloadsDir.mkdirs() + } + val fileName: String = DateTimeFormatter.ISO_INSTANT.format(Instant.now()).replace(":", "-") + .replace(".", "-") + "." + messages[0].type + val outputFile = File(downloadsDir, fileName) + + messages.joinToString(separator = "") { it.payload } + + try { + FileOutputStream(outputFile).use { fileOutputStream -> + val encodedPayload = messages.joinToString(separator = "") { it.payload } + val compressedBytes = Base64.getDecoder().decode(encodedPayload) + val byteInputStream = ByteArrayInputStream(compressedBytes) + InflaterInputStream(byteInputStream).use { inflater -> + val buffer = ByteArray(1024) + var bytesRead: Int + while (inflater.read(buffer).also { bytesRead = it } != -1) { + fileOutputStream.write(buffer, 0, bytesRead) + } + } + } + } catch (e: Exception) { + e.printStackTrace() + return null + } + return outputFile.name +} + +fun DecodeMessage(input: String): Message? { + val parts = input.split(":") + if (parts.size != 5 || parts[0] != "msg") { + return null + } + return Message( + type = parts[1], + number = parts[2].toInt(), + total = parts[3].toInt(), + payload = parts[4] + ) +} diff --git a/app/src/main/java/eu/sailwithdamian/message_decoder/ui/Screen.kt b/app/src/main/java/eu/sailwithdamian/message_decoder/ui/Screen.kt new file mode 100644 index 0000000..4d83245 --- /dev/null +++ b/app/src/main/java/eu/sailwithdamian/message_decoder/ui/Screen.kt @@ -0,0 +1,148 @@ +package eu.sailwithdamian.message_decoder.ui + +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size +import androidx.compose.foundation.lazy.LazyColumn +import androidx.compose.material3.AlertDialog +import androidx.compose.material3.Button +import androidx.compose.material3.OutlinedTextField +import androidx.compose.material3.Scaffold +import androidx.compose.material3.Text +import androidx.compose.material3.TextButton +import androidx.compose.runtime.Composable +import androidx.compose.runtime.mutableStateListOf +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.unit.dp +import eu.sailwithdamian.message_decoder.DecodeMessage +import eu.sailwithdamian.message_decoder.ExportMessages +import eu.sailwithdamian.message_decoder.Message + +@Composable +fun DecoderScreen() { + val messages = remember { mutableStateListOf() } + val inputMessage = remember { mutableStateOf("") } + + val alertMessage = remember { mutableStateOf("") } + val showAlert = remember { mutableStateOf(false) } + + Scaffold { padding -> + Column( + modifier = Modifier + .padding(padding) + .fillMaxSize() + .padding(32.dp), + verticalArrangement = Arrangement.Center, + horizontalAlignment = Alignment.CenterHorizontally + ) { + OutlinedTextField( + value = inputMessage.value, + onValueChange = { newText -> inputMessage.value = newText }, + label = { Text("Enter message") }, + modifier = Modifier.fillMaxWidth() + ) + Spacer(modifier = Modifier.height(20.dp)) + Button( + onClick = { + if (inputMessage.value.length > 0) { + val decodedMessage = DecodeMessage(inputMessage.value); + if (decodedMessage == null) { + alertMessage.value = "Failed to decode message" + showAlert.value = true + } else { + val expectedMessageNumber = messages.size + 1; + var messageIsValid = false; + + if (messages.size == 0) { + if (decodedMessage.number != 1) { + alertMessage.value = "Expected first message, got ${decodedMessage.number}" + showAlert.value = true + } else { + // First message - setup + messageIsValid = true; + } + } else { + // Not first message - verify in sequence + if (decodedMessage.number != expectedMessageNumber) { + alertMessage.value = "Invalid Message - Expected index ${expectedMessageNumber} got ${decodedMessage.number}" + showAlert.value = true + } else if (decodedMessage.type != messages[0].type) { + alertMessage.value = "Invalid Message - Expected type ${messages[0].type} got ${decodedMessage.type}" + showAlert.value = true + } else { + messageIsValid = true; + } + } + + if (messageIsValid) { + // Valid message - add to array + messages.add(decodedMessage) + if (decodedMessage.number == decodedMessage.total) { + // Last message - process + val outputFileName = ExportMessages(messages) + if (outputFileName == null) { + alertMessage.value = "Failed to write message" + showAlert.value = true + } else { + alertMessage.value = "Output to ${outputFileName}" + showAlert.value = true + } + messages.clear() + } + } + } + inputMessage.value = "" + } + }, + modifier = Modifier.size(width = 1000.dp, height = 100.dp) + ) { + Text("Add") + } + Spacer(modifier = Modifier.height(20.dp)) + LazyColumn( + modifier = Modifier.weight(1f) + ) { + item { + if (messages.isNotEmpty()) { + Column { + Text("Expected Messages: ${messages[0].total}") + Text("Current Messages: ${messages.size}") + } + } else { + Text("Waiting for first message") + } + } + } + Spacer(modifier = Modifier.height(20.dp)) + Button( + onClick = { + messages.clear(); + }, + modifier = Modifier.size(width = 1000.dp, height = 100.dp) + ) { + Text("Reset") + } + if (showAlert.value) { + AlertDialog( + onDismissRequest = { }, + confirmButton = { + TextButton(onClick = { + showAlert.value = false + }) { + Text("OK") + } + }, + text = { Text(alertMessage.value) } + ) + } + } + } +} diff --git a/app/src/main/java/eu/sailwithdamian/message_decoder/ui/Theme.kt b/app/src/main/java/eu/sailwithdamian/message_decoder/ui/Theme.kt new file mode 100644 index 0000000..3416d5c --- /dev/null +++ b/app/src/main/java/eu/sailwithdamian/message_decoder/ui/Theme.kt @@ -0,0 +1,33 @@ +package eu.sailwithdamian.message_decoder.ui + +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.Typography +import androidx.compose.material3.lightColorScheme +import androidx.compose.runtime.Composable +import androidx.compose.ui.graphics.Color +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 + +@Composable +fun DecoderTheme( + content: @Composable () -> Unit +) { + MaterialTheme( + colorScheme = lightColorScheme( + primary = Color(0xFF6650a4), + secondary = Color(0xFF625b71) + ), + typography = Typography( + bodyLarge = TextStyle( + fontFamily = FontFamily.Default, + fontWeight = FontWeight.Normal, + fontSize = 16.sp, + lineHeight = 24.sp, + letterSpacing = 0.5.sp + ) + ), + content = content + ) +} diff --git a/app/src/main/res/drawable/ic_launcher_foreground.xml b/app/src/main/res/drawable/ic_launcher_foreground.xml new file mode 100644 index 0000000..f91f949 --- /dev/null +++ b/app/src/main/res/drawable/ic_launcher_foreground.xml @@ -0,0 +1,16 @@ + + + + + diff --git a/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml b/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml new file mode 100644 index 0000000..ac94b34 --- /dev/null +++ b/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml b/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml new file mode 100644 index 0000000..ac94b34 --- /dev/null +++ b/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/app/src/main/res/mipmap-hdpi/ic_launcher.webp b/app/src/main/res/mipmap-hdpi/ic_launcher.webp new file mode 100644 index 0000000..59ca1c0 Binary files /dev/null and b/app/src/main/res/mipmap-hdpi/ic_launcher.webp differ diff --git a/app/src/main/res/mipmap-hdpi/ic_launcher_foreground.webp b/app/src/main/res/mipmap-hdpi/ic_launcher_foreground.webp new file mode 100644 index 0000000..43dc2a1 Binary files /dev/null and b/app/src/main/res/mipmap-hdpi/ic_launcher_foreground.webp differ diff --git a/app/src/main/res/mipmap-hdpi/ic_launcher_round.webp b/app/src/main/res/mipmap-hdpi/ic_launcher_round.webp new file mode 100644 index 0000000..6b8962a Binary files /dev/null and b/app/src/main/res/mipmap-hdpi/ic_launcher_round.webp differ diff --git a/app/src/main/res/mipmap-mdpi/ic_launcher.webp b/app/src/main/res/mipmap-mdpi/ic_launcher.webp new file mode 100644 index 0000000..3ff1b99 Binary files /dev/null and b/app/src/main/res/mipmap-mdpi/ic_launcher.webp differ diff --git a/app/src/main/res/mipmap-mdpi/ic_launcher_foreground.webp b/app/src/main/res/mipmap-mdpi/ic_launcher_foreground.webp new file mode 100644 index 0000000..00c09d8 Binary files /dev/null and b/app/src/main/res/mipmap-mdpi/ic_launcher_foreground.webp differ diff --git a/app/src/main/res/mipmap-mdpi/ic_launcher_round.webp b/app/src/main/res/mipmap-mdpi/ic_launcher_round.webp new file mode 100644 index 0000000..e028d4b Binary files /dev/null and b/app/src/main/res/mipmap-mdpi/ic_launcher_round.webp differ diff --git a/app/src/main/res/mipmap-xhdpi/ic_launcher.webp b/app/src/main/res/mipmap-xhdpi/ic_launcher.webp new file mode 100644 index 0000000..c411e11 Binary files /dev/null and b/app/src/main/res/mipmap-xhdpi/ic_launcher.webp differ diff --git a/app/src/main/res/mipmap-xhdpi/ic_launcher_foreground.webp b/app/src/main/res/mipmap-xhdpi/ic_launcher_foreground.webp new file mode 100644 index 0000000..6602189 Binary files /dev/null and b/app/src/main/res/mipmap-xhdpi/ic_launcher_foreground.webp differ diff --git a/app/src/main/res/mipmap-xhdpi/ic_launcher_round.webp b/app/src/main/res/mipmap-xhdpi/ic_launcher_round.webp new file mode 100644 index 0000000..d9dc76f Binary files /dev/null and b/app/src/main/res/mipmap-xhdpi/ic_launcher_round.webp differ diff --git a/app/src/main/res/mipmap-xxhdpi/ic_launcher.webp b/app/src/main/res/mipmap-xxhdpi/ic_launcher.webp new file mode 100644 index 0000000..38765be Binary files /dev/null and b/app/src/main/res/mipmap-xxhdpi/ic_launcher.webp differ diff --git a/app/src/main/res/mipmap-xxhdpi/ic_launcher_foreground.webp b/app/src/main/res/mipmap-xxhdpi/ic_launcher_foreground.webp new file mode 100644 index 0000000..cdea376 Binary files /dev/null and b/app/src/main/res/mipmap-xxhdpi/ic_launcher_foreground.webp differ diff --git a/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp b/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp new file mode 100644 index 0000000..0dff8e1 Binary files /dev/null and b/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp differ diff --git a/app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp b/app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp new file mode 100644 index 0000000..bc6dea6 Binary files /dev/null and b/app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp differ diff --git a/app/src/main/res/mipmap-xxxhdpi/ic_launcher_foreground.webp b/app/src/main/res/mipmap-xxxhdpi/ic_launcher_foreground.webp new file mode 100644 index 0000000..0129efe Binary files /dev/null and b/app/src/main/res/mipmap-xxxhdpi/ic_launcher_foreground.webp differ diff --git a/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp b/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp new file mode 100644 index 0000000..4605f99 Binary files /dev/null and b/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp differ diff --git a/app/src/main/res/values/colors.xml b/app/src/main/res/values/colors.xml new file mode 100644 index 0000000..f8c6127 --- /dev/null +++ b/app/src/main/res/values/colors.xml @@ -0,0 +1,10 @@ + + + #FFBB86FC + #FF6200EE + #FF3700B3 + #FF03DAC5 + #FF018786 + #FF000000 + #FFFFFFFF + \ No newline at end of file diff --git a/app/src/main/res/values/ic_launcher_background.xml b/app/src/main/res/values/ic_launcher_background.xml new file mode 100644 index 0000000..b1f848b --- /dev/null +++ b/app/src/main/res/values/ic_launcher_background.xml @@ -0,0 +1,4 @@ + + + #DC3DA2 + \ No newline at end of file diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml new file mode 100644 index 0000000..b440fb9 --- /dev/null +++ b/app/src/main/res/values/strings.xml @@ -0,0 +1,3 @@ + + Message Decoder + \ No newline at end of file diff --git a/app/src/main/res/values/themes.xml b/app/src/main/res/values/themes.xml new file mode 100644 index 0000000..df69727 --- /dev/null +++ b/app/src/main/res/values/themes.xml @@ -0,0 +1,5 @@ + + + +