feat(android): wire up import/export settings to UI

This commit is contained in:
Harsh Shandilya 2023-06-04 18:17:54 +05:30
parent fb0e372917
commit 3738d94789
No known key found for this signature in database
5 changed files with 142 additions and 1 deletions

View file

@ -18,6 +18,7 @@ import androidx.compose.material.icons.filled.Favorite
import androidx.compose.material.icons.filled.NewReleases
import androidx.compose.material.icons.filled.Whatshot
import androidx.compose.material.icons.outlined.FavoriteBorder
import androidx.compose.material.icons.outlined.ImportExport
import androidx.compose.material.icons.outlined.NavigateBefore
import androidx.compose.material.icons.outlined.NewReleases
import androidx.compose.material.icons.outlined.Whatshot
@ -49,6 +50,7 @@ import androidx.navigation.navDeepLink
import androidx.paging.compose.collectAsLazyPagingItems
import com.deliveryhero.whetstone.compose.injectedViewModel
import dev.msfjarvis.claw.android.R
import dev.msfjarvis.claw.android.ui.datatransfer.DataTransferScreen
import dev.msfjarvis.claw.android.ui.decorations.ClawNavigationBar
import dev.msfjarvis.claw.android.ui.decorations.ClawNavigationRail
import dev.msfjarvis.claw.android.ui.decorations.NavigationItem
@ -149,6 +151,14 @@ fun LobstersApp(
Text(text = stringResource(R.string.app_name), fontWeight = FontWeight.Bold)
}
},
actions = {
IconButton(onClick = { navController.navigate(Destinations.DataTransfer.route) }) {
Icon(
imageVector = Icons.Outlined.ImportExport,
contentDescription = "Data transfer options"
)
}
},
)
},
bottomBar = {
@ -237,6 +247,12 @@ fun LobstersApp(
getProfile = viewModel::getUserProfile,
)
}
composable(route = Destinations.DataTransfer.route) {
DataTransferScreen(
context = context,
dataTransferRepository = viewModel.dataTransferRepository,
)
}
}
}
}

View file

@ -0,0 +1,116 @@
/*
* Copyright © 2023 Harsh Shandilya.
* Use of this source code is governed by an MIT-style
* license that can be found in the LICENSE file or at
* https://opensource.org/licenses/MIT.
*/
package dev.msfjarvis.claw.android.ui.datatransfer
import android.content.Context
import androidx.activity.compose.rememberLauncherForActivityResult
import androidx.activity.result.contract.ActivityResultContracts.CreateDocument
import androidx.activity.result.contract.ActivityResultContracts.GetContent
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.height
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.outlined.NorthEast
import androidx.compose.material.icons.outlined.SouthWest
import androidx.compose.material3.Icon
import androidx.compose.material3.ListItem
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.vector.ImageVector
import androidx.compose.ui.unit.dp
import dev.msfjarvis.claw.android.viewmodel.DataTransferRepository
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.launch
private const val MIME_TYPE = "application/json"
@Composable
fun DataTransferScreen(
context: Context,
dataTransferRepository: DataTransferRepository,
modifier: Modifier = Modifier,
) {
val coroutineScope = rememberCoroutineScope()
Column(modifier = modifier) {
ImportOption(context, coroutineScope, dataTransferRepository)
ExportOption(context, coroutineScope, dataTransferRepository)
}
}
@Composable
private fun ImportOption(
context: Context,
coroutineScope: CoroutineScope,
dataTransferRepository: DataTransferRepository,
) {
val importAction =
rememberLauncherForActivityResult(GetContent()) { uri ->
if (uri == null) return@rememberLauncherForActivityResult
coroutineScope.launch {
context.contentResolver.openInputStream(uri)?.use { stream ->
dataTransferRepository.importPosts(stream)
}
}
}
SettingsActionItem(
title = "Import saved posts",
description = "Import saved posts from a previously generated export",
icon = Icons.Outlined.SouthWest,
) {
importAction.launch(MIME_TYPE)
}
}
@Composable
private fun ExportOption(
context: Context,
coroutineScope: CoroutineScope,
dataTransferRepository: DataTransferRepository,
) {
val exportAction =
rememberLauncherForActivityResult(CreateDocument(MIME_TYPE)) { uri ->
if (uri == null) return@rememberLauncherForActivityResult
coroutineScope.launch {
context.contentResolver.openOutputStream(uri)?.use { stream ->
dataTransferRepository.exportPosts(stream)
}
}
}
SettingsActionItem(
title = "Export posts to file",
description = "Write all saved posts into a JSON file that can be imported at a later date",
icon = Icons.Outlined.NorthEast,
) {
exportAction.launch("claw-export.json")
}
}
@Composable
private fun SettingsActionItem(
title: String,
modifier: Modifier = Modifier,
description: String? = null,
icon: ImageVector? = null,
onClick: (() -> Unit)? = null,
) {
ListItem(
headlineContent = { Text(title) },
supportingContent = { description?.let { Text(it) } },
leadingContent = {
icon?.let {
Icon(
imageVector = icon,
contentDescription = null,
modifier = Modifier.height(32.dp),
)
}
},
modifier = modifier.clickable { onClick?.invoke() },
)
}

View file

@ -1,5 +1,5 @@
/*
* Copyright © 2021-2022 Harsh Shandilya.
* Copyright © 2021-2023 Harsh Shandilya.
* Use of this source code is governed by an MIT-style
* license that can be found in the LICENSE file or at
* https://opensource.org/licenses/MIT.
@ -31,6 +31,10 @@ sealed class Destinations {
override val route = "user/$placeholder"
}
object DataTransfer : Destinations() {
override val route: String = "datatransfer"
}
companion object {
val startDestination
get() = Hottest

View file

@ -41,6 +41,7 @@ constructor(
private val savedPostsRepository: SavedPostsRepository,
private val commentsRepository: CommentsRepository,
private val linkMetadataRepository: LinkMetadataRepository,
val dataTransferRepository: DataTransferRepository,
private val pagingSourceFactory: LobstersPagingSource.Factory,
@IODispatcher private val ioDispatcher: CoroutineDispatcher,
) : ViewModel() {