[+] Android

This commit is contained in:
2025-10-26 06:28:50 +08:00
parent cb36ee118d
commit 9bc9a23537
42 changed files with 1381 additions and 0 deletions
+1
View File
@@ -0,0 +1 @@
/build
+72
View File
@@ -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)
}
+21
View File
@@ -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
@@ -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)
}
}
+29
View File
@@ -0,0 +1,29 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools">
<uses-permission android:name="android.permission.READ_MEDIA_IMAGES" />
<application
android:allowBackup="true"
android:dataExtractionRules="@xml/data_extraction_rules"
android:fullBackupContent="@xml/backup_rules"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="@style/Theme.ProjectInstant">
<activity
android:name=".MainActivity"
android:exported="true"
android:label="@string/app_name"
android:theme="@style/Theme.ProjectInstant">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
</application>
</manifest>
@@ -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()
}
@@ -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!!
)
}
@@ -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<Picture?>(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()
}
}
@@ -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("Nag<tspan x=\"1309.04px 1369.61px 1408.89px 1468.58px 1508.64px \" y=\"4531.84px 4531.84px 4531.84px 4531.84px 4531.84px \">atoro</tspan>, Saitama, Japan", exif.location)
// .replace("""Son<tspan x="4080.43px " y="4377.36px ">y</tspan> IL<tspan x="4189.39px " y="4377.36px ">C</tspan>E-6700 + T<tspan x="4553.27px " y="4377.36px ">a</tspan>mr<tspan x="4679.42px " y="4377.36px ">o</tspan>n 18-300 F3.5-6.3""", exif.cameraInfo)
.replace("245mm  F6.3  1/500s  ISO-100", exif.shutterInfo)
.replace("a12ym", exif.urlName)
.replace("""<image id="_Image1" width="6192px" height="4128px" xlink:href="data:image/jpeg;base64,.*?"/>""".toRegex(),
"""<image id=\"_Image1\" width=\"${exif.w}px\" height=\"${exif.h}px\" xlink:href=\"data:image/jpeg;base64,${Base64.getEncoder().encodeToString(imageData)}\"/>""")
.replace("""<image id="_Image3" width="100px" height="100px" xlink:href="data:image/png;base64,.*?"/>""".toRegex(),
"""<image id=\"_Image3\" width=\"100px\" height=\"100px\" xlink:href=\"data:image/png;base64,${Base64.getEncoder().encodeToString(stream.toByteArray())}\"/>""")
return svg
}
fun renderSvgAndroid(svg: String): Picture? = SVG.getFromString(svg).renderToPicture(6192 /6, 4128/6)
@@ -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)
@@ -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
)
}
@@ -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
)
*/
)
@@ -0,0 +1,170 @@
<?xml version="1.0" encoding="utf-8"?>
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="108dp"
android:height="108dp"
android:viewportWidth="108"
android:viewportHeight="108">
<path
android:fillColor="#3DDC84"
android:pathData="M0,0h108v108h-108z" />
<path
android:fillColor="#00000000"
android:pathData="M9,0L9,108"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M19,0L19,108"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M29,0L29,108"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M39,0L39,108"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M49,0L49,108"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M59,0L59,108"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M69,0L69,108"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M79,0L79,108"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M89,0L89,108"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M99,0L99,108"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,9L108,9"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,19L108,19"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,29L108,29"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,39L108,39"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,49L108,49"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,59L108,59"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,69L108,69"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,79L108,79"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,89L108,89"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,99L108,99"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M19,29L89,29"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M19,39L89,39"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M19,49L89,49"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M19,59L89,59"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M19,69L89,69"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M19,79L89,79"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M29,19L29,89"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M39,19L39,89"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M49,19L49,89"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M59,19L59,89"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M69,19L69,89"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M79,19L79,89"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
</vector>
@@ -0,0 +1,30 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:aapt="http://schemas.android.com/aapt"
android:width="108dp"
android:height="108dp"
android:viewportWidth="108"
android:viewportHeight="108">
<path android:pathData="M31,63.928c0,0 6.4,-11 12.1,-13.1c7.2,-2.6 26,-1.4 26,-1.4l38.1,38.1L107,108.928l-32,-1L31,63.928z">
<aapt:attr name="android:fillColor">
<gradient
android:endX="85.84757"
android:endY="92.4963"
android:startX="42.9492"
android:startY="49.59793"
android:type="linear">
<item
android:color="#44000000"
android:offset="0.0" />
<item
android:color="#00000000"
android:offset="1.0" />
</gradient>
</aapt:attr>
</path>
<path
android:fillColor="#FFFFFF"
android:fillType="nonZero"
android:pathData="M65.3,45.828l3.8,-6.6c0.2,-0.4 0.1,-0.9 -0.3,-1.1c-0.4,-0.2 -0.9,-0.1 -1.1,0.3l-3.9,6.7c-6.3,-2.8 -13.4,-2.8 -19.7,0l-3.9,-6.7c-0.2,-0.4 -0.7,-0.5 -1.1,-0.3C38.8,38.328 38.7,38.828 38.9,39.228l3.8,6.6C36.2,49.428 31.7,56.028 31,63.928h46C76.3,56.028 71.8,49.428 65.3,45.828zM43.4,57.328c-0.8,0 -1.5,-0.5 -1.8,-1.2c-0.3,-0.7 -0.1,-1.5 0.4,-2.1c0.5,-0.5 1.4,-0.7 2.1,-0.4c0.7,0.3 1.2,1 1.2,1.8C45.3,56.528 44.5,57.328 43.4,57.328L43.4,57.328zM64.6,57.328c-0.8,0 -1.5,-0.5 -1.8,-1.2s-0.1,-1.5 0.4,-2.1c0.5,-0.5 1.4,-0.7 2.1,-0.4c0.7,0.3 1.2,1 1.2,1.8C66.5,56.528 65.6,57.328 64.6,57.328L64.6,57.328z"
android:strokeWidth="1"
android:strokeColor="#00000000" />
</vector>
@@ -0,0 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
<background android:drawable="@drawable/ic_launcher_background" />
<foreground android:drawable="@drawable/ic_launcher_foreground" />
<monochrome android:drawable="@drawable/ic_launcher_foreground" />
</adaptive-icon>
@@ -0,0 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
<background android:drawable="@drawable/ic_launcher_background" />
<foreground android:drawable="@drawable/ic_launcher_foreground" />
<monochrome android:drawable="@drawable/ic_launcher_foreground" />
</adaptive-icon>
Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 982 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.6 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 2.5 MiB

