Skip to content

Commit aaa6154

Browse files
authored
Merge pull request #417 from hossain-khan/feature/dev-portal-phase1-core-structure
[Dev Portal] Phase 1: Core Screen Structure Implementation
2 parents 9a63d62 + e7f7fbd commit aaa6154

File tree

2 files changed

+271
-0
lines changed

2 files changed

+271
-0
lines changed

app/src/main/java/dev/hossain/remotenotify/ui/about/AboutAppScreen.kt

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,7 @@ import dev.hossain.remotenotify.analytics.Analytics
5555
import dev.hossain.remotenotify.theme.ComposeAppTheme
5656
import dev.hossain.remotenotify.ui.alertlist.AppUsageEducationSheetUi
5757
import dev.hossain.remotenotify.ui.backup.BackupRestoreScreen
58+
import dev.hossain.remotenotify.ui.devportal.DeveloperPortalScreen
5859
import dev.zacsweers.metro.AppScope
5960
import dev.zacsweers.metro.Assisted
6061
import dev.zacsweers.metro.AssistedFactory
@@ -80,6 +81,8 @@ data object AboutAppScreen : Screen {
8081
data object DismissLearnMoreSheet : Event()
8182

8283
data object OpenBackupRestore : Event()
84+
85+
data object OpenDeveloperPortal : Event()
8386
}
8487
}
8588

