@@ -57,6 +57,7 @@ import androidx.compose.foundation.layout.fillMaxWidth
5757import androidx.compose.foundation.layout.padding
5858import androidx.compose.foundation.lazy.LazyRow
5959import androidx.compose.foundation.lazy.items
60+ import androidx.compose.runtime.remember
6061import androidx.compose.foundation.shape.CircleShape
6162import androidx.compose.material3.LocalMinimumInteractiveComponentSize
6263import androidx.compose.material3.MaterialTheme
@@ -535,11 +536,6 @@ class MangaHeaderHolder(
535536 }
536537
537538 private fun setGenreTags (binding : MangaHeaderItemBinding , manga : Manga ) {
538- // EH/EXH sources show namespace-grouped tags in metadata section instead
539- if (manga.source == EH_SOURCE_ID || manga.source == EXH_SOURCE_ID ) {
540- binding.mangaGenresTags.isVisible = false
541- return
542- }
543539 val genres = if (manga.genre.isNullOrBlank()) emptyList() else (manga.getGenres() ? : emptyList())
544540 val delegate = adapter.delegate
545541 val context = binding.root.context
@@ -574,17 +570,29 @@ class MangaHeaderHolder(
574570 if (dark) 0.945f else 0.175f ,
575571 ),
576572 )
573+ val isEH = manga.source == EH_SOURCE_ID || manga.source == EXH_SOURCE_ID
577574 binding.mangaGenresTags.setViewCompositionStrategy(ViewCompositionStrategy .DisposeOnDetachedFromWindow )
578575 binding.mangaGenresTags.setContent {
579576 yokai.presentation.theme.YokaiTheme {
580- GenreTagsSection (
581- genres = genres,
582- containerColor = ComposeColor (containerColorInt),
583- labelColor = ComposeColor (labelColorInt),
584- isExpanded = descriptionExpandedState.value,
585- onTagClick = { genre -> delegate.searchFromMetadata(genre) },
586- onTagLongClick = { genre -> delegate.copyContentToClipboard(genre, genre) },
587- )
577+ if (isEH) {
578+ NamespaceGenreTagsSection (
579+ genres = genres,
580+ containerColor = ComposeColor (containerColorInt),
581+ labelColor = ComposeColor (labelColorInt),
582+ isExpanded = descriptionExpandedState.value,
583+ onTagClick = { genre -> delegate.searchFromMetadata(genre) },
584+ onTagLongClick = { genre -> delegate.copyContentToClipboard(genre, genre) },
585+ )
586+ } else {
587+ GenreTagsSection (
588+ genres = genres,
589+ containerColor = ComposeColor (containerColorInt),
590+ labelColor = ComposeColor (labelColorInt),
591+ isExpanded = descriptionExpandedState.value,
592+ onTagClick = { genre -> delegate.searchFromMetadata(genre) },
593+ onTagLongClick = { genre -> delegate.copyContentToClipboard(genre, genre) },
594+ )
595+ }
588596 }
589597 }
590598 }
@@ -817,3 +825,90 @@ private fun GenreTagsSection(
817825 }
818826 }
819827}
828+
829+ @OptIn(ExperimentalFoundationApi ::class )
830+ @Composable
831+ private fun NamespaceGenreTagsSection (
832+ genres : List <String >,
833+ containerColor : ComposeColor ,
834+ labelColor : ComposeColor ,
835+ isExpanded : Boolean ,
836+ onTagClick : (String ) -> Unit ,
837+ onTagLongClick : (String ) -> Unit ,
838+ ) {
839+ // Parse "namespace: name" format into grouped map
840+ val grouped = remember(genres) {
841+ val map = linkedMapOf<String , MutableList <String >>()
842+ genres.forEach { genre ->
843+ val colonIdx = genre.indexOf(" : " )
844+ if (colonIdx > 0 ) {
845+ val ns = genre.substring(0 , colonIdx)
846+ val name = genre.substring(colonIdx + 2 )
847+ map.getOrPut(ns) { mutableListOf () }.add(name)
848+ } else {
849+ map.getOrPut(" " ) { mutableListOf () }.add(genre)
850+ }
851+ }
852+ map.toMap()
853+ }
854+
855+ @Composable
856+ fun Chip (text : String , searchQuery : String , isLabel : Boolean = false) {
857+ CompositionLocalProvider (LocalMinimumInteractiveComponentSize provides 0 .dp) {
858+ Surface (
859+ modifier = Modifier
860+ .padding(vertical = 4 .dp)
861+ .combinedClickable(
862+ onClick = { if (! isLabel) onTagClick(searchQuery) },
863+ onLongClick = { if (! isLabel) onTagLongClick(text) },
864+ ),
865+ shape = CircleShape ,
866+ color = if (isLabel) ComposeColor .Transparent else containerColor,
867+ border = if (isLabel) {
868+ BorderStroke (1 .dp, labelColor.copy(alpha = 0.5f ))
869+ } else {
870+ BorderStroke (1 .dp, MaterialTheme .colorScheme.outline)
871+ },
872+ ) {
873+ Text (
874+ modifier = Modifier .padding(horizontal = 8 .dp, vertical = 4 .dp),
875+ text = text,
876+ style = MaterialTheme .typography.bodySmall,
877+ color = labelColor,
878+ maxLines = 1 ,
879+ overflow = TextOverflow .Ellipsis ,
880+ )
881+ }
882+ }
883+ }
884+
885+ Column (modifier = Modifier .padding(horizontal = 16 .dp)) {
886+ if (isExpanded) {
887+ grouped.forEach { (namespace, tags) ->
888+ FlowRow (horizontalArrangement = Arrangement .spacedBy(4 .dp)) {
889+ if (namespace.isNotEmpty()) {
890+ Chip (text = namespace, searchQuery = " " , isLabel = true )
891+ }
892+ tags.forEach { tag ->
893+ val searchQuery = if (namespace.isNotEmpty()) " $namespace :\" $tag \" " else tag
894+ Chip (text = tag, searchQuery = searchQuery)
895+ }
896+ }
897+ }
898+ } else {
899+ LazyRow (horizontalArrangement = Arrangement .spacedBy(4 .dp)) {
900+ grouped.forEach { (namespace, tags) ->
901+ if (namespace.isNotEmpty()) {
902+ item(key = " ns_$namespace " ) {
903+ Chip (text = namespace, searchQuery = " " , isLabel = true )
904+ }
905+ }
906+ items(tags, key = { " ${namespace} _$it " }) { tag ->
907+ val searchQuery = if (namespace.isNotEmpty()) " $namespace :\" $tag \" " else tag
908+ Chip (text = tag, searchQuery = searchQuery)
909+ }
910+ }
911+ }
912+ }
913+ }
914+ }
0 commit comments