-
Notifications
You must be signed in to change notification settings - Fork 30
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
Update Image Loader and Implement Naive content templating #109
base: main
Are you sure you want to change the base?
Changes from all commits
5258902
c11a9a5
d59e1c8
4c0bbe5
9c5edfb
146f20c
42c9a89
a6bac8e
324fcdd
1201e83
2308c25
f5a4c93
cb895a5
2c82f3b
36ce08a
ee77e1b
5476f65
70ea9c6
d156b69
5d52624
997c106
41e597f
f9a53d8
41b5549
a865d14
3c9a5f4
ad7f6d9
f7e7ed2
d419760
2e582d5
e96e7b6
2396112
47f6962
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -5,6 +5,9 @@ plugins { | |
|
||
android { | ||
namespace = "social.androiddev.common" | ||
packagingOptions { | ||
exclude("META-INF/DEPENDENCIES") | ||
} | ||
} | ||
|
||
kotlin { | ||
|
@@ -15,7 +18,23 @@ kotlin { | |
implementation(projects.logging) | ||
api(libs.com.arkivanov.decompose) | ||
api(libs.com.arkivanov.decompose.extensions.compose.jetbrains) | ||
api(libs.kotlinx.collections.immutable) | ||
implementation(libs.kotlinx.collections.immutable) | ||
implementation("com.alialbaali.kamel:kamel-image:0.4.0") | ||
implementation("it.skrape:skrapeit:1.2.2") | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Is this also JVM only? Maybe we can use this as a resource and roll our own purely kotlin parser which also supports iOS There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. yup yup jvm only. I think this gets us off the ground and agree we would need something more robust in future
digitalbuddha marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
} | ||
} | ||
val commonTest by getting { | ||
dependencies { | ||
implementation(kotlin("test")) | ||
implementation(libs.org.jetbrains.kotlin.test.common) | ||
implementation(libs.org.jetbrains.kotlin.test.annotations.common) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Formatting here looks off, but it could be because I'm on my phone There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. ran spotless apply just in case |
||
} | ||
} | ||
val androidTest by getting { | ||
dependencies { | ||
implementation(kotlin("test")) | ||
implementation(libs.org.jetbrains.kotlin.test.junit) | ||
} | ||
} | ||
} | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -12,27 +12,25 @@ | |
*/ | ||
package social.androiddev.common.composables | ||
|
||
import androidx.compose.foundation.layout.height | ||
import androidx.compose.foundation.layout.width | ||
import androidx.compose.foundation.shape.RoundedCornerShape | ||
import androidx.compose.runtime.Composable | ||
import androidx.compose.runtime.remember | ||
import androidx.compose.ui.Modifier | ||
import androidx.compose.ui.draw.clip | ||
import androidx.compose.ui.unit.dp | ||
import social.androiddev.common.theme.DodoTheme | ||
import social.androiddev.common.utils.AsyncImage | ||
import social.androiddev.common.utils.loadImageIntoPainter | ||
|
||
@Composable | ||
fun UserAvatar( | ||
url: String, | ||
modifier: Modifier = Modifier | ||
) { | ||
AsyncImage( | ||
load = { loadImageIntoPainter(url = url) }, | ||
painterFor = { remember { it } }, | ||
url = url, | ||
contentDescription = "User avatar", | ||
modifier = modifier.width(48.dp).clip(RoundedCornerShape(5.dp)) | ||
modifier = modifier.width(48.dp).height(48.dp).clip(RoundedCornerShape(5.dp)) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. modifier.size(48.dp) should already set it for both dimensions There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. for some reason it was not rendering the avatars after switching to the new library. We can play with it later but yeah it never assumed a height |
||
) | ||
} | ||
|
||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -12,59 +12,29 @@ | |
*/ | ||
package social.androiddev.common.utils | ||
|
||
import androidx.compose.foundation.Image | ||
import androidx.compose.foundation.layout.Spacer | ||
import androidx.compose.runtime.Composable | ||
import androidx.compose.runtime.getValue | ||
import androidx.compose.runtime.produceState | ||
import androidx.compose.ui.Alignment | ||
import androidx.compose.ui.Modifier | ||
import androidx.compose.ui.graphics.painter.Painter | ||
import androidx.compose.ui.layout.ContentScale | ||
import kotlinx.coroutines.Dispatchers | ||
import kotlinx.coroutines.withContext | ||
import social.androiddev.common.logging.DodoLogger | ||
import social.androiddev.common.network.util.runCatchingIgnoreCancelled | ||
import io.kamel.image.KamelImage | ||
import io.kamel.image.lazyPainterResource | ||
|
||
/** | ||
* Use this helper until we switch to a image loading library which supports multiplatform | ||
*/ | ||
@Composable | ||
fun <T> AsyncImage( | ||
fun AsyncImage( | ||
contentDescription: String, | ||
load: suspend () -> T, | ||
painterFor: @Composable (T) -> Painter, | ||
url: Any, | ||
modifier: Modifier = Modifier, | ||
alignment: Alignment = Alignment.Center, | ||
contentScale: ContentScale = ContentScale.Fit, | ||
contentScale: ContentScale = ContentScale.Fit | ||
) { | ||
val image: T? by produceState<T?>(null) { | ||
value = withContext(Dispatchers.IO) { | ||
runCatchingIgnoreCancelled { | ||
load() | ||
}.fold( | ||
onSuccess = { it }, | ||
onFailure = { t -> | ||
DodoLogger.w(throwable = t) { | ||
"Error when loading image." | ||
} | ||
null | ||
} | ||
) | ||
} | ||
} | ||
|
||
if (image != null) { | ||
Image( | ||
painter = painterFor(image!!), | ||
contentDescription = contentDescription, | ||
contentScale = contentScale, | ||
modifier = modifier, | ||
alignment = alignment, | ||
) | ||
} else { | ||
Spacer( | ||
modifier = modifier | ||
) | ||
} | ||
KamelImage( | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Is this truly KMP support or just JVM? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. just jvm, but ios has native support afaik There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Cool, we should follow-up on this and change this to an expect/actual |
||
resource = lazyPainterResource(data = url), | ||
contentDescription = contentDescription, | ||
modifier = modifier, | ||
alignment = alignment, | ||
contentScale = contentScale | ||
) | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,91 @@ | ||
/* | ||
* This file is part of Dodo. | ||
* | ||
* Dodo is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as | ||
* published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. | ||
* | ||
* Dodo is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty | ||
* of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. | ||
* | ||
* You should have received a copy of the GNU General Public License along with Dodo. | ||
* If not, see <https://www.gnu.org/licenses/>. | ||
*/ | ||
package social.androiddev.common.utils | ||
|
||
import androidx.compose.ui.graphics.Color | ||
import androidx.compose.ui.text.AnnotatedString | ||
import androidx.compose.ui.text.SpanStyle | ||
import androidx.compose.ui.text.buildAnnotatedString | ||
import androidx.compose.ui.text.font.FontWeight | ||
import androidx.compose.ui.text.withStyle | ||
import it.skrape.core.htmlDocument | ||
import it.skrape.selects.eachLink | ||
import it.skrape.selects.eachText | ||
import it.skrape.selects.html5.a | ||
import it.skrape.selects.html5.body | ||
|
||
/** | ||
* Takes a String reciever of html text | ||
* converts it to an annotated string of text and links | ||
*/ | ||
@Suppress("ReturnCount") | ||
fun String.extractContentFromMicroFormat(): AnnotatedString { | ||
val newlineReplace = this.replace("<br>", "\n") | ||
|
||
if (newlineReplace.contains("<p>").not()) return buildAnnotatedString { } | ||
|
||
val content = htmlDocument(newlineReplace) | ||
|
||
val body = content.body { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. At this point, the only thing we are using skrape for is to get the paragraph body without any other tags and then for getting the list of links. We can hand roll that logic ourselves and target ios |
||
findAll { | ||
eachText | ||
} | ||
} | ||
val paragraph = body | ||
|
||
@Suppress("SwallowedException") | ||
val links = try { | ||
content.a { findAll { eachLink } } | ||
} catch (exception: Exception) { | ||
emptyMap() | ||
} | ||
|
||
val plainText = paragraph.joinToString("\n") | ||
if (links.isNullOrEmpty()) { | ||
buildAnnotatedString { | ||
append(plainText) | ||
} | ||
} | ||
return buildAnnotatedString { | ||
// everything before first link | ||
append(plainText.substringBefore(links.keys.first())) | ||
|
||
withStyle(style = SpanStyle(color = Color.Blue, fontWeight = FontWeight.Bold)) { | ||
appendLink(links.keys.first(), links.values.first()) | ||
} | ||
val restOfLinks = links.entries.drop(1) | ||
var restOfPlainText = plainText.substringAfter(links.keys.first()) | ||
restOfLinks.forEach { | ||
val (key, value) = it | ||
val segment = restOfPlainText.substringBefore(key) | ||
append(segment) | ||
withStyle(style = SpanStyle(color = Color.Blue, fontWeight = FontWeight.Bold)) { | ||
appendLink(key, value) | ||
} | ||
restOfPlainText = restOfPlainText.substringAfter(key) | ||
} | ||
append(restOfPlainText) | ||
} | ||
} | ||
|
||
private fun AnnotatedString.Builder.appendLink(linkText: String, linkUrl: String) { | ||
pushStringAnnotation(tag = linkUrl, annotation = linkUrl) | ||
append(linkText) | ||
pop() | ||
} | ||
|
||
// private fun AnnotatedString.onLinkClick(offset: Int, onClick: (String) -> Unit) { | ||
// getStringAnnotations(start = offset, end = offset).firstOrNull()?.let { | ||
// onClick(it.item) | ||
// } | ||
// } |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,29 @@ | ||
/* | ||
* This file is part of Dodo. | ||
* | ||
* Dodo is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as | ||
* published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. | ||
* | ||
* Dodo is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty | ||
* of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. | ||
* | ||
* You should have received a copy of the GNU General Public License along with Dodo. | ||
* If not, see <https://www.gnu.org/licenses/>. | ||
*/ | ||
package social.androiddev.common.utils | ||
import org.junit.Test | ||
import kotlin.test.assertTrue | ||
|
||
class HtmlParserKtTest { | ||
|
||
@Test | ||
fun renderHtml() { | ||
val result = """ | ||
<p>Been debating for a few days, and I think its time to open the <a href="https://eggy.app/tags/relay" class="mention hashtag" rel="nofollow noopener noreferrer" target="_blank">#<span>relay</span></a> to anyone. If you're looking for a general Relay please feel free to add <a href="https://relay.eggy.app/inbox" rel="nofollow noopener noreferrer" target="_blank"><span class="invisible">https://</span><span class="">relay.eggy.app/inbox</span><span class="invisible"></span></a> to your instances. More information can be found <a href="https://relay.eggy.app" rel="nofollow noopener noreferrer" target="_blank"><span class="invisible">https://</span><span class="">relay.eggy.app</span><span class="invisible"></span></a>, but Its a general open relay for anybody to use. <a href="https://eggy.app/tags/mastoadmin" class="mention hashtag" rel="nofollow noopener noreferrer" target="_blank">#<span>mastoadmin</span></a> Only thing I'm missing is a metrics tracking type page.. Hoping to have something soon.</p> | ||
""".extractContentFromMicroFormat() | ||
|
||
assertTrue { result.spanStyles.size == 4 } | ||
assertTrue { result.paragraphStyles.isEmpty() } | ||
assertTrue { result.length == 393 } | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I've always wondering what this stuff is for? Is this generated or you had to add manually to fix a build issue?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Manually to fix a build issue. My naive understanding is that multiple libraries have the same metadata file name and clash