@@ -138,6 +141,10 @@ class AboutAppPresenter
138141
AboutAppScreen.Event.OpenBackupRestore -> {
139142
navigator.goTo(BackupRestoreScreen)
140143
}
144+
145+
AboutAppScreen.Event.OpenDeveloperPortal -> {
146+
navigator.goTo(DeveloperPortalScreen)
147+
}
141148
}
142149
}
143150
}
@@ -221,6 +228,13 @@ fun AboutAppScreen(
221228
TextButton(onClick = {
222229
state.eventSink(AboutAppScreen.Event.OpenBackupRestore)
223230
}, modifier = Modifier.align(Alignment.CenterHorizontally)) { Text("Backup & Restore") }
231+
if (BuildConfig.DEBUG) {
232+
Spacer(modifier = Modifier.height(8.dp))
233+
TextButton(
234+
onClick = { state.eventSink(AboutAppScreen.Event.OpenDeveloperPortal) },
235+
modifier = Modifier.align(Alignment.CenterHorizontally),
236+
) { Text("🔧 Developer Portal") }
237+
}
224238
}
225239
Column(modifier = Modifier.fillMaxWidth()) {
226240
if (BuildConfig.DEBUG) {
Lines changed: 257 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,257 @@
1+
package dev.hossain.remotenotify.ui.devportal
2+
3+
import androidx.compose.foundation.layout.Arrangement
4+
import androidx.compose.foundation.layout.Column
5+
import androidx.compose.foundation.layout.Row
6+
import androidx.compose.foundation.layout.Spacer
7+
import androidx.compose.foundation.layout.fillMaxSize
8+
import androidx.compose.foundation.layout.fillMaxWidth
9+
import androidx.compose.foundation.layout.height
10+
import androidx.compose.foundation.layout.padding
11+
import androidx.compose.foundation.layout.size
12+
import androidx.compose.foundation.rememberScrollState
13+
import androidx.compose.foundation.verticalScroll
14+
import androidx.compose.material.icons.Icons
15+
import androidx.compose.material.icons.automirrored.filled.ArrowBack
16+
import androidx.compose.material.icons.filled.Build
17+
import androidx.compose.material3.Card
18+
import androidx.compose.material3.CardDefaults
19+
import androidx.compose.material3.ExperimentalMaterial3Api
20+
import androidx.compose.material3.Icon
21+
import androidx.compose.material3.IconButton
22+
import androidx.compose.material3.MaterialTheme
23+
import androidx.compose.material3.Scaffold
24+
import androidx.compose.material3.Text
25+
import androidx.compose.material3.TopAppBar
26+
import androidx.compose.runtime.Composable
27+
import androidx.compose.ui.Alignment
28+
import androidx.compose.ui.Modifier
29+
import androidx.compose.ui.tooling.preview.PreviewDynamicColors
30+
import androidx.compose.ui.tooling.preview.PreviewLightDark
31+
import androidx.compose.ui.unit.dp
32+
import com.slack.circuit.codegen.annotations.CircuitInject
33+
import com.slack.circuit.runtime.CircuitUiEvent
34+
import com.slack.circuit.runtime.CircuitUiState
35+
import com.slack.circuit.runtime.Navigator
36+
import com.slack.circuit.runtime.presenter.Presenter
37+
import com.slack.circuit.runtime.screen.Screen
38+
import com.slack.circuitx.effects.LaunchedImpressionEffect
39+
import dev.hossain.remotenotify.BuildConfig
40+
import dev.hossain.remotenotify.analytics.Analytics
41+
import dev.hossain.remotenotify.monitor.BatteryMonitor
42+
import dev.hossain.remotenotify.monitor.StorageMonitor
43+
import dev.hossain.remotenotify.theme.ComposeAppTheme
44+
import dev.zacsweers.metro.AppScope
45+
import dev.zacsweers.metro.Assisted
46+
import dev.zacsweers.metro.AssistedFactory
47+
import dev.zacsweers.metro.AssistedInject
48+
import kotlinx.parcelize.Parcelize
49+
50+
/**
51+
* Developer Portal screen for debug builds only.
52+
* Provides tools to test and simulate app features without waiting for actual device conditions.
53+
*/
54+
@Parcelize
55+
data object DeveloperPortalScreen : Screen {
56+
data class State(
57+
val currentBatteryLevel: Int,
58+
val currentStorageGb: Long,
59+
val buildVersion: String,
60+
val eventSink: (Event) -> Unit,
61+
) : CircuitUiState
62+
63+
sealed class Event : CircuitUiEvent {
64+
data object GoBack : Event()
65+
}
66+
}
67+
68+
@AssistedInject
69+
class DeveloperPortalPresenter
70+
constructor(
71+
@Assisted private val navigator: Navigator,
72+
private val analytics: Analytics,
73+
private val batteryMonitor: BatteryMonitor,
74+
private val storageMonitor: StorageMonitor,
75+
) : Presenter<DeveloperPortalScreen.State> {
76+
@Composable
77+
override fun present(): DeveloperPortalScreen.State {
78+
LaunchedImpressionEffect {
79+
analytics.logScreenView(DeveloperPortalScreen::class)
80+
}
81+
82+
// Get current device status
83+
val currentBatteryLevel = batteryMonitor.getBatteryLevel()
84+
val currentStorageGb = storageMonitor.getAvailableStorageInGB()
85+
86+
val buildVersion =
87+
buildString {
88+
append("v")
89+
append(BuildConfig.VERSION_NAME)
90+
append(" (")
91+
append(BuildConfig.GIT_COMMIT_HASH)
92+
append(")")
93+
}
94+
95+
return DeveloperPortalScreen.State(
96+
currentBatteryLevel = currentBatteryLevel,
97+
currentStorageGb = currentStorageGb,
98+
buildVersion = buildVersion,
99+
) { event ->
100+
when (event) {
101+
DeveloperPortalScreen.Event.GoBack -> {
102+
navigator.pop()
103+
}
104+
}
105+
}
106+
}
107+
108+
@CircuitInject(DeveloperPortalScreen::class, AppScope::class)
109+
@AssistedFactory
110+
interface Factory {
111+
fun create(navigator: Navigator): DeveloperPortalPresenter
112+
}
113+
}
114+
115+
@OptIn(ExperimentalMaterial3Api::class)
116+
@CircuitInject(DeveloperPortalScreen::class, AppScope::class)
117+
@Composable
118+
fun DeveloperPortalUi(
119+
state: DeveloperPortalScreen.State,
120+
modifier: Modifier = Modifier,
121+
) {
122+
Scaffold(
123+
modifier = modifier,
124+
topBar = {
125+
TopAppBar(
126+
title = { Text("🔧 Developer Portal") },
127+
navigationIcon = {
128+
IconButton(onClick = {
129+
state.eventSink(DeveloperPortalScreen.Event.GoBack)
130+
}) {
131+
Icon(
132+
imageVector = Icons.AutoMirrored.Filled.ArrowBack,
133+
contentDescription = "Go back",
134+
)
135+
}
136+
},
137+
)
138+
},
139+
) { contentPaddingValues ->
140+
Column(
141+
modifier =
142+
Modifier
143+
.fillMaxSize()
144+
.padding(contentPaddingValues)
145+
.padding(horizontal = 16.dp)
146+
.verticalScroll(rememberScrollState()),
147+
verticalArrangement = Arrangement.spacedBy(16.dp),
148+
) {
149+
Spacer(modifier = Modifier.height(0.dp))
150+
151+
// Warning banner
152+
Card(
153+
modifier = Modifier.fillMaxWidth(),
154+
colors =
155+
CardDefaults.cardColors(
156+
containerColor = MaterialTheme.colorScheme.errorContainer,
157+
),
158+
) {
159+
Row(
160+
modifier = Modifier.padding(16.dp),
161+
verticalAlignment = Alignment.CenterVertically,
162+
) {
163+
Icon(
164+
imageVector = Icons.Default.Build,
165+
contentDescription = "Debug Mode",
166+
tint = MaterialTheme.colorScheme.onErrorContainer,
167+
modifier = Modifier.size(32.dp),
168+
)
169+
Column(modifier = Modifier.padding(start = 16.dp)) {
170+
Text(
171+
text = "Debug Build Only",
172+
style = MaterialTheme.typography.titleMedium,
173+
color = MaterialTheme.colorScheme.onErrorContainer,
174+
)
175+
Text(
176+
text = "This screen is not available in release builds",
177+
style = MaterialTheme.typography.bodySmall,
178+
color = MaterialTheme.colorScheme.onErrorContainer,
179+
)
180+
}
181+
}
182+
}
183+
184+
// Build info card
185+
Card(modifier = Modifier.fillMaxWidth()) {
186+
Column(modifier = Modifier.padding(16.dp)) {
187+
Text(
188+
text = "Build Information",
189+
style = MaterialTheme.typography.titleMedium,
190+
color = MaterialTheme.colorScheme.primary,
191+
)
192+
Spacer(modifier = Modifier.height(8.dp))
193+
Text(
194+
text = "Version: ${state.buildVersion}",
195+
style = MaterialTheme.typography.bodyMedium,
196+
)
197+
}
198+
}
199+
200+
// Current device status card
201+
Card(modifier = Modifier.fillMaxWidth()) {
202+
Column(modifier = Modifier.padding(16.dp)) {
203+
Text(
204+
text = "📱 Current Device Status",
205+
style = MaterialTheme.typography.titleMedium,
206+
color = MaterialTheme.colorScheme.primary,
207+
)
208+
Spacer(modifier = Modifier.height(8.dp))
209+
Text(
210+
text = "🔋 Battery: ${state.currentBatteryLevel}%",
211+
style = MaterialTheme.typography.bodyMedium,
212+
)
213+
Text(
214+
text = "💾 Storage: ${state.currentStorageGb} GB available",
215+
style = MaterialTheme.typography.bodyMedium,
216+
)
217+
}
218+
}
219+
220+
// Placeholder cards for upcoming features
221+
Card(
222+
modifier = Modifier.fillMaxWidth(),
223+
colors = CardDefaults.cardColors(containerColor = MaterialTheme.colorScheme.surfaceVariant),
224+
) {
225+
Column(modifier = Modifier.padding(16.dp)) {
226+
Text(
227+
text = "🚧 Coming Soon",
228+
style = MaterialTheme.typography.titleMedium,
229+
)
230+
Spacer(modifier = Modifier.height(8.dp))
231+
Text(
232+
text = "• Device Simulation Tools\n• WorkManager Testing\n• Notification Channel Testing\n• Log Management",
233+
style = MaterialTheme.typography.bodyMedium,
234+
)
235+
}
236+
}
237+
238+
Spacer(modifier = Modifier.height(16.dp))
239+
}
240+
}
241+
}
242+
243+
@Composable
244+
@PreviewLightDark
245+
@PreviewDynamicColors
246+
private fun DeveloperPortalScreenPreview() {
247+
val sampleState =
248+
DeveloperPortalScreen.State(
249+
currentBatteryLevel = 75,
250+
currentStorageGb = 45,
251+
buildVersion = "v1.17.0 (abc1234)",
252+
eventSink = {},
253+
)
254+
ComposeAppTheme {
255+
DeveloperPortalUi(state = sampleState)
256+
}
257+
}

0 commit comments

Comments
 (0)