Output:
Chunk ID: e932c8
Wall time: 0.1200 seconds
Process exited with code 0
Original token count: 455
Output:
   240	                            map["notPackage"] = screenshotTestPackage
   241	                            map["additionalTestOutputDir"] = smokeDeviceOutputDir
   242	                        }
   243	                    }
   244	                }
   245	                map
   246	            },
   247	        )
   248	    }
   249	}
   250
   251	tasks.register<Copy>("generateGmdCoverage") {
   252	    group = "Verification"
   253	    description = "Runs GMD tests and copies the coverage file to the baseline directory."
   254
   255	    // Coverage is always collected from the debug variant of the smoke device
   256	    // regardless of the current -PscreenshotBuild setting.
   257	    val smokeTask = "pixel2api33DebugAndroidTest"
   258	    dependsOn(smokeTask)
   259
   260	    from(layout.buildDirectory.dir("outputs/managed_device_code_coverage/debug/pixel2api33")) {
   261	        include("coverage.ec")
   262	    }
   263	    into(layout.projectDirectory.dir("coverage-baselines"))
   264	    rename { "gmd_smoke.ec" }
   265
   266	    includeEmptyDirs = false
   267	    duplicatesStrategy = DuplicatesStrategy.FAIL
   268
   269	    doLast {
   270	        val destFile = layout.projectDirectory.file("coverage-baselines/gmd_smoke.ec").asFile
   271	        if (!destFile.exists()) {
   272	            throw GradleException("Failed to generate GMD coverage. Expected file not found: $destFile")
   273	        }
   274	    }
   275	}
   276
   277	val storeScreenshotFiles =
   278	    listOf(
   279	        "screen_01_vehicle_list.png",
   280	        "screen_02_vehicle_detail.png",
   281	        "screen_03_add_record.png",
   282	        "screen_04_categories.png",
   283	        "screen_05_settings_privacy.png",
   284	        "screen_06_paywall_dialog.png",
   285	    )


./gradlew :app:tasks --all -PscreenshotBuild --no-daemon


Output:
Chunk ID: ec230b
Wall time: 1.0018 seconds
Process running with session ID 82932
Original token count: 72
Output:
Picked up JAVA_TOOL_OPTIONS: -Xlog:os+container=off
To honour the JVM settings for this build a single-use Daemon process will be forked. For more on this, please refer to https://docs.gradle.org/9.5.1/userguide/gradle_daemon.html#sec:disabling_the_daemon in the Gradle documentation.


Output:
Chunk ID: fdd653
Wall time: 5.0013 seconds
Process running with session ID 82932
Original token count: 44
Output:
Daemon will be stopped at the end of the build

> Configure project :app
WARNING: The option setting 'android.testoptions.manageddevices.emulator.gpu=host' is experimental.


Output:
Chunk ID: 2ced45
Wall time: 1.6374 seconds
Process exited with code 0
Original token count: 3499
Output:

> Task :app:tasks

------------------------------------------------------------
Tasks runnable from project ':app'
------------------------------------------------------------

Android tasks
-------------
signingReport - Displays the signing info for the base and test modules

Build tasks
-----------
assemble - Assemble main outputs for all the variants.
assembleAndroidTest - Assembles all the Test applications.
assembleUnitTest - Assembles all the unit test applications.
build - Assembles and tests this project.
buildDependents - Assembles and tests this project and all projects that depend on it.
buildNeeded - Assembles and tests this project and all projects it depends on.
bundle - Assemble bundles for all the variants.
clean - Deletes the build directory.
compileDebugSources
compileReleaseAndroidTestSources
compileReleaseSources
compileReleaseUnitTestSources

Formatting tasks
----------------
ktlintFormat - Runs the ktlint formatter on all kotlin sources in this project.

