Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(export): add additional info option when export as opml file #567

Merged
merged 3 commits into from
Jan 26, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
24 changes: 15 additions & 9 deletions app/src/main/java/me/ash/reader/domain/service/OpmlService.kt
Expand Up @@ -60,7 +60,7 @@ class OpmlService @Inject constructor(
* Exports OPML file.
*/
@Throws(Exception::class)
suspend fun saveToString(accountId: Int): String {
suspend fun saveToString(accountId: Int, attachInfo: Boolean): String {
val defaultGroup = groupDao.queryById(getDefaultGroupId(accountId))!!
return OpmlWriter().write(
Opml(
Expand All @@ -73,21 +73,27 @@ class OpmlService @Inject constructor(
),
Body(groupDao.queryAllGroupWithFeed(accountId).map {
Outline(
mapOf(
mutableMapOf(
"text" to it.group.name,
"title" to it.group.name,
"isDefault" to (it.group.id == defaultGroup.id).toString()
),
).apply {
if (attachInfo) {
put("isDefault", (it.group.id == defaultGroup.id).toString())
}
},
it.feeds.map { feed ->
Outline(
mapOf(
mutableMapOf(
"text" to feed.name,
"title" to feed.name,
"xmlUrl" to feed.url,
"htmlUrl" to feed.url,
"isNotification" to feed.isNotification.toString(),
"isFullContent" to feed.isFullContent.toString(),
),
"htmlUrl" to feed.url
).apply {
if (attachInfo) {
put("isNotification", feed.isNotification.toString())
put("isFullContent", feed.isFullContent.toString())
}
},
listOf()
)
}
Expand Down
Expand Up @@ -20,11 +20,10 @@ class CrashHandler(private val context: Context) : UncaughtExceptionHandler {
*/
override fun uncaughtException(p0: Thread, p1: Throwable) {
val causeMessage = getCauseMessage(p1)
Log.e("RLog", "uncaughtException: $causeMessage")
Log.e("RLog", "uncaughtException: $causeMessage", p1)
Looper.myLooper() ?: Looper.prepare()
context.showToastLong(causeMessage)
Looper.loop()
p1.printStackTrace()
// android.os.Process.killProcess(android.os.Process.myPid());
// exitProcess(1)
}
Expand Down
Expand Up @@ -20,6 +20,7 @@ import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Shape
import androidx.compose.ui.platform.LocalFocusManager
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import me.ash.reader.R
import me.ash.reader.ui.theme.palette.alwaysLight
Expand Down Expand Up @@ -79,4 +80,14 @@ fun RYSelectionChip(
)
},
)
}
}

@Preview
@Composable
private fun RYSelectionChipPreview() {
RYSelectionChip(
content = "Test",
selected = true,
onClick = {},
)
}
11 changes: 11 additions & 0 deletions app/src/main/java/me/ash/reader/ui/component/base/RadioDialog.kt
Expand Up @@ -2,7 +2,9 @@ package me.ash.reader.ui.component.base

import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.items
Expand All @@ -27,6 +29,7 @@ fun RadioDialog(
modifier: Modifier = Modifier,
visible: Boolean = false,
title: String = "",
description: String? = null,
options: List<RadioDialogOption> = emptyList(),
onDismissRequest: () -> Unit = {},
) {
Expand All @@ -44,6 +47,14 @@ fun RadioDialog(
},
text = {
LazyColumn {
if (description != null) {
item {
Text(text = description)
if (options.isNotEmpty()) {
Spacer(modifier = Modifier.height(16.dp))
}
}
}
items(options) { option ->
Row(
modifier = Modifier
Expand Down
14 changes: 13 additions & 1 deletion app/src/main/java/me/ash/reader/ui/ext/DateExt.kt
@@ -1,12 +1,24 @@
package me.ash.reader.ui.ext

import android.annotation.SuppressLint
import android.content.Context
import androidx.core.os.ConfigurationCompat
import me.ash.reader.R
import java.text.DateFormat
import java.text.ParsePosition
import java.text.SimpleDateFormat
import java.util.*
import java.util.Calendar
import java.util.Date

@SuppressLint("SimpleDateFormat")
object DateFormat {
val YYYY_MM_DD_HH_MM_SS = SimpleDateFormat("yyyy-MM-dd HH:mm:ss")
val YYYY_MM_DD_DASH_HH_MM_SS = SimpleDateFormat("yyyy-MM-dd-HH:mm:ss")
}

fun Date.toString(format: SimpleDateFormat): String {
return format.format(this)
}

fun Date.formatAsString(
context: Context,
Expand Down
@@ -1,9 +1,17 @@
package me.ash.reader.ui.page.settings.accounts

import android.content.Context
import android.net.Uri
import androidx.activity.compose.ManagedActivityResultLauncher
import androidx.activity.compose.rememberLauncherForActivityResult
import androidx.activity.result.contract.ActivityResultContracts
import androidx.compose.animation.ExperimentalAnimationApi
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.WindowInsets
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.navigationBars
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.windowInsetsBottomHeight
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.outlined.DeleteSweep
Expand All @@ -13,7 +21,12 @@ import androidx.compose.material3.Icon
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text
import androidx.compose.material3.TextButton
import androidx.compose.runtime.*
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.Modifier
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.stringResource
Expand All @@ -26,13 +39,26 @@ import me.ash.reader.infrastructure.preference.KeepArchivedPreference
import me.ash.reader.infrastructure.preference.SyncBlockListPreference
import me.ash.reader.infrastructure.preference.SyncIntervalPreference
import me.ash.reader.infrastructure.preference.not
import me.ash.reader.ui.component.base.*
import me.ash.reader.ui.component.base.DisplayText
import me.ash.reader.ui.component.base.FeedbackIconButton
import me.ash.reader.ui.component.base.RYDialog
import me.ash.reader.ui.component.base.RYScaffold
import me.ash.reader.ui.component.base.RYSwitch
import me.ash.reader.ui.component.base.RadioDialog
import me.ash.reader.ui.component.base.RadioDialogOption
import me.ash.reader.ui.component.base.Subtitle
import me.ash.reader.ui.component.base.TextFieldDialog
import me.ash.reader.ui.component.base.Tips
import me.ash.reader.ui.ext.DateFormat
import me.ash.reader.ui.ext.collectAsStateValue
import me.ash.reader.ui.ext.getCurrentVersion
import me.ash.reader.ui.ext.showToast
import me.ash.reader.ui.ext.showToastLong
import me.ash.reader.ui.ext.toString
import me.ash.reader.ui.page.settings.SettingItem
import me.ash.reader.ui.page.settings.accounts.connection.AccountConnection
import me.ash.reader.ui.theme.palette.onLight
import java.util.Date

@OptIn(ExperimentalAnimationApi::class)
@Composable
Expand All @@ -54,6 +80,7 @@ fun AccountDetailsPage(
var blockListDialogVisible by remember { mutableStateOf(false) }
var syncIntervalDialogVisible by remember { mutableStateOf(false) }
var keepArchivedDialogVisible by remember { mutableStateOf(false) }
var exportOPMLModeDialogVisible by remember { mutableStateOf(false) }

LaunchedEffect(Unit) {
navController.currentBackStackEntryFlow.collect {
Expand All @@ -64,7 +91,7 @@ fun AccountDetailsPage(
}

val launcher = rememberLauncherForActivityResult(
ActivityResultContracts.CreateDocument()
ActivityResultContracts.CreateDocument("*/*")
) { result ->
viewModel.exportAsOPML(selectedAccount!!.id!!) { string ->
result?.let { uri ->
Expand Down Expand Up @@ -184,7 +211,7 @@ fun AccountDetailsPage(
SettingItem(
title = stringResource(R.string.export_as_opml),
onClick = {
launcher.launch("ReadYou.opml")
exportOPMLModeDialogVisible = true
},
) {}
SettingItem(
Expand Down Expand Up @@ -374,4 +401,38 @@ fun AccountDetailsPage(
}
},
)

RadioDialog(
visible = exportOPMLModeDialogVisible,
title = stringResource(R.string.export_as_opml),
description = stringResource(R.string.additional_info_desc),
options = listOf(
RadioDialogOption(
text = stringResource(R.string.include_additional_info),
selected = uiState.exportOPMLMode == ExportOPMLMode.ATTACH_INFO,
) {
viewModel.changeExportOPMLMode(ExportOPMLMode.ATTACH_INFO)
launcherOPMLFile(context, launcher)
},
RadioDialogOption(
text = stringResource(R.string.exclude),
selected = uiState.exportOPMLMode == ExportOPMLMode.NO_ATTACH,
) {
viewModel.changeExportOPMLMode(ExportOPMLMode.NO_ATTACH)
launcherOPMLFile(context, launcher)
}
)
) {
exportOPMLModeDialogVisible = false
}
}

private fun launcherOPMLFile(
context: Context,
launcher: ManagedActivityResultLauncher<String, Uri?>,
) {
launcher.launch("" +
"${context.getString(R.string.read_you)}-" +
"${context.getCurrentVersion()}-export-" +
"${Date().toString(DateFormat.YYYY_MM_DD_DASH_HH_MM_SS)}.opml")
}
Expand Up @@ -5,7 +5,12 @@ import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import dagger.hilt.android.lifecycle.HiltViewModel
import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.flow.*
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.flow.emptyFlow
import kotlinx.coroutines.flow.update
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import me.ash.reader.domain.model.account.Account
Expand Down Expand Up @@ -50,7 +55,8 @@ class AccountViewModel @Inject constructor(
fun exportAsOPML(accountId: Int, callback: (String) -> Unit = {}) {
viewModelScope.launch(defaultDispatcher) {
try {
callback(opmlService.saveToString(accountId))
callback(opmlService.saveToString(accountId,
_accountUiState.value.exportOPMLMode == ExportOPMLMode.ATTACH_INFO))
} catch (e: Exception) {
Log.e("FeedsViewModel", "exportAsOpml: ", e)
}
Expand Down Expand Up @@ -119,10 +125,26 @@ class AccountViewModel @Inject constructor(
}
}
}

fun changeExportOPMLMode(mode: ExportOPMLMode) {
viewModelScope.launch {
_accountUiState.update {
it.copy(
exportOPMLMode = mode
)
}
}
}
}

data class AccountUiState(
val selectedAccount: Flow<Account?> = emptyFlow(),
val deleteDialogVisible: Boolean = false,
val clearDialogVisible: Boolean = false,
val exportOPMLMode: ExportOPMLMode = ExportOPMLMode.ATTACH_INFO,
)

sealed class ExportOPMLMode {
object ATTACH_INFO : ExportOPMLMode()
object NO_ATTACH : ExportOPMLMode()
}
5 changes: 4 additions & 1 deletion app/src/main/res/values-zh-rCN/strings.xml
Expand Up @@ -261,4 +261,7 @@
<string name="default_browser">强制使用默认浏览器</string>
<string name="open_link_something_wrong">“打开链接”设置被忽略,因为出现了错误。</string>
<string name="open_link_specific_browser_not_selected">未选择</string>
</resources>
<string name="include_additional_info">包含附加信息</string>
<string name="exclude">不包含</string>
<string name="additional_info_desc">附加信息中包含了每个订阅源的配置选项,例如是否允许通知、是否全文解析等。当您期望将导出的 OPML 文件用于其他阅读器时,请选择“不包含”。</string>
</resources>
5 changes: 4 additions & 1 deletion app/src/main/res/values/strings.xml
Expand Up @@ -403,4 +403,7 @@
<string name="open_link_specific_browser">Browser</string>
<string name="open_link_something_wrong">\"Open Link\" setting ignored because something went wrong.</string>
<string name="open_link_ask_dialog_title">Open with…</string>
</resources>
<string name="include_additional_info">Include additional info</string>
<string name="exclude">Exclude</string>
<string name="additional_info_desc">Additional information includes configuration options for each feed, such as whether to allow notification, parse full content, etc. When you intend to use the exported OPML file with other readers, please select \"Exclude\".</string>
</resources>