git.iko.soy/parseable-android
on master help sign in
Add About screen with GitHub repo and website links (#20)
a02f8b3iko
app/src/main/java/com/parseable/android/ui/navigation/NavGraph.kt+21
@@ -13,6 +13,7 @@ import com.parseable.android.ui.screens.alerts.AlertsScreen
13import com.parseable.android.ui.screens.login.LoginScreen
13import com.parseable.android.ui.screens.login.LoginScreen
14import com.parseable.android.ui.screens.logviewer.LogViewerScreen
14import com.parseable.android.ui.screens.logviewer.LogViewerScreen
15import com.parseable.android.ui.screens.logviewer.StreamInfoScreen
15import com.parseable.android.ui.screens.logviewer.StreamInfoScreen
16+import com.parseable.android.ui.screens.about.AboutScreen
16import com.parseable.android.ui.screens.settings.SettingsScreen
17import com.parseable.android.ui.screens.settings.SettingsScreen
17import com.parseable.android.ui.screens.streams.StreamsScreen
18import com.parseable.android.ui.screens.streams.StreamsScreen
18import java.net.URLDecoder
19import java.net.URLDecoder
@@ -25,6 +26,7 @@ object Routes {
25 const val STREAM_INFO = "stream_info/{streamName}"
26 const val STREAM_INFO = "stream_info/{streamName}"
26 const val ALERTS = "alerts"
27 const val ALERTS = "alerts"
27 const val SETTINGS = "settings"
28 const val SETTINGS = "settings"
29+ const val ABOUT = "about"
28
30
29 fun login(sessionExpired: Boolean = false) =
31 fun login(sessionExpired: Boolean = false) =
30 if (sessionExpired) "login?sessionExpired=true" else LOGIN
32 if (sessionExpired) "login?sessionExpired=true" else LOGIN
@@ -170,6 +172,25 @@ fun ParseableNavGraph(
170 }
172 }
171 SettingsScreen(
173 SettingsScreen(
172 onBack = { navController.popBackStack() },
174 onBack = { navController.popBackStack() },
175+ onAboutClick = {
176+ navController.navigate(Routes.ABOUT) {
177+ launchSingleTop = true
178+ }
179+ },
180+ )
181+ }
182+
183+ composable(Routes.ABOUT) {
184+ if (!repository.isConfigured) {
185+ LaunchedEffect(Unit) {
186+ navController.navigate(Routes.LOGIN) {
187+ popUpTo(0) { inclusive = true }
188+ }
189+ }
190+ return@composable
191+ }
192+ AboutScreen(
193+ onBack = { navController.popBackStack() },
173 )
194 )
174 }
195 }
175 }
196 }
app/src/main/java/com/parseable/android/ui/screens/about/AboutScreen.ktnew file+176
@@ -0,0 +1,176 @@
1+package com.parseable.android.ui.screens.about
2+
3+import android.content.Intent
4+import android.net.Uri
5+import androidx.compose.foundation.layout.*
6+import androidx.compose.foundation.rememberScrollState
7+import androidx.compose.foundation.verticalScroll
8+import androidx.compose.material.icons.Icons
9+import androidx.compose.material.icons.automirrored.filled.ArrowBack
10+import androidx.compose.material.icons.filled.*
11+import androidx.compose.material3.*
12+import androidx.compose.runtime.Composable
13+import androidx.compose.ui.Alignment
14+import androidx.compose.ui.Modifier
15+import androidx.compose.ui.platform.LocalContext
16+import androidx.compose.ui.text.font.FontWeight
17+import androidx.compose.ui.unit.dp
18+import com.parseable.android.BuildConfig
19+
20+private const val GITHUB_REPO_URL = "https://github.com/parseablehq/parseable-android"
21+private const val PARSEABLE_WEBSITE_URL = "https://www.parseable.com"
22+
23+@OptIn(ExperimentalMaterial3Api::class)
24+@Composable
25+fun AboutScreen(
26+ onBack: () -> Unit,
27+) {
28+ val context = LocalContext.current
29+
30+ Scaffold(
31+ topBar = {
32+ TopAppBar(
33+ title = { Text("About") },
34+ navigationIcon = {
35+ IconButton(onClick = onBack) {
36+ Icon(Icons.AutoMirrored.Filled.ArrowBack, contentDescription = "Back")
37+ }
38+ },
39+ )
40+ },
41+ ) { padding ->
42+ Column(
43+ modifier = Modifier
44+ .fillMaxSize()
45+ .padding(padding)
46+ .verticalScroll(rememberScrollState())
47+ .padding(16.dp),
48+ verticalArrangement = Arrangement.spacedBy(16.dp),
49+ ) {
50+ // App header
51+ Card(
52+ modifier = Modifier.fillMaxWidth(),
53+ elevation = CardDefaults.cardElevation(defaultElevation = 1.dp),
54+ ) {
55+ Column(
56+ modifier = Modifier
57+ .fillMaxWidth()
58+ .padding(24.dp),
59+ horizontalAlignment = Alignment.CenterHorizontally,
60+ ) {
61+ Icon(
62+ Icons.Filled.Terminal,
63+ contentDescription = null,
64+ modifier = Modifier.size(48.dp),
65+ tint = MaterialTheme.colorScheme.primary,
66+ )
67+ Spacer(modifier = Modifier.height(12.dp))
68+ Text(
69+ text = "Parseable",
70+ style = MaterialTheme.typography.headlineSmall,
71+ fontWeight = FontWeight.Bold,
72+ )
73+ Text(
74+ text = "Android Client",
75+ style = MaterialTheme.typography.titleMedium,
76+ color = MaterialTheme.colorScheme.onSurfaceVariant,
77+ )
78+ Spacer(modifier = Modifier.height(8.dp))
79+ Text(
80+ text = "Version ${BuildConfig.VERSION_NAME}",
81+ style = MaterialTheme.typography.bodyMedium,
82+ color = MaterialTheme.colorScheme.onSurfaceVariant,
83+ )
84+ }
85+ }
86+
87+ // Description
88+ Card(
89+ modifier = Modifier.fillMaxWidth(),
90+ elevation = CardDefaults.cardElevation(defaultElevation = 1.dp),
91+ ) {
92+ Column(modifier = Modifier.padding(16.dp)) {
93+ Row(verticalAlignment = Alignment.CenterVertically) {
94+ Icon(
95+ Icons.Filled.Info,
96+ contentDescription = null,
97+ tint = MaterialTheme.colorScheme.primary,
98+ )
99+ Spacer(modifier = Modifier.width(8.dp))
100+ Text(
101+ text = "About",
102+ style = MaterialTheme.typography.titleMedium,
103+ fontWeight = FontWeight.Bold,
104+ )
105+ }
106+ Spacer(modifier = Modifier.height(12.dp))
107+ Text(
108+ text = "Parseable is an open-source log analytics platform. " +
109+ "This app connects to a Parseable server instance and lets you " +
110+ "browse, search, filter, and live-tail log data.",
111+ style = MaterialTheme.typography.bodyMedium,
112+ )
113+ }
114+ }
115+
116+ // Links
117+ Card(
118+ modifier = Modifier.fillMaxWidth(),
119+ elevation = CardDefaults.cardElevation(defaultElevation = 1.dp),
120+ ) {
121+ Column(modifier = Modifier.padding(16.dp)) {
122+ Row(verticalAlignment = Alignment.CenterVertically) {
123+ Icon(
124+ Icons.Filled.Link,
125+ contentDescription = null,
126+ tint = MaterialTheme.colorScheme.primary,
127+ )
128+ Spacer(modifier = Modifier.width(8.dp))
129+ Text(
130+ text = "Links",
131+ style = MaterialTheme.typography.titleMedium,
132+ fontWeight = FontWeight.Bold,
133+ )
134+ }
135+ Spacer(modifier = Modifier.height(12.dp))
136+
137+ OutlinedButton(
138+ onClick = {
139+ context.startActivity(
140+ Intent(Intent.ACTION_VIEW, Uri.parse(GITHUB_REPO_URL))
141+ )
142+ },
143+ modifier = Modifier.fillMaxWidth(),
144+ ) {
145+ Icon(
146+ Icons.Filled.Code,
147+ contentDescription = null,
148+ modifier = Modifier.size(18.dp),
149+ )
150+ Spacer(modifier = Modifier.width(8.dp))
151+ Text("GitHub Repository")
152+ }
153+
154+ Spacer(modifier = Modifier.height(8.dp))
155+
156+ OutlinedButton(
157+ onClick = {
158+ context.startActivity(
159+ Intent(Intent.ACTION_VIEW, Uri.parse(PARSEABLE_WEBSITE_URL))
160+ )
161+ },
162+ modifier = Modifier.fillMaxWidth(),
163+ ) {
164+ Icon(
165+ Icons.Filled.Language,
166+ contentDescription = null,
167+ modifier = Modifier.size(18.dp),
168+ )
169+ Spacer(modifier = Modifier.width(8.dp))
170+ Text("Parseable Website")
171+ }
172+ }
173+ }
174+ }
175+ }
176+}
app/src/main/java/com/parseable/android/ui/screens/settings/SettingsScreen.kt+33
@@ -23,6 +23,7 @@ import kotlinx.serialization.json.jsonPrimitive
23@Composable
23@Composable
24fun SettingsScreen(
24fun SettingsScreen(
25 onBack: () -> Unit,
25 onBack: () -> Unit,
26+ onAboutClick: () -> Unit,
26 viewModel: SettingsViewModel = hiltViewModel(),
27 viewModel: SettingsViewModel = hiltViewModel(),
27) {
28) {
28 val state by viewModel.state.collectAsStateWithLifecycle()
29 val state by viewModel.state.collectAsStateWithLifecycle()
@@ -218,6 +219,38 @@ fun SettingsScreen(
218 SettingsRow("Platform", "Android")
219 SettingsRow("Platform", "Android")
219 }
220 }
220 }
221 }
222+
223+ // About
224+ Card(
225+ onClick = onAboutClick,
226+ modifier = Modifier.fillMaxWidth(),
227+ elevation = CardDefaults.cardElevation(defaultElevation = 1.dp),
228+ ) {
229+ Row(
230+ modifier = Modifier
231+ .fillMaxWidth()
232+ .padding(16.dp),
233+ verticalAlignment = Alignment.CenterVertically,
234+ ) {
235+ Icon(
236+ Icons.Filled.Info,
237+ contentDescription = "About",
238+ tint = MaterialTheme.colorScheme.primary,
239+ )
240+ Spacer(modifier = Modifier.width(8.dp))
241+ Text(
242+ text = "About",
243+ style = MaterialTheme.typography.titleMedium,
244+ fontWeight = FontWeight.Bold,
245+ modifier = Modifier.weight(1f),
246+ )
247+ Icon(
248+ Icons.Filled.ChevronRight,
249+ contentDescription = null,
250+ tint = MaterialTheme.colorScheme.onSurfaceVariant,
251+ )
252+ }
253+ }
221 }
254 }
222 }
255 }
223 }
256 }