Help tasks
----------
artifactTransforms - Displays the Artifact Transforms that can be executed in project ':app'.
buildEnvironment - Displays all buildscript dependencies declared in project ':app'.
dependencies - Displays all dependencies declared in project ':app'.
dependencyInsight - Displays the insight into a specific dependency in project ':app'.
help - Displays a help message.
javaToolchains - Displays the detected java toolchains.
kotlinDslAccessorsReport - Prints the Kotlin code for accessing the currently available project extensions.
ktlintGenerateBaseline - Generates KtLint baseline file
outgoingVariants - Displays the outgoing variants of project ':app'.
projects - Displays the sub-projects of project ':app'.
properties - Displays the properties of project ':app'.
resolvableConfigurations - Displays the configurations that can be resolved in project ':app'.
tasks - Displays the tasks runnable from project ':app'.

Install tasks
-------------
installDebug - Installs the Debug build.
installRelease - Installs the Release build.
installReleaseAndroidTest - Installs the android (on device) tests for the Release build.
uninstallAll - Uninstall all applications.
uninstallDebug - Uninstalls the Debug build.
uninstallRelease - Uninstalls the Release build.
uninstallReleaseAndroidTest - Uninstalls the android (on device) tests for the Release build.

Verification tasks
------------------
allDevicesCheck - Runs all device checks on all managed devices defined in the TestOptions dsl.
allDevicesReleaseAndroidTest - Runs the tests for release on all managed devices in the dsl.
check - Runs all checks.
checkJetifier - Checks whether Jetifier is needed for the current project
compareRoborazziRelease
connectedAndroidTest - Installs and runs instrumentation tests for all flavors on connected devices.
connectedCheck - Runs all device checks on currently connected devices.
connectedReleaseAndroidTest - Installs and runs the tests for release on connected devices.
detekt
detektBaseline - Creates a detekt baseline on the given --baseline path.
detektGenerateConfig - Generate a detekt configuration file inside your project.
deviceAndroidTest - Installs and runs instrumentation tests using all Device Providers.
deviceCheck - Runs all device checks using Device Providers and Test Servers.
generateGmdCoverage - Runs GMD tests and copies the coverage file to the baseline directory.
generateStoreScreenshots - Runs store screenshot tests on managed devices and copies PNGs into distribution assets.
ktlintCheck - Runs ktlint on all kotlin sources in this project.
lint - Runs lint on the default variant.
lintDebug - Print text output from the corresponding lint report task
lintFix - Runs lint on the default variant and applies any safe suggestions to the source code.
lintRelease - Print text output from the corresponding lint report task
lintVitalRelease - Print text output from the corresponding lint report task
phonePixel6api34Check - Runs all device checks on the managed device phonePixel6api34.
phonePixel6api34ReleaseAndroidTest - Installs and runs the test for release  on the managed device phonePixel6api34
pixel2api33Check - Runs all device checks on the managed device pixel2api33.
pixel2api33ReleaseAndroidTest - Installs and runs the test for release  on the managed device pixel2api33
recordRoborazziRelease
tablet10PixelTabletApi34Check - Runs all device checks on the managed device tablet10PixelTabletApi34.
tablet10PixelTabletApi34ReleaseAndroidTest - Installs and runs the test for release  on the managed device tablet10PixelTabletApi34
tablet7Nexus7api34Check - Runs all device checks on the managed device tablet7Nexus7api34.
tablet7Nexus7api34ReleaseAndroidTest - Installs and runs the test for release  on the managed device tablet7Nexus7api34
test - Run unit tests for all variants.
testReleaseUnitTest - Run unit tests for the release build.
updateLintBaseline - Updates the lint baseline using the default variant.
verifyAndRecordRoborazziRelease
verifyRoborazziRelease
verifyWithCoverage - Runs JVM unit tests and generates a combined coverage report using JaCoCo with the static GMD baseline.

