Skip to content

Commit 121c099

Browse files
Merge pull request #55 from Android-PowerUser/feature-all-tasks
Feature all tasks
2 parents b56f067 + 5d57c7b commit 121c099

File tree

14 files changed

+769
-154
lines changed

14 files changed

+769
-154
lines changed

.github/workflows/manual.yml

Lines changed: 46 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -21,16 +21,7 @@ jobs:
2121
- name: Detect changed files
2222
id: changes
2323
run: |
24-
# Bei workflow_dispatch immer alles bauen
25-
if [ "${{ github.event_name }}" = "workflow_dispatch" ]; then
26-
echo "app=true" >> $GITHUB_OUTPUT
27-
echo "humanoperator=true" >> $GITHUB_OUTPUT
28-
echo "shared=true" >> $GITHUB_OUTPUT
29-
echo "Manual dispatch - building all modules"
30-
exit 0
31-
fi
32-
33-
# Geänderte Dateien im letzten Commit ermitteln
24+
# Geänderte Dateien im letzten Commit ermitteln (gilt für push UND workflow_dispatch)
3425
CHANGED_FILES=$(git diff --name-only HEAD~1 HEAD 2>/dev/null || echo "")
3526
3627
# Falls kein vorheriger Commit existiert (erster Commit), alles bauen
@@ -69,9 +60,53 @@ jobs:
6960
7061
echo "Results: app=$APP_CHANGED, humanoperator=$HUMANOPERATOR_CHANGED, shared=$SHARED_CHANGED"
7162
72-
build:
63+
compile-check:
7364
needs: detect-changes
7465
runs-on: ubuntu-latest
66+
steps:
67+
- name: Checkout code
68+
uses: actions/checkout@v4
69+
70+
- name: Set up JDK 17
71+
uses: actions/setup-java@v4
72+
with:
73+
java-version: '17'
74+
distribution: 'temurin'
75+
cache: gradle
76+
77+
- name: Decode google-services.json (app)
78+
env:
79+
GOOGLE_SERVICES_JSON: ${{ secrets.GOOGLE_SERVICES_JSON_APP }}
80+
run: printf '%s' "$GOOGLE_SERVICES_JSON" > app/google-services.json
81+
82+
- name: Decode google-services.json (humanoperator)
83+
env:
84+
GOOGLE_SERVICES_JSON: ${{ secrets.GOOGLE_SERVICES_JSON_HUMANOPERATOR }}
85+
run: printf '%s' "$GOOGLE_SERVICES_JSON" > humanoperator/google-services.json
86+
87+
- name: Create local.properties
88+
run: echo "sdk.dir=$ANDROID_HOME" > local.properties
89+
90+
- name: Fix gradle.properties for CI
91+
run: |
92+
sed -i '/org.gradle.java.home=/d' gradle.properties
93+
sed -i 's/org.gradle.jvmargs=.*/org.gradle.jvmargs=-Xmx2048m -XX:MaxMetaspaceSize=512m/' gradle.properties
94+
sed -i 's/kotlin.daemon.jvmargs=.*/kotlin.daemon.jvmargs=-Xmx1536m -XX:MaxMetaspaceSize=512m/' gradle.properties
95+
96+
- name: Grant execute permission for gradlew
97+
run: chmod +x gradlew
98+
99+
- name: Compile Kotlin (app)
100+
if: needs.detect-changes.outputs.app_changed == 'true' || needs.detect-changes.outputs.shared_changed == 'true'
101+
run: ./gradlew :app:compileDebugKotlin
102+
103+
- name: Compile Kotlin (humanoperator)
104+
if: needs.detect-changes.outputs.humanoperator_changed == 'true' || needs.detect-changes.outputs.shared_changed == 'true'
105+
run: ./gradlew :humanoperator:compileDebugKotlin
106+
107+
build:
108+
needs: [detect-changes, compile-check]
109+
runs-on: ubuntu-latest
75110
env:
76111
BUILD_APP: ${{ needs.detect-changes.outputs.app_changed == 'true' || needs.detect-changes.outputs.shared_changed == 'true' }}
77112
BUILD_HUMANOPERATOR: ${{ needs.detect-changes.outputs.humanoperator_changed == 'true' || needs.detect-changes.outputs.shared_changed == 'true' }}

