Auto commit (MindMachine) Fri Apr 10 12:01:02 PM CDT 2026
This commit is contained in:
@@ -65,6 +65,10 @@ dependencies {
|
|||||||
implementation("androidx.compose.material:material-icons-extended")
|
implementation("androidx.compose.material:material-icons-extended")
|
||||||
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-android:1.8.1")
|
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-android:1.8.1")
|
||||||
|
|
||||||
|
// Monetization
|
||||||
|
implementation("com.google.android.gms:play-services-ads:23.1.0")
|
||||||
|
implementation("com.android.billingclient:billing-ktx:7.1.1")
|
||||||
|
|
||||||
testImplementation("junit:junit:4.13.2")
|
testImplementation("junit:junit:4.13.2")
|
||||||
testImplementation("org.jetbrains.kotlinx:kotlinx-coroutines-test:1.8.1")
|
testImplementation("org.jetbrains.kotlinx:kotlinx-coroutines-test:1.8.1")
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,8 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
|
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
|
|
||||||
|
<uses-permission android:name="android.permission.INTERNET" />
|
||||||
|
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
|
||||||
<uses-permission android:name="android.permission.WAKE_LOCK" />
|
<uses-permission android:name="android.permission.WAKE_LOCK" />
|
||||||
|
|
||||||
<application
|
<application
|
||||||
@@ -10,6 +12,12 @@
|
|||||||
android:roundIcon="@mipmap/ic_launcher_round"
|
android:roundIcon="@mipmap/ic_launcher_round"
|
||||||
android:supportsRtl="true"
|
android:supportsRtl="true"
|
||||||
android:theme="@style/Theme.MindMachine">
|
android:theme="@style/Theme.MindMachine">
|
||||||
|
|
||||||
|
<!-- AdMob test app id (replace with your real id before release) -->
|
||||||
|
<meta-data
|
||||||
|
android:name="com.google.android.gms.ads.APPLICATION_ID"
|
||||||
|
android:value="ca-app-pub-3940256099942544~3347511713" />
|
||||||
|
|
||||||
<activity
|
<activity
|
||||||
android:name=".MainActivity"
|
android:name=".MainActivity"
|
||||||
android:exported="true">
|
android:exported="true">
|
||||||
|
|||||||
@@ -96,8 +96,11 @@ import androidx.navigation.compose.NavHost
|
|||||||
import androidx.navigation.compose.composable
|
import androidx.navigation.compose.composable
|
||||||
import androidx.navigation.compose.rememberNavController
|
import androidx.navigation.compose.rememberNavController
|
||||||
import androidx.navigation.navArgument
|
import androidx.navigation.navArgument
|
||||||
|
import com.google.android.gms.ads.MobileAds
|
||||||
import com.mindmachine.mvp.audio.BinauralAudioEngine
|
import com.mindmachine.mvp.audio.BinauralAudioEngine
|
||||||
import com.mindmachine.mvp.audio.HeadsetMonitor
|
import com.mindmachine.mvp.audio.HeadsetMonitor
|
||||||
|
import com.mindmachine.mvp.ads.AdBanner
|
||||||
|
import com.mindmachine.mvp.ads.rememberInterstitialController
|
||||||
import com.mindmachine.mvp.data.SettingsRepository
|
import com.mindmachine.mvp.data.SettingsRepository
|
||||||
import com.mindmachine.mvp.data.UserProgramRepository
|
import com.mindmachine.mvp.data.UserProgramRepository
|
||||||
import com.mindmachine.mvp.domain.RuntimeState
|
import com.mindmachine.mvp.domain.RuntimeState
|
||||||
@@ -136,16 +139,27 @@ private val MindMachineColors = darkColorScheme(
|
|||||||
|
|
||||||
class MainActivity : ComponentActivity() {
|
class MainActivity : ComponentActivity() {
|
||||||
private val vm: MainViewModel by viewModels {
|
private val vm: MainViewModel by viewModels {
|
||||||
|
val settingsRepo = SettingsRepository(applicationContext)
|
||||||
|
val billing = com.mindmachine.mvp.billing.BillingManager(
|
||||||
|
applicationContext,
|
||||||
|
onNoAdsEntitlementChanged = { hasNoAds ->
|
||||||
|
// Cache the entitlement so UI can gate ads.
|
||||||
|
// Billing will refresh on each app start.
|
||||||
|
lifecycleScope.launch { settingsRepo.updateNoAds(hasNoAds) }
|
||||||
|
},
|
||||||
|
)
|
||||||
MainViewModel.Factory(
|
MainViewModel.Factory(
|
||||||
SettingsRepository(applicationContext),
|
settingsRepo,
|
||||||
UserProgramRepository(applicationContext),
|
UserProgramRepository(applicationContext),
|
||||||
HeadsetMonitor(applicationContext),
|
HeadsetMonitor(applicationContext),
|
||||||
BinauralAudioEngine(),
|
BinauralAudioEngine(),
|
||||||
|
billing,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onCreate(savedInstanceState: Bundle?) {
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
super.onCreate(savedInstanceState)
|
super.onCreate(savedInstanceState)
|
||||||
|
MobileAds.initialize(this) {}
|
||||||
setContent {
|
setContent {
|
||||||
MaterialTheme(colorScheme = MindMachineColors) {
|
MaterialTheme(colorScheme = MindMachineColors) {
|
||||||
App(vm)
|
App(vm)
|
||||||
|
|||||||
7
app/src/main/java/com/mindmachine/mvp/ads/AdUnits.kt
Normal file
7
app/src/main/java/com/mindmachine/mvp/ads/AdUnits.kt
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
package com.mindmachine.mvp.ads
|
||||||
|
|
||||||
|
object AdUnits {
|
||||||
|
// Google test ad unit ids. Replace with your real unit ids before release.
|
||||||
|
const val BANNER = "ca-app-pub-3940256099942544/6300978111"
|
||||||
|
const val INTERSTITIAL = "ca-app-pub-3940256099942544/1033173712"
|
||||||
|
}
|
||||||
107
app/src/main/java/com/mindmachine/mvp/ads/AdsComposable.kt
Normal file
107
app/src/main/java/com/mindmachine/mvp/ads/AdsComposable.kt
Normal file
@@ -0,0 +1,107 @@
|
|||||||
|
package com.mindmachine.mvp.ads
|
||||||
|
|
||||||
|
import android.app.Activity
|
||||||
|
import android.view.ViewGroup
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.runtime.DisposableEffect
|
||||||
|
import androidx.compose.runtime.LaunchedEffect
|
||||||
|
import androidx.compose.runtime.MutableState
|
||||||
|
import androidx.compose.runtime.mutableStateOf
|
||||||
|
import androidx.compose.runtime.remember
|
||||||
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.platform.LocalContext
|
||||||
|
import androidx.compose.ui.viewinterop.AndroidView
|
||||||
|
import com.google.android.gms.ads.AdRequest
|
||||||
|
import com.google.android.gms.ads.AdSize
|
||||||
|
import com.google.android.gms.ads.AdView
|
||||||
|
import com.google.android.gms.ads.FullScreenContentCallback
|
||||||
|
import com.google.android.gms.ads.LoadAdError
|
||||||
|
import com.google.android.gms.ads.interstitial.InterstitialAd
|
||||||
|
import com.google.android.gms.ads.interstitial.InterstitialAdLoadCallback
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun AdBanner(modifier: Modifier = Modifier, enabled: Boolean) {
|
||||||
|
if (!enabled) return
|
||||||
|
|
||||||
|
val context = LocalContext.current
|
||||||
|
|
||||||
|
AndroidView(
|
||||||
|
modifier = modifier,
|
||||||
|
factory = {
|
||||||
|
AdView(context).apply {
|
||||||
|
setAdSize(AdSize.BANNER)
|
||||||
|
adUnitId = AdUnits.BANNER
|
||||||
|
layoutParams = ViewGroup.LayoutParams(
|
||||||
|
ViewGroup.LayoutParams.MATCH_PARENT,
|
||||||
|
ViewGroup.LayoutParams.WRAP_CONTENT
|
||||||
|
)
|
||||||
|
loadAd(AdRequest.Builder().build())
|
||||||
|
}
|
||||||
|
},
|
||||||
|
update = { /* no-op */ }
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
class InterstitialController(private val activity: Activity) {
|
||||||
|
private var interstitial: InterstitialAd? = null
|
||||||
|
private var loading: Boolean = false
|
||||||
|
|
||||||
|
fun preload() {
|
||||||
|
if (interstitial != null || loading) return
|
||||||
|
loading = true
|
||||||
|
InterstitialAd.load(
|
||||||
|
activity,
|
||||||
|
AdUnits.INTERSTITIAL,
|
||||||
|
AdRequest.Builder().build(),
|
||||||
|
object : InterstitialAdLoadCallback() {
|
||||||
|
override fun onAdLoaded(ad: InterstitialAd) {
|
||||||
|
loading = false
|
||||||
|
interstitial = ad
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onAdFailedToLoad(error: LoadAdError) {
|
||||||
|
loading = false
|
||||||
|
interstitial = null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun show(onDone: () -> Unit) {
|
||||||
|
val ad = interstitial
|
||||||
|
if (ad == null) {
|
||||||
|
onDone()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
ad.fullScreenContentCallback = object : FullScreenContentCallback() {
|
||||||
|
override fun onAdDismissedFullScreenContent() {
|
||||||
|
interstitial = null
|
||||||
|
preload()
|
||||||
|
onDone()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onAdFailedToShowFullScreenContent(p0: com.google.android.gms.ads.AdError) {
|
||||||
|
interstitial = null
|
||||||
|
preload()
|
||||||
|
onDone()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ad.show(activity)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun rememberInterstitialController(enabled: Boolean): InterstitialController? {
|
||||||
|
if (!enabled) return null
|
||||||
|
val activity = LocalContext.current as? Activity ?: return null
|
||||||
|
|
||||||
|
val controller = remember(activity) { InterstitialController(activity) }
|
||||||
|
|
||||||
|
LaunchedEffect(controller) {
|
||||||
|
controller.preload()
|
||||||
|
}
|
||||||
|
|
||||||
|
return controller
|
||||||
|
}
|
||||||
159
app/src/main/java/com/mindmachine/mvp/billing/BillingManager.kt
Normal file
159
app/src/main/java/com/mindmachine/mvp/billing/BillingManager.kt
Normal file
@@ -0,0 +1,159 @@
|
|||||||
|
package com.mindmachine.mvp.billing
|
||||||
|
|
||||||
|
import android.app.Activity
|
||||||
|
import android.content.Context
|
||||||
|
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.QueryProductDetailsParams
|
||||||
|
import com.android.billingclient.api.QueryPurchasesParams
|
||||||
|
import kotlinx.coroutines.flow.MutableStateFlow
|
||||||
|
import kotlinx.coroutines.flow.StateFlow
|
||||||
|
import kotlinx.coroutines.flow.asStateFlow
|
||||||
|
|
||||||
|
class BillingManager(
|
||||||
|
context: Context,
|
||||||
|
private val onNoAdsEntitlementChanged: (Boolean) -> Unit,
|
||||||
|
) {
|
||||||
|
private val appContext = context.applicationContext
|
||||||
|
|
||||||
|
private val _hasNoAds = MutableStateFlow(false)
|
||||||
|
val hasNoAds: StateFlow<Boolean> = _hasNoAds.asStateFlow()
|
||||||
|
|
||||||
|
private val _productDetails = MutableStateFlow<Map<String, ProductDetails>>(emptyMap())
|
||||||
|
val productDetails: StateFlow<Map<String, ProductDetails>> = _productDetails.asStateFlow()
|
||||||
|
|
||||||
|
private val billingClient: BillingClient = BillingClient.newBuilder(appContext)
|
||||||
|
.setListener { _, purchases ->
|
||||||
|
if (purchases != null) {
|
||||||
|
handlePurchases(purchases)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.enablePendingPurchases()
|
||||||
|
.build()
|
||||||
|
|
||||||
|
fun start() {
|
||||||
|
if (billingClient.isReady) {
|
||||||
|
refresh()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
billingClient.startConnection(object : BillingClientStateListener {
|
||||||
|
override fun onBillingSetupFinished(result: BillingResult) {
|
||||||
|
if (result.responseCode == BillingClient.BillingResponseCode.OK) {
|
||||||
|
refresh()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onBillingServiceDisconnected() {
|
||||||
|
// No-op, will retry on next call.
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fun refresh() {
|
||||||
|
queryProductDetails()
|
||||||
|
queryActivePurchases()
|
||||||
|
}
|
||||||
|
|
||||||
|
fun launchPurchase(activity: Activity, productId: String) {
|
||||||
|
val details = _productDetails.value[productId] ?: return
|
||||||
|
|
||||||
|
val productDetailsParams = when (details.productType) {
|
||||||
|
BillingClient.ProductType.SUBS -> {
|
||||||
|
val offerToken = details.subscriptionOfferDetails
|
||||||
|
?.firstOrNull()
|
||||||
|
?.offerToken
|
||||||
|
?: return
|
||||||
|
BillingFlowParams.ProductDetailsParams.newBuilder()
|
||||||
|
.setProductDetails(details)
|
||||||
|
.setOfferToken(offerToken)
|
||||||
|
.build()
|
||||||
|
}
|
||||||
|
|
||||||
|
BillingClient.ProductType.INAPP -> {
|
||||||
|
BillingFlowParams.ProductDetailsParams.newBuilder()
|
||||||
|
.setProductDetails(details)
|
||||||
|
.build()
|
||||||
|
}
|
||||||
|
|
||||||
|
else -> return
|
||||||
|
}
|
||||||
|
|
||||||
|
val params = BillingFlowParams.newBuilder()
|
||||||
|
.setProductDetailsParamsList(listOf(productDetailsParams))
|
||||||
|
.build()
|
||||||
|
|
||||||
|
billingClient.launchBillingFlow(activity, params)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun queryProductDetails() {
|
||||||
|
if (!billingClient.isReady) return
|
||||||
|
|
||||||
|
val products = listOf(
|
||||||
|
QueryProductDetailsParams.Product.newBuilder()
|
||||||
|
.setProductId(BillingProducts.SUB_REMOVE_ADS_MONTHLY)
|
||||||
|
.setProductType(BillingClient.ProductType.SUBS)
|
||||||
|
.build(),
|
||||||
|
QueryProductDetailsParams.Product.newBuilder()
|
||||||
|
.setProductId(BillingProducts.INAPP_REMOVE_ADS_FOREVER)
|
||||||
|
.setProductType(BillingClient.ProductType.INAPP)
|
||||||
|
.build(),
|
||||||
|
)
|
||||||
|
|
||||||
|
val params = QueryProductDetailsParams.newBuilder()
|
||||||
|
.setProductList(products)
|
||||||
|
.build()
|
||||||
|
|
||||||
|
billingClient.queryProductDetailsAsync(params, ProductDetailsResponseListener { result, detailsList ->
|
||||||
|
if (result.responseCode == BillingClient.BillingResponseCode.OK) {
|
||||||
|
_productDetails.value = detailsList.associateBy { it.productId }
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun queryActivePurchases() {
|
||||||
|
if (!billingClient.isReady) return
|
||||||
|
|
||||||
|
val listener = PurchasesResponseListener { result, purchases ->
|
||||||
|
if (result.responseCode == BillingClient.BillingResponseCode.OK) {
|
||||||
|
handlePurchases(purchases)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
billingClient.queryPurchasesAsync(
|
||||||
|
QueryPurchasesParams.newBuilder().setProductType(BillingClient.ProductType.SUBS).build(),
|
||||||
|
listener
|
||||||
|
)
|
||||||
|
billingClient.queryPurchasesAsync(
|
||||||
|
QueryPurchasesParams.newBuilder().setProductType(BillingClient.ProductType.INAPP).build(),
|
||||||
|
listener
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun handlePurchases(purchases: List<Purchase>) {
|
||||||
|
val active = purchases.any { p ->
|
||||||
|
p.purchaseState == Purchase.PurchaseState.PURCHASED &&
|
||||||
|
p.products.any { it == BillingProducts.SUB_REMOVE_ADS_MONTHLY || it == BillingProducts.INAPP_REMOVE_ADS_FOREVER }
|
||||||
|
}
|
||||||
|
|
||||||
|
if (_hasNoAds.value != active) {
|
||||||
|
_hasNoAds.value = active
|
||||||
|
onNoAdsEntitlementChanged(active)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Acknowledge where needed
|
||||||
|
purchases.filter { it.purchaseState == Purchase.PurchaseState.PURCHASED && !it.isAcknowledged }
|
||||||
|
.forEach { purchase ->
|
||||||
|
val params = AcknowledgePurchaseParams.newBuilder()
|
||||||
|
.setPurchaseToken(purchase.purchaseToken)
|
||||||
|
.build()
|
||||||
|
billingClient.acknowledgePurchase(params) { /* no-op */ }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
package com.mindmachine.mvp.billing
|
||||||
|
|
||||||
|
object BillingProducts {
|
||||||
|
// Configure these in Google Play Console
|
||||||
|
const val SUB_REMOVE_ADS_MONTHLY = "remove_ads_monthly"
|
||||||
|
const val INAPP_REMOVE_ADS_FOREVER = "remove_ads_forever"
|
||||||
|
|
||||||
|
val allProductIds = listOf(SUB_REMOVE_ADS_MONTHLY, INAPP_REMOVE_ADS_FOREVER)
|
||||||
|
}
|
||||||
@@ -22,6 +22,7 @@ class SettingsRepository(private val context: Context) {
|
|||||||
val showImmersiveProgressBar = booleanPreferencesKey("show_immersive_progress_bar")
|
val showImmersiveProgressBar = booleanPreferencesKey("show_immersive_progress_bar")
|
||||||
val immersiveBrightnessPercent = intPreferencesKey("immersive_brightness_percent")
|
val immersiveBrightnessPercent = intPreferencesKey("immersive_brightness_percent")
|
||||||
val lastPresetId = stringPreferencesKey("last_preset")
|
val lastPresetId = stringPreferencesKey("last_preset")
|
||||||
|
val noAds = booleanPreferencesKey("no_ads")
|
||||||
}
|
}
|
||||||
|
|
||||||
val settings: Flow<AppSettings> = context.dataStore.data.map { p ->
|
val settings: Flow<AppSettings> = context.dataStore.data.map { p ->
|
||||||
@@ -35,6 +36,7 @@ class SettingsRepository(private val context: Context) {
|
|||||||
showImmersiveProgressBar = p[Keys.showImmersiveProgressBar] ?: true,
|
showImmersiveProgressBar = p[Keys.showImmersiveProgressBar] ?: true,
|
||||||
immersiveBrightnessPercent = (p[Keys.immersiveBrightnessPercent] ?: 100).coerceIn(5, 100),
|
immersiveBrightnessPercent = (p[Keys.immersiveBrightnessPercent] ?: 100).coerceIn(5, 100),
|
||||||
lastPresetId = p[Keys.lastPresetId],
|
lastPresetId = p[Keys.lastPresetId],
|
||||||
|
noAds = p[Keys.noAds] ?: false,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -63,6 +65,8 @@ class SettingsRepository(private val context: Context) {
|
|||||||
|
|
||||||
suspend fun updateLastPreset(id: String) = context.dataStore.edit { it[Keys.lastPresetId] = id }
|
suspend fun updateLastPreset(id: String) = context.dataStore.edit { it[Keys.lastPresetId] = id }
|
||||||
|
|
||||||
|
suspend fun updateNoAds(value: Boolean) = context.dataStore.edit { it[Keys.noAds] = value }
|
||||||
|
|
||||||
private fun legacyCountdownStringToSeconds(raw: String?): Int? = when (raw) {
|
private fun legacyCountdownStringToSeconds(raw: String?): Int? = when (raw) {
|
||||||
"OFF" -> 0
|
"OFF" -> 0
|
||||||
"FIVE" -> 5
|
"FIVE" -> 5
|
||||||
|
|||||||
@@ -38,6 +38,7 @@ data class AppSettings(
|
|||||||
val showImmersiveProgressBar: Boolean = true,
|
val showImmersiveProgressBar: Boolean = true,
|
||||||
val immersiveBrightnessPercent: Int = 100,
|
val immersiveBrightnessPercent: Int = 100,
|
||||||
val lastPresetId: String? = null,
|
val lastPresetId: String? = null,
|
||||||
|
val noAds: Boolean = false,
|
||||||
)
|
)
|
||||||
|
|
||||||
object Presets {
|
object Presets {
|
||||||
|
|||||||
@@ -80,6 +80,7 @@ class MainViewModel(
|
|||||||
private val userProgramRepository: UserProgramRepository,
|
private val userProgramRepository: UserProgramRepository,
|
||||||
private val headsetMonitor: HeadsetMonitor,
|
private val headsetMonitor: HeadsetMonitor,
|
||||||
private val audioEngine: BinauralAudioEngine,
|
private val audioEngine: BinauralAudioEngine,
|
||||||
|
private val billingManager: com.mindmachine.mvp.billing.BillingManager,
|
||||||
) : ViewModel() {
|
) : ViewModel() {
|
||||||
private val _ui = MutableStateFlow(UiState())
|
private val _ui = MutableStateFlow(UiState())
|
||||||
val ui: StateFlow<UiState> = _ui.asStateFlow()
|
val ui: StateFlow<UiState> = _ui.asStateFlow()
|
||||||
@@ -89,6 +90,7 @@ class MainViewModel(
|
|||||||
private var cachedPrograms: List<UserProgramEntity> = emptyList()
|
private var cachedPrograms: List<UserProgramEntity> = emptyList()
|
||||||
|
|
||||||
init {
|
init {
|
||||||
|
billingManager.start()
|
||||||
viewModelScope.launch {
|
viewModelScope.launch {
|
||||||
combine(settingsRepository.settings, userProgramRepository.userPrograms) { settings, userPrograms ->
|
combine(settingsRepository.settings, userProgramRepository.userPrograms) { settings, userPrograms ->
|
||||||
settings to userPrograms
|
settings to userPrograms
|
||||||
@@ -486,8 +488,9 @@ class MainViewModel(
|
|||||||
private val userProgramRepository: UserProgramRepository,
|
private val userProgramRepository: UserProgramRepository,
|
||||||
private val headsetMonitor: HeadsetMonitor,
|
private val headsetMonitor: HeadsetMonitor,
|
||||||
private val audioEngine: BinauralAudioEngine,
|
private val audioEngine: BinauralAudioEngine,
|
||||||
|
private val billingManager: com.mindmachine.mvp.billing.BillingManager,
|
||||||
) : ViewModelProvider.Factory {
|
) : ViewModelProvider.Factory {
|
||||||
override fun <T : ViewModel> create(modelClass: Class<T>): T =
|
override fun <T : ViewModel> create(modelClass: Class<T>): T =
|
||||||
MainViewModel(settingsRepository, userProgramRepository, headsetMonitor, audioEngine) as T
|
MainViewModel(settingsRepository, userProgramRepository, headsetMonitor, audioEngine, billingManager) as T
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user