Skip to content

Commit 6ded619

Browse files
Merge pull request #64 from Android-PowerUser/refactor-god-classes-in-/app
Refactor media-projection/screenshot flow and photo-reasoning into modular components
2 parents e0681f9 + 0f5ee9d commit 6ded619

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

41 files changed

+3009
-2352
lines changed
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
package com.google.ai.sample
2+
3+
import android.content.ContentResolver
4+
import android.provider.Settings
5+
6+
internal object AccessibilityServiceStatusResolver {
7+
fun isServiceEnabled(contentResolver: ContentResolver, packageName: String): Boolean {
8+
val service = "$packageName/${ScreenOperatorAccessibilityService::class.java.canonicalName}"
9+
val enabledServices = Settings.Secure.getString(
10+
contentResolver,
11+
Settings.Secure.ENABLED_ACCESSIBILITY_SERVICES
12+
)
13+
return enabledServices?.contains(service, ignoreCase = true) == true
14+
}
15+
}

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

Lines changed: 125 additions & 338 deletions
Large diffs are not rendered by default.
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
package com.google.ai.sample
2+
3+
import com.android.billingclient.api.BillingClient
4+
5+
internal object MainActivityBillingClientState {
6+
fun isInitializedAndReady(isInitialized: Boolean, isReady: Boolean): Boolean {
7+
return isInitialized && isReady
8+
}
9+
10+
fun isConnecting(isInitialized: Boolean, connectionState: Int): Boolean {
11+
return isInitialized && connectionState == BillingClient.ConnectionState.CONNECTING
12+
}
13+
14+
fun shouldReconnect(connectionState: Int): Boolean {
15+
return connectionState == BillingClient.ConnectionState.CLOSED ||
16+
connectionState == BillingClient.ConnectionState.DISCONNECTED
17+
}
18+
}
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
package com.google.ai.sample
2+
3+
import com.android.billingclient.api.Purchase
4+
5+
internal object MainActivityBillingStateEvaluator {
6+
fun shouldStartTrialService(state: TrialManager.TrialState): Boolean {
7+
return state != TrialManager.TrialState.PURCHASED &&
8+
state != TrialManager.TrialState.EXPIRED_INTERNET_TIME_CONFIRMED
9+
}
10+
11+
fun containsSubscriptionProduct(purchase: Purchase, subscriptionProductId: String): Boolean {
12+
return purchase.products.any { it == subscriptionProductId }
13+
}
14+
15+
fun isPurchasedSubscription(purchase: Purchase, subscriptionProductId: String): Boolean {
16+
return containsSubscriptionProduct(purchase, subscriptionProductId) &&
17+
purchase.purchaseState == Purchase.PurchaseState.PURCHASED
18+
}
19+
}
Lines changed: 235 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,235 @@
1+
package com.google.ai.sample
2+
3+
import android.util.Log
4+
import androidx.compose.foundation.layout.Arrangement
5+
import androidx.compose.foundation.layout.Column
6+
import androidx.compose.foundation.layout.Spacer
7+
import androidx.compose.foundation.layout.fillMaxWidth
8+
import androidx.compose.foundation.layout.height
9+
import androidx.compose.foundation.layout.padding
10+
import androidx.compose.material3.Button
11+
import androidx.compose.material3.Card
12+
import androidx.compose.material3.MaterialTheme
13+
import androidx.compose.material3.Text
14+
import androidx.compose.material3.TextButton
15+
import androidx.compose.runtime.Composable
16+
import androidx.compose.ui.Alignment
17+
import androidx.compose.ui.Modifier
18+
import androidx.compose.ui.unit.dp
19+
import androidx.compose.ui.window.Dialog
20+
21+
@Composable
22+
internal fun TrialStateDialogs(
23+
trialState: TrialManager.TrialState,
24+
showTrialInfoDialog: Boolean,
25+
trialInfoMessage: String,
26+
onDismissTrialInfo: () -> Unit,
27+
onPurchaseClick: () -> Unit
28+
) {
29+
when (trialState) {
30+
TrialManager.TrialState.EXPIRED_INTERNET_TIME_CONFIRMED -> {
31+
TrialExpiredDialog(
32+
onPurchaseClick = onPurchaseClick,
33+
onDismiss = {}
34+
)
35+
}
36+
37+
TrialManager.TrialState.NOT_YET_STARTED_AWAITING_INTERNET,
38+
TrialManager.TrialState.INTERNET_UNAVAILABLE_CANNOT_VERIFY -> {
39+
if (showTrialInfoDialog) {
40+
InfoDialog(
41+
message = trialInfoMessage,
42+
onDismiss = onDismissTrialInfo
43+
)
44+
}
45+
}
46+
47+
TrialManager.TrialState.ACTIVE_INTERNET_TIME_CONFIRMED,
48+
TrialManager.TrialState.PURCHASED -> Unit
49+
}
50+
}
51+
52+
@Composable
53+
internal fun PaymentMethodDialog(
54+
onDismiss: () -> Unit,
55+
onPayPalClick: () -> Unit,
56+
onGooglePlayClick: () -> Unit
57+
) {
58+
androidx.compose.material3.AlertDialog(
59+
onDismissRequest = onDismiss,
60+
title = { Text("Choose Payment Method") },
61+
text = {
62+
Column {
63+
Button(
64+
onClick = onPayPalClick,
65+
modifier = Modifier.fillMaxWidth().padding(bottom = 8.dp)
66+
) {
67+
Text("PayPal (2,60 €/Month)")
68+
}
69+
Button(
70+
onClick = onGooglePlayClick,
71+
modifier = Modifier.fillMaxWidth()
72+
) {
73+
Text("Google Play (2,90 €/Month)")
74+
}
75+
}
76+
},
77+
confirmButton = {},
78+
dismissButton = {
79+
TextButton(onClick = onDismiss) {
80+
Text("Cancel")
81+
}
82+
}
83+
)
84+
}
85+
86+
@Composable
87+
internal fun ApiKeyDialogSection(
88+
apiKeyManager: ApiKeyManager,
89+
isFirstLaunch: Boolean,
90+
initialProvider: ApiProvider?,
91+
onDismiss: () -> Unit
92+
) {
93+
ApiKeyDialog(
94+
apiKeyManager = apiKeyManager,
95+
isFirstLaunch = isFirstLaunch,
96+
initialProvider = initialProvider,
97+
onDismiss = onDismiss
98+
)
99+
}
100+
101+
@Composable
102+
fun FirstLaunchInfoDialog(onDismiss: () -> Unit) {
103+
Log.d("FirstLaunchInfoDialog", "Composing FirstLaunchInfoDialog")
104+
Dialog(onDismissRequest = {
105+
Log.d("FirstLaunchInfoDialog", "onDismissRequest called")
106+
onDismiss()
107+
}) {
108+
Card(
109+
modifier = Modifier
110+
.fillMaxWidth()
111+
.padding(16.dp),
112+
) {
113+
Column(
114+
modifier = Modifier
115+
.padding(16.dp)
116+
.fillMaxWidth(),
117+
verticalArrangement = Arrangement.Center,
118+
horizontalAlignment = Alignment.CenterHorizontally
119+
) {
120+
Text(
121+
text = "Trial Information",
122+
style = MaterialTheme.typography.titleLarge
123+
)
124+
Spacer(modifier = Modifier.height(16.dp))
125+
Text(
126+
text = "You can try Screen Operator for 7 days before you have to subscribe to support the development of more features.",
127+
style = MaterialTheme.typography.bodyMedium,
128+
modifier = Modifier.align(Alignment.CenterHorizontally)
129+
)
130+
Spacer(modifier = Modifier.height(24.dp))
131+
TextButton(
132+
onClick = {
133+
Log.d("FirstLaunchInfoDialog", "OK button clicked")
134+
onDismiss()
135+
},
136+
modifier = Modifier.fillMaxWidth()
137+
) {
138+
Text("OK")
139+
}
140+
}
141+
}
142+
}
143+
}
144+
145+
146+
147+
@Composable
148+
fun TrialExpiredDialog(
149+
onPurchaseClick: () -> Unit,
150+
@Suppress("UNUSED_PARAMETER") onDismiss: () -> Unit
151+
) {
152+
Log.d("TrialExpiredDialog", "Composing TrialExpiredDialog")
153+
Dialog(onDismissRequest = {
154+
Log.d("TrialExpiredDialog", "onDismissRequest called (persistent dialog - user tried to dismiss)")
155+
}) {
156+
Card(
157+
modifier = Modifier
158+
.fillMaxWidth()
159+
.padding(16.dp),
160+
) {
161+
Column(
162+
modifier = Modifier
163+
.padding(16.dp)
164+
.fillMaxWidth(),
165+
verticalArrangement = Arrangement.Center,
166+
horizontalAlignment = Alignment.CenterHorizontally
167+
) {
168+
Text(
169+
text = "Trial period expired",
170+
style = MaterialTheme.typography.titleLarge
171+
)
172+
Spacer(modifier = Modifier.height(16.dp))
173+
Text(
174+
text = "Please support the development of the app so that you can continue using it \uD83C\uDF89",
175+
style = MaterialTheme.typography.bodyMedium,
176+
modifier = Modifier.align(Alignment.CenterHorizontally)
177+
)
178+
Spacer(modifier = Modifier.height(24.dp))
179+
Button(
180+
onClick = {
181+
Log.d("TrialExpiredDialog", "Purchase button clicked")
182+
onPurchaseClick()
183+
},
184+
modifier = Modifier.fillMaxWidth()
185+
) {
186+
Text("Subscribe")
187+
}
188+
}
189+
}
190+
}
191+
}
192+
193+
@Composable
194+
fun InfoDialog(
195+
message: String,
196+
onDismiss: () -> Unit
197+
) {
198+
Log.d("InfoDialog", "Composing InfoDialog with message: $message")
199+
Dialog(onDismissRequest = {
200+
Log.d("InfoDialog", "onDismissRequest called")
201+
onDismiss()
202+
}) {
203+
Card(
204+
modifier = Modifier
205+
.fillMaxWidth()
206+
.padding(16.dp)
207+
) {
208+
Column(
209+
modifier = Modifier
210+
.padding(16.dp)
211+
.fillMaxWidth(),
212+
verticalArrangement = Arrangement.Center,
213+
horizontalAlignment = Alignment.CenterHorizontally
214+
) {
215+
Text(
216+
text = "Information",
217+
style = MaterialTheme.typography.titleMedium
218+
)
219+
Spacer(modifier = Modifier.height(16.dp))
220+
Text(
221+
text = message,
222+
style = MaterialTheme.typography.bodyMedium,
223+
modifier = Modifier.align(Alignment.CenterHorizontally)
224+
)
225+
Spacer(modifier = Modifier.height(24.dp))
226+
TextButton(onClick = {
227+
Log.d("InfoDialog", "OK button clicked")
228+
onDismiss()
229+
}) {
230+
Text("OK")
231+
}
232+
}
233+
}
234+
}
235+
}
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
package com.google.ai.sample
2+
3+
import android.content.Context
4+
import android.content.Intent
5+
6+
internal object MainActivityMediaProjectionIntents {
7+
fun startCapture(
8+
context: Context,
9+
resultCode: Int,
10+
resultData: Intent,
11+
takeScreenshotOnStart: Boolean
12+
): Intent {
13+
return Intent(context, ScreenCaptureService::class.java).apply {
14+
action = ScreenCaptureService.ACTION_START_CAPTURE
15+
putExtra(ScreenCaptureService.EXTRA_RESULT_CODE, resultCode)
16+
putExtra(ScreenCaptureService.EXTRA_RESULT_DATA, resultData)
17+
putExtra(ScreenCaptureService.EXTRA_TAKE_SCREENSHOT_ON_START, takeScreenshotOnStart)
18+
}
19+
}
20+
21+
fun keepAliveForWebRtc(context: Context): Intent {
22+
return Intent(context, ScreenCaptureService::class.java).apply {
23+
action = ScreenCaptureService.ACTION_KEEP_ALIVE_FOR_WEBRTC
24+
}
25+
}
26+
27+
fun takeScreenshot(context: Context): Intent {
28+
return Intent(context, ScreenCaptureService::class.java).apply {
29+
action = ScreenCaptureService.ACTION_TAKE_SCREENSHOT
30+
}
31+
}
32+
33+
fun stopCapture(context: Context): Intent {
34+
return Intent(context, ScreenCaptureService::class.java).apply {
35+
action = ScreenCaptureService.ACTION_STOP_CAPTURE
36+
}
37+
}
38+
}
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
package com.google.ai.sample
2+
3+
internal object MainActivityScreenshotFlowDecider {
4+
enum class Action {
5+
TAKE_ADDITIONAL_SCREENSHOT,
6+
REQUEST_PERMISSION
7+
}
8+
9+
fun decide(isScreenCaptureServiceRunning: Boolean): Action {
10+
return if (isScreenCaptureServiceRunning) {
11+
Action.TAKE_ADDITIONAL_SCREENSHOT
12+
} else {
13+
Action.REQUEST_PERMISSION
14+
}
15+
}
16+
}
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
package com.google.ai.sample
2+
3+
import android.content.Intent
4+
import android.net.Uri
5+
6+
internal object MainActivityScreenshotIntents {
7+
fun extractScreenInfo(intent: Intent): String? {
8+
return intent.getStringExtra(MainActivity.EXTRA_SCREEN_INFO)
9+
}
10+
11+
fun extractScreenshotUri(intent: Intent): Uri? {
12+
val uriString = intent.getStringExtra(MainActivity.EXTRA_SCREENSHOT_URI)
13+
return uriString?.let(Uri::parse)
14+
}
15+
}
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
package com.google.ai.sample
2+
3+
import android.content.Context
4+
import android.util.Log
5+
import android.widget.Toast
6+
7+
internal object MainActivityStatusNotifier {
8+
fun showStatusMessage(context: Context, tag: String, message: String, isError: Boolean) {
9+
Toast.makeText(context, message, if (isError) Toast.LENGTH_LONG else Toast.LENGTH_SHORT).show()
10+
if (isError) {
11+
Log.e(tag, "updateStatusMessage (Error): $message")
12+
} else {
13+
Log.d(tag, "updateStatusMessage (Info): $message")
14+
}
15+
}
16+
}

0 commit comments

Comments
 (0)