Improve in-app update release notes formatting
This commit is contained in:
parent
a503091ab2
commit
5f5d4cde98
|
@ -2,6 +2,9 @@ package publish
|
|||
|
||||
import com.google.gson.GsonBuilder
|
||||
|
||||
private const val RELEASE_NOTES_MAX_LENGTH = 500
|
||||
private const val NEW_LINE = "\n"
|
||||
|
||||
data class ChangelogEntry(
|
||||
val version: String,
|
||||
val date: String,
|
||||
|
@ -27,9 +30,17 @@ data class ChangelogEntry(
|
|||
}
|
||||
|
||||
private fun StringBuilder.appendChangeLogSection(section: ChangelogEntrySection) {
|
||||
appendLine(section.title)
|
||||
appendLine(section.content)
|
||||
appendLine()
|
||||
appendIfCan(section.title)
|
||||
appendIfCan(section.content)
|
||||
appendIfCan(NEW_LINE)
|
||||
}
|
||||
|
||||
private fun StringBuilder.appendIfCan(content: String) {
|
||||
if (length + content.length <= RELEASE_NOTES_MAX_LENGTH) {
|
||||
append(content)
|
||||
} else {
|
||||
println("WARN: Some in-app update release notes have been skipped: $content")
|
||||
}
|
||||
}
|
||||
|
||||
fun toJsonString(): String =
|
||||
|
|
|
@ -10,7 +10,7 @@ import java.util.Locale
|
|||
|
||||
object ChangelogParser {
|
||||
// Enable this when you need detailed parser logging. This should be turned off for production builds.
|
||||
private const val DEBUG_LOGS_ENABLED = false
|
||||
private const val DEBUG_LOGS_ENABLED = true
|
||||
|
||||
private const val CHANGELOG_TITLE_POSITION = 0
|
||||
private const val UNRELEASED_TITLE_POSITION = 4
|
||||
|
@ -23,7 +23,8 @@ object ChangelogParser {
|
|||
|
||||
fun getChangelogEntry(
|
||||
filePath: String,
|
||||
versionNameFallback: String
|
||||
versionNameFallback: String,
|
||||
keywords: LocalizedKeywords,
|
||||
): ChangelogEntry {
|
||||
log("Parser: starting...")
|
||||
|
||||
|
@ -40,13 +41,13 @@ object ChangelogParser {
|
|||
|
||||
// Validate content
|
||||
check(
|
||||
nodes[CHANGELOG_TITLE_POSITION].contains("# Changelog") &&
|
||||
nodes[UNRELEASED_TITLE_POSITION].contains("## [Unreleased]")
|
||||
nodes[CHANGELOG_TITLE_POSITION].contains("# ${keywords.changelog}") &&
|
||||
nodes[UNRELEASED_TITLE_POSITION].contains("## [${keywords.unreleased}]")
|
||||
) {
|
||||
"Provided changelog file is incorrect or its structure is malformed."
|
||||
}
|
||||
|
||||
val fromIndex = findFirstValidNodeIndex(nodes)
|
||||
val fromIndex = findFirstValidNodeIndex(nodes, keywords)
|
||||
log("Parser: index from: $fromIndex")
|
||||
|
||||
val toIndex =
|
||||
|
@ -65,12 +66,12 @@ object ChangelogParser {
|
|||
val lastChangelogEntry =
|
||||
nodes.subList(fromIndex = fromIndex, toIndex = toIndex).let { parts ->
|
||||
ChangelogEntry(
|
||||
version = parts.getVersionPart(versionNameFallback),
|
||||
date = parts.getDatePart(),
|
||||
added = parts.getNodePart("Added"),
|
||||
changed = parts.getNodePart("Changed"),
|
||||
fixed = parts.getNodePart("Fixed"),
|
||||
removed = parts.getNodePart("Removed"),
|
||||
version = parts.getVersionPart(versionNameFallback, keywords),
|
||||
date = parts.getDatePart(keywords),
|
||||
added = parts.getNodePart(keywords.added),
|
||||
changed = parts.getNodePart(keywords.changed),
|
||||
fixed = parts.getNodePart(keywords.fixed),
|
||||
removed = parts.getNodePart(keywords.removed),
|
||||
)
|
||||
}
|
||||
|
||||
|
@ -78,9 +79,9 @@ object ChangelogParser {
|
|||
return lastChangelogEntry
|
||||
}
|
||||
|
||||
private fun findFirstValidNodeIndex(nodes: List<String>): Int {
|
||||
private fun findFirstValidNodeIndex(nodes: List<String>, keywords: LocalizedKeywords): Int {
|
||||
nodes.forEachIndexed { index, node ->
|
||||
if (findNodeByPrefix(node) && findValidSubNodeByPrefix(nodes[index + 1])) {
|
||||
if (findNodeByPrefix(node) && findValidSubNodeByPrefix(nodes[index + 1], keywords)) {
|
||||
return index
|
||||
}
|
||||
}
|
||||
|
@ -90,25 +91,25 @@ object ChangelogParser {
|
|||
|
||||
private fun findNodeByPrefix(node: String): Boolean = node.startsWith("## [")
|
||||
|
||||
private fun findValidSubNodeByPrefix(subNode: String): Boolean =
|
||||
subNode.startsWith("### Added") ||
|
||||
subNode.startsWith("### Changed") ||
|
||||
subNode.startsWith("### Fixed") ||
|
||||
subNode.startsWith("### Removed")
|
||||
private fun findValidSubNodeByPrefix(subNode: String, keywords: LocalizedKeywords): Boolean =
|
||||
subNode.startsWith("### ${keywords.added}") ||
|
||||
subNode.startsWith("### ${keywords.changed}") ||
|
||||
subNode.startsWith("### ${keywords.fixed}") ||
|
||||
subNode.startsWith("### ${keywords.removed}")
|
||||
|
||||
private fun List<String>.getVersionPart(versionNameFallback: String): String {
|
||||
return if (this.contains("## [Unreleased]")) {
|
||||
private fun List<String>.getVersionPart(versionNameFallback: String, keywords: LocalizedKeywords): String {
|
||||
return if (this.contains("## [${keywords.unreleased}]")) {
|
||||
versionNameFallback
|
||||
} else {
|
||||
this[0].split("[")[1].split("]")[0].trim()
|
||||
}
|
||||
}
|
||||
|
||||
private val dateFormatter = SimpleDateFormat("yyyy-MM-dd", Locale.ENGLISH)
|
||||
private val fallbackDateFormatter = SimpleDateFormat("yyyy-MM-dd", Locale.ENGLISH)
|
||||
|
||||
private fun List<String>.getDatePart(): String {
|
||||
return if (this.contains("## [Unreleased]")) {
|
||||
dateFormatter.format(Date())
|
||||
private fun List<String>.getDatePart(keywords: LocalizedKeywords): String {
|
||||
return if (this.contains("## [${keywords.unreleased}]")) {
|
||||
fallbackDateFormatter.format(Date())
|
||||
} else {
|
||||
this[0].split("- ")[1].trim()
|
||||
}
|
||||
|
@ -134,9 +135,19 @@ object ChangelogParser {
|
|||
}
|
||||
}
|
||||
return subList(startIndex, endIndex)
|
||||
.joinToString(prefix = "\n", separator = "\n")
|
||||
.map { it.replace("\n- ", "\n• ") }
|
||||
.joinToString(separator = "\n")
|
||||
.takeIf { it.isNotBlank() }?.let {
|
||||
ChangelogEntrySection(title = title, content = it)
|
||||
}
|
||||
}
|
||||
|
||||
data class LocalizedKeywords(
|
||||
val changelog: String,
|
||||
val unreleased: String,
|
||||
val added: String,
|
||||
val changed: String,
|
||||
val fixed: String,
|
||||
val removed: String,
|
||||
)
|
||||
}
|
||||
|
|
|
@ -246,7 +246,15 @@ abstract class PublishToGooglePlay @Inject constructor(
|
|||
language = Locale.ENGLISH.toLanguageTag()
|
||||
text = ChangelogParser.getChangelogEntry(
|
||||
filePath = "docs/whatsNew/WHATS_NEW_EN.md",
|
||||
versionNameFallback = gradleVersionName
|
||||
versionNameFallback = gradleVersionName,
|
||||
keywords = ChangelogParser.LocalizedKeywords(
|
||||
changelog = "Changelog",
|
||||
unreleased = "Unreleased",
|
||||
added = "Added",
|
||||
changed = "Changed",
|
||||
fixed = "Fixed",
|
||||
removed = "Removed",
|
||||
)
|
||||
).toInAppUpdateReleaseNotesText()
|
||||
}
|
||||
val releaseNotes: MutableList<LocalizedText> = arrayListOf(localizedText)
|
||||
|
|
|
@ -26,7 +26,15 @@ val generateBuildConfigTask = tasks.create("buildConfig") {
|
|||
|
||||
val releaseNotesJson = ChangelogParser.getChangelogEntry(
|
||||
filePath = "docs/whatsNew/WHATS_NEW_EN.md",
|
||||
versionNameFallback = gradleVersionName
|
||||
versionNameFallback = gradleVersionName,
|
||||
keywords = ChangelogParser.LocalizedKeywords(
|
||||
changelog = "Changelog",
|
||||
unreleased = "Unreleased",
|
||||
added = "Added",
|
||||
changed = "Changed",
|
||||
fixed = "Fixed",
|
||||
removed = "Removed",
|
||||
)
|
||||
).toJsonString()
|
||||
|
||||
inputs.property("gitSha", gitInfo.sha)
|
||||
|
|
|
@ -20,3 +20,6 @@ directly impact users rather than highlighting other key architectural updates.*
|
|||
### Fixed
|
||||
- Support Screen now shows the Send button above keyboard instead of overlaying it
|
||||
- QR code scanning speed and reliability have been improved to address the latest reported scan issue
|
||||
|
||||
### Removed
|
||||
- Test - test - test fine!
|
||||
|
|
|
@ -8,19 +8,11 @@ import androidx.compose.foundation.layout.height
|
|||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.rememberScrollState
|
||||
import androidx.compose.foundation.verticalScroll
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.platform.LocalDensity
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.text.ParagraphStyle
|
||||
import androidx.compose.ui.text.buildAnnotatedString
|
||||
import androidx.compose.ui.text.rememberTextMeasurer
|
||||
import androidx.compose.ui.text.style.TextAlign
|
||||
import androidx.compose.ui.text.style.TextIndent
|
||||
import androidx.compose.ui.text.withStyle
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import androidx.compose.ui.unit.sp
|
||||
import co.electriccoin.zcash.ui.R
|
||||
|
@ -50,15 +42,15 @@ fun WhatsNewView(
|
|||
) { paddingValues ->
|
||||
Column(
|
||||
modifier =
|
||||
Modifier
|
||||
.fillMaxSize()
|
||||
.padding(
|
||||
top = paddingValues.calculateTopPadding() + ZcashTheme.dimens.spacingDefault,
|
||||
bottom = paddingValues.calculateBottomPadding() + ZcashTheme.dimens.spacingDefault,
|
||||
start = ZcashTheme.dimens.screenHorizontalSpacingRegular,
|
||||
end = ZcashTheme.dimens.screenHorizontalSpacingRegular
|
||||
)
|
||||
.verticalScroll(rememberScrollState())
|
||||
Modifier
|
||||
.fillMaxSize()
|
||||
.padding(
|
||||
top = paddingValues.calculateTopPadding() + ZcashTheme.dimens.spacingDefault,
|
||||
bottom = paddingValues.calculateBottomPadding() + ZcashTheme.dimens.spacingDefault,
|
||||
start = ZcashTheme.dimens.screenHorizontalSpacingRegular,
|
||||
end = ZcashTheme.dimens.screenHorizontalSpacingRegular
|
||||
)
|
||||
.verticalScroll(rememberScrollState())
|
||||
) {
|
||||
Row {
|
||||
Text(
|
||||
|
@ -87,43 +79,17 @@ fun WhatsNewView(
|
|||
|
||||
@Composable
|
||||
private fun WhatsNewSection(state: WhatsNewSectionState) {
|
||||
val bulletString = "\u2022\t\t"
|
||||
val bulletTextStyle = MaterialTheme.typography.bodySmall
|
||||
val bulletTextMeasurer = rememberTextMeasurer()
|
||||
val bulletStringWidth =
|
||||
remember(bulletTextStyle, bulletTextMeasurer) {
|
||||
bulletTextMeasurer.measure(text = bulletString, style = bulletTextStyle).size.width
|
||||
}
|
||||
val bulletRestLine = with(LocalDensity.current) { bulletStringWidth.toSp() }
|
||||
val bulletParagraphStyle = ParagraphStyle(textIndent = TextIndent(restLine = bulletRestLine))
|
||||
val bulletStyle =
|
||||
state.content.getValue().split("\n-")
|
||||
.filter { it.isNotBlank() }
|
||||
.map {
|
||||
it.replace("\n-", "").trim()
|
||||
}
|
||||
.let { text ->
|
||||
buildAnnotatedString {
|
||||
text.forEach {
|
||||
withStyle(style = bulletParagraphStyle) {
|
||||
append(bulletString)
|
||||
append(it)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Column {
|
||||
Text(
|
||||
text = state.title.getValue(),
|
||||
text = stringResource(id = R.string.whats_new_entry_title, state.title.getValue()),
|
||||
style = ZcashTheme.typography.primary.titleSmall,
|
||||
)
|
||||
|
||||
Spacer(modifier = Modifier.height(ZcashTheme.dimens.spacingMin))
|
||||
|
||||
Text(
|
||||
text = bulletStyle,
|
||||
style = bulletTextStyle
|
||||
text = state.content.getValue(),
|
||||
style = ZcashTheme.typography.primary.bodySmall
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -2,4 +2,5 @@
|
|||
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
|
||||
<string name="whats_new_title">What\'s new</string>
|
||||
<string name="whats_new_version">Zashi Version %s</string>
|
||||
<string name="whats_new_entry_title"><xliff:g id="entry_title" example="Added">%1$s</xliff:g>:</string>
|
||||
</resources>
|
||||
|
|
Loading…
Reference in New Issue