diff --git a/android/.gitignore b/android/.gitignore new file mode 100644 index 0000000..5bd175f --- /dev/null +++ b/android/.gitignore @@ -0,0 +1,16 @@ +*.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 +.idea diff --git a/android/app/.gitignore b/android/app/.gitignore new file mode 100644 index 0000000..42afabf --- /dev/null +++ b/android/app/.gitignore @@ -0,0 +1 @@ +/build \ No newline at end of file diff --git a/android/app/build.gradle.kts b/android/app/build.gradle.kts new file mode 100644 index 0000000..0002aab --- /dev/null +++ b/android/app/build.gradle.kts @@ -0,0 +1,72 @@ +plugins { + alias(libs.plugins.android.application) + alias(libs.plugins.kotlin.android) + alias(libs.plugins.kotlin.compose) + alias(libs.plugins.kotlin.serialization) +} + +android { + namespace = "aza.instant" + compileSdk { + version = release(36) + } + + defaultConfig { + applicationId = "aza.instant" + minSdk = 33 + targetSdk = 36 + versionCode = 1 + versionName = "1.0" + + testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner" + } + + buildTypes { + release { + isMinifyEnabled = false + proguardFiles( + getDefaultProguardFile("proguard-android-optimize.txt"), + "proguard-rules.pro" + ) + } + } + compileOptions { + sourceCompatibility = JavaVersion.VERSION_11 + targetCompatibility = JavaVersion.VERSION_11 + } + kotlinOptions { + jvmTarget = "11" + } + buildFeatures { + compose = 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.compose.ui) + implementation(libs.androidx.compose.ui.graphics) + implementation(libs.androidx.compose.ui.tooling.preview) + implementation(libs.androidx.compose.material3) + testImplementation(libs.junit) + androidTestImplementation(libs.androidx.junit) + androidTestImplementation(libs.androidx.espresso.core) + androidTestImplementation(platform(libs.androidx.compose.bom)) + androidTestImplementation(libs.androidx.compose.ui.test.junit4) + debugImplementation(libs.androidx.compose.ui.tooling) + debugImplementation(libs.androidx.compose.ui.test.manifest) + + implementation(libs.ktor.client.cio.jvm) + implementation(libs.qrcode.kotlin) + implementation(libs.kim) + implementation(libs.ktor.client.cio) + implementation(libs.ktor.client.core) + implementation(libs.ktor.client.content.negotiation) + implementation(libs.ktor.serialization.kotlinx.json) + implementation(libs.kotlinx.serialization.json) + implementation(libs.androidsvg.aar) +} diff --git a/android/app/proguard-rules.pro b/android/app/proguard-rules.pro new file mode 100644 index 0000000..481bb43 --- /dev/null +++ b/android/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/android/app/src/androidTest/java/aza/instant/ExampleInstrumentedTest.kt b/android/app/src/androidTest/java/aza/instant/ExampleInstrumentedTest.kt new file mode 100644 index 0000000..ff3e0b8 --- /dev/null +++ b/android/app/src/androidTest/java/aza/instant/ExampleInstrumentedTest.kt @@ -0,0 +1,24 @@ +package aza.instant + +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("aza.instant", appContext.packageName) + } +} \ No newline at end of file diff --git a/android/app/src/main/AndroidManifest.xml b/android/app/src/main/AndroidManifest.xml new file mode 100644 index 0000000..86fc6e4 --- /dev/null +++ b/android/app/src/main/AndroidManifest.xml @@ -0,0 +1,29 @@ + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/android/app/src/main/java/aza/instant/AddressUtil.kt b/android/app/src/main/java/aza/instant/AddressUtil.kt new file mode 100644 index 0000000..9c23488 --- /dev/null +++ b/android/app/src/main/java/aza/instant/AddressUtil.kt @@ -0,0 +1,45 @@ +@file:OptIn(InternalSerializationApi::class) + +package aza.instant + +import io.ktor.client.* +import io.ktor.client.call.body +import io.ktor.client.engine.cio.CIO +import io.ktor.client.plugins.contentnegotiation.* +import io.ktor.client.request.get +import io.ktor.client.request.header +import io.ktor.client.request.parameter +import io.ktor.http.HttpHeaders +import io.ktor.serialization.kotlinx.json.* +import kotlinx.serialization.InternalSerializationApi +import kotlinx.serialization.Serializable +import kotlinx.serialization.json.Json + +@Serializable +data class NominatimResponse(val display_name: String, val address: Address) +@Serializable +data class Address( + val quarter: String? = null, + val city: String? = null, + val state: String? = null, + val province: String? = null, + val country: String? = null, +) + +private val client = HttpClient(CIO) { // Use the engine you prefer + install(ContentNegotiation) { + json(Json { + ignoreUnknownKeys = true + isLenient = true + }) + } +} + +suspend fun getAddress(latitude: Double, longitude: Double): NominatimResponse? { + return client.get("https://nominatim.openstreetmap.org/reverse") { + parameter("lat", latitude) + parameter("lon", longitude) + parameter("format", "jsonv2") + header(HttpHeaders.AcceptLanguage, "en-US") + }.body() +} \ No newline at end of file diff --git a/android/app/src/main/java/aza/instant/ExifUtils.kt b/android/app/src/main/java/aza/instant/ExifUtils.kt new file mode 100644 index 0000000..12a3f4c --- /dev/null +++ b/android/app/src/main/java/aza/instant/ExifUtils.kt @@ -0,0 +1,59 @@ +@file:OptIn(InternalSerializationApi::class) + +package aza.instant + +import com.ashampoo.kim.Kim +import com.ashampoo.kim.common.convertToPhotoMetadata +import kotlinx.serialization.InternalSerializationApi +import java.security.MessageDigest +import java.time.LocalDateTime +import java.time.ZonedDateTime +import java.time.format.DateTimeFormatter +import java.util.Base64 +import java.util.Locale +import kotlin.math.roundToInt + +data class ExifResult(val shutterInfo: String, val cameraInfo: String, val dateTime: String, val location: String, val urlName: String, val w: Int, val h: Int) { + override fun toString(): String { + return "Date/Time: $dateTime\nLocation: $location\nCamera Info: $cameraInfo\nShutter Info: $shutterInfo\nURL Name: $urlName" + } +} + +suspend fun parseExif(data: ByteArray): ExifResult? { + val exif = Kim.readMetadata(data) ?: return null + val d = exif.convertToPhotoMetadata() + + return ExifResult( + shutterInfo = "${d.focalLength?.roundToInt()}mm  f/${d.fNumber}  ${d.exposureTime}s  ISO-${d.iso}", + // TODO: Un-hardcode this in the future + cameraInfo = if (d.cameraModel == "ILCE-6700") "Sony ILCE-6700 + Tamron 18-300 F3.5-6.3" else listOfNotNull( + listOfNotNull(d.cameraMake?.lowercase()?.replaceFirstChar { it.titlecase() }, d.cameraModel).ifEmpty { null }?.joinToString(" "), + listOfNotNull(d.lensMake, d.lensModel).ifEmpty { null }?.joinToString(" "), + ).joinToString(" + "), + + dateTime = run { + val dateTime = LocalDateTime.ofEpochSecond((d.takenDate ?: System.currentTimeMillis()) / 1000, 0, java.time.ZoneOffset.UTC) + .format(DateTimeFormatter.ofPattern("yyyy-MM-dd  HH:mm")) + val tz = ZonedDateTime.now().format(DateTimeFormatter.ofPattern("z", Locale.US)) + "$dateTime $tz" + }, + + location = d.gpsCoordinates?.let { coords -> + getAddress(coords.latitude, coords.longitude)?.let { + listOfNotNull(it.address.quarter, it.address.city, it.address.state, it.address.province, it.address.country) + .joinToString(", ").ifEmpty { it.display_name }.ifEmpty { null } + } + } ?: d.locationDisplay ?: "Unknown Location", + + // Create a 5-letter short-link ID + // Hash the first 65536 bytes of the image data, and then take the first 5 letters of the Base32 encoding of the hash + urlName = run { + val hash = MessageDigest.getInstance("SHA-1").apply { update(data.sliceArray(0 until minOf(data.size, 65536))) }.digest() + val base32 = Base64.getEncoder().encodeToString(hash).replace("=", "").replace("/", "").replace("+", "") + base32.take(5) + }, + + w = d.widthPx!!, + h = d.heightPx!! + ) +} \ No newline at end of file diff --git a/android/app/src/main/java/aza/instant/MainActivity.kt b/android/app/src/main/java/aza/instant/MainActivity.kt new file mode 100644 index 0000000..fef15e1 --- /dev/null +++ b/android/app/src/main/java/aza/instant/MainActivity.kt @@ -0,0 +1,180 @@ +package aza.instant + +import android.Manifest +import android.content.pm.PackageManager +import android.graphics.Picture +import android.os.Bundle +import android.util.Log +import androidx.activity.ComponentActivity +import androidx.activity.compose.rememberLauncherForActivityResult +import androidx.activity.compose.setContent +import androidx.activity.result.contract.ActivityResultContracts +import androidx.compose.foundation.Canvas +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.width +import androidx.compose.foundation.rememberScrollState +import androidx.compose.foundation.verticalScroll +import androidx.compose.material3.Button +import androidx.compose.material3.Scaffold +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.rememberCoroutineScope +import androidx.compose.runtime.setValue +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.drawWithCache +import androidx.compose.ui.graphics.drawscope.drawIntoCanvas +import androidx.compose.ui.graphics.nativeCanvas +import androidx.compose.ui.platform.LocalContext +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.dp +import androidx.core.content.ContextCompat +import aza.instant.ui.theme.ProjectInstantTheme +import kotlinx.coroutines.launch +import java.io.BufferedReader +import java.io.File +import java.io.InputStreamReader + +class MainActivity : ComponentActivity() { + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + setContent { + ProjectInstantTheme { + Scaffold(modifier = Modifier.fillMaxSize()) { innerPadding -> + FileListerScreen( + modifier = Modifier.padding(innerPadding) + ) + } + } + } + } +} + +@Composable +fun FileListerScreen(modifier: Modifier = Modifier) { + var feedbackText by remember { mutableStateOf("Click a button to see the output here.") } + var picture by remember { mutableStateOf(null) } + val scrollState = rememberScrollState() + val coroutineScope = rememberCoroutineScope() + + Column( + modifier = modifier.fillMaxSize(), + horizontalAlignment = Alignment.CenterHorizontally + ) { + val context = LocalContext.current + val permission = Manifest.permission.READ_MEDIA_IMAGES + + val launcher = rememberLauncherForActivityResult( + ActivityResultContracts.RequestPermission() + ) { isGranted: Boolean -> + feedbackText = if (isGranted) { + Log.d("MainActivity", "Permission granted") + listFilesFromDcim() + } else { + Log.d("MainActivity", "Permission denied") + "Permission was denied. Please grant permission in settings." + } + } + + Row(modifier = Modifier.padding(16.dp)) { + Button( + onClick = { + when (ContextCompat.checkSelfPermission( + context, + permission + )) { + PackageManager.PERMISSION_GRANTED -> { + Log.d("MainActivity", "Permission already granted") + feedbackText = listFilesFromDcim() + } + else -> { + launcher.launch(permission) + } + } + }, + ) { + Text(text = "List Files") + } + Spacer(modifier = Modifier.width(8.dp)) + Button( + onClick = { + coroutineScope.launch { + feedbackText = "Rendering..." + val imageDir = File("/storage/emulated/0/DCIM/CA_IMAGES/") + val latestImage = imageDir.listFiles()?.maxByOrNull { it.lastModified() } + if (latestImage != null) { + try { + val templateStream = context.resources.openRawResource(R.raw.postcard4) + val template = BufferedReader(InputStreamReader(templateStream)).readText() + val svg = genSvg(template, latestImage.absolutePath) + picture = renderSvgAndroid(svg) + feedbackText = "Rendered ${latestImage.name}" + } catch (e: Exception) { + feedbackText = "Error rendering: ${e.message}" + Log.e("MainActivity", "Error rendering", e) + } + } else { + feedbackText = "No images found in CA_IMAGES" + } + } + }, + ) { + Text(text = "Render Latest Image") + } + } + + picture?.let { it1 -> + Canvas(modifier = Modifier.fillMaxWidth().weight(1f).drawWithCache { + onDrawWithContent{ + drawIntoCanvas { + it.nativeCanvas.drawPicture(picture!!) + } + } + }) {} + } + + + Text( + text = feedbackText, + modifier = Modifier + .weight(1f) + .fillMaxWidth() + .padding(horizontal = 16.dp) + .verticalScroll(scrollState) + ) + } +} + +private fun listFilesFromDcim(): String { + val imageDir = File("/storage/emulated/0/DCIM/CA_IMAGES/") + return if (imageDir.exists() && imageDir.isDirectory) { + val files = imageDir.listFiles() + if (files != null) { + if (files.isEmpty()) { + "No files found in ${imageDir.absolutePath}" + } else { + files.joinToString("\n") { it.name } + } + } else { + "Failed to list files in ${imageDir.absolutePath}. listFiles() returned null." + } + } else { + "Directory not found or is not a directory: ${imageDir.absolutePath}" + } +} + +@Preview(showBackground = true) +@Composable +fun FileListerScreenPreview() { + ProjectInstantTheme { + FileListerScreen() + } +} diff --git a/android/app/src/main/java/aza/instant/SvgUtil.kt b/android/app/src/main/java/aza/instant/SvgUtil.kt new file mode 100644 index 0000000..3401ffe --- /dev/null +++ b/android/app/src/main/java/aza/instant/SvgUtil.kt @@ -0,0 +1,39 @@ +package aza.instant + +import android.graphics.Picture +import com.caverock.androidsvg.SVG +import qrcode.QRCode +import qrcode.raw.ErrorCorrectionLevel +import java.io.ByteArrayOutputStream +import java.util.Base64 +import kotlin.io.path.Path +import kotlin.io.path.readBytes + +fun createQRCode(url: String) = + QRCode.ofSquares().withSize(5).withMargin(0).withErrorCorrectionLevel(ErrorCorrectionLevel.LOW) + .withInnerSpacing(0).build(url).render() + +suspend fun genSvg(template: String, imagePath: String): String { + var svg = template + + // Read target image's EXIF data + val imageData = Path(imagePath).readBytes() + val exif = parseExif(imageData) ?: error("No EXIF data found") + println(exif) + val qr = createQRCode("https://p.aza.moe/${exif.urlName}") + val stream = ByteArrayOutputStream() + qr.writeImage(stream) + + svg = svg.replace("2025-09-26  14:59", exif.dateTime) + .replace("Nagatoro, Saitama, Japan", exif.location) +// .replace("""Sony ILCE-6700 + Tamron 18-300 F3.5-6.3""", exif.cameraInfo) + .replace("245mm  F6.3  1/500s  ISO-100", exif.shutterInfo) + .replace("a12ym", exif.urlName) + .replace("""""".toRegex(), + """""") + .replace("""""".toRegex(), + """""") + return svg +} + +fun renderSvgAndroid(svg: String): Picture? = SVG.getFromString(svg).renderToPicture(6192 /6, 4128/6) \ No newline at end of file diff --git a/android/app/src/main/java/aza/instant/ui/theme/Color.kt b/android/app/src/main/java/aza/instant/ui/theme/Color.kt new file mode 100644 index 0000000..fac5a8e --- /dev/null +++ b/android/app/src/main/java/aza/instant/ui/theme/Color.kt @@ -0,0 +1,11 @@ +package aza.instant.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) \ No newline at end of file diff --git a/android/app/src/main/java/aza/instant/ui/theme/Theme.kt b/android/app/src/main/java/aza/instant/ui/theme/Theme.kt new file mode 100644 index 0000000..5c34126 --- /dev/null +++ b/android/app/src/main/java/aza/instant/ui/theme/Theme.kt @@ -0,0 +1,58 @@ +package aza.instant.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.ui.platform.LocalContext + +private val DarkColorScheme = darkColorScheme( + primary = Purple80, + secondary = PurpleGrey80, + tertiary = Pink80 +) + +private val LightColorScheme = lightColorScheme( + primary = Purple40, + secondary = PurpleGrey40, + tertiary = Pink40 + + /* Other default colors to override + background = Color(0xFFFFFBFE), + surface = Color(0xFFFFFBFE), + onPrimary = Color.White, + onSecondary = Color.White, + onTertiary = Color.White, + onBackground = Color(0xFF1C1B1F), + onSurface = Color(0xFF1C1B1F), + */ +) + +@Composable +fun ProjectInstantTheme( + 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 + } + + MaterialTheme( + colorScheme = colorScheme, + typography = Typography, + content = content + ) +} \ No newline at end of file diff --git a/android/app/src/main/java/aza/instant/ui/theme/Type.kt b/android/app/src/main/java/aza/instant/ui/theme/Type.kt new file mode 100644 index 0000000..8ea19ec --- /dev/null +++ b/android/app/src/main/java/aza/instant/ui/theme/Type.kt @@ -0,0 +1,34 @@ +package aza.instant.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 + ) + /* Other default text styles to override + 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 + ) + */ +) \ No newline at end of file diff --git a/android/app/src/main/res/drawable/ic_launcher_background.xml b/android/app/src/main/res/drawable/ic_launcher_background.xml new file mode 100644 index 0000000..07d5da9 --- /dev/null +++ b/android/app/src/main/res/drawable/ic_launcher_background.xml @@ -0,0 +1,170 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/android/app/src/main/res/drawable/ic_launcher_foreground.xml b/android/app/src/main/res/drawable/ic_launcher_foreground.xml new file mode 100644 index 0000000..2b068d1 --- /dev/null +++ b/android/app/src/main/res/drawable/ic_launcher_foreground.xml @@ -0,0 +1,30 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/android/app/src/main/res/mipmap-anydpi/ic_launcher.xml b/android/app/src/main/res/mipmap-anydpi/ic_launcher.xml new file mode 100644 index 0000000..6f3b755 --- /dev/null +++ b/android/app/src/main/res/mipmap-anydpi/ic_launcher.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/android/app/src/main/res/mipmap-anydpi/ic_launcher_round.xml b/android/app/src/main/res/mipmap-anydpi/ic_launcher_round.xml new file mode 100644 index 0000000..6f3b755 --- /dev/null +++ b/android/app/src/main/res/mipmap-anydpi/ic_launcher_round.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/android/app/src/main/res/mipmap-hdpi/ic_launcher.webp b/android/app/src/main/res/mipmap-hdpi/ic_launcher.webp new file mode 100644 index 0000000..c209e78 Binary files /dev/null and b/android/app/src/main/res/mipmap-hdpi/ic_launcher.webp differ diff --git a/android/app/src/main/res/mipmap-hdpi/ic_launcher_round.webp b/android/app/src/main/res/mipmap-hdpi/ic_launcher_round.webp new file mode 100644 index 0000000..b2dfe3d Binary files /dev/null and b/android/app/src/main/res/mipmap-hdpi/ic_launcher_round.webp differ diff --git a/android/app/src/main/res/mipmap-mdpi/ic_launcher.webp b/android/app/src/main/res/mipmap-mdpi/ic_launcher.webp new file mode 100644 index 0000000..4f0f1d6 Binary files /dev/null and b/android/app/src/main/res/mipmap-mdpi/ic_launcher.webp differ diff --git a/android/app/src/main/res/mipmap-mdpi/ic_launcher_round.webp b/android/app/src/main/res/mipmap-mdpi/ic_launcher_round.webp new file mode 100644 index 0000000..62b611d Binary files /dev/null and b/android/app/src/main/res/mipmap-mdpi/ic_launcher_round.webp differ diff --git a/android/app/src/main/res/mipmap-xhdpi/ic_launcher.webp b/android/app/src/main/res/mipmap-xhdpi/ic_launcher.webp new file mode 100644 index 0000000..948a307 Binary files /dev/null and b/android/app/src/main/res/mipmap-xhdpi/ic_launcher.webp differ diff --git a/android/app/src/main/res/mipmap-xhdpi/ic_launcher_round.webp b/android/app/src/main/res/mipmap-xhdpi/ic_launcher_round.webp new file mode 100644 index 0000000..1b9a695 Binary files /dev/null and b/android/app/src/main/res/mipmap-xhdpi/ic_launcher_round.webp differ diff --git a/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.webp b/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.webp new file mode 100644 index 0000000..28d4b77 Binary files /dev/null and b/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.webp differ diff --git a/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp b/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp new file mode 100644 index 0000000..9287f50 Binary files /dev/null and b/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp differ diff --git a/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp b/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp new file mode 100644 index 0000000..aa7d642 Binary files /dev/null and b/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp differ diff --git a/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp b/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp new file mode 100644 index 0000000..9126ae3 Binary files /dev/null and b/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp differ diff --git a/android/app/src/main/res/raw/postcard4.svg b/android/app/src/main/res/raw/postcard4.svg new file mode 100644 index 0000000..8093e04 --- /dev/null +++ b/android/app/src/main/res/raw/postcard4.svg @@ -0,0 +1,57 @@ + + + + + + + + + + + + 2025-09-26  14:59 + + + Nagatoro, Saitama, Japan + + + 245mm  F6.3  1/500s  ISO-100 + + + Sony ILCE-6700 + Tamron 18-300 F3.5-6.3 + + + + + + + + Photographed by + + + Wandering Photographer + + + Photo Download + + + https://p.aza.moe/a12ym + + + + + + + + + + + + + + + + + + + diff --git a/android/app/src/main/res/values/colors.xml b/android/app/src/main/res/values/colors.xml new file mode 100644 index 0000000..f8c6127 --- /dev/null +++ b/android/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/android/app/src/main/res/values/strings.xml b/android/app/src/main/res/values/strings.xml new file mode 100644 index 0000000..64f2349 --- /dev/null +++ b/android/app/src/main/res/values/strings.xml @@ -0,0 +1,3 @@ + + ProjectInstant + \ No newline at end of file diff --git a/android/app/src/main/res/values/themes.xml b/android/app/src/main/res/values/themes.xml new file mode 100644 index 0000000..24b68eb --- /dev/null +++ b/android/app/src/main/res/values/themes.xml @@ -0,0 +1,5 @@ + + + +