Other tasks
-----------
analyzeDebugDependencies
analyzeReleaseAndroidTestDependencies
analyzeReleaseDependencies
analyzeReleaseUnitTestDependencies
assembleDebug - Assembles main output for variant debug
assembleRelease - Assembles main output for variant release
assembleReleaseAndroidTest - Assembles main output for variant releaseAndroidTest
assembleReleaseUnitTest - Assembles main output for variant releaseUnitTest
buildDebugPreBundle
buildReleasePreBundle
bundleDebug - Assembles bundle for variant debug
bundleDebugClassesToCompileJar
bundleDebugClassesToRuntimeJar
bundleDebugResources
bundleRelease - Assembles bundle for variant release
bundleReleaseClassesToCompileJar
bundleReleaseClassesToRuntimeJar
bundleReleaseResources
checkDebugAarMetadata
checkDebugDuplicateClasses
checkDebugManifest
checkReleaseAarMetadata
checkReleaseAndroidTestAarMetadata
checkReleaseAndroidTestDuplicateClasses
checkReleaseDuplicateClasses
checkReleaseManifest
cleanManagedDevices
collectReleaseDependencies
compareRoborazzi
compileDebugArtProfile
compileDebugJavaWithJavac
compileDebugKotlin
compileDebugNavigationResources
compileLint
compileLintChecks
compileReleaseAndroidTestJavaWithJavac
compileReleaseAndroidTestKotlin
compileReleaseAndroidTestNavigationResources
compileReleaseArtProfile
compileReleaseJavaWithJavac
compileReleaseKotlin
compileReleaseNavigationResources
compileReleaseUnitTestJavaWithJavac
compileReleaseUnitTestKotlin
compressDebugAssets
compressReleaseAndroidTestAssets
compressReleaseAssets
configureReleaseDependencies
consumeConfigAttr
copyPhoneStoreScreenshots
copyTablet10StoreScreenshots
copyTablet7StoreScreenshots
createDebugApkListingFileRedirect
createDebugApksFromBundleListingFileRedirect
createDebugBundleListingFileRedirect
createDebugCompatibleScreenManifests
createMockableJar
createReleaseAndroidTestApkListingFileRedirect
createReleaseApkListingFileRedirect
createReleaseApksFromBundleListingFileRedirect
createReleaseBundleListingFileRedirect
createReleaseCompatibleScreenManifests
desugarDebugFileDependencies
dexBuilderDebug
expandReleaseArtProfileWildcards
extractApksForDebug
extractApksForRelease
extractApksFromBundleForDebug
extractApksFromBundleForRelease
extractDebugNativeDebugMetadata
extractDebugNativeSymbolTables
extractDeepLinksDebug
extractDeepLinksRelease
extractProguardFiles
extractReleaseNativeDebugMetadata
extractReleaseNativeSymbolTables
extractReleaseVersionControlInfo
finalizeTestRoborazziRelease
generateDebugAssets
generateDebugBuildConfig
generateDebugFeatureMetadata
generateDebugFeatureTransitiveDeps
generateDebugGlobalSynthetics
generateDebugLintModel
generateDebugLintReportModel
generateDebugManifestClass
generateDebugResources
generateDebugRFile
generateDebugSources
generateReleaseAndroidTestAssets
generateReleaseAndroidTestBuildConfig
generateReleaseAndroidTestLintModel
generateReleaseAndroidTestResources
generateReleaseAndroidTestRFile
generateReleaseAndroidTestSources
generateReleaseAssets
generateReleaseBuildConfig
generateReleaseFeatureMetadata
generateReleaseFeatureTransitiveDeps
generateReleaseLintModel
generateReleaseLintReportModel
generateReleaseLintVitalReportModel
generateReleaseManifestClass
generateReleaseResources
generateReleaseRFile
generateReleaseSources
generateReleaseUnitTestAssets
generateReleaseUnitTestConfig
generateReleaseUnitTestLintModel
generateReleaseUnitTestResources
generateReleaseUnitTestSources
javaPreCompileDebug
javaPreCompileRelease
javaPreCompileReleaseAndroidTest
javaPreCompileReleaseUnitTest
ktlintKotlinScriptCheck - Generates reports and prints errors into Gradle console.
ktlintKotlinScriptFormat - Generates reports and prints errors into Gradle console.
lintAnalyzeDebug - Run lint analysis on the debug component
lintAnalyzeRelease - Run lint analysis on the release component
lintAnalyzeReleaseAndroidTest - Run lint analysis on the releaseAndroidTest component
lintAnalyzeReleaseUnitTest - Run lint analysis on the releaseUnitTest component
lintFixDebug - Fix lint on the debug variant
lintFixRelease - Fix lint on the release variant
lintReportDebug - Run lint on the debug variant
lintReportRelease - Run lint on the release variant
lintVitalAnalyzeRelease - Run lint analysis with only the fatal issues enabled
lintVitalReportRelease - Run lint with only the fatal issues enabled on the release variant
loadKtlintReporters - Preloads required KtLint reporters.
makeApkFromBundleForDebug
makeApkFromBundleForRelease
mapDebugSourceSetPaths
mapReleaseAndroidTestSourceSetPaths
mapReleaseSourceSetPaths
mergeDebugArtProfile
mergeDebugAssets
mergeDebugGeneratedProguardFiles
mergeDebugJavaResource
mergeDebugJniLibFolders
mergeDebugNativeDebugMetadata
mergeDebugNativeLibs
mergeDebugResources
mergeDebugStartupProfile
mergeExtDexDebug
mergeLibDexDebug
mergeProjectDexDebug
mergeReleaseAndroidTestAssets
mergeReleaseAndroidTestGeneratedProguardFiles
mergeReleaseAndroidTestJavaResource
mergeReleaseAndroidTestJniLibFolders
mergeReleaseAndroidTestNativeLibs
mergeReleaseAndroidTestResources
mergeReleaseAndroidTestTestResultProtos
mergeReleaseArtProfile
mergeReleaseAssets
mergeReleaseComposeMapping
mergeReleaseGeneratedProguardFiles
mergeReleaseJavaResource
mergeReleaseJniLibFolders
mergeReleaseNativeDebugMetadata
mergeReleaseNativeLibs
mergeReleaseResources
mergeReleaseStartupProfile
minifyReleaseAndroidTestWithR8
minifyReleaseWithR8
optimizeReleaseResources
packageDebug
packageDebugBundle
packageDebugResources
packageDebugUniversalApk
packageRelease
packageReleaseAndroidTest
packageReleaseAndroidTestResources
packageReleaseBundle
packageReleaseResources
packageReleaseUnitTestForUnitTest
packageReleaseUniversalApk
parseDebugIntegrityConfig
parseDebugLocalResources
parseReleaseAndroidTestLocalResources
parseReleaseIntegrityConfig
parseReleaseLocalResources
phonePixel6api34Setup
pixel2api33Setup
preBuild
preDebugBuild
prepareLintJarForPublish
preReleaseAndroidTestBuild
preReleaseBuild
preReleaseUnitTestBuild
processApplicationManifestDebugForBundle
processApplicationManifestReleaseForBundle
processDebugJavaRes
processDebugMainManifest
processDebugManifest
processDebugManifestForPackage
processDebugNavigationResources
processDebugResources
processManifestDebugForFeature
processManifestReleaseForFeature
processReleaseAndroidTestJavaRes
processReleaseAndroidTestManifest
processReleaseAndroidTestNavigationResources
processReleaseAndroidTestResources
processReleaseJavaRes
processReleaseMainManifest
processReleaseManifest
processReleaseManifestForPackage
processReleaseNavigationResources
processReleaseResources
processReleaseUnitTestJavaRes
processReleaseUnitTestManifest
produceDebugBundleIdeListingFile
produceReleaseBundleIdeListingFile
produceReleaseComposeMapping
recordRoborazzi
reportReleaseComposeMappingErrors
resolveConfigAttr
restoreOutputDirRoborazzi
runKtlintCheckOverKotlinScripts - Lints all .kts files to ensure that they are formatted according to the code style.
runKtlintFormatOverKotlinScripts - Lints all .kts files to ensure that they are formatted according to the code style  and, on error, tries to format code to conform code style.
sdkReleaseDependencyData
signDebugBundle
signingConfigWriterDebug
signingConfigWriterRelease
signingConfigWriterReleaseAndroidTest
signReleaseBundle
stripDebugDebugSymbols
stripReleaseAndroidTestDebugSymbols
stripReleaseDebugSymbols
tablet10PixelTabletApi34Setup
tablet7Nexus7api34Setup
updateLintBaselineDebug - Update the lint baseline using the debug variant
updateLintBaselineRelease - Update the lint baseline using the release variant
validateSigningDebug
validateSigningRelease
validateSigningReleaseAndroidTest
verifyAndRecordRoborazzi
verifyRoborazzi
writeDebugApplicationId
writeDebugAppMetadata
writeDebugModuleMetadata
writeDebugSigningConfigVersions
writeReleaseAndroidTestSigningConfigVersions
writeReleaseApplicationId
writeReleaseAppMetadata
writeReleaseModuleMetadata
writeReleaseSigningConfigVersions
zipApksForDebug
zipApksForRelease

