diff --git a/DittoDiskUsage/src/main/java/live/ditto/dittodiskusage/usecase/GetDiskUsageMetrics.kt b/DittoDiskUsage/src/main/java/live/ditto/dittodiskusage/usecase/GetDiskUsageMetrics.kt index 7afd1ef..9388f1b 100644 --- a/DittoDiskUsage/src/main/java/live/ditto/dittodiskusage/usecase/GetDiskUsageMetrics.kt +++ b/DittoDiskUsage/src/main/java/live/ditto/dittodiskusage/usecase/GetDiskUsageMetrics.kt @@ -9,19 +9,21 @@ import live.ditto.dittodiskusage.TOTAL_SIZE import live.ditto.dittodiskusage.FIVE_HUNDRED_MEGABYTES_IN_BYTES import live.ditto.healthmetrics.HealthMetric -class GetDiskUsageMetrics() { +class GetDiskUsageMetrics { val metricName: String = METRIC_NAME var unhealthySizeInBytes: Int = FIVE_HUNDRED_MEGABYTES_IN_BYTES fun execute(currentState: DiskUsageState): HealthMetric { - val dittoStoreSize: Int = currentState.children.first { shortRelativePath(it.relativePath) == DITTO_STORE}.sizeInBytes - val dittoReplicationSize: Int = currentState.children.first { shortRelativePath(it.relativePath) == DITTO_REPLICATION}.sizeInBytes + val dittoStoreSize: Int = + currentState.children.first { shortRelativePath(it.relativePath) == DITTO_STORE }.sizeInBytes + val dittoReplicationSize: Int = currentState.children.firstOrNull { + shortRelativePath(it.relativePath) == DITTO_REPLICATION }?.sizeInBytes ?: 0 val isHealthy = healthCheckSize(dittoStoreSize, dittoReplicationSize) val details = mutableMapOf().apply { - for(child in currentState.children) { + for (child in currentState.children) { this[shortRelativePath(child.relativePath)] = child.size } this[ROOT_PATH] = currentState.rootPath diff --git a/DittoToolsViewer/.gitignore b/DittoToolsViewer/.gitignore new file mode 100644 index 0000000..42afabf --- /dev/null +++ b/DittoToolsViewer/.gitignore @@ -0,0 +1 @@ +/build \ No newline at end of file diff --git a/DittoToolsViewer/build.gradle.kts b/DittoToolsViewer/build.gradle.kts new file mode 100644 index 0000000..7f3ee04 --- /dev/null +++ b/DittoToolsViewer/build.gradle.kts @@ -0,0 +1,50 @@ +// libs. will show an IDE error. This is a bug with Android Studio/IntelliJ. This issue is +// is tracked here: https://youtrack.jetbrains.com/issue/KTIJ-19369 +// Workaround is to suppress the error until the issue linked above is fixed +@Suppress("DSL_SCOPE_VIOLATION") +plugins { + alias(libs.plugins.com.android.library) + alias(libs.plugins.org.jetbrains.kotlin.android) +} + +extra["libraryGroupId"] = "live.ditto" +extra["libraryArtifactId"] = "dittotoolsviewer" +extra["libraryVersion"] = "1.0.0" + +apply { + from("${rootProject.projectDir}/gradle/deploy.gradle") + from("${rootProject.projectDir}/gradle/android-common.gradle") +} + +android { + namespace = "live.ditto.dittotoolsviewer" +} + +dependencies { + + implementation(libs.core.ktx) + implementation(libs.androidx.appcompat.appcompat) + implementation(libs.material) + + implementation(platform(libs.androidx.compose.composeBom)) + implementation(libs.androidx.compose.ui.ui) + implementation(libs.androidx.compose.ui.uiToolingPreview) + implementation(libs.androidx.navigation.navigationCompose) + implementation(libs.androidx.compose.material3.material3) + + implementation(libs.live.ditto.ditto) + implementation(libs.live.ditto.databrowser) + implementation(libs.live.ditto.exportlogs) + implementation(libs.live.ditto.presenceviewer) + implementation(libs.live.ditto.diskusage) + implementation(libs.live.ditto.health) + implementation(libs.live.ditto.heartbeat) + implementation(libs.live.ditto.presencedegradationreporter) + implementation(libs.live.ditto.healthmetrics) + implementation(libs.live.ditto.exporter) + + testImplementation(libs.junit.junit) + + androidTestImplementation(libs.androidx.test.ext.junit) + androidTestImplementation(libs.espresso.core) +} \ No newline at end of file diff --git a/DittoToolsViewer/consumer-rules.pro b/DittoToolsViewer/consumer-rules.pro new file mode 100644 index 0000000..e69de29 diff --git a/DittoToolsViewer/proguard-rules.pro b/DittoToolsViewer/proguard-rules.pro new file mode 100644 index 0000000..481bb43 --- /dev/null +++ b/DittoToolsViewer/proguard-rules.pro @@ -0,0 +1,21 @@ +# Add project specific ProGuard rules here. +# You can control the set of applied configuration files using the +# proguardFiles setting in build.gradle. +# +# For more details, see +# http://developer.android.com/guide/developing/tools/proguard.html + +# If your project uses WebView with JS, uncomment the following +# and specify the fully qualified class name to the JavaScript interface +# class: +#-keepclassmembers class fqcn.of.javascript.interface.for.webview { +# public *; +#} + +# Uncomment this to preserve the line number information for +# debugging stack traces. +#-keepattributes SourceFile,LineNumberTable + +# If you keep the line number information, uncomment this to +# hide the original source file name. +#-renamesourcefileattribute SourceFile \ No newline at end of file diff --git a/DittoToolsViewer/src/main/AndroidManifest.xml b/DittoToolsViewer/src/main/AndroidManifest.xml new file mode 100644 index 0000000..a5918e6 --- /dev/null +++ b/DittoToolsViewer/src/main/AndroidManifest.xml @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/DittoToolsViewer/src/main/java/live/ditto/dittotoolsviewer/presentation/DittoToolsViewer.kt b/DittoToolsViewer/src/main/java/live/ditto/dittotoolsviewer/presentation/DittoToolsViewer.kt index e69de29..c1f3966 100644 --- a/DittoToolsViewer/src/main/java/live/ditto/dittotoolsviewer/presentation/DittoToolsViewer.kt +++ b/DittoToolsViewer/src/main/java/live/ditto/dittotoolsviewer/presentation/DittoToolsViewer.kt @@ -0,0 +1,168 @@ +package live.ditto.dittotoolsviewer.presentation + +import androidx.compose.foundation.layout.PaddingValues +import androidx.compose.foundation.layout.padding +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.filled.Build +import androidx.compose.material3.BottomAppBar +import androidx.compose.material3.Button +import androidx.compose.material3.ExtendedFloatingActionButton +import androidx.compose.material3.Icon +import androidx.compose.material3.Scaffold +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.tooling.preview.Preview +import androidx.navigation.NavHostController +import androidx.navigation.compose.NavHost +import androidx.navigation.compose.composable +import androidx.navigation.compose.rememberNavController +import ditto.live.dittopresenceviewer.DittoPresenceViewer +import live.ditto.Ditto +import live.ditto.dittodatabrowser.DittoDataBrowser +import live.ditto.dittodiskusage.DittoDiskUsage +import live.ditto.dittoexportlogs.ExportLogs +import live.ditto.dittotoolsviewer.R +import live.ditto.dittotoolsviewer.presentation.navigation.Screens +import live.ditto.dittotoolsviewer.presentation.viewmodel.ToolsViewerViewModel +import live.ditto.health.HealthScreen +import live.ditto.presencedegradationreporter.PresenceDegradationReporterScreen + +/** + * A Composable that you can include in your app that will give a single entry point for all Ditto + * Tools. + * + * @param modifier an optional modifier if you need to adjust the layout to fit the view + * @param ditto your instance of [Ditto] that is required + * @param onExitTools an optional lambda function that will be called whenever a user taps the + * "Exit Tools" button. Use this to do any back navigation or dismissal/hiding of the Tools Viewer + */ +@Composable +fun DittoToolsViewer( + modifier: Modifier = Modifier, + ditto: Ditto, + onExitTools: () -> Unit = { } +) { + DittoToolsViewerScaffold( + modifier = modifier, + ditto = ditto, + onExitTools = onExitTools + ) +} + +@Composable +private fun DittoToolsViewerScaffold( + modifier: Modifier, + ditto: Ditto, + onExitTools: () -> Unit, + viewModel: ToolsViewerViewModel = ToolsViewerViewModel() +) { + + val navController = rememberNavController() + + Scaffold( + modifier = modifier, + bottomBar = { + BottomAppBar( + actions = { + Button(onClick = { onExitTools() }) { + Text(text = "Exit Tools") + } + }, + floatingActionButton = { + MenuFloatingActionButton { + if (navController.currentDestination?.route != Screens.MainScreen.route) { + navController.popBackStack() + } + } + } + ) + } + ) { contentPadding -> + ToolsViewerContent( + navController = navController, + viewModel = viewModel, + contentPadding = contentPadding, + ditto = ditto + ) + } +} + +@Composable +private fun ToolsViewerContent( + navController: NavHostController, + viewModel: ToolsViewerViewModel, + contentPadding: PaddingValues, + ditto: Ditto, +) { + ToolsViewerNavHost( + navController = navController, + contentPadding = contentPadding, + ditto = ditto, + toolMenuItems = viewModel.toolsMenuItems() + ) +} + +@Composable +private fun ToolsViewerNavHost( + navController: NavHostController, + contentPadding: PaddingValues, + ditto: Ditto, + toolMenuItems: List +) { + NavHost( + modifier = Modifier.padding(contentPadding), + navController = navController, + startDestination = Screens.MainScreen.route, + ) { + composable(Screens.MainScreen.route) { + ToolsMenu( + navController = navController, + menuItems = toolMenuItems, + ) + } + composable(Screens.PresenceViewerScreen.route) { + DittoPresenceViewer(ditto = ditto) + } + composable(Screens.DataBrowserScreen.route) { + DittoDataBrowser(ditto = ditto) + } + composable(Screens.ExportLogsScreen.route) { + ExportLogs( + onDismiss = { + navController.popBackStack() + } + ) + } + composable(Screens.DiskUsageScreen.route) { + DittoDiskUsage(ditto = ditto) + } + composable(Screens.HealthScreen.route) { + HealthScreen() + } + composable(Screens.HeartbeatScreen.route) { + HeartbeatScreen(ditto = ditto) + } + composable(Screens.PresenceDegradationReporterScreen.route) { + PresenceDegradationReporterScreen(ditto = ditto) + } + } +} + +@Composable +private fun MenuFloatingActionButton(onClick: () -> Unit) { + ExtendedFloatingActionButton( + onClick = { onClick() }, + icon = { Icon(Icons.Filled.Build, stringResource(R.string.tools_menu_content_description)) }, + text = { Text(text = stringResource(R.string.tools_menu)) } + ) +} + +@Preview +@Composable +private fun MenuFloatingActionButtonPreview() { + MenuFloatingActionButton( + onClick = { } + ) +} \ No newline at end of file diff --git a/app/src/main/java/live/ditto/dittotoolsapp/HeartbeatView.kt b/DittoToolsViewer/src/main/java/live/ditto/dittotoolsviewer/presentation/HeartbeatScreen.kt similarity index 66% rename from app/src/main/java/live/ditto/dittotoolsapp/HeartbeatView.kt rename to DittoToolsViewer/src/main/java/live/ditto/dittotoolsviewer/presentation/HeartbeatScreen.kt index 37097c8..af614e0 100644 --- a/app/src/main/java/live/ditto/dittotoolsapp/HeartbeatView.kt +++ b/DittoToolsViewer/src/main/java/live/ditto/dittotoolsviewer/presentation/HeartbeatScreen.kt @@ -1,4 +1,4 @@ -package live.ditto.dittotoolsapp +package live.ditto.dittotoolsviewer.presentation import android.os.Build import androidx.annotation.RequiresApi @@ -9,6 +9,7 @@ import androidx.compose.runtime.* import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color +import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.dp import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers @@ -19,12 +20,13 @@ import live.ditto.healthmetrics.HealthMetricProvider import live.ditto.dittoheartbeat.DittoHeartbeatConfig import live.ditto.dittoheartbeat.DittoHeartbeatInfo import live.ditto.dittoheartbeat.startHeartbeat +import live.ditto.dittotoolsviewer.R import java.util.* @RequiresApi(Build.VERSION_CODES.O) @Composable -fun ShowHeartbeatData(ditto: Ditto) { +fun HeartbeatScreen(ditto: Ditto) { var heartbeatInfo by remember { mutableStateOf(null) } val healthMetricProviders: MutableList = mutableListOf() @@ -85,7 +87,8 @@ fun HeartbeatInfoCard(heartbeatInfo: DittoHeartbeatInfo) { val connection = entry.value if (connection is Map<*, *>) { // Check if connection is a Map @Suppress("UNCHECKED_CAST") - val typedConnection = connection as Map // Type cast connection to Map + val typedConnection = + connection as Map // Type cast connection to Map ConnectionInfo(connection = typedConnection) } } @@ -97,23 +100,34 @@ fun HeartbeatInfoCard(heartbeatInfo: DittoHeartbeatInfo) { @Composable fun HeartbeatHeader(heartbeatInfo: DittoHeartbeatInfo) { Column { - Text("ID: ${heartbeatInfo.id}") - Text("SDK: ${heartbeatInfo.sdk}") - Text("Last Updated: ${heartbeatInfo.lastUpdated}") - Text("remotePeersCount: ${heartbeatInfo.presenceSnapshotDirectlyConnectedPeersCount}", color = Color.Black) - Text("Peer key: ${heartbeatInfo.peerKey}") + Text(stringResource(R.string.heartbeat_id_label, heartbeatInfo.id)) + Text(stringResource(R.string.heartbeat_sdk_label, heartbeatInfo.sdk)) + Text(stringResource(R.string.heartbeat_last_updated_label, heartbeatInfo.lastUpdated)) + Text( + text = stringResource( + R.string.heartbeat_remotepeerscount_label, + heartbeatInfo.presenceSnapshotDirectlyConnectedPeersCount + ), + color = Color.Black + ) + Text(stringResource(R.string.heartbeat_peer_key_label, heartbeatInfo.peerKey)) } } @Composable fun ConnectionInfo(connection: Map) { Column { - Text("\nConnection: ${connection["deviceName"]}") - Text("SDK: ${connection["sdk"]}") - Text(text = if (connection["isConnectedToDittoCloud"] as Boolean) "Online" else "Offline") - Text("BT: ${connection["bluetooth"]}") - Text("P2PWifi: ${connection["p2pWifi"]}") - Text("LAN: ${connection["lan"]}") + Text(stringResource(R.string.connection_info_connection, connection["deviceName"] ?: "")) + Text(stringResource(R.string.connection_info_sdk, connection["sdk"] ?: "")) + val isConnectedToDittoCloudString = if (connection["isConnectedToDittoCloud"] as Boolean) { + stringResource(R.string.connection_info_online) + } else stringResource( + R.string.connection_info_offline + ) + Text(isConnectedToDittoCloudString) + Text(stringResource(R.string.connection_info_bt, connection["bluetooth"] ?: "")) + Text(stringResource(R.string.connection_info_p2pwifi, connection["p2pWifi"] ?: "")) + Text(stringResource(R.string.connection_info_lan, connection["lan"] ?: "")) } } diff --git a/DittoToolsViewer/src/main/java/live/ditto/dittotoolsviewer/presentation/ToolsMenu.kt b/DittoToolsViewer/src/main/java/live/ditto/dittotoolsviewer/presentation/ToolsMenu.kt new file mode 100644 index 0000000..7b1e964 --- /dev/null +++ b/DittoToolsViewer/src/main/java/live/ditto/dittotoolsviewer/presentation/ToolsMenu.kt @@ -0,0 +1,147 @@ +package live.ditto.dittotoolsviewer.presentation + +import androidx.annotation.StringRes +import androidx.compose.foundation.background +import androidx.compose.foundation.clickable +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.material3.Card +import androidx.compose.material3.CardDefaults +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.text.style.TextAlign +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.dp +import androidx.navigation.NavHostController +import androidx.navigation.compose.rememberNavController +import live.ditto.dittotoolsviewer.R +import live.ditto.dittotoolsviewer.presentation.navigation.Screens +import live.ditto.dittotoolsviewer.presentation.ui.theme.MenuCardContainerColor +import live.ditto.dittotoolsviewer.presentation.ui.theme.MenuItemSelectedBackgroundColor +import live.ditto.dittotoolsviewer.presentation.ui.theme.MenuItemTextColor +import live.ditto.dittotoolsviewer.presentation.ui.theme.ToolsMenuHeaderBackground +import live.ditto.dittotoolsviewer.presentation.ui.theme.ToolsMenuHeaderTextColor + +@Composable +fun ToolsMenu( + navController: NavHostController, + menuItems: List +) { + Card( + modifier = Modifier + .fillMaxWidth() + .padding(16.dp), + shape = RoundedCornerShape(16.dp), + colors = CardDefaults.cardColors( + containerColor = MenuCardContainerColor + ) + ) { + ToolsMenuHeader() + ToolsMenuItems( + menuItems = menuItems, + navController = navController + ) + } +} + +@Composable +private fun ToolsMenuItems( + navController: NavHostController, + menuItems: List +) { + Column( + modifier = Modifier.padding(8.dp), + verticalArrangement = Arrangement.spacedBy(16.dp) + ) { + menuItems.forEach { toolMenuItem -> + ToolMenuItem( + name = stringResource(id = toolMenuItem.label), + onClick = { + navController.navigate(toolMenuItem.route) { + popUpTo(route = Screens.MainScreen.route) + } + } + ) + } + } +} + +@Composable +private fun ToolMenuItem( + name: String, + containerColor: Color = MenuItemSelectedBackgroundColor, + onClick: () -> Unit +) { + Card( + modifier = Modifier + .fillMaxWidth() + .clickable { onClick() }, + colors = CardDefaults.cardColors( + containerColor = containerColor + ), + shape = RoundedCornerShape(24.dp), + ) { + Box(modifier = Modifier.padding(16.dp)) { + Text( + text = name, + style = MaterialTheme.typography.titleMedium, + color = MenuItemTextColor + ) + } + } +} + +@Composable +private fun ToolsMenuHeader() { + Box( + modifier = Modifier + .fillMaxWidth() + .background(color = ToolsMenuHeaderBackground) + .padding(8.dp) + ) { + Text( + modifier = Modifier.fillMaxWidth(), + text = stringResource(R.string.tools_menu_title), + style = MaterialTheme.typography.titleLarge, + textAlign = TextAlign.Center, + color = ToolsMenuHeaderTextColor + ) + } +} + +@Preview +@Composable +private fun ToolMenuItemPreview() { + ToolMenuItem( + name = "Tool Menu Item", + onClick = { } + ) +} + +@Preview +@Composable +private fun ToolsMenuHeaderPreview() { + ToolsMenuHeader() +} + +@Preview +@Composable +private fun ToolsMenuPreview() { + ToolsMenu( + navController = rememberNavController(), + menuItems = emptyList() + ) +} + +data class ToolMenuItem( + @StringRes val label: Int, + val route: String +) \ No newline at end of file diff --git a/DittoToolsViewer/src/main/java/live/ditto/dittotoolsviewer/presentation/navigation/Screens.kt b/DittoToolsViewer/src/main/java/live/ditto/dittotoolsviewer/presentation/navigation/Screens.kt new file mode 100644 index 0000000..fdb8df8 --- /dev/null +++ b/DittoToolsViewer/src/main/java/live/ditto/dittotoolsviewer/presentation/navigation/Screens.kt @@ -0,0 +1,32 @@ +package live.ditto.dittotoolsviewer.presentation.navigation + +interface Screen { + val route: String +} + +sealed class Screens { + object MainScreen: Screen { + override val route = "mainScreen" + } + object PresenceViewerScreen: Screen { + override val route = "presenceViewer" + } + object DataBrowserScreen: Screen { + override val route = "dataBrowser" + } + object ExportLogsScreen: Screen { + override val route = "exportLogs" + } + object DiskUsageScreen: Screen { + override val route = "diskUsage" + } + object HealthScreen: Screen { + override val route = "health" + } + object HeartbeatScreen: Screen { + override val route = "heartbeat" + } + object PresenceDegradationReporterScreen: Screen { + override val route = "presenceDegradationReporter" + } +} \ No newline at end of file diff --git a/DittoToolsViewer/src/main/java/live/ditto/dittotoolsviewer/presentation/ui/theme/Color.kt b/DittoToolsViewer/src/main/java/live/ditto/dittotoolsviewer/presentation/ui/theme/Color.kt new file mode 100644 index 0000000..f5c5e21 --- /dev/null +++ b/DittoToolsViewer/src/main/java/live/ditto/dittotoolsviewer/presentation/ui/theme/Color.kt @@ -0,0 +1,10 @@ +package live.ditto.dittotoolsviewer.presentation.ui.theme + +import androidx.compose.ui.graphics.Color + +val MenuCardContainerColor = Color(0xFFFFFFFF) +val MenuItemSelectedBackgroundColor = Color(0x330091EA) +val MenuItemExitSelectedBackgroundColor = Color(0x66FF0000) +val MenuItemTextColor = Color(0xFF000000) +val ToolsMenuHeaderBackground = Color(0xFF03A9F4) +val ToolsMenuHeaderTextColor = Color(0xFFFFFFFF) \ No newline at end of file diff --git a/DittoToolsViewer/src/main/java/live/ditto/dittotoolsviewer/presentation/viewmodel/ToolsViewerViewModel.kt b/DittoToolsViewer/src/main/java/live/ditto/dittotoolsviewer/presentation/viewmodel/ToolsViewerViewModel.kt new file mode 100644 index 0000000..9ce2293 --- /dev/null +++ b/DittoToolsViewer/src/main/java/live/ditto/dittotoolsviewer/presentation/viewmodel/ToolsViewerViewModel.kt @@ -0,0 +1,42 @@ +package live.ditto.dittotoolsviewer.presentation.viewmodel + +import androidx.lifecycle.ViewModel +import live.ditto.dittotoolsviewer.R +import live.ditto.dittotoolsviewer.presentation.ToolMenuItem +import live.ditto.dittotoolsviewer.presentation.navigation.Screens + +class ToolsViewerViewModel: ViewModel() { + + fun toolsMenuItems(): List { + return listOf( + ToolMenuItem( + label = R.string.presence_viewer_tool_label, + route = Screens.PresenceViewerScreen.route + ), + ToolMenuItem( + label = R.string.data_browser_tool_label, + route = Screens.DataBrowserScreen.route + ), + ToolMenuItem( + label = R.string.export_logs_tool_label, + route = Screens.ExportLogsScreen.route + ), + ToolMenuItem( + label = R.string.disk_usage_tool_label, + route = Screens.DiskUsageScreen.route + ), + ToolMenuItem( + label = R.string.health_viewer_tool_label, + route = Screens.HealthScreen.route + ), + ToolMenuItem( + label = R.string.heartbeat_tool_label, + route = Screens.HeartbeatScreen.route + ), + ToolMenuItem( + label = R.string.presence_degradation_reporter_tool_label, + route = Screens.PresenceDegradationReporterScreen.route + ), + ) + } +} \ No newline at end of file diff --git a/DittoToolsViewer/src/main/res/values/strings.xml b/DittoToolsViewer/src/main/res/values/strings.xml new file mode 100644 index 0000000..7a1a1e8 --- /dev/null +++ b/DittoToolsViewer/src/main/res/values/strings.xml @@ -0,0 +1,29 @@ + + + Tools Menu + Tools Menu Icon + Tools + Presence Viewer + Data Browser + Export Logs + Disk Usage + Health Viewer + Heartbeat + Presence Degradation Reporter + + ID: %1$s + SDK: %1$s + Last Updated: %1$s + remotePeersCount: %1$s + Peer key: %1$s + \nConnection: %1$s + SDK: %1$s + Online + Offline + BT: %1$s + P2PWifi: %1$s + LAN: %1$s + Close + Ditto Tools + Exit Tools + \ No newline at end of file diff --git a/Img/toolsViewer.png b/Img/toolsViewer.png new file mode 100644 index 0000000..e353ebc Binary files /dev/null and b/Img/toolsViewer.png differ diff --git a/README.md b/README.md index 43fc90e..5db727c 100644 --- a/README.md +++ b/README.md @@ -24,13 +24,14 @@ repositories { | Tool Name | Gradle artifact | |----------------------------------|------------------------------------------------------------| -| 1. Presence Viewer | `'live.ditto:dittopresenceviewer:LIBRARY_VERSION'` | -| 2. Data Browser | `'live.ditto:dittodatabrowser:LIBRARY_VERSION'` | -| 3. Export Logs | `'live.ditto:dittoexportlogs:LIBRARY_VERSION'` | -| 4. Disk Usage/Export Data | `'live.ditto:dittodiskusage:LIBRARY_VERSION'` | -| 5. Health | `'live.ditto:health:LIBRARY_VERSION'` | -| 6. Heartbeat | `'live.ditto:dittoheartbeat:LIBRARY_VERSION'` | -| 7. Presence Degradation Reporter | `'live.ditto:presencedegradationreporter:LIBRARY_VERSION'` | +| 1. Tools Viewer | `'live.ditto:ditto:dittotoolsviewer:LIBRARY_VERSION'` | +| 2. Presence Viewer | `'live.ditto:dittopresenceviewer:LIBRARY_VERSION'` | +| 3. Data Browser | `'live.ditto:dittodatabrowser:LIBRARY_VERSION'` | +| 4. Export Logs | `'live.ditto:dittoexportlogs:LIBRARY_VERSION'` | +| 5. Disk Usage/Export Data | `'live.ditto:dittodiskusage:LIBRARY_VERSION'` | +| 6. Health | `'live.ditto:health:LIBRARY_VERSION'` | +| 7. Heartbeat | `'live.ditto:dittoheartbeat:LIBRARY_VERSION'` | +| 8. Presence Degradation Reporter | `'live.ditto:presencedegradationreporter:LIBRARY_VERSION'` | You can find the list of versions and release notes in the [Releases tab](https://github.com/getditto/DittoAndroidTools/releases). @@ -52,7 +53,39 @@ ditto.onlinePlayground.appId="YOUR_APPID" ditto.onlinePlayground.token="YOUR_TOKEN" ``` -### 1. Presence Viewer +### 1. Tools Viewer +Tools viewer is the easiest way to integrate all the tools currently available. It provides a single entry point to interact with all other tools, and includes them as a dependency. + +It is available as a Composable element that requires a Ditto instance. Optional parameters include: + +- `modifier`: If you need to adjust the layout +- `onExitTools`: Lambda function that will be called when the "Exit Tools" button is tapped. Use this to do any back navigation or dismissal of the tools composable if you need to. + +Example code: + +```kotlin + +// minimum code required to get started +DittoToolsViewer( + ditto = YOUR_DITTO_INSTANCE +) +``` + + Tools Viewer Image + + +To integrate it in a Views-based app - see instructions here: https://developer.android.com/develop/ui/compose/migrate/interoperability-apis/compose-in-views + +**Download** + +Gradle: +```kotlin +dependencies { + implementation 'live.ditto:dittotoolsviewer:YOUR_LIBRARY_VERSION' +} +``` + +### 2. Presence Viewer The Presence Viewer displays a mesh graph that allows you to see all connected peers within the mesh and the transport that each peer is using to make a connection. Within a Composable, you pass ditto to the constructor: @@ -82,7 +115,7 @@ Maven: ``` -### 2. Data Browser +### 3. Data Browser The Ditto Data Browser allows you to view all your collections, documents within each collection and the propeties/values of a document. With the Data Browser, you can observe any changes that are made to your collections and documents in real time. @@ -118,7 +151,7 @@ Maven: ``` -### 3. Export Logs +### 4. Export Logs Export Logs allows you to export a file of the logs from your applcation. **Important** @@ -176,7 +209,7 @@ Maven: ``` -### 4. Disk Usage/ Export Data +### 5. Disk Usage/ Export Data Disk Usage allows you to see Ditto's file space usage. Export Data allows you to export the Ditto directory. @@ -205,7 +238,7 @@ Maven: ``` -### 5. Health +### 6. Health Health allows you to see the status of ditto's required services. @@ -235,7 +268,7 @@ Maven: ``` -### 6. Heartbeat +### 7. Heartbeat The Ditto Heartbeat tool allows you to monitor, locally or remotely, the peers in your mesh. @@ -357,7 +390,7 @@ Maven: ``` -### 7. Presence Degradation Reporter +### 8. Presence Degradation Reporter Tracks the status of your mesh, allowing to define the minimum of required peers that needs to be connected. Exposes an API to notify when the condition of minimum required peers is not met. diff --git a/app/build.gradle b/app/build.gradle index 242bb7f..257cbca 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -36,6 +36,7 @@ dependencies { implementation platform(libs.androidx.compose.composeBom) + implementation(project(":DittoToolsViewer")) implementation libs.live.ditto.databrowser implementation libs.live.ditto.exportlogs implementation libs.live.ditto.presenceviewer diff --git a/app/src/main/java/live/ditto/dittotoolsapp/MainActivity.kt b/app/src/main/java/live/ditto/dittotoolsapp/MainActivity.kt index 3ab6fbb..5b624fa 100644 --- a/app/src/main/java/live/ditto/dittotoolsapp/MainActivity.kt +++ b/app/src/main/java/live/ditto/dittotoolsapp/MainActivity.kt @@ -9,7 +9,7 @@ import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.padding import androidx.compose.material3.CircularProgressIndicator -import androidx.compose.material3.Divider +import androidx.compose.material3.HorizontalDivider import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Surface import androidx.compose.material3.Text @@ -24,10 +24,6 @@ import androidx.compose.ui.Modifier import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp -import androidx.navigation.compose.NavHost -import androidx.navigation.compose.composable -import androidx.navigation.compose.rememberNavController -import ditto.live.dittopresenceviewer.DittoPresenceViewer import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.withContext import live.ditto.Ditto @@ -35,11 +31,8 @@ import live.ditto.DittoIdentity import live.ditto.DittoLogLevel import live.ditto.DittoLogger import live.ditto.android.DefaultAndroidDittoDependencies -import live.ditto.dittodatabrowser.DittoDataBrowser -import live.ditto.dittodiskusage.DittoDiskUsage import live.ditto.dittotoolsapp.ui.theme.DittoToolsAppTheme -import live.ditto.health.HealthScreen -import live.ditto.presencedegradationreporter.PresenceDegradationReporterScreen +import live.ditto.dittotoolsviewer.presentation.DittoToolsViewer import live.ditto.transports.DittoSyncPermissions class MainActivity : ComponentActivity() { @@ -76,7 +69,10 @@ class MainActivity : ComponentActivity() { } ditto?.let { - Root(ditto = it) + DittoToolsViewer( + ditto = it, + onExitTools = { } + ) } } } @@ -115,28 +111,6 @@ class MainActivity : ComponentActivity() { } -@Composable -private fun Root(ditto: Ditto) { - val navController = rememberNavController() - - // A surface container using the 'background' color from the theme - Surface(color = MaterialTheme.colorScheme.background) { - NavHost(navController = navController, startDestination = "showViews") { - composable("showViews") { - ShowViewsScreen( - navController = navController, ditto = ditto - ) - } - composable("dataBrowser") { DittoDataBrowser(ditto = ditto) } - composable("diskUsage") { DittoDiskUsage(ditto = ditto) } - composable("presenceViewer") { DittoPresenceViewer(ditto = ditto) } - composable("health") { HealthScreen() } - composable("heartbeatInfo") { ShowHeartbeatData(ditto = ditto)} - composable("presencedegradationreporter") { PresenceDegradationReporterScreen(ditto = ditto) } - } - } -} - @Composable private fun DittoError(text: String) { DittoToolsAppTheme { @@ -149,7 +123,7 @@ private fun DittoError(text: String) { Text( text = "Ditto Error", fontWeight = FontWeight.Bold ) - Divider(modifier = Modifier.padding(vertical = 4.dp)) + HorizontalDivider(modifier = Modifier.padding(vertical = 4.dp)) Text(text = text) } } diff --git a/app/src/main/java/live/ditto/dittotoolsapp/ShowViewsScreen.kt b/app/src/main/java/live/ditto/dittotoolsapp/ShowViewsScreen.kt deleted file mode 100644 index 19ee624..0000000 --- a/app/src/main/java/live/ditto/dittotoolsapp/ShowViewsScreen.kt +++ /dev/null @@ -1,104 +0,0 @@ -package live.ditto.dittotoolsapp - -import androidx.compose.foundation.layout.Box -import androidx.compose.foundation.layout.Column -import androidx.compose.foundation.layout.Spacer -import androidx.compose.foundation.layout.fillMaxSize -import androidx.compose.foundation.layout.padding -import androidx.compose.material3.Button -import androidx.compose.material3.Snackbar -import androidx.compose.material3.Text -import androidx.compose.runtime.Composable -import androidx.compose.runtime.LaunchedEffect -import androidx.compose.runtime.getValue -import androidx.compose.runtime.mutableStateOf -import androidx.compose.runtime.remember -import androidx.compose.runtime.setValue -import androidx.compose.ui.Alignment -import androidx.compose.ui.Modifier -import androidx.compose.ui.unit.dp -import androidx.navigation.NavHostController -import live.ditto.Ditto -import live.ditto.dittoexportlogs.ExportLogs -import live.ditto.presencedegradationreporter.presenceDegradationReporterFlow - -@Composable -fun ShowViewsScreen(navController: NavHostController, ditto: Ditto) { - var message: String? by remember { mutableStateOf(null) } - - LaunchedEffect(key1 = ditto) { - ditto.presenceDegradationReporterFlow().collect { state -> - if (!state.settings.reportApiEnabled) return@collect - if (!state.settings.hasSeenExpectedPeers) return@collect - - val expectedPeers = state.settings.expectedPeers - val connectedPeers = state.remotePeers.count { it.connected } - message = "Reporting: ExpectedPeers=$expectedPeers, ConnectedPeers=$connectedPeers" - } - } - - Box(modifier = Modifier.fillMaxSize()) { - var showExportDialog by remember { mutableStateOf(false) } - - Column( - modifier = Modifier - .align(Alignment.TopStart) - .padding(16.dp) - ) { - Button( - onClick = { navController.navigate("dataBrowser") }, - modifier = Modifier.padding(bottom = 8.dp) - ) { - Text("Data Browser") - } - Button( - onClick = { navController.navigate("diskUsage") }, - modifier = Modifier.padding(bottom = 8.dp) - ) { - Text("Disk Usage") - } - Button( - onClick = { showExportDialog = !showExportDialog }, - modifier = Modifier.padding(bottom = 8.dp) - ) { - Text("Export Logs") - } - Button( - onClick = { navController.navigate("presenceViewer") }, - modifier = Modifier.padding(bottom = 8.dp) - ) { - Text("Presence Viewer") - } - - Button( - onClick = { navController.navigate("health") }, - modifier = Modifier.padding(bottom = 8.dp) - ) { - Text("Health Viewer") - } - Button( - onClick = { navController.navigate("heartbeatInfo") }, - modifier = Modifier.padding(bottom = 8.dp) - ) { - Text("Heartbeat Info") - } - - Button( - onClick = { navController.navigate("presencedegradationreporter") }, - modifier = Modifier.padding(bottom = 8.dp) - ) { - Text("Presence Degradation Reporter") - } - - if (showExportDialog) { - ExportLogs(onDismiss = { showExportDialog = false }) - } - - Spacer(modifier = Modifier.weight(1f)) - - message?.let { - Snackbar { Text(text = it) } - } - } - } -} \ No newline at end of file diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 0921bbc..236d03b 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -1,3 +1,3 @@ - DittoToolsApp + Ditto Tools App \ No newline at end of file diff --git a/gradle/android-common.gradle b/gradle/android-common.gradle index b45c709..6c1e330 100644 --- a/gradle/android-common.gradle +++ b/gradle/android-common.gradle @@ -1,5 +1,5 @@ android { - compileSdk 33 + compileSdk 34 buildFeatures { compose true diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 1eac118..10fb9e2 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -10,7 +10,7 @@ androidx-test-ext = "1.1.5" androidx-webkit = "1.7.0" datastorePreferences = "1.0.0" ditto = "4.7.4" -live-ditto-tools = "1.1.0" +live-ditto-tools = "2.0.0" junit = "4.13.2" kotlin-gradle-plugin = "1.8.10" diff --git a/settings.gradle b/settings.gradle index 8de6bf7..ae06ad5 100644 --- a/settings.gradle +++ b/settings.gradle @@ -23,3 +23,4 @@ include ':DittoHeartbeat' include ':DittoHealth' include ':DittoPresenceDegradationReporter' include ':DittoHealthMetrics' +include ':DittoToolsViewer'