Skip to content
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.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions src/main/kotlin/chatroom/viewmessages/ViewMessages.kt
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ val viewMessagesModule =
dispatchers = get(),
read = get<ChatRepositories>().readMessages(),
channel = get<ChatRepositories>().subscribeMessages(),
smartWrap = get(),
)
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,11 +12,13 @@ import kotlinx.coroutines.flow.filter
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import utils.SmartWrap

class ViewMessagesPresenter(
private val windowScope: CoroutineScope,
private val read: ReadChatRepository<ChatMessageResult>,
private val channel: SubscribeChatRepository,
private val smartWrap: SmartWrap,
dispatchers: RokyDispatchers,
) : Presenter<ViewMessagesView>(dispatchers) {
override fun onAttach(view: ViewMessagesView) {
Expand All @@ -25,6 +27,7 @@ class ViewMessagesPresenter(
read.observe()
.filter { it.isOk }
.map { it.item }
.map { smartWrap(it) }
.map(::Messages)
.collect { message ->
withContext(dispatchers.main) {
Expand Down
2 changes: 2 additions & 0 deletions src/main/kotlin/main.kt
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ import org.koin.dsl.binds
import org.koin.dsl.module
import org.koin.java.KoinJavaComponent.get
import profile.profileModules
import utils.smartWrapModule
import view.rokyTheme

fun main() {
Expand All @@ -53,6 +54,7 @@ val mainModules =
profileModules,
chatroomModules,
chatServerModule,
smartWrapModule,
)
single { DefaultTerminalFactory().createScreen() } bind Screen::class
single { MultiWindowTextGUI(get()).also { it.theme = rokyTheme } }
Expand Down
8 changes: 8 additions & 0 deletions src/main/kotlin/utils/LoggingSmartWrap.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
package utils

class LoggingSmartWrap : SmartWrap {
override fun invoke(input: String): String {
println(input)
return input
}
}
61 changes: 61 additions & 0 deletions src/main/kotlin/utils/SimpleSmartWrap.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
package utils

import org.jetbrains.annotations.VisibleForTesting
import java.lang.System.lineSeparator

class SimpleSmartWrap(
private val maxCharsPerLine: Int,
private val lineSeparator: String = lineSeparator(),
) : SmartWrap {
override operator fun invoke(input: String): String =
with(input) {
if (length <= maxCharsPerLine) {
return this
}
val words = split("\\s".toRegex())
var paragraph = ""
words.forEach { word ->
if (paragraph.wouldOverFlow(maxCharsPerLine, word)) {
if (paragraph.isNotEmpty()) paragraph += lineSeparator
paragraph +=
if (word.canFitOnALine(maxCharsPerLine)) {
word
} else {
word.chunked(maxCharsPerLine - 1).joinToString(lineSeparator) { "$it-" }.trimEnd('-')
}
} else {
paragraph += word
}
paragraph += if (paragraph.isLineFull(maxCharsPerLine)) lineSeparator else " "
}
return paragraph.trimEnd()
}

companion object {
@VisibleForTesting
fun String.isSingleLineParagraph() = !contains(lineSeparator())

@VisibleForTesting
fun String.canFitOnALine(maxCharsPerLine: Int) = length <= maxCharsPerLine

@VisibleForTesting
fun String.wouldOverFlow(
maxCharsPerLine: Int,
word: String,
) = charactersUsedInLastLine() + word.length > maxCharsPerLine

@VisibleForTesting
fun String.charactersUsedInLastLine(): Int =
if (isSingleLineParagraph()) {
length
} else {
lastIndex - (lastIndexOf(lineSeparator()) + (lineSeparator().length - 1))
}

@VisibleForTesting
fun String.charactersRemainingInLastLine(maxCharsPerLine: Int) = maxCharsPerLine - charactersUsedInLastLine()

@VisibleForTesting
fun String.isLineFull(maxCharsPerLine: Int) = charactersRemainingInLastLine(maxCharsPerLine) <= 0
}
}
61 changes: 2 additions & 59 deletions src/main/kotlin/utils/SmartWrap.kt
Original file line number Diff line number Diff line change
@@ -1,62 +1,5 @@
package utils

import org.jetbrains.annotations.VisibleForTesting
import utils.SmartWrap.Companion.canFitOnALine
import java.lang.System.lineSeparator

class SmartWrap(
private val maxCharsPerLine: Int,
private val lineSeparator: String = lineSeparator(),
) {
operator fun invoke(input: String): String =
with(input) {
if (length <= maxCharsPerLine) {
return this
}
val words = split("\\s".toRegex())
var paragraph = ""
words.forEach { word ->
if (paragraph.wouldOverFlow(maxCharsPerLine, word)) {
if (paragraph.isNotEmpty()) paragraph += lineSeparator
paragraph +=
if (word.canFitOnALine(maxCharsPerLine)) {
word
} else {
word.chunked(maxCharsPerLine - 1).joinToString(lineSeparator) { "$it-" }.trimEnd('-')
}
} else {
paragraph += word
}
paragraph += if (paragraph.isLineFull(maxCharsPerLine)) lineSeparator else " "
}
return paragraph.trimEnd()
}

companion object {
@VisibleForTesting
fun String.isSingleLineParagraph() = !contains(lineSeparator())

@VisibleForTesting
fun String.canFitOnALine(maxCharsPerLine: Int) = length <= maxCharsPerLine

@VisibleForTesting
fun String.wouldOverFlow(
maxCharsPerLine: Int,
word: String,
) = charactersUsedInLastLine() + word.length > maxCharsPerLine

@VisibleForTesting
fun String.charactersUsedInLastLine(): Int =
if (isSingleLineParagraph()) {
length
} else {
lastIndex - (lastIndexOf(lineSeparator()) + (lineSeparator().length - 1))
}

@VisibleForTesting
fun String.charactersRemainingInLastLine(maxCharsPerLine: Int) = maxCharsPerLine - charactersUsedInLastLine()

@VisibleForTesting
fun String.isLineFull(maxCharsPerLine: Int) = charactersRemainingInLastLine(maxCharsPerLine) <= 0
}
fun interface SmartWrap {
operator fun invoke(input: String): String
}
9 changes: 9 additions & 0 deletions src/main/kotlin/utils/SmartWrapDependencyInjection.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
package utils

import org.koin.dsl.factory
import org.koin.dsl.module

val smartWrapModule =
module {
factory<SmartWrap> { LoggingSmartWrap() }
}
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import kotlinx.coroutines.test.advanceUntilIdle
import kotlinx.coroutines.test.runTest
import org.junit.jupiter.api.BeforeEach
import org.junit.jupiter.api.Test
import utils.SmartWrap
import kotlin.test.assertEquals
import kotlin.test.assertFalse
import kotlin.test.assertTrue
Expand All @@ -24,6 +25,7 @@ class ViewMessagesPresenterTest {
private lateinit var channel: SubscribeChatRepository
private lateinit var read: ReadChatRepository<ChatMessageResult>
private lateinit var scope: CoroutineScope
private lateinit var smartWrap: SmartWrap
private lateinit var view: ViewMessagesView
private lateinit var presenter: ViewMessagesPresenter

Expand All @@ -32,6 +34,9 @@ class ViewMessagesPresenterTest {
channel = mockk(relaxed = true)
read = mockk()
every { read.observe() } returns flowOf()
smartWrap = mockk<SmartWrap>(relaxed = true)
every { smartWrap.invoke(any()) } answers { it.invocation.args[0] as String }

view = mockk(relaxed = true)
view = mockk(relaxed = true)
val dispatchers: RokyDispatchers =
Expand All @@ -40,7 +45,7 @@ class ViewMessagesPresenterTest {
every { io } returns dispatcher
}
scope = CoroutineScope(dispatcher)
presenter = ViewMessagesPresenter(scope, read, channel, dispatchers)
presenter = ViewMessagesPresenter(scope, read, channel, smartWrap, dispatchers)
}

@Test
Expand Down
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
package utils

import io.kotest.matchers.equals.shouldBeEqual
import io.kotest.matchers.string.shouldBeEmpty
import org.junit.jupiter.api.Test
import java.lang.System.lineSeparator

class SmartWrapKtTest {
class SimpleSmartWrapTest {
@Test
fun `given empty String, when cutoff size is 0, then return empty string`() {
"".smartWrap(0).shouldBeEmpty()
Expand Down Expand Up @@ -65,6 +66,6 @@ class SmartWrapKtTest {
}

companion object {
private fun String.smartWrap(maxCharsPerLine: Int): String = SmartWrap(maxCharsPerLine).invoke(this)
private fun String.smartWrap(maxCharsPerLine: Int): String = SimpleSmartWrap(maxCharsPerLine).invoke(this)
}
}
Original file line number Diff line number Diff line change
@@ -1,16 +1,17 @@
package utils

import io.kotest.matchers.booleans.shouldBeFalse
import io.kotest.matchers.booleans.shouldBeTrue
import io.kotest.matchers.equals.shouldBeEqual
import org.junit.jupiter.api.Test
import utils.SmartWrap.Companion.canFitOnALine
import utils.SmartWrap.Companion.charactersUsedInLastLine
import utils.SmartWrap.Companion.isLineFull
import utils.SmartWrap.Companion.isSingleLineParagraph
import utils.SmartWrap.Companion.wouldOverFlow
import utils.SimpleSmartWrap.Companion.canFitOnALine
import utils.SimpleSmartWrap.Companion.charactersUsedInLastLine
import utils.SimpleSmartWrap.Companion.isLineFull
import utils.SimpleSmartWrap.Companion.isSingleLineParagraph
import utils.SimpleSmartWrap.Companion.wouldOverFlow
import java.lang.System.lineSeparator

class StringUtilsFunctionsTest {
class SimpleSmartWrapUtilsFunctionsTest {
@Test
fun `given empty paragraph, when isSingleLineParagraph, then return true`() {
"".isSingleLineParagraph().shouldBeTrue()
Expand Down
Loading