Skip to content

Commit

Permalink
Fix missing emojis in about screen + revamp implementation (#176)
Browse files Browse the repository at this point in the history
* Refactor replaceMarkdownEmojis to extension function

* Replace markdown emojis in oss items

* Replace markdown emojis in changelog items

* Use idiomatic buildString()

* Print longest alias

* Update markdown parser with more sequence implementation

Should be more efficient and also friendlier to potentially infinite streams
  • Loading branch information
ZacSweers committed Aug 17, 2019
1 parent 0daae13 commit 9190000
Show file tree
Hide file tree
Showing 6 changed files with 115 additions and 40 deletions.
Expand Up @@ -37,6 +37,8 @@ import io.sweers.catchup.R
import io.sweers.catchup.R.layout
import io.sweers.catchup.data.LinkManager
import io.sweers.catchup.data.github.RepoReleasesQuery
import io.sweers.catchup.gemoji.EmojiMarkdownConverter
import io.sweers.catchup.gemoji.replaceMarkdownEmojisIn
import io.sweers.catchup.service.api.UrlMeta
import io.sweers.catchup.ui.Scrollable
import io.sweers.catchup.ui.base.CatchUpItemViewHolder
Expand All @@ -57,6 +59,8 @@ class ChangelogFragment : InjectableBaseFragment(), Scrollable {
lateinit var apolloClient: ApolloClient
@Inject
internal lateinit var linkManager: LinkManager
@Inject
internal lateinit var markdownConverter: EmojiMarkdownConverter

private val progressBar by bindView<ProgressBar>(R.id.progress)
private val recyclerView by bindView<RecyclerView>(R.id.list)
Expand Down Expand Up @@ -129,6 +133,12 @@ class ChangelogFragment : InjectableBaseFragment(), Scrollable {
)
}
}
.map {
it.copy(
name = markdownConverter.replaceMarkdownEmojisIn(it.name),
description = markdownConverter.replaceMarkdownEmojisIn(it.description)
)
}
.toList()
}

