init #48

Merged
admin merged 18 commits from develop into main 2026-04-30 10:35:58 +02:00
19 changed files with 8949 additions and 188 deletions
Showing only changes of commit 2ab7ef3f65 - Show all commits

View File

@@ -0,0 +1 @@
-dontobfuscate

View File

@@ -19,10 +19,16 @@ android {
versionName = "1.0" versionName = "1.0"
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner" testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
testInstrumentationRunnerArguments["androidx.compose.ui.test.tagsAsResourceId"] = "true"
} }
buildTypes { buildTypes {
debug {
applicationIdSuffix = ".debug"
isDebuggable = true
}
release { release {
signingConfig = signingConfigs.getByName("debug")
isMinifyEnabled = true isMinifyEnabled = true
isShrinkResources = true isShrinkResources = true
proguardFiles( proguardFiles(
@@ -31,24 +37,21 @@ android {
) )
} }
create("benchmark") { create("benchmark") {
initWith(getByName("release"))
isDebuggable = false
isMinifyEnabled = false isMinifyEnabled = false
isShrinkResources = false isShrinkResources = false
initWith(buildTypes.getByName("release"))
// Use release signing if needed (optional for local)
signingConfig = signingConfigs.getByName("debug") signingConfig = signingConfigs.getByName("debug")
matchingFallbacks += listOf("release") matchingFallbacks += listOf("release")
isDebuggable = false
proguardFiles("baseline-profiles-rules.pro")
} }
} }
compileOptions { compileOptions {
sourceCompatibility = JavaVersion.VERSION_11 sourceCompatibility = JavaVersion.VERSION_17
targetCompatibility = JavaVersion.VERSION_11 targetCompatibility = JavaVersion.VERSION_17
} }
kotlinOptions { kotlinOptions {
jvmTarget = "11" jvmTarget = "17"
freeCompilerArgs = listOf("-XXLanguage:+PropertyParamAnnotationDefaultTargetMode") freeCompilerArgs = listOf("-XXLanguage:+PropertyParamAnnotationDefaultTargetMode")
} }
buildFeatures { buildFeatures {
@@ -74,7 +77,6 @@ dependencies {
androidTestImplementation(libs.androidx.espresso.core) androidTestImplementation(libs.androidx.espresso.core)
androidTestImplementation(platform(libs.androidx.compose.bom)) androidTestImplementation(platform(libs.androidx.compose.bom))
androidTestImplementation(libs.androidx.compose.ui.test.junit4) androidTestImplementation(libs.androidx.compose.ui.test.junit4)
"baselineProfile"(project(":baselineprofile"))
debugImplementation(libs.androidx.compose.ui.tooling) debugImplementation(libs.androidx.compose.ui.tooling)
debugImplementation(libs.androidx.compose.ui.test.manifest) debugImplementation(libs.androidx.compose.ui.test.manifest)

View File

@@ -19,3 +19,4 @@
# If you keep the line number information, uncomment this to # If you keep the line number information, uncomment this to
# hide the original source file name. # hide the original source file name.
#-renamesourcefileattribute SourceFile #-renamesourcefileattribute SourceFile
-dontwarn edu.umd.cs.findbugs.annotations.SuppressFBWarnings

View File

@@ -12,16 +12,22 @@
android:roundIcon="@mipmap/ic_launcher_round" android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true" android:supportsRtl="true"
android:theme="@style/Theme.TripMoney"> android:theme="@style/Theme.TripMoney">
<profileable
android:shell="true"
tools:targetApi="29" />
<activity <activity
android:screenOrientation="portrait"
android:name=".MainActivity" android:name=".MainActivity"
android:exported="true" android:exported="true"
android:screenOrientation="portrait"
android:theme="@style/Theme.TripMoney"> android:theme="@style/Theme.TripMoney">
<intent-filter> <intent-filter>
<action android:name="android.intent.action.MAIN" /> <action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" /> <category android:name="android.intent.category.LAUNCHER" />
</intent-filter> </intent-filter>
</activity> </activity>
<provider <provider
android:name="androidx.core.content.FileProvider" android:name="androidx.core.content.FileProvider"
android:authorities="${applicationId}.provider" android:authorities="${applicationId}.provider"
@@ -29,9 +35,8 @@
android:grantUriPermissions="true"> android:grantUriPermissions="true">
<meta-data <meta-data
android:name="android.support.FILE_PROVIDER_PATHS" android:name="android.support.FILE_PROVIDER_PATHS"
android:resource="@xml/file_paths"/> android:resource="@xml/file_paths" />
</provider> </provider>
</application> </application>
</manifest> </manifest>

File diff suppressed because it is too large Load Diff

View File

@@ -3,6 +3,7 @@ package cc.n0th1ng.tripmoney
import android.os.Build import android.os.Build
import android.os.Bundle import android.os.Bundle
import androidx.activity.ComponentActivity import androidx.activity.ComponentActivity
import androidx.activity.compose.ReportDrawnWhen
import androidx.activity.compose.setContent import androidx.activity.compose.setContent
import androidx.activity.enableEdgeToEdge import androidx.activity.enableEdgeToEdge
import androidx.annotation.RequiresApi import androidx.annotation.RequiresApi
@@ -11,6 +12,7 @@ import androidx.compose.material3.DrawerValue
import androidx.compose.material3.Scaffold import androidx.compose.material3.Scaffold
import androidx.compose.material3.rememberDrawerState import androidx.compose.material3.rememberDrawerState
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.collectAsState import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.mutableStateOf
@@ -55,6 +57,7 @@ class MainActivity : ComponentActivity() {
NavigationDrawer() NavigationDrawer()
} }
} }
} }
} }
@@ -77,7 +80,7 @@ fun NavigationDrawer() {
val autoOpenPref by settingsViewModel.autoOpenStartupPref.collectAsState() val autoOpenPref by settingsViewModel.autoOpenStartupPref.collectAsState()
var hasHandledStartupOpen by rememberSaveable { mutableStateOf(false) } var hasHandledStartupOpen by rememberSaveable { mutableStateOf(false) }
val shouldTriggerAutoOpen = autoOpenPref == true && !hasHandledStartupOpen val shouldTriggerAutoOpen = autoOpenPref == true && !hasHandledStartupOpen
ReportDrawnWhen { !categories.isEmpty() }
CustomNavigationDrawer(navController, drawerState) { CustomNavigationDrawer(navController, drawerState) {
Scaffold( Scaffold(
topBar = { topBar = {

View File

@@ -212,7 +212,7 @@ private class DatabasePrepopulator(
color = colors.random() color = colors.random()
), ),
Category( Category(
name = "Zakupy9 ", name = "Zakupy9",
icon = Icons.GROCERIES, icon = Icons.GROCERIES,
color = colors.random() color = colors.random()
), ),

View File

@@ -19,7 +19,7 @@ import java.time.LocalDateTime
onUpdate = ForeignKey.CASCADE, onUpdate = ForeignKey.CASCADE,
onDelete = ForeignKey.CASCADE onDelete = ForeignKey.CASCADE
)], )],
indices = [Index(value = ["category_id"], unique = true)] indices = [Index(value = ["category_id"])]
) )
@Immutable @Immutable
data class Expense( data class Expense(

View File

@@ -47,8 +47,11 @@ 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.draw.clip
import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.Color
import androidx.compose.ui.platform.testTag
import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.painterResource
import androidx.compose.ui.res.stringResource import androidx.compose.ui.res.stringResource
import androidx.compose.ui.semantics.contentDescription
import androidx.compose.ui.semantics.semantics
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import androidx.core.graphics.toColorInt import androidx.core.graphics.toColorInt
import androidx.hilt.lifecycle.viewmodel.compose.hiltViewModel import androidx.hilt.lifecycle.viewmodel.compose.hiltViewModel
@@ -143,7 +146,9 @@ fun ListExpenseScreen(
{ {
Box { Box {
LazyColumn( LazyColumn(
modifier = Modifier.fillMaxSize(), modifier = Modifier.fillMaxSize().semantics {
contentDescription = "expensesList"
},
horizontalAlignment = Alignment.CenterHorizontally, horizontalAlignment = Alignment.CenterHorizontally,
state = listState state = listState
) { ) {

View File

@@ -1,53 +0,0 @@
plugins {
alias(libs.plugins.android.test)
alias(libs.plugins.kotlin.android)
alias(libs.plugins.baselineprofile)
}
android {
namespace = "cc.n0th1ng.baselineprofile"
compileSdk = 36
compileOptions {
sourceCompatibility = JavaVersion.VERSION_11
targetCompatibility = JavaVersion.VERSION_11
}
kotlinOptions {
jvmTarget = "11"
}
defaultConfig {
minSdk = 28
targetSdk = 36
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
}
targetProjectPath = ":app"
}
// This is the configuration block for the Baseline Profile plugin.
// You can specify to run the generators on a managed devices or connected devices.
baselineProfile {
useConnectedDevices = true
}
dependencies {
implementation(libs.androidx.junit)
implementation(libs.androidx.espresso.core)
implementation(libs.androidx.uiautomator)
implementation(libs.androidx.benchmark.macro.junit4)
implementation(libs.androidx.ui.test.junit4)
}
androidComponents {
onVariants { v ->
val artifactsLoader = v.artifacts.getBuiltArtifactsLoader()
v.instrumentationRunnerArguments.put(
"targetAppId",
v.testedApks.map { artifactsLoader.load(it)?.applicationId }
)
}
}

View File

@@ -1,36 +0,0 @@
package cc.n0th1ng.baselineprofile
import android.R.attr.contentDescription
import androidx.benchmark.macro.MacrobenchmarkScope
import androidx.benchmark.macro.junit4.BaselineProfileRule
import androidx.compose.ui.test.junit4.createAndroidComposeRule
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.LargeTest
import androidx.test.platform.app.InstrumentationRegistry
import androidx.test.uiautomator.By
import androidx.test.uiautomator.Direction
import androidx.test.uiautomator.Until
import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
@RunWith(AndroidJUnit4::class)
@LargeTest
class BaselineProfileGenerator {
@get:Rule
val rule = BaselineProfileRule()
@Test
fun generate() {
rule.collect(
packageName = "cc.n0th1ng.tripmoney",
includeInStartupProfile = true
) {
// pressHome()
startActivityAndWait()
device.waitForIdle()
device.wait(Until.hasObject(By.desc("list screen")), 10_000)
}
}
}

View File

@@ -1,76 +0,0 @@
package cc.n0th1ng.baselineprofile
import androidx.benchmark.macro.BaselineProfileMode
import androidx.benchmark.macro.CompilationMode
import androidx.benchmark.macro.StartupMode
import androidx.benchmark.macro.StartupTimingMetric
import androidx.benchmark.macro.junit4.MacrobenchmarkRule
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.LargeTest
import androidx.test.platform.app.InstrumentationRegistry
import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
/**
* This test class benchmarks the speed of app startup.
* Run this benchmark to verify how effective a Baseline Profile is.
* It does this by comparing [CompilationMode.None], which represents the app with no Baseline
* Profiles optimizations, and [CompilationMode.Partial], which uses Baseline Profiles.
*
* Run this benchmark to see startup measurements and captured system traces for verifying
* the effectiveness of your Baseline Profiles. You can run it directly from Android
* Studio as an instrumentation test, or run all benchmarks for a variant, for example benchmarkRelease,
* with this Gradle task:
* ```
* ./gradlew :baselineprofile:connectedBenchmarkReleaseAndroidTest
* ```
*
* You should run the benchmarks on a physical device, not an Android emulator, because the
* emulator doesn't represent real world performance and shares system resources with its host.
*
* For more information, see the [Macrobenchmark documentation](https://d.android.com/macrobenchmark#create-macrobenchmark)
* and the [instrumentation arguments documentation](https://d.android.com/topic/performance/benchmarking/macrobenchmark-instrumentation-args).
**/
@RunWith(AndroidJUnit4::class)
@LargeTest
class StartupBenchmarks {
@get:Rule
val rule = MacrobenchmarkRule()
@Test
fun startupCompilationNone() =
benchmark(CompilationMode.None())
@Test
fun startupCompilationBaselineProfiles() =
benchmark(CompilationMode.Partial(BaselineProfileMode.Require))
private fun benchmark(compilationMode: CompilationMode) {
// The application id for the running build variant is read from the instrumentation arguments.
rule.measureRepeated(
packageName = InstrumentationRegistry.getArguments().getString("targetAppId")
?: throw Exception("targetAppId not passed as instrumentation runner arg"),
metrics = listOf(StartupTimingMetric()),
compilationMode = compilationMode,
startupMode = StartupMode.COLD,
iterations = 10,
setupBlock = {
pressHome()
},
measureBlock = {
startActivityAndWait()
// TODO Add interactions to wait for when your app is fully drawn.
// The app is fully drawn when Activity.reportFullyDrawn is called.
// For Jetpack Compose, you can use ReportDrawn, ReportDrawnWhen and ReportDrawnAfter
// from the AndroidX Activity library.
// Check the UiAutomator documentation for more information on how to
// interact with the app.
// https://d.android.com/training/testing/other-components/ui-automator
}
)
}
}

View File

@@ -0,0 +1,54 @@
import com.android.build.api.dsl.ManagedVirtualDevice
kotlin {
jvmToolchain(21)
}
plugins {
alias(libs.plugins.android.test)
alias(libs.plugins.kotlin.android)
}
android {
namespace = "cc.n0th1ng.benchmark"
compileSdk = 36
defaultConfig {
minSdk = 24
targetSdk = 36
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
testInstrumentationRunnerArguments["androidx.compose.ui.test.tagsAsResourceId"] = "true"
}
testOptions.managedDevices.devices {
create<ManagedVirtualDevice>("pixel6Api31") {
device = "Pixel 6"
apiLevel = 31
systemImageSource = "aosp"
}
}
buildTypes {
create("benchmark") {
isDebuggable = true
signingConfig = getByName("debug").signingConfig
matchingFallbacks += listOf("release")
}
}
targetProjectPath = ":app"
experimentalProperties["android.experimental.self-instrumenting"] = true
}
dependencies {
implementation(libs.androidx.junit)
implementation(libs.androidx.espresso.core)
implementation(libs.androidx.uiautomator)
implementation(libs.androidx.benchmark.macro.junit4)
}
androidComponents {
beforeVariants(selector().all()) {
it.enable = it.buildType == "benchmark"
}
}

View File

@@ -0,0 +1,46 @@
package cc.n0th1ng.benchmark
import android.os.Build
import androidx.annotation.RequiresApi
import androidx.benchmark.macro.junit4.BaselineProfileRule
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.internal.runner.junit4.AndroidJUnit4ClassRunner
import androidx.test.uiautomator.By
import androidx.test.uiautomator.Direction
import androidx.test.uiautomator.Until
import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
@RunWith(AndroidJUnit4ClassRunner::class)
class BaselineProfileGenerator {
@RequiresApi(Build.VERSION_CODES.P)
@get:Rule
val rule = BaselineProfileRule()
@RequiresApi(Build.VERSION_CODES.P)
@Test
fun startup() = rule.collect(
maxIterations = 1,
packageName = "cc.n0th1ng.tripmoney",
profileBlock = {
device.executeShellCommand(
"rm -rf /data/data/cc.n0th1ng.tripmoney/files/datastore/"
)
startActivityAndWait()
device.wait(Until.hasObject(By.text("Włochy")), 10_000)
device.findObject(By.text("Włochy")).click()
val isVisible = device.wait(Until.hasObject(By.desc("expensesList")), 10_000)
assert(isVisible)
val expensesList = device.findObject(By.desc("expensesList"))
expensesList.setGestureMargin(device.displayWidth / 5)
expensesList.fling(Direction.DOWN)
expensesList.fling(Direction.UP)
expensesList.fling(Direction.DOWN)
expensesList.fling(Direction.UP)
}
)
}

View File

@@ -0,0 +1,43 @@
package cc.n0th1ng.benchmark
import androidx.benchmark.macro.CompilationMode
import androidx.benchmark.macro.StartupMode
import androidx.benchmark.macro.StartupTimingMetric
import androidx.benchmark.macro.junit4.MacrobenchmarkRule
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.uiautomator.By
import androidx.test.uiautomator.Until
import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
/**
* This is an example startup benchmark.
*
* It navigates to the device's home screen, and launches the default activity.
*
* Before running this benchmark:
* 1) switch your app's active build variant in the Studio (affedaggcts Studio runs only)
* 2) add `<profileable android:shell="true" />` to your app's manifest, within the `<application>` tag
*
* Run this benchmark from Studio to see startup measurements, and captured system traces
* for investigating your app's performance.
*/
@RunWith(AndroidJUnit4::class)
class ExampleStartupBenchmark {
@get:Rule
val benchmarkRule = MacrobenchmarkRule()
@Test
fun startup() = benchmarkRule.measureRepeated(
packageName = "cc.n0th1ng.tripmoney",
metrics = listOf(StartupTimingMetric()),
iterations = 5,
startupMode = StartupMode.COLD
) {
pressHome()
startActivityAndWait()
device.wait(Until.hasObject(By.pkg("cc.n0th1ng.tripmoney")), 10_000)
device.waitForIdle()
}
}

View File

@@ -35,10 +35,10 @@ roomRxjava2 = "2.8.4"
roomRxjava3 = "2.8.4" roomRxjava3 = "2.8.4"
roomTesting = "2.8.4" roomTesting = "2.8.4"
uiautomator = "2.3.0" uiautomator = "2.3.0"
benchmarkMacroJunit4 = "1.2.4" benchmarkMacroJunit4 = "1.4.1"
baselineprofile = "1.2.4" baselineprofile = "1.4.1"
profileinstaller = "1.3.1" profileinstaller = "1.4.1"
uiTestJunit4 = "1.10.6" uiTestJunit4 = "1.11.0"
[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" }

View File

@@ -21,4 +21,4 @@ dependencyResolutionManagement {
rootProject.name = "tripMoney" rootProject.name = "tripMoney"
include(":app") include(":app")
include(":baselineprofile") include(":benchmark")