app/src/main/kotlin/com/google/ai/sample/GenerativeAiViewModelFactory.kt

Lines changed: 18 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -137,8 +137,12 @@ object GenerativeAiViewModelFactory {
137137
private var currentModel: ModelOption = ModelOption.GPT_5_1_CODEX_MAX
138138
private var currentBackend: InferenceBackend = InferenceBackend.GPU
139139

140-
fun setModel(modelOption: ModelOption) {
140+
fun setModel(modelOption: ModelOption, context: Context? = null) {
141141
currentModel = modelOption
142+
if (context != null) {
143+
val prefs = context.getSharedPreferences("inference_prefs", Context.MODE_PRIVATE)
144+
prefs.edit().putString("selected_model", modelOption.name).apply()
145+
}
142146
}
143147

144148
fun getCurrentModel(): ModelOption {
@@ -157,11 +161,21 @@ object GenerativeAiViewModelFactory {
157161

158162
fun loadBackendPreference(context: Context) {
159163
val prefs = context.getSharedPreferences("inference_prefs", Context.MODE_PRIVATE)
160-
val backendName = prefs.getString("preferred_backend", InferenceBackend.CPU.name)
164+
val backendName = prefs.getString("preferred_backend", InferenceBackend.GPU.name)
161165
currentBackend = try {
162-
InferenceBackend.valueOf(backendName ?: InferenceBackend.CPU.name)
166+
InferenceBackend.valueOf(backendName ?: InferenceBackend.GPU.name)
167+
} catch (e: IllegalArgumentException) {
168+
InferenceBackend.GPU
169+
}
170+
}
171+
172+
fun loadModelPreference(context: Context) {
173+
val prefs = context.getSharedPreferences("inference_prefs", Context.MODE_PRIVATE)
174+
val modelNameStr = prefs.getString("selected_model", ModelOption.GPT_5_1_CODEX_MAX.name)
175+
currentModel = try {
176+
ModelOption.valueOf(modelNameStr ?: ModelOption.GPT_5_1_CODEX_MAX.name)
163177
} catch (e: IllegalArgumentException) {
164-
InferenceBackend.CPU
178+
ModelOption.GPT_5_1_CODEX_MAX
165179
}
166180
}
167181
}

app/src/main/kotlin/com/google/ai/sample/MainActivity.kt

Lines changed: 89 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,7 @@ import androidx.compose.foundation.layout.fillMaxWidth
4747
import androidx.compose.foundation.layout.height
4848
import androidx.compose.foundation.layout.PaddingValues
4949
import androidx.compose.foundation.layout.padding
50+
import androidx.compose.foundation.layout.Row
5051
import androidx.compose.material3.Button
5152
import androidx.compose.material3.Card
5253
import androidx.compose.material3.CircularProgressIndicator
@@ -66,6 +67,7 @@ import androidx.compose.ui.Modifier
6667
import androidx.compose.ui.platform.LocalContext
6768
import androidx.compose.ui.unit.dp
6869
import androidx.compose.ui.window.Dialog
70+
import androidx.compose.ui.window.DialogProperties
6971
import androidx.core.content.ContextCompat
7072
import androidx.lifecycle.lifecycleScope
7173
import androidx.navigation.NavHostController
@@ -129,6 +131,11 @@ class MainActivity : ComponentActivity() {
129131
private var onMediaProjectionPermissionGranted: (() -> Unit)? = null
130132
private var onWebRtcMediaProjectionResult: ((Int, Intent) -> Unit)? = null
131133

134+
// Payment Dialog State (Task 6)
135+
private var showPaymentMethodDialog by mutableStateOf(false)
136+
private var showPayPalWebViewDialog by mutableStateOf(false)
137+
private var paypalSubscriptionId by mutableStateOf("")
138+
132139
private val screenshotRequestHandler = object : BroadcastReceiver() {
133140
override fun onReceive(context: Context?, intent: Intent?) {
134141
if (intent?.action == ACTION_REQUEST_MEDIAPROJECTION_SCREENSHOT) {
@@ -326,7 +333,7 @@ class MainActivity : ComponentActivity() {
326333
}
327334
} else if (billingResult.responseCode == BillingClient.BillingResponseCode.USER_CANCELED) {
328335
Log.i(TAG, "purchasesUpdatedListener: User cancelled the purchase flow.")
329-
Toast.makeText(this, "Donation process cancelled.", Toast.LENGTH_SHORT).show()
336+
Toast.makeText(this, "Support cancelled.", Toast.LENGTH_SHORT).show()
330337
} else {
331338
Log.e(TAG, "purchasesUpdatedListener: Billing error: ${billingResult.debugMessage} (Code: ${billingResult.responseCode})")
332339
Toast.makeText(this, "Error during donation process: ${billingResult.debugMessage}", Toast.LENGTH_LONG).show()
@@ -405,6 +412,9 @@ class MainActivity : ComponentActivity() {
405412
Log.d(TAG, "onCreate: Calling setupBillingClient.")
406413
setupBillingClient()
407414

415+
Log.d(TAG, "onCreate: Loading Model Preference.")
416+
GenerativeAiViewModelFactory.loadModelPreference(this)
417+
408418
Log.d(TAG, "onCreate: Calling TrialManager.initializeTrialStateFlagsIfNecessary.")
409419
TrialManager.initializeTrialStateFlagsIfNecessary(this)
410420

@@ -511,7 +521,18 @@ class MainActivity : ComponentActivity() {
511521
ActivityResultContracts.StartActivityForResult()
512522
) { result ->
513523
if (result.resultCode == Activity.RESULT_OK && result.data != null) {
514-
Log.i(TAG, "WebRTC MediaProjection permission granted.")
524+
Log.i(TAG, "WebRTC MediaProjection permission granted. Starting keep-alive service.")
525+
526+
// Task 4: Keep Service Alive to satisfy Android 14 MediaProjection requirements
527+
val serviceIntent = Intent(this, ScreenCaptureService::class.java).apply {
528+
action = ScreenCaptureService.ACTION_KEEP_ALIVE_FOR_WEBRTC
529+
}
530+
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
531+
startForegroundService(serviceIntent)
532+
} else {
533+
startService(serviceIntent)
534+
}
535+
515536
onWebRtcMediaProjectionResult?.invoke(result.resultCode, result.data!!)
516537
onWebRtcMediaProjectionResult = null
517538
} else {
@@ -636,6 +657,59 @@ class MainActivity : ComponentActivity() {
636657
}
637658
}
638659
}
660+
661+
// Task 6: Payment Method Dialog
662+
if (showPaymentMethodDialog) {
663+
androidx.compose.material3.AlertDialog(
664+
onDismissRequest = { showPaymentMethodDialog = false },
665+
title = { Text("Choose Payment Method") },
666+
text = {
667+
Column {
668+
Button(
669+
onClick = {
670+
showPaymentMethodDialog = false
671+
672+
// Generate Short UUID
673+
val shortId = java.util.UUID.randomUUID().toString().substring(0, 8)
674+
675+
// Save it to SharedPreferences
676+
val ctx = this@MainActivity
677+
ctx.getSharedPreferences(PREFS_NAME, Context.MODE_PRIVATE)
678+
.edit()
679+
.putString("payment_support_id", shortId)
680+
.apply()
681+
682+
Toast.makeText(ctx, "Your Support ID is: $shortId", Toast.LENGTH_LONG).show()
683+
684+
val url = "https://www.paypal.com/webapps/billing/subscriptions?plan_id=P-5J921557TD348880GNGUCRSI&custom_id=$shortId"
685+
val intent = Intent(Intent.ACTION_VIEW, android.net.Uri.parse(url))
686+
ctx.startActivity(intent)
687+
},
688+
modifier = Modifier.fillMaxWidth().padding(bottom = 8.dp)
689+
) {
690+
Text("PayPal (2,60 €/Month)")
691+
}
692+
Button(
693+
onClick = {
694+
showPaymentMethodDialog = false
695+
launchGooglePlayBilling()
696+
},
697+
modifier = Modifier.fillMaxWidth()
698+
) {
699+
Text("Google Play (2,90 €/Month)")
700+
}
701+
}
702+
},
703+
confirmButton = {},
704+
dismissButton = {
705+
TextButton(onClick = { showPaymentMethodDialog = false }) {
706+
Text("Cancel")
707+
}
708+
}
709+
)
710+
}
711+
712+
639713
}
640714
}
641715
}
@@ -880,28 +954,32 @@ class MainActivity : ComponentActivity() {
880954
}
881955

882956
private fun initiateDonationPurchase() {
883-
Log.d(TAG, "initiateDonationPurchase called.")
957+
Log.d(TAG, "initiateDonationPurchase called. Showing Payment Method Dialog.")
958+
showPaymentMethodDialog = true
959+
}
960+
961+
private fun launchGooglePlayBilling() {
884962
if (!::billingClient.isInitialized) {
885-
Log.e(TAG, "initiateDonationPurchase: BillingClient not initialized.")
963+
Log.e(TAG, "launchGooglePlayBilling: BillingClient not initialized.")
886964
updateStatusMessage("Payment service not initialized. Please try again later.", true)
887965
return
888966
}
889967
if (!billingClient.isReady) {
890-
Log.e(TAG, "initiateDonationPurchase: BillingClient not ready. Connection state: ${billingClient.connectionState}")
968+
Log.e(TAG, "launchGooglePlayBilling: BillingClient not ready. Connection state: ${billingClient.connectionState}")
891969
updateStatusMessage("Payment service not ready. Please try again later.", true)
892970
if (billingClient.connectionState == BillingClient.ConnectionState.CLOSED || billingClient.connectionState == BillingClient.ConnectionState.DISCONNECTED){
893-
Log.d(TAG, "initiateDonationPurchase: BillingClient disconnected, attempting to reconnect.")
971+
Log.d(TAG, "launchGooglePlayBilling: BillingClient disconnected, attempting to reconnect.")
894972
billingClient.startConnection(object : BillingClientStateListener {
895973
override fun onBillingSetupFinished(setupResult: BillingResult) {
896-
Log.i(TAG, "initiateDonationPurchase (reconnect): onBillingSetupFinished. ResponseCode: ${setupResult.responseCode}")
974+
Log.i(TAG, "launchGooglePlayBilling (reconnect): onBillingSetupFinished. ResponseCode: ${setupResult.responseCode}")
897975
if (setupResult.responseCode == BillingClient.BillingResponseCode.OK) {
898-
Log.d(TAG, "initiateDonationPurchase (reconnect): Reconnection successful, retrying purchase.")
899-
initiateDonationPurchase()
976+
Log.d(TAG, "launchGooglePlayBilling (reconnect): Reconnection successful, retrying purchase.")
977+
launchGooglePlayBilling()
900978
} else {
901-
Log.e(TAG, "initiateDonationPurchase (reconnect): BillingClient setup failed after disconnect: ${setupResult.debugMessage}")
979+
Log.e(TAG, "launchGooglePlayBilling (reconnect): BillingClient setup failed after disconnect: ${setupResult.debugMessage}")
902980
}
903981
}
904-
override fun onBillingServiceDisconnected() { Log.w(TAG, "initiateDonationPurchase (reconnect): BillingClient still disconnected.") }
982+
override fun onBillingServiceDisconnected() { Log.w(TAG, "launchGooglePlayBilling (reconnect): BillingClient still disconnected.") }
905983
})
906984
}
907985
return

0 commit comments

Comments
 (0)