Commit 6dc601b0 authored by Christophe Henry's avatar Christophe Henry
Browse files

Fix #89: Feed title is not correctly removed from article title

parent c122e8be
Pipeline #3793 passed with stage
in 0 seconds
......@@ -9,6 +9,7 @@
## Bug fixes
* Fix [#89](https://git.feneas.org/christophehenry/freshrss-android/issues/89): feed title is not correctly removed from article title ([!87](https://git.feneas.org/christophehenry/freshrss-android/merge_requests/87))
* Fix [#84](https://git.feneas.org/christophehenry/freshrss-android/issues/84): article view crashing on Android 5.0 and 5.1 ([!74](https://git.feneas.org/christophehenry/freshrss-android/merge_requests/74))
* Fix a bug preventing to refresh when subscription lists are empty ([!62](https://git.feneas.org/christophehenry/freshrss-android/merge_requests/62/))
* Fix [#82](https://git.feneas.org/christophehenry/freshrss-android/issues/82): home and back buttons are sometimes not displayed correctly or not displayed at all ([!71](https://git.feneas.org/christophehenry/freshrss-android/merge_requests/71))
......
......@@ -91,25 +91,21 @@ pipeline {
}
}
stage("Run instrumented tests") {
parallel {
stage("on min SDK level") {
steps {
gitlabCommitStatus("Instrumented tests on min SDK image") {
withAvd(hardwareProfile: "Nexus 5X", systemImage: env.MIN_SDK_IMAGE, headless: true) {
sh "./gradlew clean connectedCheck"
}
}
stage("on min SDK level") {
steps {
gitlabCommitStatus("Instrumented tests on min SDK image") {
withAvd(hardwareProfile: "Nexus 5X", systemImage: env.MIN_SDK_IMAGE, headless: true) {
sh "./gradlew clean connectedCheck"
}
}
}
}
stage("on max SDK level") {
steps {
gitlabCommitStatus("Instrumented tests on max SDK image") {
withAvd(hardwareProfile: "Nexus 5X", systemImage: env.MAX_SDK_IMAGE, headless: true) {
sh "./gradlew clean connectedCheck"
}
}
stage("on max SDK level") {
steps {
gitlabCommitStatus("Instrumented tests on max SDK image") {
withAvd(hardwareProfile: "Nexus 5X", systemImage: env.MAX_SDK_IMAGE, headless: true) {
sh "./gradlew clean connectedCheck"
}
}
}
......@@ -146,6 +142,6 @@ def getBuildSteps(String branch) {
"Instrumented tests on min SDK image",
"Instrumented tests on max SDK image",
]
if(branch == "develop") return buildSteps + ["Build APK"]
if (branch == "develop") return buildSteps + ["Build APK"]
return buildSteps
}
\ No newline at end of file
......@@ -99,27 +99,26 @@ class RefreshWorker(appContext: Context, workerParams: WorkerParameters): Corout
@StringRes title: Int,
@StringRes text: Int,
@AttrRes colorAttr: Int
) =
NotificationCompat.Builder(app, chan.channelId).apply {
setSmallIcon(R.drawable.ic_rss_feed_black_24dp)
if(Build.VERSION.SDK_INT > Build.VERSION_CODES.M) {
val typedValue = TypedValue()
val attrs = app.theme.obtainStyledAttributes(typedValue.data, intArrayOf(colorAttr))
color = attrs.getColor(0, app.getColor(R.color.color_primary))
// TODO: Activate when themes are supported
//setColorized(true)
attrs.recycle()
}
) = NotificationCompat.Builder(app, chan.channelId).apply {
setSmallIcon(R.drawable.ic_rss_feed_black_24dp)
if(Build.VERSION.SDK_INT > Build.VERSION_CODES.M) {
val typedValue = TypedValue()
val attrs = app.theme.obtainStyledAttributes(typedValue.data, intArrayOf(colorAttr))
color = attrs.getColor(0, app.getColor(R.color.color_primary))
// TODO: Activate when themes are supported
//setColorized(true)
attrs.recycle()
}
if(Build.VERSION.SDK_INT > Build.VERSION_CODES.N) {
setContentTitle(applicationContext.getString(text))
setSubText(applicationContext.getString(title))
} else {
setContentTitle(applicationContext.getString(title))
setContentInfo(applicationContext.getString(text))
}
if(Build.VERSION.SDK_INT > Build.VERSION_CODES.N) {
setContentTitle(applicationContext.getString(text))
setSubText(applicationContext.getString(title))
} else {
setContentTitle(applicationContext.getString(title))
setContentInfo(applicationContext.getString(text))
}
}
companion object {
const val REFRESH_WORK_TAG = "REFRESH_WORK"
......
......@@ -5,18 +5,18 @@ import android.content.Intent.ACTION_SEND
import android.content.Intent.EXTRA_TEXT
import com.x5.template.Theme
import fr.chenry.android.freshrss.components.subscriptionarticles.webviewutils.chunkfilters.SentenceCapFilter
import fr.chenry.android.freshrss.components.subscriptionarticles.webviewutils.chunkfilters.StripFragmentFilter
import fr.chenry.android.freshrss.components.subscriptionarticles.webviewutils.chunkfilters.RemoveFeedTitleFilter
object ShareIntent {
private val defaultTemplate = """
|{${'$'}title|strip_fragment(subscription)|sentence_cap} — {${'$'}subscription|capitalize}
|{${'$'}title|remove_feed_title(${'$'}subscription)|sentence_cap} — {${'$'}subscription|capitalize}
|{${'$'}href}
""".trimMargin("|")
private val template = Theme().let {
it.registerFilter(SentenceCapFilter())
it.registerFilter(StripFragmentFilter())
it.registerFilter(RemoveFeedTitleFilter())
it.makeChunk().apply {append(defaultTemplate)}
}
......
......@@ -3,42 +3,44 @@ package fr.chenry.android.freshrss.components.subscriptionarticles.webviewutils.
import com.x5.template.Chunk
import com.x5.template.filters.FilterArgs
import com.x5.template.filters.ObjectFilter
import java.util.Locale
import java.util.regex.Pattern
import kotlin.text.RegexOption.IGNORE_CASE
object ChunkFilters {
private val excludedChars = listOf("'", ":")
// Will match any separator token, i.e, any word not containing at least a letter or a number
val sepToken = "[\\S&&[^\\p{L}]&&[^\\d]${excludedChars.joinToString(separator = "") {"&&[^$it]"}}]"
}
class SentenceCapFilter : ObjectFilter() {
class SentenceCapFilter: ObjectFilter() {
override fun getFilterName() = "sentence_cap"
override fun transformObject(chunk: Chunk?, `object`: Any?, args: FilterArgs?) =
override fun transformObject(chunk: Chunk, `object`: Any, args: FilterArgs) =
`object`.toString().let(::sentenceCap)
companion object {
fun sentenceCap(token: String): String {
var result = token
token.split("\\s+${ChunkFilters.sepToken}+\\s+".toRegex())
.map { t ->
.map {t ->
when {
t.isBlank() -> t
t.length == 1 -> t[0].toString().toUpperCase()
else -> "${t[0].toString().toUpperCase()}${t.substring(1).toLowerCase()}"
t.length == 1 -> t[0].toString().toUpperCase(Locale.ROOT)
else -> "${t[0].toString().toUpperCase(Locale.ROOT)}${t.substring(1).toLowerCase(Locale.ROOT)}"
}
}.forEach { t -> result = result.replace(t, t, ignoreCase = true) }
return result
}.forEach {t -> result = result.replace(t, t, ignoreCase = true)}
return result.trim()
}
}
}
class StripFragmentFilter : ObjectFilter() {
override fun getFilterName() = "strip_fragment"
class RemoveFeedTitleFilter: ObjectFilter() {
override fun getFilterName() = "remove_feed_title"
override fun transformObject(chunk: Chunk?, `object`: Any?, args: FilterArgs?) =
stripFragment(`object`.toString(), args?.getFilterArgs(chunk)?.getOrNull(0) ?: "")
override fun transformObject(chunk: Chunk, `object`: Any, args: FilterArgs) =
stripFragment(`object`.toString(), args.getFilterArgs(chunk)?.getOrNull(0) ?: "")
companion object {
/**
......@@ -52,15 +54,14 @@ class StripFragmentFilter : ObjectFilter() {
* "Series | My title"
*/
fun stripFragment(title: String, author: String): String {
val a = Pattern.quote(author)
val sep = ChunkFilters.sepToken
return when {
// Case where author is in the middle of the title, for instance: "Video series #1 | Author | Title"
title.contains("\\s*$sep+\\s*$a\\s*$sep+\\s*".toRegex()) ->
"\\s*$sep*\\s*$a".toRegex(IGNORE_CASE).replace(title, "")
title.contains("\\s*$sep+\\s*$author\\s*$sep+\\s*".toRegex(IGNORE_CASE)) ->
"\\s*$sep*\\s*$author".toRegex(IGNORE_CASE).replace(title, "")
else ->
"\\s*$sep*\\s*$a\\s*$sep*\\s*".toRegex(IGNORE_CASE).replace(title, "")
}
"\\s*$sep*\\s*$author\\s*$sep*\\s*".toRegex(IGNORE_CASE).replace(title, "")
}.trim()
}
}
}
......@@ -2,7 +2,7 @@ package fr.chenry.android.freshrss.components.subscriptionarticles
import fr.chenry.android.freshrss.components.subscriptionarticles.webviewutils.ShareIntent
import fr.chenry.android.freshrss.components.subscriptionarticles.webviewutils.chunkfilters.SentenceCapFilter
import fr.chenry.android.freshrss.components.subscriptionarticles.webviewutils.chunkfilters.StripFragmentFilter
import fr.chenry.android.freshrss.components.subscriptionarticles.webviewutils.chunkfilters.RemoveFeedTitleFilter
import org.junit.Assert.assertEquals
import org.junit.Test
......@@ -11,20 +11,20 @@ class ShareIntentTest {
get() = mapOf(
"subscription" to "The subscription",
"author" to "Humble Me",
"title" to "My Title - My Series #1 - THIS IS AWESOME!",
"title" to "My Title - My Series #1 | The Subscription - THIS IS AWESOME!",
"content" to "Lorem ipsum",
"href" to "http://example.com"
)
@Test
fun stripAuthorTest() {
assertEquals("My title", StripFragmentFilter.stripFragment("My title - Author", "Author"))
assertEquals("My title", StripFragmentFilter.stripFragment("Author | My title", "Author"))
assertEquals("My title", StripFragmentFilter.stripFragment("Author | My title", "author"))
assertEquals("My title", StripFragmentFilter.stripFragment("author & My title", "Author"))
assertEquals("My title", RemoveFeedTitleFilter.stripFragment("My title - Author", "Author"))
assertEquals("My title", RemoveFeedTitleFilter.stripFragment("Author | My title", "Author"))
assertEquals("My title", RemoveFeedTitleFilter.stripFragment("Author | My title", "author"))
assertEquals("My title", RemoveFeedTitleFilter.stripFragment("author & My title", "Author"))
assertEquals(
"Video series #1 | Title",
StripFragmentFilter.stripFragment("Video series #1 | Author | Title", "Author")
RemoveFeedTitleFilter.stripFragment("Video series #1 | Author | Title", "Author")
)
}
......
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment