mirror of
https://github.com/vitorpamplona/amethyst.git
synced 2025-09-28 18:17:07 +02:00
- Adds support for AVIF images
- Removes gray border in image urls that couldn't be loaded.
This commit is contained in:
@@ -34,7 +34,7 @@ import kotlinx.coroutines.Dispatchers
|
|||||||
import kotlinx.coroutines.Job
|
import kotlinx.coroutines.Job
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
|
|
||||||
val SUPPORTED_VIDEO_FEED_MIME_TYPES = listOf("image/jpeg", "image/gif", "image/png", "image/webp", "video/mp4", "video/mpeg", "video/webm", "audio/aac", "audio/mpeg", "audio/webm", "audio/wav")
|
val SUPPORTED_VIDEO_FEED_MIME_TYPES = listOf("image/jpeg", "image/gif", "image/png", "image/webp", "video/mp4", "video/mpeg", "video/webm", "audio/aac", "audio/mpeg", "audio/webm", "audio/wav", "image/avif")
|
||||||
val SUPPORTED_VIDEO_FEED_MIME_TYPES_SET = SUPPORTED_VIDEO_FEED_MIME_TYPES.toSet()
|
val SUPPORTED_VIDEO_FEED_MIME_TYPES_SET = SUPPORTED_VIDEO_FEED_MIME_TYPES.toSet()
|
||||||
|
|
||||||
object NostrVideoDataSource : NostrDataSource("VideoFeed") {
|
object NostrVideoDataSource : NostrDataSource("VideoFeed") {
|
||||||
|
@@ -402,6 +402,7 @@ private fun RenderImageOrVideo(
|
|||||||
UrlImageView(
|
UrlImageView(
|
||||||
content = content,
|
content = content,
|
||||||
mainImageModifier = mainModifier,
|
mainImageModifier = mainModifier,
|
||||||
|
loadedImageModifier = Modifier.fillMaxWidth(),
|
||||||
isFiniteHeight = isFiniteHeight,
|
isFiniteHeight = isFiniteHeight,
|
||||||
controllerVisible = controllerVisible,
|
controllerVisible = controllerVisible,
|
||||||
accountViewModel = accountViewModel,
|
accountViewModel = accountViewModel,
|
||||||
@@ -436,6 +437,7 @@ private fun RenderImageOrVideo(
|
|||||||
LocalImageView(
|
LocalImageView(
|
||||||
content = content,
|
content = content,
|
||||||
mainImageModifier = mainModifier,
|
mainImageModifier = mainModifier,
|
||||||
|
loadedImageModifier = Modifier.fillMaxWidth(),
|
||||||
isFiniteHeight = isFiniteHeight,
|
isFiniteHeight = isFiniteHeight,
|
||||||
controllerVisible = controllerVisible,
|
controllerVisible = controllerVisible,
|
||||||
accountViewModel = accountViewModel,
|
accountViewModel = accountViewModel,
|
||||||
|
@@ -117,32 +117,14 @@ fun ZoomableContentView(
|
|||||||
// store the dialog open or close state
|
// store the dialog open or close state
|
||||||
var dialogOpen by remember(content) { mutableStateOf(false) }
|
var dialogOpen by remember(content) { mutableStateOf(false) }
|
||||||
|
|
||||||
var mainImageModifier =
|
val mainImageModifier = Modifier.fillMaxWidth().clickable { dialogOpen = true }
|
||||||
if (roundedCorner) {
|
val loadedImageModifier = if (roundedCorner) MaterialTheme.colorScheme.imageModifier else Modifier.fillMaxWidth()
|
||||||
MaterialTheme.colorScheme.imageModifier
|
|
||||||
} else {
|
|
||||||
Modifier.fillMaxWidth()
|
|
||||||
}
|
|
||||||
|
|
||||||
if (content is MediaUrlContent) {
|
|
||||||
mainImageModifier =
|
|
||||||
mainImageModifier.clickable(
|
|
||||||
onClick = { dialogOpen = true },
|
|
||||||
)
|
|
||||||
} else if (content is MediaPreloadedContent) {
|
|
||||||
mainImageModifier =
|
|
||||||
mainImageModifier.clickable(
|
|
||||||
onClick = { dialogOpen = true },
|
|
||||||
)
|
|
||||||
} else {
|
|
||||||
mainImageModifier = mainImageModifier.clickable { dialogOpen = true }
|
|
||||||
}
|
|
||||||
|
|
||||||
when (content) {
|
when (content) {
|
||||||
is MediaUrlImage ->
|
is MediaUrlImage ->
|
||||||
SensitivityWarning(content.contentWarning != null, accountViewModel) {
|
SensitivityWarning(content.contentWarning != null, accountViewModel) {
|
||||||
TwoSecondController(content) { controllerVisible ->
|
TwoSecondController(content) { controllerVisible ->
|
||||||
UrlImageView(content, mainImageModifier, isFiniteHeight, controllerVisible, accountViewModel = accountViewModel)
|
UrlImageView(content, mainImageModifier, loadedImageModifier, isFiniteHeight, controllerVisible, accountViewModel = accountViewModel)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
is MediaUrlVideo ->
|
is MediaUrlVideo ->
|
||||||
@@ -166,7 +148,7 @@ fun ZoomableContentView(
|
|||||||
}
|
}
|
||||||
is MediaLocalImage ->
|
is MediaLocalImage ->
|
||||||
TwoSecondController(content) { controllerVisible ->
|
TwoSecondController(content) { controllerVisible ->
|
||||||
LocalImageView(content, mainImageModifier, isFiniteHeight, controllerVisible, accountViewModel = accountViewModel)
|
LocalImageView(content, mainImageModifier, loadedImageModifier, isFiniteHeight, controllerVisible, accountViewModel = accountViewModel)
|
||||||
}
|
}
|
||||||
is MediaLocalVideo ->
|
is MediaLocalVideo ->
|
||||||
content.localFile?.let {
|
content.localFile?.let {
|
||||||
@@ -215,6 +197,7 @@ fun TwoSecondController(
|
|||||||
fun LocalImageView(
|
fun LocalImageView(
|
||||||
content: MediaLocalImage,
|
content: MediaLocalImage,
|
||||||
mainImageModifier: Modifier,
|
mainImageModifier: Modifier,
|
||||||
|
loadedImageModifier: Modifier,
|
||||||
isFiniteHeight: Boolean,
|
isFiniteHeight: Boolean,
|
||||||
controllerVisible: MutableState<Boolean>,
|
controllerVisible: MutableState<Boolean>,
|
||||||
accountViewModel: AccountViewModel,
|
accountViewModel: AccountViewModel,
|
||||||
@@ -252,14 +235,14 @@ fun LocalImageView(
|
|||||||
content.blurhash,
|
content.blurhash,
|
||||||
content.description,
|
content.description,
|
||||||
contentScale,
|
contentScale,
|
||||||
Modifier.aspectRatio(ratio),
|
loadedImageModifier.aspectRatio(ratio),
|
||||||
)
|
)
|
||||||
} else {
|
} else {
|
||||||
DisplayBlurHash(
|
DisplayBlurHash(
|
||||||
content.blurhash,
|
content.blurhash,
|
||||||
content.description,
|
content.description,
|
||||||
contentScale,
|
contentScale,
|
||||||
Modifier,
|
loadedImageModifier,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
@@ -267,10 +250,10 @@ fun LocalImageView(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
is AsyncImagePainter.State.Error -> {
|
is AsyncImagePainter.State.Error -> {
|
||||||
BlankNote()
|
BlankNote(loadedImageModifier)
|
||||||
}
|
}
|
||||||
is AsyncImagePainter.State.Success -> {
|
is AsyncImagePainter.State.Success -> {
|
||||||
SubcomposeAsyncImageContent()
|
SubcomposeAsyncImageContent(loadedImageModifier)
|
||||||
|
|
||||||
content.isVerified?.let {
|
content.isVerified?.let {
|
||||||
AnimatedVisibility(
|
AnimatedVisibility(
|
||||||
@@ -294,7 +277,7 @@ fun LocalImageView(
|
|||||||
content.blurhash,
|
content.blurhash,
|
||||||
content.description,
|
content.description,
|
||||||
ContentScale.Crop,
|
ContentScale.Crop,
|
||||||
mainImageModifier
|
loadedImageModifier
|
||||||
.aspectRatio(ratio)
|
.aspectRatio(ratio)
|
||||||
.clickable { showImage.value = true },
|
.clickable { showImage.value = true },
|
||||||
)
|
)
|
||||||
@@ -310,7 +293,7 @@ fun LocalImageView(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
BlankNote()
|
BlankNote(loadedImageModifier)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -318,6 +301,7 @@ fun LocalImageView(
|
|||||||
fun UrlImageView(
|
fun UrlImageView(
|
||||||
content: MediaUrlImage,
|
content: MediaUrlImage,
|
||||||
mainImageModifier: Modifier,
|
mainImageModifier: Modifier,
|
||||||
|
loadedImageModifier: Modifier,
|
||||||
isFiniteHeight: Boolean,
|
isFiniteHeight: Boolean,
|
||||||
controllerVisible: MutableState<Boolean>,
|
controllerVisible: MutableState<Boolean>,
|
||||||
accountViewModel: AccountViewModel,
|
accountViewModel: AccountViewModel,
|
||||||
@@ -351,14 +335,14 @@ fun UrlImageView(
|
|||||||
content.blurhash,
|
content.blurhash,
|
||||||
content.description,
|
content.description,
|
||||||
ContentScale.Crop,
|
ContentScale.Crop,
|
||||||
Modifier.aspectRatio(ratio),
|
loadedImageModifier.aspectRatio(ratio),
|
||||||
)
|
)
|
||||||
} else {
|
} else {
|
||||||
DisplayBlurHash(
|
DisplayBlurHash(
|
||||||
content.blurhash,
|
content.blurhash,
|
||||||
content.description,
|
content.description,
|
||||||
ContentScale.Crop,
|
ContentScale.Crop,
|
||||||
Modifier,
|
loadedImageModifier,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
@@ -369,7 +353,7 @@ fun UrlImageView(
|
|||||||
ClickableUrl(urlText = "${content.url} ", url = content.url)
|
ClickableUrl(urlText = "${content.url} ", url = content.url)
|
||||||
}
|
}
|
||||||
is AsyncImagePainter.State.Success -> {
|
is AsyncImagePainter.State.Success -> {
|
||||||
SubcomposeAsyncImageContent()
|
SubcomposeAsyncImageContent(loadedImageModifier)
|
||||||
|
|
||||||
AnimatedVisibility(
|
AnimatedVisibility(
|
||||||
visible = controllerVisible.value,
|
visible = controllerVisible.value,
|
||||||
@@ -391,7 +375,7 @@ fun UrlImageView(
|
|||||||
content.blurhash,
|
content.blurhash,
|
||||||
content.description,
|
content.description,
|
||||||
ContentScale.Crop,
|
ContentScale.Crop,
|
||||||
mainImageModifier
|
loadedImageModifier
|
||||||
.aspectRatio(ratio)
|
.aspectRatio(ratio)
|
||||||
.clickable { showImage.value = true },
|
.clickable { showImage.value = true },
|
||||||
)
|
)
|
||||||
|
@@ -688,7 +688,7 @@ class RichTextParserTest {
|
|||||||
fun testTextToParse() {
|
fun testTextToParse() {
|
||||||
val state =
|
val state =
|
||||||
RichTextParser()
|
RichTextParser()
|
||||||
.parseText(textToParse, EmptyTagList)
|
.parseText(textToParse, EmptyTagList, null)
|
||||||
org.junit.Assert.assertEquals(
|
org.junit.Assert.assertEquals(
|
||||||
"relay.shitforce.one, relayable.org, universe.nostrich.land, nos.lol, universe.nostrich.land?lang=zh, universe.nostrich.land?lang=en, relay.damus.io, relay.nostr.wirednet.jp, offchain.pub, nostr.rocks, relay.wellorder.net, nostr.oxtr.dev, universe.nostrich.land?lang=ja, relay.mostr.pub, nostr.bitcoiner.social, Nostr-Check.com, MR.Rabbit, Ancap.su, ⚡\uFE0Fsatscoinsv@getalby.com, miceliomad@miceliomad.github.io/nostr/, zapper.lol, smies.me, baller.hodl",
|
"relay.shitforce.one, relayable.org, universe.nostrich.land, nos.lol, universe.nostrich.land?lang=zh, universe.nostrich.land?lang=en, relay.damus.io, relay.nostr.wirednet.jp, offchain.pub, nostr.rocks, relay.wellorder.net, nostr.oxtr.dev, universe.nostrich.land?lang=ja, relay.mostr.pub, nostr.bitcoiner.social, Nostr-Check.com, MR.Rabbit, Ancap.su, ⚡\uFE0Fsatscoinsv@getalby.com, miceliomad@miceliomad.github.io/nostr/, zapper.lol, smies.me, baller.hodl",
|
||||||
state.urlSet.joinToString(", "),
|
state.urlSet.joinToString(", "),
|
||||||
@@ -4037,7 +4037,7 @@ class RichTextParserTest {
|
|||||||
fun testShortTextToParse() {
|
fun testShortTextToParse() {
|
||||||
val state =
|
val state =
|
||||||
RichTextParser()
|
RichTextParser()
|
||||||
.parseText("Hi, how are you doing? ", EmptyTagList)
|
.parseText("Hi, how are you doing? ", EmptyTagList, null)
|
||||||
org.junit.Assert.assertTrue(state.urlSet.isEmpty())
|
org.junit.Assert.assertTrue(state.urlSet.isEmpty())
|
||||||
org.junit.Assert.assertTrue(state.imagesForPager.isEmpty())
|
org.junit.Assert.assertTrue(state.imagesForPager.isEmpty())
|
||||||
org.junit.Assert.assertTrue(state.imageList.isEmpty())
|
org.junit.Assert.assertTrue(state.imageList.isEmpty())
|
||||||
@@ -4051,7 +4051,7 @@ class RichTextParserTest {
|
|||||||
@Test
|
@Test
|
||||||
fun testShortNewLinesTextToParse() {
|
fun testShortNewLinesTextToParse() {
|
||||||
val state =
|
val state =
|
||||||
RichTextParser().parseText("\nHi, \nhow\n\n\n are you doing? \n", EmptyTagList)
|
RichTextParser().parseText("\nHi, \nhow\n\n\n are you doing? \n", EmptyTagList, null)
|
||||||
org.junit.Assert.assertTrue(state.urlSet.isEmpty())
|
org.junit.Assert.assertTrue(state.urlSet.isEmpty())
|
||||||
org.junit.Assert.assertTrue(state.imagesForPager.isEmpty())
|
org.junit.Assert.assertTrue(state.imagesForPager.isEmpty())
|
||||||
org.junit.Assert.assertTrue(state.imageList.isEmpty())
|
org.junit.Assert.assertTrue(state.imageList.isEmpty())
|
||||||
@@ -4076,7 +4076,7 @@ class RichTextParserTest {
|
|||||||
|
|
||||||
val state =
|
val state =
|
||||||
RichTextParser()
|
RichTextParser()
|
||||||
.parseText(text, EmptyTagList)
|
.parseText(text, EmptyTagList, null)
|
||||||
org.junit.Assert.assertEquals(
|
org.junit.Assert.assertEquals(
|
||||||
"https://lnshort.it/live-stream-embeds/",
|
"https://lnshort.it/live-stream-embeds/",
|
||||||
state.urlSet.firstOrNull(),
|
state.urlSet.firstOrNull(),
|
||||||
@@ -4153,7 +4153,7 @@ class RichTextParserTest {
|
|||||||
|
|
||||||
val state =
|
val state =
|
||||||
RichTextParser()
|
RichTextParser()
|
||||||
.parseText(text, EmptyTagList)
|
.parseText(text, EmptyTagList, null)
|
||||||
|
|
||||||
printStateForDebug(state)
|
printStateForDebug(state)
|
||||||
|
|
||||||
@@ -4186,7 +4186,7 @@ class RichTextParserTest {
|
|||||||
|
|
||||||
val state =
|
val state =
|
||||||
RichTextParser()
|
RichTextParser()
|
||||||
.parseText(text, EmptyTagList)
|
.parseText(text, EmptyTagList, null)
|
||||||
|
|
||||||
printStateForDebug(state)
|
printStateForDebug(state)
|
||||||
|
|
||||||
@@ -4211,13 +4211,46 @@ class RichTextParserTest {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun testAVif() {
|
||||||
|
val text =
|
||||||
|
"Goon Night everybody :sleep:\n" +
|
||||||
|
"81ca16-b665-4f57-80cb-11a58461fb61.avif\n" +
|
||||||
|
"\n" +
|
||||||
|
"https://bae.st/media/66b08dde784287ed8f92c455bc62076a04671ccb44097550626a532185a5d3ed.avif?name=81ca16-b665-4f57-80cb-11a58461fb61.avif"
|
||||||
|
|
||||||
|
val state =
|
||||||
|
RichTextParser()
|
||||||
|
.parseText(text, EmptyTagList, null)
|
||||||
|
|
||||||
|
printStateForDebug(state)
|
||||||
|
|
||||||
|
val expectedResult =
|
||||||
|
listOf<String>(
|
||||||
|
"RegularText(Goon Night everybody :sleep:)",
|
||||||
|
"Image(81ca16-b665-4f57-80cb-11a58461fb61.avif)",
|
||||||
|
"RegularText()",
|
||||||
|
"Image(https://bae.st/media/66b08dde784287ed8f92c455bc62076a04671ccb44097550626a532185a5d3ed.avif?name=81ca16-b665-4f57-80cb-11a58461fb61.avif)",
|
||||||
|
)
|
||||||
|
|
||||||
|
state.paragraphs
|
||||||
|
.map { it.words }
|
||||||
|
.flatten()
|
||||||
|
.forEachIndexed { index, seg ->
|
||||||
|
org.junit.Assert.assertEquals(
|
||||||
|
expectedResult[index],
|
||||||
|
"${seg.javaClass.simpleName.replace("Segment", "")}(${seg.segmentText})",
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun testUrlsEndingInPeriod() {
|
fun testUrlsEndingInPeriod() {
|
||||||
val text = "That’s it! http://vitorpamplona.com/. That’s the note"
|
val text = "That’s it! http://vitorpamplona.com/. That’s the note"
|
||||||
|
|
||||||
val state =
|
val state =
|
||||||
RichTextParser()
|
RichTextParser()
|
||||||
.parseText(text, EmptyTagList)
|
.parseText(text, EmptyTagList, null)
|
||||||
|
|
||||||
printStateForDebug(state)
|
printStateForDebug(state)
|
||||||
|
|
||||||
@@ -4258,7 +4291,7 @@ class RichTextParserTest {
|
|||||||
"https://misskey.io/play/9g3qza4jow"
|
"https://misskey.io/play/9g3qza4jow"
|
||||||
|
|
||||||
val state =
|
val state =
|
||||||
RichTextParser().parseText(text, ImmutableListOfLists(tags))
|
RichTextParser().parseText(text, ImmutableListOfLists(tags), null)
|
||||||
|
|
||||||
printStateForDebug(state)
|
printStateForDebug(state)
|
||||||
|
|
||||||
|
@@ -318,7 +318,7 @@ class RichTextParser() {
|
|||||||
"^((http|https)://)?([A-Za-z0-9-_]+(\\.[A-Za-z0-9-_]+)+)(:[0-9]+)?(/[^?#]*)?(\\?[^#]*)?(#.*)?"
|
"^((http|https)://)?([A-Za-z0-9-_]+(\\.[A-Za-z0-9-_]+)+)(:[0-9]+)?(/[^?#]*)?(\\?[^#]*)?(#.*)?"
|
||||||
.toRegex(RegexOption.IGNORE_CASE)
|
.toRegex(RegexOption.IGNORE_CASE)
|
||||||
|
|
||||||
val imageExtensions = listOf("png", "jpg", "gif", "bmp", "jpeg", "webp", "svg")
|
val imageExtensions = listOf("png", "jpg", "gif", "bmp", "jpeg", "webp", "svg", "avif")
|
||||||
val videoExtensions = listOf("mp4", "avi", "wmv", "mpg", "amv", "webm", "mov", "mp3", "m3u8")
|
val videoExtensions = listOf("mp4", "avi", "wmv", "mpg", "amv", "webm", "mov", "mp3", "m3u8")
|
||||||
|
|
||||||
val tagIndex = Pattern.compile("\\#\\[([0-9]+)\\](.*)")
|
val tagIndex = Pattern.compile("\\#\\[([0-9]+)\\](.*)")
|
||||||
|
@@ -88,7 +88,7 @@ class ClassifiedsEvent(
|
|||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
const val KIND = 30402
|
const val KIND = 30402
|
||||||
private val imageExtensions = listOf("png", "jpg", "gif", "bmp", "jpeg", "webp", "svg")
|
private val imageExtensions = listOf("png", "jpg", "gif", "bmp", "jpeg", "webp", "svg", "avif")
|
||||||
const val ALT = "Classifieds listing"
|
const val ALT = "Classifieds listing"
|
||||||
|
|
||||||
fun create(
|
fun create(
|
||||||
|
Reference in New Issue
Block a user