Rules
-----
Pattern: clean<TaskName>: Cleans the output files of a task.
Pattern: build<ConfigurationName>: Assembles the artifacts of a configuration.

[Incubating] Problems report is available at: file:///home/Messier82/projects/service-book/build/reports/problems/problems-report.html

Deprecated Gradle features were used in this build, making it incompatible with Gradle 10.

You can use '--warning-mode all' to show the individual deprecation warnings and determine if they come from your own scripts or plugins.

For more on this, please refer to https://docs.gradle.org/9.5.1/userguide/command_line_interface.html#sec:command_line_warnings in the Gradle documentation.

BUILD SUCCESSFUL in 12s
1 actionable task: 1 executed
Consider enabling configuration cache to speed up this build: https://docs.gradle.org/9.5.1/userguide/configuration_cache_enabling.html


grep -R "debugBillingOverride" -n app/src/main app/src/debug app/src/release app/src/androidTest | head -50 && grep -R "class YorvanaApplication" -n app/src


Output:
Chunk ID: 07668c
Wall time: 0.1614 seconds
Process exited with code 0
Original token count: 515
Output:
grep: app/src/debug: No such file or directory
grep: app/src/release: No such file or directory
app/src/main/java/com/yorvana/YorvanaApplication.kt:57:    open var debugBillingOverride: DebugBillingOverride? = null
app/src/main/java/com/yorvana/YorvanaApplication.kt:168:            debugBillingOverride = DebugBillingOverride(preferences, applicationScope)
app/src/main/java/com/yorvana/YorvanaApplication.kt:176:                debugOverride = debugBillingOverride,
app/src/main/java/com/yorvana/data/billing/DebugBillingOverride.kt:25:            .map { it.debugBillingOverrideMode }
app/src/main/java/com/yorvana/data/preferences/AppPreferences.kt:13:    val debugBillingOverrideMode: DebugBillingOverrideMode = DebugBillingOverrideMode.NONE,
app/src/main/java/com/yorvana/data/preferences/AppPreferencesImpl.kt:43:                debugBillingOverrideMode =
app/src/main/java/com/yorvana/ui/settings/SettingsViewModel.kt:127:    private val debugBillingOverride = dependencies.debugBillingOverride
app/src/main/java/com/yorvana/ui/settings/SettingsViewModel.kt:164:        debugBillingOverride?.mode
app/src/main/java/com/yorvana/ui/settings/SettingsViewModel.kt:258:            viewModelScope.launch { debugBillingOverride?.setMode(mode) }
app/src/main/java/com/yorvana/ui/util/ViewModelDependencies.kt:27:    val debugBillingOverride: DebugBillingOverride?,
app/src/main/java/com/yorvana/ui/util/ViewModelFactory.kt:43:                debugBillingOverride = app.debugBillingOverride,
app/src/androidTest/java/com/yorvana/screenshots/StoreScreenshotTest.kt:97:            app.debugBillingOverride?.setMode(mode)
app/src/androidTest/java/com/yorvana/testsupport/VaultTestSetup.kt:20:        app.debugBillingOverride?.setMode(DebugBillingOverrideMode.NONE)
app/src/androidTest/java/com/yorvana/SmokeTest.kt:93:            app.debugBillingOverride?.setMode(mode)
app/src/main/java/com/yorvana/YorvanaApplication.kt:36:open class YorvanaApplication : Application() {
app/src/test/java/com/yorvana/YorvanaApplicationTest.kt:82:class YorvanaApplicationTest {


sed -n '130,190p' app/src/main/java/com/yorvana/YorvanaApplication.kt && grep -R "BuildConfig.DEBUG" -n app/src/main/java | head -50


Output:
Chunk ID: efef9e
Wall time: 0.1224 seconds
Process exited with code 0
Original token count: 721
Output:
    /** Closes Sentry and flushes pending events on [Dispatchers.IO]; may block up to ~2 s. */
    suspend fun disableSentryNow() {
        if (Sentry.isEnabled()) {
            withContext(Dispatchers.IO) {
                Sentry.close()
            }
        }
    }

    @Suppress("UnusedParameter")
    internal fun beforeSend(
        event: SentryEvent,
        hint: Hint,
    ): SentryEvent? {
        if (!preferences.isCrashReportingEnabledSync()) {
            return null
        }
        return event.apply {
            user = null
            serverName = null
        }
    }

    protected open fun installCrashReporter() {
        CrashReporter.install(this)
    }

    protected open fun initDependencies() {
        preferences = AppPreferencesImpl(this, this.dataStore)

        val storage = VaultStorageImpl(this)
        vaultStorage = storage

        categoryRepository = CategoryRepositoryImpl(this, storage)
        vehicleRepository = VehicleRepositoryImpl(storage)
        recordRepository = RecordRepositoryImpl(storage, categoryRepository, vehicleRepository)

        if (BuildConfig.DEBUG) {
            debugBillingOverride = DebugBillingOverride(preferences, applicationScope)
        }
        val clientProvider = DefaultBillingClientProvider(this)
        billingManager =
            BillingManagerImpl(
                preferences = preferences,
                coroutineScope = applicationScope,
                billingClientProvider = clientProvider,
                debugOverride = debugBillingOverride,
            )
        appGate = AppGate(vehicleRepository, billingManager, applicationScope)
    }

    open fun applyVaultUri(uriString: String?) {
        val uri = uriString?.let { Uri.parse(it) }
        vaultStorage.setVaultUri(uri)

        // Refresh VehicleRepository to load data from the new vault
        applicationScope.launch {
            vehicleRepository.refresh()
        }
    }
}
app/src/main/java/com/yorvana/YorvanaApplication.kt:118:        get() = if (BuildConfig.DEBUG) "debug" else "release"
app/src/main/java/com/yorvana/YorvanaApplication.kt:167:        if (BuildConfig.DEBUG) {
app/src/main/java/com/yorvana/data/billing/DebugBillingOverride.kt:13: * These overrides are only intended to be consulted when [com.yorvana.BuildConfig.DEBUG] is true.
app/src/main/java/com/yorvana/data/billing/BillingManagerImpl.kt:45: * The [BuildConfig.DEBUG] guard at the call-site ensures [debugOverride] is always `null`
app/src/main/java/com/yorvana/ui/settings/SettingsScreen.kt:550:            if (BuildConfig.DEBUG) {
app/src/main/java/com/yorvana/ui/settings/SettingsViewModel.kt:239:        if (BuildConfig.DEBUG) {
app/src/main/java/com/yorvana/ui/settings/SettingsViewModel.kt:248:        if (BuildConfig.DEBUG) {
app/src/main/java/com/yorvana/ui/settings/SettingsViewModel.kt:257:        if (BuildConfig.DEBUG) {


sed -n '1,140p' app/src/main/java/com/yorvana/data/billing/BillingManagerImpl.kt && sed -n '1,120p' app/src/main/java/com/yorvana/data/billing/BillingManager.kt && sed -n '1,80p' app/src/main/java/com/yorvana/data/billing/BillingState.kt


Output:
Chunk ID: c8ab3f
Wall time: 0.1246 seconds
Process exited with code 2
Original token count: 1988
Output:
package com.yorvana.data.billing

import android.app.Activity
import android.util.Log
import com.android.billingclient.api.AcknowledgePurchaseParams
import com.android.billingclient.api.BillingClient
import com.android.billingclient.api.BillingClientStateListener
import com.android.billingclient.api.BillingFlowParams
import com.android.billingclient.api.BillingResult
import com.android.billingclient.api.ProductDetails
import com.android.billingclient.api.ProductDetailsResponseListener
import com.android.billingclient.api.Purchase
import com.android.billingclient.api.PurchasesResponseListener
import com.android.billingclient.api.PurchasesUpdatedListener
import com.android.billingclient.api.QueryProductDetailsParams
import com.android.billingclient.api.QueryPurchasesParams
import com.yorvana.data.preferences.AppPreferencesStore
import com.yorvana.data.preferences.DebugBillingOverrideMode
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.stateIn
import kotlinx.coroutines.launch
import kotlinx.coroutines.suspendCancellableCoroutine
import java.util.concurrent.atomic.AtomicReference
import kotlin.coroutines.resume

private const val TAG = "BillingManager"

/**
 * Production implementation of [BillingManager].
 *
 * **Side Effects:** Creating an instance of this class eagerly starts flow collectors
 * (via [stateIn]) on the provided [coroutineScope]. Callers (especially in tests) must
 * ensure the scope is cancellable to avoid leaking collectors.
 *
 * **Note on [DebugBillingOverride]:** Rather than wrapping this class with a decorator,
 * the debug override is checked inline in [launchPremiumPurchaseFlow] and [restorePurchases].
 * This keeps the purchase-flow integration (client readiness, product details) testable
 * end-to-end in debug builds while keeping the override logic contained in a single class.
 * The [BuildConfig.DEBUG] guard at the call-site ensures [debugOverride] is always `null`
 * in release builds.
 */
class BillingManagerImpl(
    private val preferences: AppPreferencesStore,
    private val coroutineScope: CoroutineScope,
    billingClientProvider: BillingClientProvider,
    private val debugOverride: DebugBillingOverride? = null,
) : BillingManager, DebugBillingActions {
    companion object {
        internal const val PRODUCT_ID = "premium_lifetime"
    }

    private val billingDerivedPending = MutableStateFlow(false)

    /**
     * Holds the premium status as determined by the most recent successful billing query,
     * or `null` if no billing query has completed yet. When `null`, [isPremium] falls back
     * to the DataStore-cached value so there is no flicker before the billing client connects.
     */
    private val billingDerivedPremium = MutableStateFlow<Boolean?>(null)

    /**
     * Mirrors the [com.yorvana.data.preferences.AppPreferences.isPremiumCached] field from
     * DataStore, collected once via [stateIn] so that [processPurchases] can read the latest
     * value synchronously (via `.value`) without an extra [kotlinx.coroutines.flow.Flow.first]
     * suspension on every billing update.
     *
     * Seeded with `null` (not `false`) to signal "not yet loaded" — until the first DataStore
     * emission arrives, [isPremium] falls back to `false` via the combine's `?: false` default
     * rather than treating the absence of data as an explicit non-premium signal.
     */
    private val cachedPremiumFlow: StateFlow<Boolean?> =
        preferences.preferences
            .map { it.isPremiumCached }
            .stateIn(coroutineScope, SharingStarted.Eagerly, null)

    override val isPremium: StateFlow<Boolean> =
        if (debugOverride != null) {
            combine(
                billingDerivedPremium,
                cachedPremiumFlow,
                debugOverride.mode,
            ) { billingPremium, cachedPremium, overrideMode ->
                when (overrideMode) {
                    DebugBillingOverrideMode.FORCE_FREE -> false
                    DebugBillingOverrideMode.FORCE_PREMIUM -> true
                    DebugBillingOverrideMode.FORCE_PENDING -> false
                    DebugBillingOverrideMode.NONE -> billingPremium ?: cachedPremium ?: false
                }
            }.stateIn(coroutineScope, SharingStarted.Eagerly, false)
        } else {
            combine(
                billingDerivedPremium,
                cachedPremiumFlow,
            ) { billingPremium, cachedPremium ->
                billingPremium ?: cachedPremium ?: false
            }.stateIn(coroutineScope, SharingStarted.Eagerly, false)
        }

    override val hasPendingPurchase: StateFlow<Boolean> =
        if (debugOverride != null) {
            combine(
                billingDerivedPending,
                debugOverride.mode,
            ) { billingPending, overrideMode ->
                when (overrideMode) {
                    DebugBillingOverrideMode.FORCE_PENDING -> true
                    else -> billingPending
                }
            }.stateIn(coroutineScope, SharingStarted.Eagerly, false)
        } else {
            billingDerivedPending.asStateFlow()
        }

    private val cachedProductDetails = AtomicReference<ProductDetails?>(null)

    private val purchasesUpdatedListener =
        PurchasesUpdatedListener { billingResult, purchases ->
            when (billingResult.responseCode) {
                BillingClient.BillingResponseCode.OK -> {
                    coroutineScope.launch {
                        processPurchases(purchases.orEmpty())
                    }
                }
                BillingClient.BillingResponseCode.ITEM_ALREADY_OWNED -> {
                    // Play may return ITEM_ALREADY_OWNED without a purchase list; re-query to
                    // ensure the existing entitlement is reflected in isPremium and the cache.
                    coroutineScope.launch { queryPurchases() }
                }
                // For USER_CANCELED and other non-success responses, we clear the pending state
                // as the purchase flow is no longer active.
                else -> {
                    billingDerivedPending.value = false
                }
            }
package com.yorvana.data.billing

import android.app.Activity
import kotlinx.coroutines.flow.StateFlow

interface BillingManager {
    /** Emits true when the user has an active premium entitlement. */
    val isPremium: StateFlow<Boolean>

    /** Emits true when a purchase is pending confirmation from Google Play. */
    val hasPendingPurchase: StateFlow<Boolean>

    /** Connect to Google Play and query existing purchases. */
    fun startConnection()

    /** Disconnect from Google Play. */
    fun endConnection()

    /**
     * Launch the purchase flow for the premium product.
     * Returns a [BillingLaunchResult] indicating whether the flow was launched successfully.
     * This function is synchronous — it returns immediately after attempting to launch the
     * Google Play purchase sheet. The final purchase outcome is delivered asynchronously via
     * the [isPremium] state.
     */
    fun launchPremiumPurchaseFlow(activity: Activity): BillingLaunchResult

    /** Restore purchases (re-query from Google Play). */
    suspend fun restorePurchases(): RestoreResult

    /**
     * Triggers a purchase re-query from Google Play. The query itself guards against a
     * not-ready client, so this is safe to call at any time (e.g. on app resume).
     * Useful for catching entitlement updates that occurred while the app was in the background.
     */
    fun refresh()
}
sed: can't read app/src/main/java/com/yorvana/data/billing/BillingState.kt: No such file or directory


./gradlew :app:compileReleaseAndroidTestKotlin -PscreenshotBuild --no-daemon


Output:
Chunk ID: 42fce1
Wall time: 1.0011 seconds
Process running with session ID 73701
Original token count: 72
Output:
Picked up JAVA_TOOL_OPTIONS: -Xlog:os+container=off
To honour the JVM settings for this build a single-use Daemon process will be forked. For more on this, please refer to https://docs.gradle.org/9.5.1/userguide/gradle_daemon.html#sec:disabling_the_daemon in the Gradle documentation.


<turn_aborted>
The user interrupted the previous turn on purpose. Any running unified exec processes may still be running in the background. If any tools/commands were aborted, they may have partially executed.
</turn_aborted>