@@ -0,0 +1,10 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<color name="purple_200">#FFBB86FC</color>
<color name="purple_500">#FF6200EE</color>
<color name="purple_700">#FF3700B3</color>
<color name="teal_200">#FF03DAC5</color>
<color name="teal_700">#FF018786</color>
<color name="black">#FF000000</color>
<color name="white">#FFFFFFFF</color>
</resources>
@@ -0,0 +1,3 @@
<resources>
<string name="app_name">ProjectInstant</string>
</resources>
@@ -0,0 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<style name="Theme.ProjectInstant" parent="android:Theme.Material.Light.NoActionBar" />
</resources>
@@ -0,0 +1,13 @@
<?xml version="1.0" encoding="utf-8"?><!--
Sample backup rules file; uncomment and customize as necessary.
See https://developer.android.com/guide/topics/data/autobackup
for details.
Note: This file is ignored for devices older than API 31
See https://developer.android.com/about/versions/12/backup-restore
-->
<full-backup-content>
<!--
<include domain="sharedpref" path="."/>
<exclude domain="sharedpref" path="device.xml"/>
-->
</full-backup-content>
@@ -0,0 +1,19 @@
<?xml version="1.0" encoding="utf-8"?><!--
Sample data extraction rules file; uncomment and customize as necessary.
See https://developer.android.com/about/versions/12/backup-restore#xml-changes
for details.
-->
<data-extraction-rules>
<cloud-backup>
<!-- TODO: Use <include> and <exclude> to control what is backed up.
<include .../>
<exclude .../>
-->
</cloud-backup>
<!--
<device-transfer>
<include .../>
<exclude .../>
</device-transfer>
-->
</data-extraction-rules>
@@ -0,0 +1,17 @@
package aza.instant
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)
}
}