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

@ -7,6 +7,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
## [Unreleased] ## [Unreleased]
### Added
* Backup and restore options for saved posts
## [1.28.0] - 2023-06-03 ## [1.28.0] - 2023-06-03
### Changed ### Changed

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.NewReleases
import androidx.compose.material.icons.filled.Whatshot import androidx.compose.material.icons.filled.Whatshot
import androidx.compose.material.icons.outlined.FavoriteBorder 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.NavigateBefore
import androidx.compose.material.icons.outlined.NewReleases import androidx.compose.material.icons.outlined.NewReleases
import androidx.compose.material.icons.outlined.Whatshot import androidx.compose.material.icons.outlined.Whatshot
@ -49,6 +50,7 @@ import androidx.navigation.navDeepLink
import androidx.paging.compose.collectAsLazyPagingItems import androidx.paging.compose.collectAsLazyPagingItems
import com.deliveryhero.whetstone.compose.injectedViewModel import com.deliveryhero.whetstone.compose.injectedViewModel
import dev.msfjarvis.claw.android.R 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.ClawNavigationBar
import dev.msfjarvis.claw.android.ui.decorations.ClawNavigationRail import dev.msfjarvis.claw.android.ui.decorations.ClawNavigationRail
import dev.msfjarvis.claw.android.ui.decorations.NavigationItem import dev.msfjarvis.claw.android.ui.decorations.NavigationItem
@ -149,6 +151,14 @@ fun LobstersApp(
Text(text = stringResource(R.string.app_name), fontWeight = FontWeight.Bold) 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 = { bottomBar = {
@ -237,6 +247,12 @@ fun LobstersApp(
getProfile = viewModel::getUserProfile, 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 * Use of this source code is governed by an MIT-style
* license that can be found in the LICENSE file or at * license that can be found in the LICENSE file or at
* https://opensource.org/licenses/MIT. * https://opensource.org/licenses/MIT.
@ -31,6 +31,10 @@ sealed class Destinations {
override val route = "user/$placeholder" override val route = "user/$placeholder"
} }
object DataTransfer : Destinations() {
override val route: String = "datatransfer"
}
companion object { companion object {
val startDestination val startDestination
get() = Hottest get() = Hottest

View file

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