Expand Down
16 changes: 16 additions & 0 deletions app/src/main/kotlin/io/sweers/catchup/ui/about/LicensesFragment.kt
Expand Up @@ -58,6 +58,8 @@ import io.sweers.catchup.data.github.ProjectOwnersByIdsQuery.AsUser
import io.sweers.catchup.data.github.RepositoriesByIdsQuery
import io.sweers.catchup.data.github.RepositoryByNameAndOwnerQuery
import io.sweers.catchup.flowbinding.safeOffer
import io.sweers.catchup.gemoji.EmojiMarkdownConverter
import io.sweers.catchup.gemoji.replaceMarkdownEmojisIn
import io.sweers.catchup.service.api.TemporaryScopeHolder
import io.sweers.catchup.service.api.UrlMeta
import io.sweers.catchup.service.api.temporaryScope
Expand Down Expand Up @@ -116,6 +118,9 @@ class LicensesFragment : InjectableBaseFragment(), Scrollable {
@Inject
internal lateinit var linkManager: LinkManager

@Inject
internal lateinit var markdownConverter: EmojiMarkdownConverter

private val dimenSize by lazy {
resources.getDimensionPixelSize(R.dimen.avatar)
}
Expand Down Expand Up @@ -249,6 +254,17 @@ class LicensesFragment : InjectableBaseFragment(), Scrollable {
.groupBy { it.author }
.sortBy { it.first }
.flatMapConcat { it.second.asFlow().sortBy { it.name } }
.map {
// TODO use CopyDynamic when 0.3.0 is out
it.copy(
author = markdownConverter.replaceMarkdownEmojisIn(it.author),
name = markdownConverter.replaceMarkdownEmojisIn(it.name),
description = it.description?.let {
markdownConverter.replaceMarkdownEmojisIn(it)
} ?: it.description
)
}
.flowOn(Dispatchers.IO)
.toList()
.let {
val collector = mutableListOf<OssBaseItem>()
Expand Down
Expand Up @@ -26,50 +26,92 @@ internal class GemojiEmojiMarkdownConverter(
private val gemojiDao: GemojiDao
) : EmojiMarkdownConverter {
override fun convert(alias: String): String? {
return if (alias.startsWith(':') && alias.endsWith(":")) {
gemojiDao.getEmoji(alias.substring(1, alias.lastIndex))
} else {
null
}
return gemojiDao.getEmoji(alias)
}
}

fun Sequence<Char>.asString(): String {
return buildString {
this@asString.forEach { append(it) }
}
}

fun Sequence<Char>.asString(capacity: Int): String {
return buildString(capacity) {
this@asString.forEach { append(it) }
}
}

/**
* Returns a [String] that replaces occurrences of markdown emojis with android render-able emojis.
*/
fun replaceMarkdownEmojis(markdown: String, converter: EmojiMarkdownConverter): String {
val sb = StringBuilder(markdown.length)
var potentialAliasStart = -1
fun EmojiMarkdownConverter.replaceMarkdownEmojisIn(markdown: String): String {
return replaceMarkdownEmojisIn(markdown.asSequence()).asString(markdown.length)
}

/**
* This is the longest possible alias length, so we can use its length for our aliasBuilder var
* below to reuse it and never have to resize it.
*/
private const val MAX_ALIAS_LENGTH = "south_georgia_south_sandwich_islands".length

markdown.forEachIndexed { index, char ->
if (char == ':') {
potentialAliasStart = if (potentialAliasStart == -1) {
// If we have no potential start, any : is a potential start
index
/**
* Returns a [Sequence<Char>][Sequence] that replaces occurrences of markdown emojis with android
* render-able emojis.
*/
fun EmojiMarkdownConverter.replaceMarkdownEmojisIn(markdown: Sequence<Char>): Sequence<Char> {
val aliasBuilder = StringBuilder(MAX_ALIAS_LENGTH)
var startAlias = false
return sequence {
markdown.forEach { char ->
if (startAlias || aliasBuilder.isNotEmpty()) {
if (startAlias && char == ':') {
// Double ::, so emit a colon and keep startAlias set
yield(':')
return@forEach
}
startAlias = false
when (char) {
' ' -> {
// Aliases can't have spaces, so bomb out and restart
yield(':')
yieldAll(aliasBuilder.asSequence())
yield(' ')
aliasBuilder.setLength(0)
}
':' -> {
val potentialAlias = aliasBuilder.toString()
val potentialEmoji = convert(potentialAlias)
// If we find an emoji append it and reset alias start, if we don't find an emoji
// append between the potential start and this index *and* consider this index the new
// potential start.
if (potentialEmoji != null) {
yieldAll(potentialEmoji.asSequence())
} else {
yield(':')
yieldAll(potentialAlias.asSequence())
// Start a new alias from this colon as we didn't have a match with the existing close
startAlias = true
}
aliasBuilder.setLength(0)
}
else -> aliasBuilder.append(char)
}
} else {
val potentialAlias = markdown.substring(potentialAliasStart, index + 1)
val potentialEmoji = converter.convert(potentialAlias)
// If we find an emoji append it and reset alias start, if we don't find an emoji
// append between the potential start and this index *and* consider this index the new
// potential start.
if (potentialEmoji != null) {
sb.append(potentialEmoji)
-1
if (char == ':') {
startAlias = true
} else {
sb.append(markdown, potentialAliasStart, index)
index
yield(char)
}
}
// While not looking for an alias end append all non possible alias chars to the string
} else if (potentialAliasStart == -1) {
sb.append(char)
}
}

// Finished iterating markdown while looking for an end, append anything remaining
if (potentialAliasStart != -1) {
sb.append(markdown, potentialAliasStart, markdown.length)
// If we started an alias but ran out of characters, flush it
if (startAlias) {
yield(':')
} else if (aliasBuilder.isNotEmpty()) {
yield(':')
yieldAll(aliasBuilder.asSequence())
}
}

return sb.toString()
}
Expand Up @@ -20,15 +20,18 @@ import org.junit.Test

class EmojiMarkdownConverterTest {

val replaced = "replaced"
val emoji = ":emoji:"
val converter = object : EmojiMarkdownConverter {
override fun convert(alias: String) = if (alias == ":emoji:") replaced else null
companion object {
const val replaced = "replaced"
const val emoji = ":emoji:"
}

private val converter = object : EmojiMarkdownConverter {
override fun convert(alias: String) = if (alias == "emoji") replaced else null
}

@Test
fun testEmpty() {
val converted = replaceMarkdownEmojis("", converter)
val converted = converter.replaceMarkdownEmojisIn("")
assertThat(converted).isEmpty()
}

Expand Down Expand Up @@ -92,5 +95,5 @@ class EmojiMarkdownConverterTest {
assertThat(converted).isEqualTo("$replaced:notEmoji:$replaced:")
}

private fun convert(markdown: String) = replaceMarkdownEmojis(markdown, converter)
private fun convert(markdown: String) = converter.replaceMarkdownEmojisIn(markdown)
}
4 changes: 4 additions & 0 deletions scripts/gemoji/gemoji.py
Expand Up @@ -43,3 +43,7 @@

connection.commit()
connection.close()

print("Longest alias")
longestAlias = max([alias for alias in [max(gemoji['aliases'], key=len) for gemoji in gemojis]], key=len)
print(longestAlias)
Expand Up @@ -30,7 +30,7 @@ import dagger.multibindings.IntoMap
import io.reactivex.Observable
import io.reactivex.Single
import io.sweers.catchup.gemoji.EmojiMarkdownConverter
import io.sweers.catchup.gemoji.replaceMarkdownEmojis
import io.sweers.catchup.gemoji.replaceMarkdownEmojisIn
import io.sweers.catchup.libraries.retrofitconverters.DecodingConverter
import io.sweers.catchup.libraries.retrofitconverters.delegatingCallFactory
import io.sweers.catchup.service.api.CatchUpItem
Expand Down Expand Up @@ -140,7 +140,7 @@ internal class GitHubService @Inject constructor(
.map {
with(it) {
val description = description
?.let { "${replaceMarkdownEmojis(it, emojiMarkdownConverter.get())}" }
?.let { "${emojiMarkdownConverter.get().replaceMarkdownEmojisIn(it)}" }
.orEmpty()

CatchUpItem(
Expand Down

0 comments on commit 9190000

Please sign in to comment.