package ac.mdiq.podcini.ui.screens

import ac.mdiq.podcini.PodciniApp.Companion.getAppContext
import ac.mdiq.podcini.R
import ac.mdiq.podcini.activity.MainActivity
import ac.mdiq.podcini.config.settings.DocumentFileExportWorker
import ac.mdiq.podcini.config.settings.ExportTypes
import ac.mdiq.podcini.config.settings.ExportWorker
import ac.mdiq.podcini.config.settings.OpmlTransporter.OpmlWriter
import ac.mdiq.podcini.net.feed.FeedUpdateManager.checkAndScheduleUpdateTaskOnce
import ac.mdiq.podcini.net.feed.FeedUpdateManager.runOnce
import ac.mdiq.podcini.net.feed.FeedUpdateManager.runOnceOrAsk
import ac.mdiq.podcini.net.sync.transceive.CatalogReceiver
import ac.mdiq.podcini.net.sync.transceive.ContentType
import ac.mdiq.podcini.net.sync.transceive.EpisodesReceiver
import ac.mdiq.podcini.net.sync.transceive.FeedReceiver
import ac.mdiq.podcini.net.sync.transceive.Receiver
import ac.mdiq.podcini.net.sync.transceive.broadcastPresence
import ac.mdiq.podcini.net.sync.transceive.sendCatalog
import ac.mdiq.podcini.net.sync.transceive.sendFeed
import ac.mdiq.podcini.net.utils.NetworkUtils.getLocalIpAddress
import ac.mdiq.podcini.shared.getEntityId
import ac.mdiq.podcini.storage.database.appAttribs
import ac.mdiq.podcini.storage.database.feedCount
import ac.mdiq.podcini.storage.database.feedOperationText
import ac.mdiq.podcini.storage.database.loadLocalFolder
import ac.mdiq.podcini.storage.database.queuesFlow
import ac.mdiq.podcini.storage.database.queuesLive
import ac.mdiq.podcini.storage.database.realm
import ac.mdiq.podcini.storage.database.runOnIOScope
import ac.mdiq.podcini.storage.database.upsert
import ac.mdiq.podcini.storage.database.upsertBlk
import ac.mdiq.podcini.storage.model.Episode
import ac.mdiq.podcini.storage.model.Feed
import ac.mdiq.podcini.storage.model.SubscriptionsPrefs
import ac.mdiq.podcini.storage.model.Volume
import ac.mdiq.podcini.storage.model.deleteVolumeTree
import ac.mdiq.podcini.storage.specs.EpisodeFilter
import ac.mdiq.podcini.storage.specs.EpisodeState
import ac.mdiq.podcini.storage.specs.FeedFilter
import ac.mdiq.podcini.storage.specs.Rating
import ac.mdiq.podcini.storage.utils.AddLocalFolder
import ac.mdiq.podcini.storage.utils.durationInHours
import ac.mdiq.podcini.storage.utils.durationStringFull
import ac.mdiq.podcini.storage.utils.durationStringShort
import ac.mdiq.podcini.storage.utils.findRootForUri
import ac.mdiq.podcini.storage.utils.persistedTrees
import ac.mdiq.podcini.storage.utils.toSafeUri
import ac.mdiq.podcini.ui.compose.CommonConfirmAttrib
import ac.mdiq.podcini.ui.compose.CommonPopupCard
import ac.mdiq.podcini.ui.compose.CustomTextStyles
import ac.mdiq.podcini.ui.compose.PutToQueueDialog
import ac.mdiq.podcini.ui.compose.RemoveFeedDialog
import ac.mdiq.podcini.ui.compose.AmendSyntheticFeed
import ac.mdiq.podcini.ui.compose.ScrollRowGrid
import ac.mdiq.podcini.ui.compose.SelectLowerAllUpper
import ac.mdiq.podcini.ui.compose.SendToDevice
import ac.mdiq.podcini.ui.compose.Spinner
import ac.mdiq.podcini.ui.compose.TagSettingDialog
import ac.mdiq.podcini.ui.compose.TagType
import ac.mdiq.podcini.ui.compose.borderColor
import ac.mdiq.podcini.ui.compose.buttonColor
import ac.mdiq.podcini.ui.compose.commonConfirm
import ac.mdiq.podcini.ui.compose.complementaryColorOf
import ac.mdiq.podcini.ui.compose.filterChipBorder
import ac.mdiq.podcini.ui.compose.textColor
import ac.mdiq.podcini.utils.Logd
import ac.mdiq.podcini.utils.Loge
import ac.mdiq.podcini.utils.Logs
import ac.mdiq.podcini.utils.Logt
import ac.mdiq.podcini.utils.formatDateTimeFlex
import ac.mdiq.podcini.utils.formatWithGrouping
import ac.mdiq.podcini.utils.timeIt
import android.annotation.SuppressLint
import android.app.Activity.RESULT_OK
import android.content.ActivityNotFoundException
import android.content.ContextWrapper
import android.content.Intent
import android.net.Uri
import android.view.Gravity
import androidx.activity.compose.BackHandler
import androidx.activity.compose.rememberLauncherForActivityResult
import androidx.activity.result.ActivityResult
import androidx.activity.result.ActivityResultLauncher
import androidx.activity.result.contract.ActivityResultContracts
import androidx.compose.foundation.BorderStroke
import androidx.compose.foundation.background
import androidx.compose.foundation.border
import androidx.compose.foundation.clickable
import androidx.compose.foundation.combinedClickable
import androidx.compose.foundation.horizontalScroll
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.FlowRow
import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.aspectRatio
import androidx.compose.foundation.layout.fillMaxHeight
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.heightIn
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.layout.statusBarsPadding
import androidx.compose.foundation.layout.width
import androidx.compose.foundation.layout.widthIn
import androidx.compose.foundation.layout.wrapContentHeight
import androidx.compose.foundation.layout.wrapContentSize
import androidx.compose.foundation.layout.wrapContentWidth
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.grid.GridCells
import androidx.compose.foundation.lazy.grid.LazyVerticalGrid
import androidx.compose.foundation.lazy.grid.items
import androidx.compose.foundation.lazy.grid.rememberLazyGridState
import androidx.compose.foundation.lazy.items
import androidx.compose.foundation.lazy.rememberLazyListState
import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.foundation.text.KeyboardOptions
import androidx.compose.foundation.verticalScroll
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.Edit
import androidx.compose.material.icons.filled.Menu
import androidx.compose.material.icons.filled.MoreVert
import androidx.compose.material3.AlertDialog
import androidx.compose.material3.Button
import androidx.compose.material3.Checkbox
import androidx.compose.material3.DropdownMenu
import androidx.compose.material3.DropdownMenuItem
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.FilterChip
import androidx.compose.material3.FloatingActionButton
import androidx.compose.material3.HorizontalDivider
import androidx.compose.material3.Icon
import androidx.compose.material3.IconButton
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.OutlinedButton
import androidx.compose.material3.Scaffold
import androidx.compose.material3.Surface
import androidx.compose.material3.Text
import androidx.compose.material3.TextButton
import androidx.compose.material3.TextField
import androidx.compose.material3.pulltorefresh.PullToRefreshBox
import androidx.compose.runtime.Composable
import androidx.compose.runtime.DisposableEffect
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableIntStateOf
import androidx.compose.runtime.mutableStateListOf
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.runtime.rememberUpdatedState
import androidx.compose.runtime.setValue
import androidx.compose.runtime.snapshotFlow
import androidx.compose.runtime.toMutableStateList
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.scale
import androidx.compose.ui.graphics.BlendMode
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.ColorFilter
import androidx.compose.ui.graphics.lerp
import androidx.compose.ui.graphics.vector.ImageVector
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.platform.LocalView
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.res.vectorResource
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.text.input.KeyboardType
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.unit.dp
import androidx.compose.ui.window.Dialog
import androidx.compose.ui.window.DialogProperties
import androidx.compose.ui.window.DialogWindowProvider
import androidx.constraintlayout.compose.ConstraintLayout
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.LifecycleEventObserver
import androidx.lifecycle.ViewModel
import androidx.lifecycle.compose.LocalLifecycleOwner
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import androidx.lifecycle.viewModelScope
import androidx.lifecycle.viewmodel.compose.viewModel
import androidx.lifecycle.viewmodel.initializer
import androidx.lifecycle.viewmodel.viewModelFactory
import coil3.compose.AsyncImage
import coil3.request.CachePolicy
import coil3.request.ImageRequest
import io.github.xilinjia.krdb.ext.toRealmSet
import io.github.xilinjia.krdb.query.RealmResults
import io.github.xilinjia.krdb.query.Sort
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.Job
import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.flatMapLatest
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.stateIn
import kotlinx.coroutines.launch
import kotlinx.datetime.LocalDate
import kotlinx.datetime.TimeZone
import kotlinx.datetime.format
import kotlinx.datetime.format.char
import kotlinx.datetime.toLocalDateTime
import kotlin.time.Clock
import kotlin.time.Duration.Companion.milliseconds

private const val TAG = "LibraryScreen"

var feedIdsToUse by mutableStateOf<List<Long>>(listOf())

class LibraryVM : ViewModel() {
    var subPrefs by mutableStateOf( realm.query(SubscriptionsPrefs::class).query("id == 0").first().find() ?: SubscriptionsPrefs().apply { this.queueSelIds.add(0L) })

//    val prefsFlow = realm.query(SubscriptionsPrefs::class).query("id == 0").first().asFlow().map { it.obj }
//        .distinctUntilChanged().stateIn(scope = viewModelScope, started = SharingStarted.WhileSubscribed(5_000), initialValue = subPrefs)

    var showAllFeeds by mutableStateOf(feedIdsToUse.isNotEmpty())
    var isViewGarden by mutableStateOf(feedIdsToUse.isNotEmpty())   // TODO, check

    val subVolumesFlow: StateFlow<List<Volume>> = snapshotFlow { Pair(curVolume?.id, subPrefs.showArchived) }.flatMapLatest {
        val qStrArchive = if (curVolume == null) ( if (subPrefs.showArchived) "" else "AND id >= -1" ) else ""
        Logd(TAG, "getVolumesFlow qStrArchive: $qStrArchive")
        val realmFlow = realm.query(Volume::class).query("parentId == ${curVolume?.id ?: -1L} $qStrArchive").sort("name").asFlow()
        realmFlow.map { it.list }
    }.distinctUntilChanged().stateIn(scope = viewModelScope, started = SharingStarted.WhileSubscribed(5_000), initialValue = emptyList())

    var curVolume by mutableStateOf<Volume?>(null)

    var queueNames by mutableStateOf<List<String>>(listOf())
    var queueIds by mutableStateOf<List<Long>>(listOf())

    var playStateQueries by mutableStateOf("")
    var ratingQueries by mutableStateOf("")
    var downloadedQuery by mutableStateOf("")
    var commentedQuery by mutableStateOf("")

    fun preparePropertySort(feeds: List<Feed>, subIndex: FeedPropertySortIndex? = null) {
        val subIndexOrdinal = subIndex?.ordinal ?: subPrefs.propertySortIndex
        Logd(TAG, "preparePropertySort subIndexOrdinal: $subIndexOrdinal")
        runOnIOScope {
            realm.write {
                for (f_ in feeds) {
                    val f = findLatest(f_) ?: continue
                    Logd(TAG, "preparePropertySort f: ${f.title}")
                    f.sortInfo = when(subIndexOrdinal) {
                        FeedPropertySortIndex.Rating.ordinal -> Rating.fromCode(f.rating).name
                        FeedPropertySortIndex.Score.ordinal -> "${f.score}(${f.scoreCount})"
                        FeedPropertySortIndex.ScoreCount.ordinal -> "${f.score}(${f.scoreCount})"
                        FeedPropertySortIndex.Updated.ordinal -> formatDateTimeFlex(f.lastUpdateTime)
                        FeedPropertySortIndex.FullUpdate.ordinal -> formatDateTimeFlex(f.lastFullUpdateTime)
                        FeedPropertySortIndex.TotleDuration.ordinal -> durationStringShort(f.totleDuration, true)
                        FeedPropertySortIndex.Commented.ordinal -> formatDateTimeFlex(f.commentTime)
                        else -> formatDateTimeFlex(f.lastUpdateTime)
                    }
                }
            }
            subPrefs = upsert(subPrefs) {
                it.sortIndex = FeedSortIndex.Feed.ordinal
                it.propertySortIndex = subIndexOrdinal
                it.sortProperty =
                    when(subIndexOrdinal) {
                        FeedPropertySortIndex.Title.ordinal -> "eigenTitle"
                        FeedPropertySortIndex.Author.ordinal -> "author"
                        FeedPropertySortIndex.Rating.ordinal -> "rating"
                        FeedPropertySortIndex.Score.ordinal -> "score"
                        FeedPropertySortIndex.ScoreCount.ordinal -> "scoreCount"
                        FeedPropertySortIndex.Updated.ordinal -> "lastUpdateTime"
                        FeedPropertySortIndex.FullUpdate.ordinal -> "lastFullUpdateTime"
                        FeedPropertySortIndex.TotleDuration.ordinal -> "totleDuration"
                        FeedPropertySortIndex.Commented.ordinal -> "commentTime"
                        else -> "eigenTitle"
                    }
                it.feedsSortedInc()
            }
        }
    }
    fun prepareDateSort(feeds: List<Feed>, subIndex: FeedDateSortIndex? = null) {
        val subIndexOrdinal = subIndex?.ordinal ?: subPrefs.dateSortIndex
        Logd(TAG, "prepareDateSort")
        suspend fun persistDateSort() {
            subPrefs = upsert(subPrefs) {
                it.sortIndex = FeedSortIndex.Date.ordinal
                it.dateSortIndex = subIndexOrdinal
                it.sortProperty = "sortValue"
                it.feedsSortedInc()
            }
        }
        runOnIOScope {
            when (subIndexOrdinal) {
                FeedDateSortIndex.Publish.ordinal -> {  // date publish
                    var queryString = "feedId == $0"
                    if (playStateQueries.isNotEmpty()) queryString += " AND ($playStateQueries)"
                    queryString += " SORT(pubDate DESC)"
                    Logd(TAG, "prepareSort queryString: $queryString")
                    realm.write {
                        for (f_ in feeds) {
                            val f = findLatest(f_) ?: continue
                            val d = query(Episode::class).query(queryString, f.id).first().find()?.pubDate ?: 0L
                            f.sortValue = d
                            f.sortInfo = formatDateTimeFlex(d)
                        }
                    }
                    persistDateSort()
                }
                FeedDateSortIndex.Downloaded.ordinal -> {  // date downloaded
                    val queryString = "feedId == $0 SORT(downloadTime DESC)"
                    realm.write {
                        for (f_ in feeds) {
                            val f = findLatest(f_) ?: continue
                            val d = query(Episode::class).query(queryString, f.id).first().find()?.downloadTime ?: 0L
                            f.sortValue = d
                            f.sortInfo = "D: ${formatDateTimeFlex(d)}"
                        }
                    }
                    Logd(TAG, "prepareSort queryString: $queryString")
                    persistDateSort()
                }
                FeedDateSortIndex.Played.ordinal -> {  // date last played
                    val queryString = "feedId == $0 SORT(lastPlayedTime DESC)"
                    realm.write {
                        for (f_ in feeds) {
                            val f = findLatest(f_) ?: continue
                            val d = query(Episode::class).query(queryString, f.id).first().find()?.lastPlayedTime ?: 0L
                            f.sortValue = d
                            f.sortInfo = "P: ${formatDateTimeFlex(d)}"
                        }
                    }
                    Logd(TAG, "prepareSort queryString: $queryString")
                    persistDateSort()
                }
                FeedDateSortIndex.Commented.ordinal -> {  // date last commented
                    val queryString = "feedId == $0 SORT(commentTime DESC)"
                    realm.write {
                        for (f_ in feeds) {
                            val f = findLatest(f_) ?: continue
                            val d = query(Episode::class).query(queryString, f.id).first().find()?.commentTime ?: 0L
                            f.sortValue = d
                            f.sortInfo = "C: ${formatDateTimeFlex(d)}"
                        }
                    }
                    Logd(TAG, "prepareSort queryString: $queryString")
                    persistDateSort()
                }
                else -> Loge(TAG, "No such date sorting ${subPrefs.dateSortIndex}")
            }
        }
    }

    fun prepareTimeSort(feeds: List<Feed>, subIndex: FeedTimeSortIndex? = null) {
        val subIndexOrdinal = subIndex?.ordinal ?: subPrefs.timeSortIndex
        Logd(TAG, "prepareTimeSort")
        suspend fun persistTimeSort() {
            subPrefs = upsert(subPrefs) {
                it.sortIndex = FeedSortIndex.Time.ordinal
                it.timeSortIndex = subIndexOrdinal
                it.sortProperty = "sortValue"
                //                it.timeAscending = !it.timeAscending
                it.feedsSortedInc()
            }
        }
        runOnIOScope {
            when (subIndexOrdinal) {
                FeedTimeSortIndex.Total.ordinal -> { // total duration
                    realm.write {
                        for (f_ in feeds) {
                            val f = findLatest(f_) ?: continue
                            f.sortValue = f.totleDuration
                            f.sortInfo = "Total D: ${durationStringShort(f.totleDuration, true)}"
                        }
                    }
                    persistTimeSort()
                }
                FeedTimeSortIndex.Min.ordinal -> {  // min duration
                    val queryString = "feedId == $0 SORT(duration ASC)"
                    realm.write {
                        for (f_ in feeds) {
                            val f = findLatest(f_) ?: continue
                            val d = query(Episode::class).query(queryString, f.id).first().find()?.duration?.toLong() ?: 0L
                            f.sortValue = d
                            f.sortInfo = "Min D: ${durationStringFull(d.toInt())}"
                        }
                    }
                    Logd(TAG, "prepareSort queryString: $queryString")
                    persistTimeSort()
                }
                FeedTimeSortIndex.Max.ordinal -> {  // max duration
                    val queryString = "feedId == $0 SORT(duration DESC)"
                    realm.write {
                        for (f_ in feeds) {
                            val f = findLatest(f_) ?: continue
                            val d = query(Episode::class).query(queryString, f.id).first().find()?.duration?.toLong() ?: 0L
                            f.sortValue = d
                            f.sortInfo = "Max D: ${durationStringShort(d, true)}"
                        }
                    }
                    Logd(TAG, "prepareSort queryString: $queryString")
                    persistTimeSort()
                }
                FeedTimeSortIndex.Average.ordinal -> {  // average duration
                    realm.write {
                        for (f_ in feeds) {
                            val f = findLatest(f_) ?: continue
                            val ln = f.episodesCount
                            val aveDur = if (ln > 0) f.totleDuration/ln else 0
                            f.sortValue = aveDur
                            f.sortInfo = "Ave D: ${durationStringShort(aveDur, true)}"
                        }
                    }
                    persistTimeSort()
                }
                else -> Loge(TAG, "No such time sorting ${subPrefs.timeSortIndex}")
            }
        }
    }

    fun prepareCountSort(feeds: List<Feed>) {
        Logd(TAG, "prepareCountSort")
        runOnIOScope {
            val sb = StringBuilder("feedId == $0")
            if (playStateQueries.isNotEmpty()) sb.append(" AND ($playStateQueries)")
            if (ratingQueries.isNotEmpty()) sb.append(" AND ($ratingQueries)")
            if (downloadedQuery.isNotEmpty()) sb.append(" AND ($downloadedQuery)")
            if (commentedQuery.isNotEmpty()) sb.append(" AND ($commentedQuery)")
            val queryString = sb.toString()
            Logd(TAG, "prepareCountSort queryString: $queryString")
            realm.write {
                for (f_ in feeds) {
                    val f = findLatest(f_) ?: continue
                    val c = query(Episode::class).query(queryString, f.id).count().find()
                    f.sortValue = c
                    f.sortInfo = "Sort value: $c"
                }
            }
            subPrefs = upsert(subPrefs) {
                it.sortIndex = FeedSortIndex.Count.ordinal
                it.sortProperty = "sortValue"
                //                it.countAscending = !it.countAscending
                it.feedsSortedInc()
            }
        }
    }

    suspend fun feedsRealmFlows(): Flow<RealmResults<Feed>> {
        Logd(TAG, "feedsRealmFlows subPrefs.sortProperty: ${subPrefs.sortProperty}")
        fun languagesQS() : String {
            var qrs  = ""
            when {
                subPrefs.langsSel.isEmpty() -> qrs = " (langSet.@count > 0) "
                subPrefs.langsSel.size == appAttribs.langSet.size -> qrs = ""
                else -> {
                    for (l in subPrefs.langsSel) qrs += if (qrs.isEmpty()) " ( ANY langSet == '$l' " else " OR ANY langSet == '$l' "
                    if (qrs.isNotEmpty()) qrs += " ) "
                }
            }
            Logd(TAG, "languagesQS: $qrs")
            return qrs
        }
        fun tagsQS() : String {
            var qrs  = ""
            when {
//                subPrefs.tagsSel.isEmpty() -> qrs = " (tags.@count == 0 OR (tags.@count != 0 AND ALL tags == $TAG_ROOT )) "
                subPrefs.tagsSel.isEmpty() -> qrs = " (tags.@count == 0) "
                subPrefs.tagsSel.size == appAttribs.feedTagSet.size -> qrs = ""
                else -> {
                    for (t in subPrefs.tagsSel) {
                        qrs += if (qrs.isEmpty()) " ( ANY tags == '$t' " else " OR ANY tags == '$t' "
                    }
                    if (qrs.isNotEmpty()) qrs += " ) "
                }
            }
            Logd(TAG, "tagsQS: $qrs")
            return qrs
        }
        fun queuesQS() : String {
            val qSelIds_ = subPrefs.queueSelIds.toMutableSet()
            if (qSelIds_.isEmpty()) qSelIds_.add(-2L)
            else {
                if ((queueIds - qSelIds_).isEmpty()) qSelIds_.clear()
                else qSelIds_.remove(-2L)
            }
            var qrs  = ""
            for (id in qSelIds_) qrs += if (qrs.isEmpty()) " ( queueId == '$id' " else " OR queueId == '$id' "
            if (qrs.isNotEmpty()) qrs += " ) "
            Logd(TAG, "queuesQS: $qrs")
            return qrs
        }

        val sortPair = Pair(subPrefs.sortProperty.ifBlank { "eigenTitle" }, if (subPrefs.sortDirCode == 0) Sort.ASCENDING else Sort.DESCENDING)

        if (feedIdsToUse.isEmpty()) {
            val sb = StringBuilder(FeedFilter(subPrefs.feedsFilter).queryString())
            val langsStr = languagesQS()
            if (langsStr.isNotEmpty()) sb.append(" AND $langsStr")
            val tagsStr = tagsQS()
            if (tagsStr.isNotEmpty()) sb.append(" AND $tagsStr")
            val queuesStr = queuesQS()
            if (queuesStr.isNotEmpty()) sb.append(" AND $queuesStr")
            if (!subPrefs.showArchived && curVolume?.id == -1L && !showAllFeeds) sb.append(" AND volumeId >= -1 ")

            val fetchQS = sb.toString()
            Logd(TAG, "fetchQS: $fetchQS ${sortPair.first} ${sortPair.second.name}")

            val realmFlow = if (showAllFeeds) realm.query(Feed::class).query(fetchQS).sort(sortPair).asFlow()
            else realm.query(Feed::class).query("volumeId == ${curVolume?.id ?: -1L}").query(fetchQS).sort(sortPair).asFlow()

            return realmFlow.map { it.list }
        } else return realm.query(Feed::class).query("id IN $0", feedIdsToUse).sort(sortPair).asFlow().map  { it.list}
    }

    data class FeedsFlowkeys(
        val id: Long?,
        val showAllFeeds: Boolean,
        val feedsFiltered: Int,
        val feedsSorted: Int,
        val showArchived: Boolean
    )

    val feedsFlow: StateFlow<List<Feed>> = snapshotFlow { Pair(feedIdsToUse.size, FeedsFlowkeys(curVolume?.id, showAllFeeds, subPrefs.feedsFiltered, subPrefs.feedsSorted, subPrefs.showArchived)) }
        .distinctUntilChanged().flatMapLatest {
            Logd(TAG, "feedsFlow ${feedIdsToUse.size} ${curVolume?.id} $showAllFeeds ${subPrefs.feedsFiltered} ${subPrefs.feedsSorted} ${subPrefs.showArchived}")
            feedsRealmFlows() }
        .distinctUntilChanged().stateIn(scope = viewModelScope, started = SharingStarted.WhileSubscribed(5_000), initialValue = emptyList())

    init {
        Logd(TAG, "vm init")
        timeIt("$TAG start of vm init")

        viewModelScope.launch {
            snapshotFlow { feedIdsToUse.size }.distinctUntilChanged().collect {
                showAllFeeds = feedIdsToUse.isNotEmpty()
                isViewGarden = feedIdsToUse.isNotEmpty()
            }
        }

        viewModelScope.launch(Dispatchers.IO) {
            queuesFlow.collect { changes ->
                Logd(TAG, "queuesFlow.collect")
                queueIds = changes.list.map { it.id }
                queueNames = changes.list.map { it.name }
            }
        }

        viewModelScope.launch(Dispatchers.IO) {
            snapshotFlow { subPrefs.playStateCodeSet }.distinctUntilChanged().collect {
                val sb = StringBuilder()
                subPrefs.playStateCodeSet.forEach {
                    if (sb.isNotEmpty()) sb.append(" OR ")
                    sb.append(" playState == $it ")
                }
                playStateQueries = sb.toString()
            }
        }
        viewModelScope.launch(Dispatchers.IO) {
            snapshotFlow { subPrefs.ratingCodeSet }.distinctUntilChanged().collect {
                val sb = StringBuilder()
                subPrefs.ratingCodeSet.forEach {
                    if (sb.isNotEmpty()) sb.append(" OR ")
                    sb.append(" rating == $it ")
                }
                ratingQueries = sb.toString()
            }
        }
        viewModelScope.launch(Dispatchers.IO) {
            snapshotFlow { subPrefs.downlaodedSortIndex }.distinctUntilChanged().collect {
                downloadedQuery = when (subPrefs.downlaodedSortIndex) {
                    0 -> " fileUrl != nil "
                    1 -> " fileUrl == nil "
                    else -> ""
                }
            }
        }
        viewModelScope.launch(Dispatchers.IO) {
            snapshotFlow { subPrefs.commentedSortIndex }.distinctUntilChanged().collect {
                commentedQuery = when (subPrefs.commentedSortIndex) {
                    0 -> " comment != '' "
                    1 -> " comment == '' "
                    else -> ""
                }
            }
        }

        timeIt("$TAG end of vm init")
    }

    override fun onCleared() {
        Logd(TAG, "VM onCleared")
        curVolume = null
        queueIds = listOf()
        queueNames = listOf()
        feedIdsToUse = listOf()
    }
}

@ExperimentalMaterial3Api
@Composable
fun LibraryScreen() {
    val lifecycleOwner = LocalLifecycleOwner.current
    val scope = rememberCoroutineScope()
    val context by rememberUpdatedState(LocalContext.current)
    val drawerController = LocalDrawerController.current

//    val muteColor = MaterialTheme.colorScheme.onSurfaceVariant
    
    val buttonAltColor = lerp(MaterialTheme.colorScheme.tertiary, Color.Green, 0.5f)

//    val vm: LibraryVM = viewModel()

//    val scopedOwner = remember(feedIdsToUse) { object : ViewModelStoreOwner { override val viewModelStore = ViewModelStore(); } }
//    DisposableEffect(scopedOwner) { onDispose { scopedOwner.viewModelStore.clear() } }
//    val vm: LibraryVM = viewModel(viewModelStoreOwner = scopedOwner, factory = viewModelFactory { initializer { LibraryVM() } })

    val vm: LibraryVM = viewModel(key = feedIdsToUse.hashCode().toString(), factory = viewModelFactory { initializer { LibraryVM() } })

    val connectLocalFolderLauncher: ActivityResultLauncher<Uri?> = rememberLauncherForActivityResult(contract = AddLocalFolder()) { uri: Uri? ->
        if (uri == null) return@rememberLauncherForActivityResult
        persistedTrees.add(uri)
        getAppContext().contentResolver.takePersistableUriPermission(uri, Intent.FLAG_GRANT_READ_URI_PERMISSION)
    }

    var showFilterDialog by remember { mutableStateOf(false) }
    var showSortDialog by remember { mutableStateOf(false) }
    var showNewSynthetic by remember { mutableStateOf(false) }
    var showNewVolume by remember { mutableStateOf(false) }
    var selectMode by remember { mutableStateOf(false) }

    var showReceiverDialog by remember { mutableStateOf(false) }
    var showSendCatalogDialog by remember { mutableStateOf(false) }
    var showToDeviceDialog by remember { mutableStateOf(false) }
    var showRemoveFeedDialog by remember { mutableStateOf(false) }
    var showChooseRatingDialog by remember { mutableStateOf(false) }
    var showAssociateDialog by remember { mutableStateOf(false) }
    var showToVolumeDialog by remember { mutableStateOf(false) }
    var showTagsSettingDialog by remember { mutableStateOf(false) }

    var episodesToQueue by remember { mutableStateOf<List<Episode>>(listOf()) }
    if (episodesToQueue.isNotEmpty()) PutToQueueDialog(episodesToQueue.toList()) { episodesToQueue = listOf() }
    var volumeToOperate by remember { mutableStateOf<Volume?>(null) }
    var showEditVolume by remember { mutableStateOf(false) }

    var isFeedsOptionsExpanded by remember { mutableStateOf(false) }
    fun exitSelectMode() {
        isFeedsOptionsExpanded = false
        selectMode = false
    }

    val feedsSelected = remember { mutableStateListOf<Feed>() }
    val feedsOptionsMap = remember { linkedMapOf<String, @Composable ()->Unit>(
        "AddToQueue" to { Row(verticalAlignment = Alignment.CenterVertically, modifier = Modifier.padding(horizontal = 16.dp).clickable {
            exitSelectMode()
            episodesToQueue = realm.query(Episode::class).query("feedId IN $0", feedsSelected.map { it.id }).limit(200).find()
        }) {
            Icon(imageVector = ImageVector.vectorResource(id = R.drawable.outline_playlist_add_24), "Add to queue")
            Text(stringResource(id = R.string.add_to_queue) + " (max 200)") } },
        "SetRating" to { Row(verticalAlignment = Alignment.CenterVertically, modifier = Modifier.padding(horizontal = 16.dp).clickable {
            exitSelectMode()
            showChooseRatingDialog = true
        }) {
            Icon(imageVector = ImageVector.vectorResource(id = R.drawable.ic_star), "Set rating")
            Text(stringResource(id = R.string.set_rating_label)) } },
        "AssociatedQueue" to { Row(verticalAlignment = Alignment.CenterVertically, modifier = Modifier.padding(horizontal = 16.dp).clickable {
            showAssociateDialog = true
            exitSelectMode()
        }) {
            Icon(imageVector = ImageVector.vectorResource(id = R.drawable.ic_playlist_play), "associated queue")
            Text(stringResource(id = R.string.pref_feed_associated_queue)) } },
        "ParentVolume" to { Row(verticalAlignment = Alignment.CenterVertically, modifier = Modifier.padding(horizontal = 16.dp).clickable {
            showToVolumeDialog = true
            exitSelectMode()
        }) {
            Icon(imageVector = ImageVector.vectorResource(id = R.drawable.rounded_books_movies_and_music_24), "set parent volume", modifier = Modifier.height(24.dp))
            Text(stringResource(id = R.string.pref_parent_volume)) } },
        "FullSettings" to { Row(verticalAlignment = Alignment.CenterVertically, modifier = Modifier.padding(horizontal = 16.dp).clickable {
            feedsToSet = feedsSelected
            navTo(FeedsSettings)
            exitSelectMode()
        }) {
            Icon(imageVector = ImageVector.vectorResource(id = R.drawable.ic_settings), "full settings")
            Text(stringResource(id = R.string.full_settings)) } },
        "SendToDevice" to { Row(verticalAlignment = Alignment.CenterVertically, modifier = Modifier.padding(horizontal = 16.dp).clickable {
            showToDeviceDialog = true
            exitSelectMode()
        }) {
            Icon(imageVector = ImageVector.vectorResource(id = R.drawable.baseline_offline_share_24), "send to device")
            Text(stringResource(id = R.string.transfer_to_device)) } },
        "RemoveFeeds" to { Row(verticalAlignment = Alignment.CenterVertically, modifier = Modifier.padding(horizontal = 16.dp).clickable {
            showRemoveFeedDialog = true
            exitSelectMode()
        }) {
            Icon(imageVector = ImageVector.vectorResource(id = R.drawable.ic_delete), "remove feed")
            Text(stringResource(id = R.string.remove_feed_label)) } },
        "OPMLExport" to { Row(verticalAlignment = Alignment.CenterVertically, modifier = Modifier.padding(horizontal = 16.dp).clickable {
            exitSelectMode()
            val exportType = ExportTypes.OPML_SELECTED
            val ExportDateFormat = LocalDate.Format {
                year()
                char('-')
                monthNumber()
                char('-')
                day()
            }
            fun getExportFileName(outputNameTemplate: String): String {
                val today = Clock.System.now().toLocalDateTime(TimeZone.currentSystemDefault()).date
                val dateString = today.format(ExportDateFormat)
                return outputNameTemplate.replace("%s", dateString)
            }
            val title = getExportFileName(exportType.outputNameTemplate)
            val intentPickAction = Intent(Intent.ACTION_CREATE_DOCUMENT)
                .addCategory(Intent.CATEGORY_OPENABLE)
                .setType(exportType.contentType)
                .putExtra(Intent.EXTRA_TITLE, title)
            try {
                val actMain: MainActivity? = generateSequence(context) { if (it is ContextWrapper) it.baseContext else null }.filterIsInstance<MainActivity>().firstOrNull()
                actMain?.registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { result: ActivityResult ->
                    if (result.resultCode != RESULT_OK || result.data == null) return@registerForActivityResult
                    val uri = result.data!!.data
                    runOnIOScope {
                        try {
                            Logd(TAG, "selectedFeeds: ${feedsSelected.size}")
                            if (uri == null) ExportWorker(OpmlWriter()).exportFile(feedsSelected)
                            else {
                                val worker = DocumentFileExportWorker(OpmlWriter(), uri)
                                worker.exportFile(feedsSelected)
                            }
                        } catch (e: Exception) { Loge(TAG, "exportOPML error: ${e.message}") }
                    }
                }?.launch(intentPickAction)
                return@clickable
            } catch (e: ActivityNotFoundException) { Loge(TAG, "No activity found. Should never happen...") }
        }) {
            Icon(imageVector = ImageVector.vectorResource(id = R.drawable.baseline_import_export_24), "")
            Text(stringResource(id = R.string.opml_export_label)) } }
    ) }

//    val prefsState by vm.prefsFlow.collectAsStateWithLifecycle()
//    if (prefsState != null) vm.subPrefs = prefsState!!

    val feedList by vm.feedsFlow.collectAsStateWithLifecycle()
    val volumes by vm.subVolumesFlow.collectAsStateWithLifecycle()

    DisposableEffect(lifecycleOwner) {
        val observer = LifecycleEventObserver { _, event ->
            Logd(TAG, "DisposableEffect Lifecycle.Event: $event")
            when (event) {
                Lifecycle.Event.ON_CREATE -> {}
                Lifecycle.Event.ON_START -> {
//                    runOnIOScope { extensionClient.withProvider { it.cleanData() } }
                }
                Lifecycle.Event.ON_RESUME -> {}
                Lifecycle.Event.ON_STOP -> {}
                Lifecycle.Event.ON_DESTROY -> {}
                else -> {}
            }
        }
        lifecycleOwner.lifecycle.addObserver(observer)
        onDispose {
            Logd(TAG, "Lifecycle.Event onDispose")
            lifecycleOwner.lifecycle.removeObserver(observer)
        }
    }

    DisposableEffect(vm.curVolume) {
        if (vm.curVolume != null) handleBackSubScreens.add(TAG)
        else handleBackSubScreens.remove(TAG)
        onDispose { handleBackSubScreens.remove(TAG) }
    }

    BackHandler(enabled = handleBackSubScreens.contains(TAG)) { vm.curVolume = realm.query(Volume::class).query("id == ${vm.curVolume?.parentId ?: -1L}").first().find() }

    LaunchedEffect(vm.subPrefs.sortIndex, feedOperationText, feedList.size, vm.curVolume?.id) {
        Logd(TAG, "combine(feedsFlow, snapshotFlow {feedOperationText}) sortIndex: ${vm.subPrefs.sortIndex}")
        if (feedOperationText.isBlank()) when (vm.subPrefs.sortIndex) {
            FeedSortIndex.Feed.ordinal -> vm.preparePropertySort(feedList)
            FeedSortIndex.Date.ordinal -> vm.prepareDateSort(feedList)
            FeedSortIndex.Time.ordinal -> vm.prepareTimeSort(feedList)
            FeedSortIndex.Count.ordinal -> vm.prepareCountSort(feedList)
            else -> {}
        }
    }

    @Composable
    fun TopBar() {
        var expanded by remember { mutableStateOf(false) }
        val isFiltered = remember(vm.subPrefs.feedsFilter, vm.subPrefs.tagsSel.size, vm.subPrefs.langsSel.size, vm.subPrefs.queueSelIds.size) { vm.subPrefs.feedsFilter.isNotEmpty() || vm.subPrefs.tagsSel.size != appAttribs.feedTagSet.size || vm.subPrefs.langsSel.size != appAttribs.langSet.size || vm.subPrefs.queueSelIds.size != vm.queueIds.size }
        Row(modifier = Modifier.fillMaxWidth().statusBarsPadding().padding(start = 8.dp), verticalAlignment = Alignment.CenterVertically) {
            Icon(imageVector = ImageVector.vectorResource(R.drawable.ic_subscriptions), contentDescription = "Open Drawer", modifier = Modifier.padding(end = 10.dp).clickable { drawerController?.open() })
            if (feedOperationText.isNotEmpty()) Text(feedOperationText, color = MaterialTheme.colorScheme.onSurfaceVariant, style = MaterialTheme.typography.bodyMedium, modifier = Modifier.clickable {})
            else {
                var feedCountState by remember(feedList.size, feedCount) { mutableStateOf("${feedList.size}/$feedCount") }
                Text(feedCountState, maxLines=1, style = MaterialTheme.typography.titleSmall, fontWeight = FontWeight.Bold, color = textColor, modifier = Modifier.scale(scaleX = 1f, scaleY = 1.8f))
            }
            Spacer(Modifier.weight(1f))
            if (!vm.isViewGarden) IconButton(onClick = { navTo(Search) }) { Icon(imageVector = ImageVector.vectorResource(R.drawable.ic_search), contentDescription = "search") }
            if (!vm.isViewGarden) IconButton(onClick = { showFilterDialog = true }) { Icon(imageVector = ImageVector.vectorResource(R.drawable.ic_filter), tint = if (isFiltered) buttonAltColor else MaterialTheme.colorScheme.onSurface, contentDescription = "filter") }
            IconButton(onClick = { showSortDialog = true }) { Icon(imageVector = ImageVector.vectorResource(R.drawable.arrows_sort), contentDescription = "sort") }
            if (!vm.isViewGarden) IconButton(onClick = {
                facetsCustomTag = "Subscriptions"
                facetsCustomQuery = realm.query(Episode::class).query("feedId IN $0", feedList.map { it.id })
                navTo(Facets(modeName = QuickAccess.Custom.name))
            }) { Icon(imageVector = ImageVector.vectorResource(R.drawable.baseline_view_in_ar_24), contentDescription = "facets") }
            Box(modifier = Modifier.wrapContentSize(Alignment.TopEnd)) {
                IconButton(onClick = { expanded = true }) { Icon(Icons.Default.MoreVert, contentDescription = "Menu") }
                DropdownMenu(expanded = expanded, border = BorderStroke(1.dp, borderColor), onDismissRequest = { expanded = false }) {
                    DropdownMenuItem(text = { Text(stringResource(R.string.toggle_grid_list)) }, onClick = {
                        runOnIOScope { vm.subPrefs = upsert(vm.subPrefs) { it.prefFeedGridLayout = !it.prefFeedGridLayout } }
                        expanded = false
                    })
                    if (!vm.isViewGarden) {
                        fun toggleAllFeeds() {
                            vm.curVolume = null
                            vm.showAllFeeds = !vm.showAllFeeds
                        }
                        DropdownMenuItem(text = {
                            Row(verticalAlignment = Alignment.CenterVertically) {
                                Text(stringResource(R.string.show_all_feeds))
                                Checkbox(checked = vm.showAllFeeds, onCheckedChange = { toggleAllFeeds() })
                            }
                        }, onClick = { toggleAllFeeds() })
                        DropdownMenuItem(text = { Text(stringResource(R.string.new_volume_label)) }, onClick = {
                            showNewVolume = true
                            expanded = false
                        })
                        DropdownMenuItem(text = { Text(stringResource(R.string.new_synth_label)) }, onClick = {
                            showNewSynthetic = true
                            expanded = false
                        })
                        DropdownMenuItem(text = { Text(stringResource(R.string.add_feed_label)) }, onClick = {
                            navTo(FindFeeds)
                            expanded = false
                        })
                        DropdownMenuItem(text = { Text(stringResource(R.string.receive_contents)) }, onClick = {
                            showReceiverDialog = true
                            expanded = false
                        })
                        // TODO
                        //                        DropdownMenuItem(text = { Text(stringResource(R.string.send_catalog)) }, onClick = {
                        //                            showSendCatalogDialog = true
                        //                            expanded = false
                        //                        })
                    }
                    DropdownMenuItem(text = { Text(stringResource(R.string.full_refresh_label)) }, onClick = {
                        if (vm.curVolume == null) runOnceOrAsk(fullUpdate = true, removeUnlisted = true)
                        else {
                            if (vm.curVolume!!.isLocal) {
                                runOnIOScope {
                                    var uri = vm.curVolume!!.uriString.toSafeUri()
                                    val rootUri =  findRootForUri(uri)
                                    if (rootUri != null && uri != rootUri) {
                                        Logt(TAG, "Loading from root folder: $rootUri")
                                        uri = rootUri
                                    }
                                    loadLocalFolder(uri, vm.curVolume!!.allFeeds.filter { it.isLocal })
                                }
                            } else runOnceOrAsk(vm.curVolume!!.allFeeds, fullUpdate = true, doItWanyway = vm.curVolume!!.isNormal, removeUnlisted = true)
                        }
                        expanded = false
                    })
                    if (!vm.isViewGarden) {
                        fun toggleArchived() {
                            vm.subPrefs = upsertBlk(vm.subPrefs) { it.showArchived = !it.showArchived }
                            expanded = false
                        }
                        DropdownMenuItem(text = {
                            Row(verticalAlignment = Alignment.CenterVertically) {
                                Text(stringResource(R.string.show_archived))
                                Checkbox(checked = vm.subPrefs.showArchived, onCheckedChange = { toggleArchived() })
                            }
                        }, onClick = { toggleArchived() })
                    }
                }
            }
        }
    }

    @Composable
    fun OpenDialogs() {
        @Composable
        fun SortDialog(onDismiss: () -> Unit) {
            Dialog(properties = DialogProperties(usePlatformDefaultWidth = false), onDismissRequest = { onDismiss() }) {
                val dialogWindowProvider = LocalView.current.parent as? DialogWindowProvider
                dialogWindowProvider?.window?.setGravity(Gravity.BOTTOM)
                Surface(modifier = Modifier.fillMaxWidth().padding(top = 10.dp, bottom = 10.dp).height(350.dp), color = MaterialTheme.colorScheme.surface.copy(alpha = 0.8f), shape = RoundedCornerShape(16.dp), border = BorderStroke(1.dp, borderColor)) {
                    Column(Modifier.fillMaxSize().verticalScroll(rememberScrollState())) {
                        Row(Modifier.fillMaxWidth().horizontalScroll(rememberScrollState())) {
                            OutlinedButton(modifier = Modifier.padding(5.dp), elevation = null, border = BorderStroke(2.dp, if (vm.subPrefs.sortIndex != FeedSortIndex.Feed.ordinal) borderColor else buttonAltColor),
                                onClick = {
                                    if (vm.subPrefs.sortIndex == FeedSortIndex.Feed.ordinal)
                                        runOnIOScope {
                                            vm.subPrefs = upsert(vm.subPrefs) {
                                                if (it.sortIndex == FeedSortIndex.Feed.ordinal) {
                                                    it.propertyAscending = !it.propertyAscending
                                                    it.sortDirCode = if (it.propertyAscending) 0 else 1
                                                } else {
                                                    it.sortIndex = FeedSortIndex.Feed.ordinal
                                                    it.sortProperty = "eigenTitle"
                                                }
                                                it.feedsSortedInc()
                                            }
                                        }
                                    else vm.preparePropertySort(feedList)
                                }
                            ) { Text(text = stringResource(FeedSortIndex.Feed.res) + if (vm.subPrefs.propertyAscending) "\u00A0▲" else "\u00A0▼", color = textColor) }
                            OutlinedButton(modifier = Modifier.padding(5.dp), elevation = null, border = BorderStroke(2.dp, if (vm.subPrefs.sortIndex != FeedSortIndex.Date.ordinal) borderColor else buttonAltColor),
                                onClick = {
                                    if (vm.subPrefs.sortIndex == FeedSortIndex.Date.ordinal)
                                        runOnIOScope {
                                            vm.subPrefs = upsert(vm.subPrefs) {
                                                it.dateAscending = !it.dateAscending
                                                it.sortDirCode = if (it.dateAscending) 0 else 1
                                                it.feedsSortedInc()
                                            }
                                        }
                                    else vm.prepareDateSort(feedList)
                                }
                            ) { Text(text = stringResource(FeedSortIndex.Date.res) + if (vm.subPrefs.dateAscending) "\u00A0▲" else "\u00A0▼", color = textColor) }
                            OutlinedButton(modifier = Modifier.padding(5.dp), elevation = null, border = BorderStroke(2.dp, if (vm.subPrefs.sortIndex != FeedSortIndex.Time.ordinal) borderColor else buttonAltColor),
                                onClick = {
                                    if (vm.subPrefs.sortIndex == FeedSortIndex.Time.ordinal)
                                        runOnIOScope {
                                            vm.subPrefs = upsert(vm.subPrefs) {
                                                it.timeAscending = !it.timeAscending
                                                it.sortDirCode = if (it.timeAscending) 0 else 1
                                                it.feedsSortedInc()
                                            }
                                        }
                                    else vm.prepareTimeSort(feedList)
                                }
                            ) { Text(text = stringResource(FeedSortIndex.Time.res) + if (vm.subPrefs.timeAscending) "\u00A0▲" else "\u00A0▼", color = textColor) }
                            OutlinedButton(modifier = Modifier.padding(5.dp), elevation = null, border = BorderStroke(2.dp, if (vm.subPrefs.sortIndex != FeedSortIndex.Count.ordinal) borderColor else buttonAltColor),
                                onClick = {
                                    if (vm.subPrefs.sortIndex == FeedSortIndex.Count.ordinal)
                                        runOnIOScope {
                                            vm.subPrefs = upsert(vm.subPrefs) {
                                                it.countAscending = !it.countAscending
                                                it.sortDirCode = if (it.countAscending) 0 else 1
                                                it.feedsSortedInc()
                                            }
                                        }
                                    else vm.prepareCountSort(feedList)
                                }
                            ) { Text(text = stringResource(FeedSortIndex.Count.res) + if (vm.subPrefs.countAscending) "\u00A0▲" else "\u00A0▼", color = textColor) }
                        }
                        HorizontalDivider(color = MaterialTheme.colorScheme.onTertiaryContainer, thickness = 1.dp)
                        when (vm.subPrefs.sortIndex) {
                            FeedSortIndex.Feed.ordinal -> {
                                FlowRow(horizontalArrangement = Arrangement.spacedBy(10.dp), modifier = Modifier.padding(10.dp)) {
                                    for (sd in FeedPropertySortIndex.entries) {
                                        OutlinedButton(modifier = Modifier.padding(5.dp), elevation = null, border = BorderStroke(2.dp, if (vm.subPrefs.propertySortIndex != sd.ordinal) borderColor else buttonAltColor), onClick = { if (vm.subPrefs.propertySortIndex != sd.ordinal) vm.preparePropertySort(feedList, sd) }) { Text(stringResource(sd.res)) }
                                    }
                                }
                            }
                            FeedSortIndex.Date.ordinal -> {
                                FlowRow(horizontalArrangement = Arrangement.spacedBy(10.dp), modifier = Modifier.padding(10.dp)) {
                                    for (sd in FeedDateSortIndex.entries) {
                                        OutlinedButton(modifier = Modifier.padding(5.dp), elevation = null, border = BorderStroke(2.dp, if (vm.subPrefs.dateSortIndex != sd.ordinal) borderColor else buttonAltColor), onClick = { if (vm.subPrefs.dateSortIndex != sd.ordinal) vm.prepareDateSort(feedList,sd) }) { Text(stringResource(sd.res)) }
                                    }
                                }
                            }
                            FeedSortIndex.Time.ordinal -> {
                                FlowRow(horizontalArrangement = Arrangement.spacedBy(10.dp), modifier = Modifier.padding(10.dp)) {
                                    for (sd in FeedTimeSortIndex.entries) {
                                        OutlinedButton(modifier = Modifier.padding(5.dp), elevation = null, border = BorderStroke(2.dp, if (vm.subPrefs.timeSortIndex != sd.ordinal) borderColor else buttonAltColor), onClick = { if (vm.subPrefs.timeSortIndex != sd.ordinal) vm.prepareTimeSort(feedList, sd) }) { Text(stringResource(sd.res)) }
                                    }
                                }
                            }
                            else -> {
                                HorizontalDivider(color = MaterialTheme.colorScheme.onTertiaryContainer, thickness = 1.dp)
                                Column(modifier = Modifier.padding(start = 5.dp, bottom = 2.dp).fillMaxWidth()) {
                                    if (vm.subPrefs.sortIndex == FeedSortIndex.Count.ordinal) {
                                        Row(modifier = Modifier.padding(2.dp).fillMaxWidth(), horizontalArrangement = Arrangement.Center, verticalAlignment = Alignment.CenterVertically) {
                                            val item = EpisodeFilter.EpisodesFilterGroup.DOWNLOADED
                                            Text(stringResource(item.nameRes) + " :", fontWeight = FontWeight.Bold, style = MaterialTheme.typography.headlineSmall, color = textColor, modifier = Modifier.padding(end = 10.dp))
                                            Spacer(Modifier.weight(0.3f))
                                            fun persistDLSort(i: Int) {
                                                runOnIOScope {
                                                    val downlaodedSortIndex = if (vm.subPrefs.downlaodedSortIndex != i) i else -1
                                                    vm.subPrefs = upsert(vm.subPrefs) { it.downlaodedSortIndex = downlaodedSortIndex }
                                                    vm.downloadedQuery = when (downlaodedSortIndex) {
                                                        0 -> " fileUrl != nil "
                                                        1 -> " fileUrl == nil "
                                                        else -> ""
                                                    }
                                                    vm.prepareCountSort(feedList)
                                                }
                                            }
                                            OutlinedButton(
                                                modifier = Modifier.padding(0.dp), border = BorderStroke(2.dp, if (vm.subPrefs.downlaodedSortIndex != 0) borderColor else buttonAltColor),
                                                onClick = { persistDLSort(0) },
                                            ) { Text(text = stringResource(item.properties[0].displayName), color = textColor) }
                                            Spacer(Modifier.weight(0.1f))
                                            OutlinedButton(
                                                modifier = Modifier.padding(0.dp), border = BorderStroke(2.dp, if (vm.subPrefs.downlaodedSortIndex != 1) borderColor else buttonAltColor),
                                                onClick = { persistDLSort(1) },
                                            ) { Text(text = stringResource(item.properties[1].displayName), color = textColor) }
                                            Spacer(Modifier.weight(0.5f))
                                        }
                                        Row(modifier = Modifier.padding(2.dp).fillMaxWidth(), horizontalArrangement = Arrangement.Center, verticalAlignment = Alignment.CenterVertically) {
                                            val item = EpisodeFilter.EpisodesFilterGroup.OPINION
                                            Text(stringResource(item.nameRes) + " :", fontWeight = FontWeight.Bold, style = MaterialTheme.typography.headlineSmall, color = textColor, modifier = Modifier.padding(end = 10.dp))
                                            Spacer(Modifier.weight(0.3f))
                                            fun persistCommentSort(i: Int) {
                                                runOnIOScope {
                                                    val commentedSortIndex = if (vm.subPrefs.commentedSortIndex != i) i else -1
                                                    vm.subPrefs = upsert(vm.subPrefs) { it.commentedSortIndex = commentedSortIndex }
                                                    vm.commentedQuery = when (vm.subPrefs.commentedSortIndex) {
                                                        0 -> " comment != '' "
                                                        1 -> " comment == '' "
                                                        else -> ""
                                                    }
                                                    vm.prepareCountSort(feedList)
                                                }
                                            }
                                            OutlinedButton(
                                                modifier = Modifier.padding(0.dp), border = BorderStroke(2.dp, if (vm.subPrefs.commentedSortIndex != 0) borderColor else buttonAltColor),
                                                onClick = { persistCommentSort(0) },
                                            ) { Text(text = stringResource(item.properties[0].displayName), color = textColor) }
                                            Spacer(Modifier.weight(0.1f))
                                            OutlinedButton(
                                                modifier = Modifier.padding(0.dp), border = BorderStroke(2.dp, if (vm.subPrefs.commentedSortIndex != 1) borderColor else buttonAltColor),
                                                onClick = { persistCommentSort(1) },
                                            ) { Text(text = stringResource(item.properties[1].displayName), color = textColor) }
                                            Spacer(Modifier.weight(0.5f))
                                        }
                                    }
                                    val episodeStateSort = remember { MutableList(EpisodeState.entries.size) { mutableStateOf(false) } }
                                    val ratingSort = remember { MutableList(Rating.entries.size) { mutableStateOf(false) } }
                                    if ((vm.subPrefs.sortIndex == FeedSortIndex.Date.ordinal && vm.subPrefs.dateSortIndex == FeedDateSortIndex.Publish.ordinal) || vm.subPrefs.sortIndex == FeedSortIndex.Count.ordinal) {
                                        var allOrNone by remember { mutableStateOf(false) }
                                        LaunchedEffect(Unit) {
                                            for (i in episodeStateSort.indices) {
                                                if (EpisodeState.entries[i].code.toString() in vm.subPrefs.playStateCodeSet) episodeStateSort[i].value = true
                                            }
                                            val c = episodeStateSort.count { it.value }
                                            allOrNone = c == 0 || c == episodeStateSort.size
                                        }
                                        fun persistStateSort() {
                                            runOnIOScope {
                                                val sb = StringBuilder()
                                                val playStateCodeSet = mutableSetOf<String>()
                                                for (i in episodeStateSort.indices) {
                                                    if (episodeStateSort[i].value) {
                                                        playStateCodeSet.add(EpisodeState.entries[i].code.toString())
                                                        if (sb.isNotEmpty()) sb.append(" OR ")
                                                        sb.append(" playState == ${EpisodeState.entries[i].code} ")
                                                    }
                                                }
                                                vm.playStateQueries = sb.toString()
                                                vm.subPrefs = upsert(vm.subPrefs) {
                                                    it.playStateCodeSet.clear()
                                                    it.playStateCodeSet.addAll(playStateCodeSet)
                                                }
                                                when (vm.subPrefs.sortIndex) {
                                                    FeedSortIndex.Date.ordinal -> vm.prepareDateSort(feedList)
                                                    FeedSortIndex.Count.ordinal -> vm.prepareCountSort(feedList)
                                                    else -> {}
                                                }
                                            }
                                        }

                                        val item = EpisodeFilter.EpisodesFilterGroup.PLAY_STATE
                                        var expandRow by remember { mutableStateOf(false) }
                                        Row {
                                            Text(stringResource(item.nameRes) + "… :", fontWeight = FontWeight.Bold, style = MaterialTheme.typography.headlineSmall, color = if (allOrNone) buttonColor else buttonAltColor, modifier = Modifier.clickable { expandRow = !expandRow })
                                            if (expandRow) {
                                                var lowerSelected by remember { mutableStateOf(false) }
                                                var higherSelected by remember { mutableStateOf(false) }
                                                Spacer(Modifier.weight(1f))
                                                Text("<<<", color = if (lowerSelected) buttonAltColor else buttonColor, style = MaterialTheme.typography.headlineSmall, modifier = Modifier.clickable {
                                                    runOnIOScope {
                                                        val hIndex = episodeStateSort.indexOfLast { it.value }
                                                        if (hIndex < 0) return@runOnIOScope
                                                        if (!lowerSelected) {
                                                            for (i in 0..hIndex) episodeStateSort[i].value = true
                                                        } else {
                                                            for (i in 0..hIndex) episodeStateSort[i].value = false
                                                            episodeStateSort[hIndex].value = true
                                                        }
                                                        val c = episodeStateSort.count { it.value }
                                                        allOrNone = c == 0 || c == episodeStateSort.size
                                                        lowerSelected = !lowerSelected
                                                        persistStateSort()
                                                    }
                                                })
                                                Spacer(Modifier.weight(1f))
                                                Text("A", color = buttonColor, style = MaterialTheme.typography.headlineSmall, modifier = Modifier.clickable {
                                                    runOnIOScope {
                                                        val selectAll = !(lowerSelected && higherSelected)
                                                        lowerSelected = selectAll
                                                        higherSelected = selectAll
                                                        for (i in item.properties.indices) episodeStateSort[i].value = selectAll
                                                        val c = episodeStateSort.count { it.value }
                                                        allOrNone = c == 0 || c == episodeStateSort.size
                                                        persistStateSort()
                                                    }
                                                })
                                                Spacer(Modifier.weight(1f))
                                                Text(">>>", color = if (higherSelected) buttonAltColor else buttonColor, style = MaterialTheme.typography.headlineSmall, modifier = Modifier.clickable {
                                                    runOnIOScope {
                                                        val lIndex = episodeStateSort.indexOfFirst { it.value }
                                                        if (lIndex < 0) return@runOnIOScope
                                                        if (!higherSelected) {
                                                            for (i in lIndex..<item.properties.size) episodeStateSort[i].value = true
                                                        } else {
                                                            for (i in lIndex..<item.properties.size) episodeStateSort[i].value = false
                                                            episodeStateSort[lIndex].value = true
                                                        }
                                                        val c = episodeStateSort.count { it.value }
                                                        allOrNone = c == 0 || c == episodeStateSort.size
                                                        higherSelected = !higherSelected
                                                        persistStateSort()
                                                    }
                                                })
                                            }
                                            Spacer(Modifier.weight(1f))
                                        }
                                        if (expandRow) ScrollRowGrid(columns = 3, itemCount = item.properties.size, modifier = Modifier.padding(start = 10.dp)) { index ->
                                            OutlinedButton(
                                                modifier = Modifier.padding(0.dp).heightIn(min = 20.dp).widthIn(min = 20.dp).wrapContentWidth(), border = BorderStroke(2.dp, if (episodeStateSort[index].value) buttonAltColor else borderColor),
                                                onClick = {
                                                    episodeStateSort[index].value = !episodeStateSort[index].value
                                                    val c = episodeStateSort.count { it.value }
                                                    allOrNone = c == 0 || c == episodeStateSort.size
                                                    persistStateSort()
                                                },
                                            ) { Text(text = stringResource(item.properties[index].displayName), maxLines = 1, color = textColor) }
                                        }
                                    }
                                    if (vm.subPrefs.sortIndex == FeedSortIndex.Count.ordinal) {
                                        var allOrNone by remember { mutableStateOf(false) }
                                        LaunchedEffect(Unit) {
                                            for (i in ratingSort.indices) {
                                                if (Rating.entries[i].code.toString() in vm.subPrefs.ratingCodeSet) ratingSort[i].value = true
                                            }
                                            val c = ratingSort.count { it.value }
                                            allOrNone = c == 0 || c == ratingSort.size
                                        }
                                        fun persistRatingSort() {
                                            runOnIOScope {
                                                val sb = StringBuilder()
                                                val ratingCodeSet = mutableSetOf<String>()
                                                for (i in ratingSort.indices) {
                                                    if (ratingSort[i].value) {
                                                        ratingCodeSet.add(Rating.entries[i].code.toString())
                                                        if (sb.isNotEmpty()) sb.append(" OR ")
                                                        sb.append(" rating == ${Rating.entries[i].code} ")
                                                    }
                                                }
                                                vm.ratingQueries = sb.toString()
                                                vm.subPrefs = upsert(vm.subPrefs) {
                                                    it.ratingCodeSet.clear()
                                                    it.ratingCodeSet.addAll(ratingCodeSet)
                                                }
                                                when (vm.subPrefs.sortIndex) {
                                                    FeedSortIndex.Count.ordinal -> vm.prepareCountSort(feedList)
                                                    else -> {}
                                                }
                                            }
                                        }

                                        val item = EpisodeFilter.EpisodesFilterGroup.RATING
                                        var expandRow by remember { mutableStateOf(false) }
                                        Row {
                                            Text(stringResource(item.nameRes) + "… :", fontWeight = FontWeight.Bold, style = MaterialTheme.typography.headlineSmall, color = if (allOrNone) buttonColor else buttonAltColor, modifier = Modifier.clickable { expandRow = !expandRow })
                                            if (expandRow) {
                                                var lowerSelected by remember { mutableStateOf(false) }
                                                var higherSelected by remember { mutableStateOf(false) }
                                                Spacer(Modifier.weight(1f))
                                                Text("<<<", color = if (lowerSelected) buttonAltColor else buttonColor, style = MaterialTheme.typography.headlineSmall, modifier = Modifier.clickable {
                                                    runOnIOScope {
                                                        val hIndex = ratingSort.indexOfLast { it.value }
                                                        if (hIndex < 0) return@runOnIOScope
                                                        if (!lowerSelected) {
                                                            for (i in 0..hIndex) ratingSort[i].value = true
                                                        } else {
                                                            for (i in 0..hIndex) ratingSort[i].value = false
                                                            ratingSort[hIndex].value = true
                                                        }
                                                        val c = ratingSort.count { it.value }
                                                        allOrNone = c == 0 || c == ratingSort.size
                                                        lowerSelected = !lowerSelected
                                                        persistRatingSort()
                                                    }
                                                })
                                                Spacer(Modifier.weight(1f))
                                                Text("A", color = buttonColor, style = MaterialTheme.typography.headlineSmall, modifier = Modifier.clickable {
                                                    runOnIOScope {
                                                        val selectAll = !(lowerSelected && higherSelected)
                                                        lowerSelected = selectAll
                                                        higherSelected = selectAll
                                                        for (i in item.properties.indices) ratingSort[i].value = selectAll
                                                        val c = ratingSort.count { it.value }
                                                        allOrNone = c == 0 || c == ratingSort.size
                                                        persistRatingSort()
                                                    }
                                                })
                                                Spacer(Modifier.weight(1f))
                                                Text(">>>", color = if (higherSelected) buttonAltColor else buttonColor, style = MaterialTheme.typography.headlineSmall, modifier = Modifier.clickable {
                                                    runOnIOScope {
                                                        val lIndex = ratingSort.indexOfFirst { it.value }
                                                        if (lIndex >= 0) {
                                                            if (!higherSelected) {
                                                                for (i in lIndex..<item.properties.size) ratingSort[i].value = true
                                                            } else {
                                                                for (i in lIndex..<item.properties.size) ratingSort[i].value = false
                                                                ratingSort[lIndex].value = true
                                                            }
                                                            val c = ratingSort.count { it.value }
                                                            allOrNone = c == 0 || c == ratingSort.size
                                                            higherSelected = !higherSelected
                                                            persistRatingSort()
                                                        }
                                                    }
                                                })
                                            }
                                            Spacer(Modifier.weight(1f))
                                        }
                                        if (expandRow) ScrollRowGrid(columns = 3, itemCount = item.properties.size, modifier = Modifier.padding(start = 10.dp)) { index ->
                                            OutlinedButton(
                                                modifier = Modifier.padding(0.dp).heightIn(min = 20.dp).widthIn(min = 20.dp).wrapContentWidth(), border = BorderStroke(2.dp, if (ratingSort[index].value) buttonAltColor else borderColor),
                                                onClick = {
                                                    ratingSort[index].value = !ratingSort[index].value
                                                    val c = ratingSort.count { it.value }
                                                    allOrNone = c == 0 || c == ratingSort.size
                                                    persistRatingSort()
                                                },
                                            ) { Text(text = stringResource(item.properties[index].displayName), maxLines = 1, color = textColor) }
                                        }
                                    }
                                }
                            }
                        }
                    }
                }
            }
        }

        @Composable
        fun FilterDialog(filter: FeedFilter? = null, onDismiss: () -> Unit) {
            val filterValues = remember { filter?.properties ?: mutableSetOf() }
            var reset by remember { mutableIntStateOf(0) }
            var langFull by remember(vm.subPrefs.langsSel.size) { mutableStateOf(vm.subPrefs.langsSel.size == appAttribs.langSet.size) }
            var tagsFull by remember(vm.subPrefs.tagsSel.size) { mutableStateOf(vm.subPrefs.tagsSel.size == appAttribs.feedTagSet.size) }
            var queuesFull by remember(vm.subPrefs.queueSelIds.size) { mutableStateOf(vm.subPrefs.queueSelIds.size == vm.queueNames.size) }
            fun onFilterChanged(newFilterValues: Set<String>) {
                runOnIOScope {
                    vm.subPrefs = upsert(vm.subPrefs) {
                        it.feedsFilter = newFilterValues.joinToString(",")
                        it.feedsFilteredInc()
                    }
                }
                Logd(TAG, "onFilterChanged: ${vm.subPrefs.feedsFilter}")
            }
            Dialog(properties = DialogProperties(usePlatformDefaultWidth = false), onDismissRequest = { onDismiss() }) {
                val dialogWindowProvider = LocalView.current.parent as? DialogWindowProvider
                dialogWindowProvider?.window?.setGravity(Gravity.BOTTOM)
                Surface(modifier = Modifier.fillMaxWidth().padding(top = 10.dp, bottom = 10.dp).height(350.dp), color = MaterialTheme.colorScheme.surface.copy(alpha = 0.8f), shape = RoundedCornerShape(16.dp), border = BorderStroke(1.dp, borderColor)) {
                    Column(Modifier.fillMaxSize()) {
                        Column(Modifier.fillMaxSize().verticalScroll(rememberScrollState())) {
                            Logd(TAG, "appAttribs.langSet: ${appAttribs.langSet.size}")
                            if (appAttribs.langSet.isNotEmpty()) {
                                val langs = remember(appAttribs.langSet.size) { appAttribs.langSet.toList().sorted().toMutableStateList() }
                                Column(modifier = Modifier.fillMaxWidth()) {
                                    val selectedList = remember { MutableList(langs.size) { mutableStateOf(false) } }
                                    LaunchedEffect(reset) {
                                        Logd(TAG, "LaunchedEffect(reset) lang")
                                        for (index in selectedList.indices) {
                                            if (langs[index] in vm.subPrefs.langsSel) selectedList[index].value = true
                                            langFull = selectedList.count { it.value } == selectedList.size
                                        }
                                    }
                                    var expandRow by remember { mutableStateOf(false) }
                                    Row(modifier = Modifier.padding(start = 5.dp, bottom = 2.dp).fillMaxWidth()) {
                                        Text(stringResource(R.string.languages) + "… :", fontWeight = FontWeight.Bold, style = MaterialTheme.typography.titleLarge, color = if (langFull) buttonColor else buttonAltColor, modifier = Modifier.clickable { expandRow = !expandRow })
                                        if (expandRow) {
                                            val cb = {
                                                runOnIOScope {
                                                    val langsSel = mutableSetOf<String>()
                                                    for (i in langs.indices) if (selectedList[i].value) langsSel.add(langs[i])
                                                    vm.subPrefs = upsert(vm.subPrefs) {
                                                        it.langsSel = langsSel.toRealmSet()
                                                        it.feedsFilteredInc()
                                                    }
                                                }
                                                Logd(TAG, "langsSel: ${vm.subPrefs.langsSel.size} ${langs.size}")
                                            }
                                            SelectLowerAllUpper(selectedList, lowerCB = cb, allCB = cb, upperCB = cb)
                                        }
                                    }
                                    if (expandRow) ScrollRowGrid(columns = 3, itemCount = langs.size, modifier = Modifier.padding(start = 10.dp)) { index ->
                                        OutlinedButton(modifier = Modifier.padding(0.dp).heightIn(min = 20.dp).widthIn(min = 20.dp).wrapContentWidth(), border = BorderStroke(2.dp, if (selectedList[index].value) buttonAltColor else borderColor),
                                            onClick = {
                                                selectedList[index].value = !selectedList[index].value
                                                runOnIOScope {
                                                    val langsSel = vm.subPrefs.langsSel.toMutableSet()
                                                    if (selectedList[index].value) langsSel.add(langs[index])
                                                    else langsSel.remove(langs[index])
                                                    vm.subPrefs = upsert(vm.subPrefs) {
                                                        it.langsSel = langsSel.toRealmSet()
                                                        it.feedsFilteredInc()
                                                    }
                                                }
                                            },
                                        ) { Text(text = langs[index], maxLines = 1, color = textColor) }
                                    }
                                }
                            }
                            Column(modifier = Modifier.fillMaxWidth()) {
                                val selectedList = remember { MutableList(vm.queueNames.size) { mutableStateOf(false) } }
                                LaunchedEffect(reset) {
                                    Logd(TAG, "LaunchedEffect(reset) queue")
                                    for (index in selectedList.indices) {
                                        if (vm.queueIds[index] in vm.subPrefs.queueSelIds) selectedList[index].value = true
                                        queuesFull = selectedList.count { it.value } == selectedList.size
                                    }
                                }
                                var expandRow by remember { mutableStateOf(false) }
                                Row(modifier = Modifier.padding(start = 5.dp, bottom = 2.dp).fillMaxWidth()) {
                                    Text(stringResource(R.string.queue_label) + "… :", fontWeight = FontWeight.Bold, style = MaterialTheme.typography.titleLarge, color = if (queuesFull) buttonColor else buttonAltColor, modifier = Modifier.clickable { expandRow = !expandRow })
                                    if (expandRow) {
                                        val cb = {
                                            runOnIOScope {
                                                val qSelIds = mutableSetOf<Long>()
                                                for (i in vm.queueNames.indices) if (selectedList[i].value) qSelIds.add(vm.queueIds[i])
                                                vm.subPrefs = upsert(vm.subPrefs) {
                                                    it.queueSelIds = qSelIds.toRealmSet()
                                                    it.feedsFilteredInc()
                                                }
                                            }
                                            Unit
                                        }
                                        SelectLowerAllUpper(selectedList, lowerCB = cb, allCB = cb, upperCB = cb)
                                    }
                                }
                                if (expandRow) ScrollRowGrid(columns = 3, itemCount = vm.queueNames.size, modifier = Modifier.padding(start = 10.dp)) { index ->
                                    OutlinedButton(modifier = Modifier.padding(0.dp).heightIn(min = 20.dp).widthIn(min = 20.dp).wrapContentWidth(), border = BorderStroke(2.dp, if (selectedList[index].value) buttonAltColor else borderColor),
                                        onClick = {
                                            selectedList[index].value = !selectedList[index].value
                                            runOnIOScope {
                                                val qSelIds = vm.subPrefs.queueSelIds.toMutableSet()
                                                if (selectedList[index].value) qSelIds.add(vm.queueIds[index])
                                                else qSelIds.remove(vm.queueIds[index])
                                                vm.subPrefs = upsert(vm.subPrefs) {
                                                    it.queueSelIds = qSelIds.toRealmSet()
                                                    it.feedsFilteredInc()
                                                }
                                            }
                                        },
                                    ) { Text(text = vm.queueNames[index], maxLines = 1, color = textColor) }
                                }
                            }
                            if (appAttribs.feedTagSet.isNotEmpty()) {
                                val tagList = remember(appAttribs.feedTagSet.size) { appAttribs.feedTagSet.toList().sorted().toMutableStateList() }
                                Column(modifier = Modifier.fillMaxWidth()) {
                                    val selectedList = remember { MutableList(tagList.size) { mutableStateOf(false) } }
                                    LaunchedEffect(reset) {
                                        Logd(TAG, "LaunchedEffect(reset) tag")
                                        for (index in selectedList.indices) {
                                            if (tagList[index] in vm.subPrefs.tagsSel) selectedList[index].value = true
                                            tagsFull = selectedList.count { it.value } == selectedList.size
                                        }
                                    }
                                    var expandRow by remember { mutableStateOf(false) }
                                    Row(modifier = Modifier.padding(start = 5.dp, bottom = 2.dp).fillMaxWidth()) {
                                        Text(stringResource(R.string.tags_label) + "… :", fontWeight = FontWeight.Bold, style = MaterialTheme.typography.titleLarge, color = if (tagsFull) buttonColor else buttonAltColor, modifier = Modifier.clickable { expandRow = !expandRow })
                                        if (expandRow) {
                                            val cb = {
                                                runOnIOScope {
                                                    val tagsSel = mutableSetOf<String>()
                                                    for (i in tagList.indices) if (selectedList[i].value) tagsSel.add(tagList[i])
                                                    vm.subPrefs = upsert(vm.subPrefs) {
                                                        it.tagsSel = tagsSel.toRealmSet()
                                                        it.feedsFilteredInc()
                                                    }
                                                }
                                                Unit
                                            }
                                            SelectLowerAllUpper(selectedList, lowerCB = cb, allCB = cb, upperCB = cb)
                                        }
                                    }
                                    if (expandRow) ScrollRowGrid(columns = 3, itemCount = tagList.size, modifier = Modifier.padding(start = 10.dp)) { index ->
                                        OutlinedButton(modifier = Modifier.padding(0.dp).heightIn(min = 20.dp).widthIn(min = 20.dp).wrapContentWidth(), border = BorderStroke(2.dp, if (selectedList[index].value) buttonAltColor else borderColor),
                                            onClick = {
                                                selectedList[index].value = !selectedList[index].value
                                                runOnIOScope {
                                                    val tagsSel = vm.subPrefs.tagsSel.toMutableSet()
                                                    if (selectedList[index].value) tagsSel.add(tagList[index])
                                                    else tagsSel.remove(tagList[index])
                                                    vm.subPrefs = upsert(vm.subPrefs) {
                                                        it.tagsSel = tagsSel.toRealmSet()
                                                        it.feedsFilteredInc()
                                                    }
                                                }
                                            },
                                        ) { Text(text = tagList[index], maxLines = 1, color = textColor) }
                                    }
                                }
                            }
                            var selectNone by remember { mutableStateOf(false) }
                            for (item in FeedFilter.FeedFilterGroup.entries) {
                                if (item.values.size == 2) {
                                    Row(modifier = Modifier.padding(start = 5.dp).fillMaxWidth().horizontalScroll(rememberScrollState()), horizontalArrangement = Arrangement.Absolute.Left, verticalAlignment = Alignment.CenterVertically) {
                                        var selectedIndex by remember(selectNone) { mutableIntStateOf(
                                            if (selectNone) -1
                                            else if (filter != null) {
                                                if (item.values[0].filterId in filter.properties) 0
                                                else if (item.values[1].filterId in filter.properties) 1
                                                else -1
                                            } else -1
                                        ) }
                                        Text(stringResource(item.nameRes) + " :", fontWeight = FontWeight.Bold, style = MaterialTheme.typography.titleLarge, color = textColor, modifier = Modifier.padding(end = 10.dp))
                                        Spacer(Modifier.width(30.dp))
                                        OutlinedButton(
                                            modifier = Modifier.padding(0.dp).heightIn(min = 20.dp).widthIn(min = 20.dp), border = BorderStroke(2.dp, if (selectedIndex != 0) borderColor else buttonAltColor),
                                            onClick = {
                                                if (selectedIndex != 0) {
                                                    selectNone = false
                                                    selectedIndex = 0
                                                    filterValues.add(item.values[0].filterId)
                                                    filterValues.remove(item.values[1].filterId)
                                                } else {
                                                    selectedIndex = -1
                                                    filterValues.remove(item.values[0].filterId)
                                                }
                                                onFilterChanged(filterValues)
                                            },
                                        ) { Text(text = stringResource(item.values[0].displayName), color = textColor) }
                                        Spacer(Modifier.width(20.dp))
                                        OutlinedButton(
                                            modifier = Modifier.padding(0.dp).heightIn(min = 20.dp).widthIn(min = 20.dp), border = BorderStroke(2.dp, if (selectedIndex != 1) borderColor else buttonAltColor),
                                            onClick = {
                                                if (selectedIndex != 1) {
                                                    selectNone = false
                                                    selectedIndex = 1
                                                    filterValues.add(item.values[1].filterId)
                                                    filterValues.remove(item.values[0].filterId)
                                                } else {
                                                    selectedIndex = -1
                                                    filterValues.remove(item.values[1].filterId)
                                                }
                                                onFilterChanged(filterValues)
                                            },
                                        ) { Text(text = stringResource(item.values[1].displayName), color = textColor) } //                                    Spacer(Modifier.weight(0.5f))
                                    }
                                } else {
                                    Column(modifier = Modifier.fillMaxWidth()) {
                                        val selectedList = remember { MutableList(item.values.size) { mutableStateOf(false) } }
                                        var allOrNone by remember { mutableStateOf(false) }
                                        LaunchedEffect(reset) {
                                            Logd(TAG, "LaunchedEffect(reset) filter")
                                            if (filter != null) {
                                                for (index in selectedList.indices) {
                                                    if (item.values[index].filterId in filter.properties) selectedList[index].value = true
                                                }
                                                val c = selectedList.count { it.value }
                                                allOrNone = c == 0 || c == item.values.size
                                            } else allOrNone = true
                                        }
                                        var expandRow by remember { mutableStateOf(false) }
                                        Row(modifier = Modifier.padding(start = 5.dp, bottom = 2.dp).fillMaxWidth()) {
                                            Text(stringResource(item.nameRes) + "… :", fontWeight = FontWeight.Bold, style = MaterialTheme.typography.titleLarge, color = if (allOrNone) buttonColor else buttonAltColor, modifier = Modifier.clickable { expandRow = !expandRow })
                                            if (expandRow) {
                                                val cb = {
                                                    for (i in item.values.indices) {
                                                        if (selectedList[i].value) filterValues.add(item.values[i].filterId)
                                                        else filterValues.remove(item.values[i].filterId)
                                                    }
                                                    val c = selectedList.count { it.value }
                                                    allOrNone = c == 0 || c == item.values.size
                                                    onFilterChanged(filterValues)
                                                }
                                                SelectLowerAllUpper(selectedList, lowerCB = cb, allCB = cb, upperCB = cb)
                                            }
                                        }
                                        if (expandRow) ScrollRowGrid(columns = 3, itemCount = item.values.size, modifier = Modifier.padding(start = 10.dp)) { index ->
                                            if (selectNone) selectedList[index].value = false
                                            OutlinedButton(
                                                modifier = Modifier.padding(0.dp).heightIn(min = 20.dp).widthIn(min = 20.dp).wrapContentWidth(), border = BorderStroke(2.dp, if (selectedList[index].value) buttonAltColor else borderColor),
                                                onClick = {
                                                    selectNone = false
                                                    selectedList[index].value = !selectedList[index].value
                                                    if (selectedList[index].value) filterValues.add(item.values[index].filterId)
                                                    else filterValues.remove(item.values[index].filterId)
                                                    val c = selectedList.count { it.value }
                                                    allOrNone = c == 0 || c == item.values.size
                                                    onFilterChanged(filterValues)
                                                },
                                            ) { Text(text = stringResource(item.values[index].displayName), maxLines = 1, color = textColor) }
                                        }
                                    }
                                }
                            }
                            Row(modifier = Modifier.fillMaxWidth()) {
                                Spacer(Modifier.weight(0.3f))
                                Button(onClick = {
                                    runOnIOScope {
                                        vm.subPrefs = upsert(vm.subPrefs) {
                                            it.tagsSel = appAttribs.feedTagSet.toRealmSet()
                                            it.queueSelIds = vm.queueIds.toRealmSet()
                                            it.langsSel = appAttribs.langSet.toRealmSet()
                                            it.feedsFilteredInc()
                                        }
                                    }
                                    selectNone = true
                                    reset++
                                    onFilterChanged(setOf(""))
                                }) { Text(stringResource(R.string.reset)) }
                                Spacer(Modifier.weight(0.4f))
                                Button(onClick = { onDismiss() }) { Text(stringResource(R.string.close_label)) }
                                Spacer(Modifier.weight(0.3f))
                            }
                        }
                    }
                }
            }
        }

        @Composable
        fun CreateVolume(parent: Volume?, onDismiss: () -> Unit) {
            CommonPopupCard(onDismiss = { onDismiss() }) {
                Column(modifier = Modifier.fillMaxWidth().padding(16.dp)) {
                    Text(stringResource(R.string.rename_feed_label), color = textColor, style = MaterialTheme.typography.bodyLarge)
                    var name by remember { mutableStateOf("") }
                    TextField(value = name, singleLine = true, onValueChange = { name = it }, label = { Text(stringResource(R.string.new_namee)) })
                    Row {
                        Button({ onDismiss() }) { Text(stringResource(R.string.cancel_label)) }
                        Spacer(Modifier.weight(1f))
                        Button({
                            val v = Volume()
                            v.id = getEntityId()
                            upsertBlk(v) {
                                it.name = name
                                it.parentId = parent?.id ?: -1L
                            }
                            onDismiss()
                        }) { Text(stringResource(R.string.confirm_label)) }
                    }
                }
            }
        }

        @Composable
        fun ReceiveDialog() {
            var tcpPort by remember(appAttribs.transceivePort) { mutableIntStateOf(appAttribs.transceivePort) }
            var udpPort by remember(appAttribs.udpPort) { mutableIntStateOf(appAttribs.udpPort) }
            val ip = remember { getLocalIpAddress() }
            var contentType by remember { mutableStateOf(ContentType.Feed) }
            var receiver by remember { mutableStateOf<Receiver?>(null) }
            var receiveJob by remember { mutableStateOf<Job?>(null) }
            var broadcastJob by remember { mutableStateOf<Job?>(null) }

            fun onDismiss() {
                broadcastJob?.cancel()
                broadcastJob = null
                receiver?.stop()
                receiveJob?.cancel()
                showReceiverDialog = false
            }
            AlertDialog(modifier = Modifier.border(1.dp, MaterialTheme.colorScheme.tertiary, MaterialTheme.shapes.extraLarge), onDismissRequest = {  },
                title = { Text(stringResource(R.string.receive_contents), style = CustomTextStyles.titleCustom) },
                text = {
                    Column {
                        Text(appAttribs.name + " at: " + (ip ?: "address unknown"))
                        Text(stringResource(R.string.ports_sum), style = MaterialTheme.typography.bodySmall)
                        if (receiveJob == null) Row(modifier = Modifier.fillMaxWidth().padding(5.dp), horizontalArrangement = Arrangement.spacedBy(5.dp)) {
                            TextField(value = udpPort.toString(), keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Number), label = { Text(stringResource(R.string.broadcast_port)) }, singleLine = true, modifier = Modifier.weight(1f), onValueChange = { udpPort = it.toIntOrNull() ?: 0 })
                            TextField(value = tcpPort.toString(), keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Number), label = { Text(stringResource(R.string.port_label)) }, singleLine = true, modifier = Modifier.weight(1f), onValueChange = { tcpPort = it.toIntOrNull() ?: 0 })
                        } else Text("Receiving at port $tcpPort")
                        if (receiveJob == null) Row {
                            Text(stringResource(R.string.content_type), modifier = Modifier.padding(end = 5.dp))
                            Spinner(ContentType.entries.map { it.name }, ContentType.Feed.name) { index -> contentType = ContentType.entries.toTypedArray()[index] }
                        } else Text("Receiving: ${contentType.name}")
                    }
                },
                confirmButton = {
                    if (receiveJob == null && !ip.isNullOrBlank()) TextButton(onClick = {
                        if (udpPort != appAttribs.udpPort) upsertBlk(appAttribs) { it.udpPort = udpPort }
                        broadcastJob = scope.launch { broadcastPresence(udpPort, tcpPort) }
                        if (tcpPort != appAttribs.transceivePort) upsertBlk(appAttribs) { it.transceivePort = tcpPort }
                        receiver = when (contentType) {
                            ContentType.Feed -> FeedReceiver(tcpPort)
                            ContentType.Catalog -> CatalogReceiver(tcpPort) { onDismiss() }
                            ContentType.Episodes -> EpisodesReceiver(tcpPort) { onDismiss() }
                        }
                        receiveJob = scope.launch(Dispatchers.IO) { receiver!!.start() }
                    }) { Text(stringResource(R.string.start)) }
                },
                dismissButton = { TextButton(onClick = { onDismiss() }) { Text(stringResource(R.string.stop)) } }
            )
        }

        if (showReceiverDialog) ReceiveDialog()

        if (showSendCatalogDialog) SendToDevice(onDismiss = { showSendCatalogDialog = false}) { host, port -> sendCatalog(host, port) { showSendCatalogDialog =  false } }

        if (showFilterDialog) FilterDialog(FeedFilter(vm.subPrefs.feedsFilter)) { showFilterDialog = false }
        if (showSortDialog) SortDialog { showSortDialog = false }
        if (showNewSynthetic) AmendSyntheticFeed(volume = vm.curVolume, onDismiss = { showNewSynthetic = false }) {}
        if (showNewVolume) CreateVolume(parent = vm.curVolume) { showNewVolume = false }

        @SuppressLint("LocalContextResourcesRead")
        fun saveSelectedFeeds(cbBlock: (Feed)->Unit) {
            runOnIOScope { realm.write { for (f_ in feedsSelected) findLatest(f_)?.let { cbBlock(it) } } }
            val numItems = feedsSelected.size
            Logt(TAG, context.resources.getQuantityString(R.plurals.updated_feeds_batch_label, numItems, numItems))
        }

        @Composable
        fun SetToVolumeDialog(onDismiss: () -> Unit) {
            CommonPopupCard(onDismiss = { onDismiss() }) {
                Column(modifier = Modifier.padding(16.dp), verticalArrangement = Arrangement.spacedBy(8.dp)) {
                    var selectedOption by remember {mutableStateOf("")}
                    val custom = "Custom"
                    val none = "None"
                    var selected by remember {mutableStateOf(if (selectedOption == none) none else custom)}
                    Row(modifier = Modifier.fillMaxWidth(), verticalAlignment = Alignment.CenterVertically) {
                        Checkbox(checked = none == selected,
                            onCheckedChange = {
                                selected = none
                                saveSelectedFeeds { it: Feed -> it.volumeId = -1L }
                                onDismiss()
                            })
                        Text(none)
                        Spacer(Modifier.width(50.dp))
                        Checkbox(checked = custom == selected, onCheckedChange = { selected = custom })
                        Text(custom)
                    }
                    if (selected == custom) {
                        Logd(TAG, "volumes: ${volumes.size}")
                        FlowRow(horizontalArrangement = Arrangement.spacedBy(5.dp)) {
                            for (index in volumes.indices) {
                                FilterChip(label = { Text(volumes[index].name) }, selected = false,
                                    onClick = {
                                        saveSelectedFeeds { it: Feed -> it.volumeId = volumes[index].id }
                                        onDismiss()
                                    })
                            }
                        }
                    }
                }
            }
        }

        @Composable
        fun SetAssociateQueueDialog(onDismiss: () -> Unit) {
            CommonPopupCard(onDismiss = { onDismiss() }) {
                Column(modifier = Modifier.padding(16.dp), verticalArrangement = Arrangement.spacedBy(8.dp)) {
                    var selectedOption by remember {mutableStateOf("")}
                    val custom = "Custom"
                    val none = "None"
                    var selected by remember {mutableStateOf(if (selectedOption == none) none else custom)}
                    Row(modifier = Modifier.fillMaxWidth(), verticalAlignment = Alignment.CenterVertically) {
                        Checkbox(checked = none == selected,
                            onCheckedChange = {
                                selected = none
                                saveSelectedFeeds { it: Feed ->
                                    it.queue = null
                                    it.autoDownload = false
                                    it.autoEnqueue = false
                                }
                                onDismiss()
                            })
                        Text(none)
                        Spacer(Modifier.width(50.dp))
                        Checkbox(checked = custom == selected, onCheckedChange = { selected = custom })
                        Text(custom)
                    }
                    if (selected == custom) {
                        Logd(TAG, "queues: ${queuesLive.size}")
                        FlowRow(horizontalArrangement = Arrangement.spacedBy(5.dp)) {
                            for (index in queuesLive.indices) {
                                FilterChip(label = { Text(queuesLive[index].name) }, selected = false,
                                    onClick = {
                                        saveSelectedFeeds { it: Feed -> it.queue = queuesLive[index] }
                                        onDismiss()
                                    })
                            }
                        }
                    }
                }
            }
        }

        @Composable
        fun ChooseRatingDialog(selected: List<Feed>, onDismiss: () -> Unit) {
            CommonPopupCard(onDismiss = onDismiss) {
                Column(modifier = Modifier.padding(16.dp), verticalArrangement = Arrangement.spacedBy(16.dp)) {
                    for (rating in Rating.entries.reversed()) {
                        Row(verticalAlignment = Alignment.CenterVertically, modifier = Modifier.padding(4.dp).clickable {
                            runOnIOScope {
                                realm.write {
                                    for (f_ in selected) {
                                        val f = findLatest(f_)
                                        if (f != null) f.rating = rating.code
                                    }
                                }
                            }
                            onDismiss()
                        }) {
                            Icon(imageVector = ImageVector.vectorResource(id = rating.res), "")
                            Text(rating.name, Modifier.padding(start = 4.dp))
                        }
                    }
                }
            }
        }

        @Composable
        fun VolumeOptionsMenu(onDismiss: ()->Unit) {
            CommonPopupCard(onDismiss = onDismiss) {
                Column(modifier = Modifier.padding(16.dp), verticalArrangement = Arrangement.spacedBy(16.dp)) {
                    feedsOptionsMap.entries.forEachIndexed { _, entry -> if (entry.key != "ParentVolume") entry.value() }
                    HorizontalDivider(modifier = Modifier.fillMaxWidth().padding(top = 5.dp))
                    Row(verticalAlignment = Alignment.CenterVertically, modifier = Modifier.padding(horizontal = 16.dp).clickable { showEditVolume = true }) {
                        Icon(imageVector = Icons.Filled.Edit, "edit volume")
                        Text(stringResource(id = R.string.edit_volume)) }
                    if (volumeToOperate != null && volumeToOperate!!.id >= 0L) Row(verticalAlignment = Alignment.CenterVertically, modifier = Modifier.padding(horizontal = 16.dp).clickable {
                        commonConfirm = CommonConfirmAttrib(
                            title = context.getString(R.string.remove_volume) + "?",
                            message = context.getString(R.string.remove_volume_msg) + "\n" + volumeToOperate?.name,
                            confirmRes = R.string.confirm_label,
                            cancelRes = R.string.cancel_label,
                            onConfirm = {
                                Logd(TAG, "removing volume: ${volumeToOperate?.name}")
                                runOnIOScope {
                                    feedOperationText = "Processing"
                                    deleteVolumeTree(volumeToOperate!!)
                                    feedOperationText = ""
                                    onDismiss()
                                }
                                commonConfirm = null
                            },
                        )
                    }) {
                        Icon(imageVector = ImageVector.vectorResource(id = R.drawable.ic_delete), "remove volume")
                        Text(stringResource(id = R.string.remove_volume)) }
                    if (volumeToOperate?.isLocal == true) Row(verticalAlignment = Alignment.CenterVertically, modifier = Modifier.padding(horizontal = 16.dp).clickable {
                        commonConfirm = CommonConfirmAttrib(
                            title = context.getString(R.string.reconnect_local_folder) + "?",
                            message = volumeToOperate?.name + "\n" + context.getString(R.string.reconnect_local_folder_warning),
                            confirmRes = R.string.confirm_label,
                            cancelRes = R.string.cancel_label,
                            onConfirm = {
                                Logd(TAG, "reconnecting folder: ${volumeToOperate?.name}")
                                try { connectLocalFolderLauncher.launch(null) } catch (e: ActivityNotFoundException) { Logs(TAG, e, "No activity found. Should never happen...") }
                                commonConfirm = null
                            },
                        )
                    }) {
                        Icon(imageVector = ImageVector.vectorResource(id = R.drawable.rounded_books_movies_and_music_24), "reconnect folder", modifier = Modifier.size(24.dp))
                        Text(stringResource(id = R.string.reconnect_local_folder)) }
                }
            }
        }
        if (volumeToOperate != null) VolumeOptionsMenu { volumeToOperate = null }

        if (showToDeviceDialog) SendToDevice(onDismiss = { showToDeviceDialog = false }) { host, port ->
            runOnIOScope {
                var i = 0
                val num = feedsSelected.size
                for (feed in feedsSelected) {
                    sendFeed(host, port, feed.id) { }
                    Logt(TAG, "feeds transferred ${++i} / $num, waiting 5 seconds to do next")
                    delay(10000.milliseconds)
                }
                showToDeviceDialog = false
            }
        }

        if (showRemoveFeedDialog) RemoveFeedDialog(feedsSelected, onDismiss = {showRemoveFeedDialog = false}) {}

        if (showChooseRatingDialog) ChooseRatingDialog(feedsSelected) { showChooseRatingDialog = false }
        if (showAssociateDialog) SetAssociateQueueDialog {showAssociateDialog = false}
        if (showToVolumeDialog) SetToVolumeDialog {showToVolumeDialog = false}
        if (showTagsSettingDialog) TagSettingDialog(TagType.Feed, setOf(), multiples = true, { showTagsSettingDialog = false } ) { tags ->
            runOnIOScope { realm.write { for (f_ in feedsSelected) findLatest(f_)?.tags?.addAll(tags) } }
        }
        @Composable
        fun EditVolume(volume: Volume, onDismiss: () -> Unit) {
            CommonPopupCard(onDismiss = { onDismiss() }) {
                Column(modifier = Modifier.fillMaxWidth().padding(16.dp)) {
                    Text(stringResource(R.string.rename_feed_label), color = textColor, style = MaterialTheme.typography.bodyLarge)
                    var name by remember { mutableStateOf(volume.name) }
                    TextField(value = name, singleLine = true, onValueChange = { name = it }, label = { Text(stringResource(R.string.rename)) })
                    var parent by remember { mutableStateOf<Volume?>(null) }
                    val custom = "Custom"
                    val none = "None"
                    var selected by remember { mutableStateOf(none) }
                    Row(modifier = Modifier.fillMaxWidth(), verticalAlignment = Alignment.CenterVertically) {
                        Checkbox(checked = none == selected,
                            onCheckedChange = {
                                selected = none
                                parent = null
                            })
                        Text(none)
                        Spacer(Modifier.width(50.dp))
                        if (volumes.size > 1) {
                            Checkbox(checked = custom == selected, onCheckedChange = { selected = custom })
                            Text(custom)
                        }
                    }
                    if (selected == custom) {
                        Logd(TAG, "volumes: ${volumes.size}")
                        FlowRow(horizontalArrangement = Arrangement.spacedBy(5.dp)) {
                            for (index in volumes.indices) if (volumes[index].id != volume.id) FilterChip(onClick = { parent = volumes[index] }, label = { Text(volumes[index].name) }, selected = parent == volumes[index], border = filterChipBorder(parent == volumes[index]))
                        }
                    }
                    Row {
                        Button({ onDismiss() }) { Text(stringResource(R.string.cancel_label)) }
                        Spacer(Modifier.weight(1f))
                        Button({
                            upsertBlk(volume) {
                                it.name = name
                                it.parentId = parent?.id ?: -1L
                            }
                            onDismiss()
                        }) { Text(stringResource(R.string.confirm_label)) }
                    }
                }
            }
        }
        if (showEditVolume) EditVolume(volumeToOperate!!) { showEditVolume = false }
    }

    OpenDialogs()

    Scaffold(topBar = { TopBar() }) { innerPadding ->
        var selectedSize by remember { mutableIntStateOf(0) }
        var longPressIndex by remember { mutableIntStateOf(-1) }
        var refreshing by remember { mutableStateOf(false)}

        PullToRefreshBox(modifier = Modifier.padding(innerPadding).fillMaxSize().background(MaterialTheme.colorScheme.surface), isRefreshing = refreshing, indicator = {}, onRefresh = {
            refreshing = true
            commonConfirm = CommonConfirmAttrib(
                title = context.getString(R.string.feed_refresh_title) + "?",
                message = "",
                confirmRes = R.string.confirm_label,
                cancelRes = R.string.cancel_label,
                onConfirm = {
                    if (vm.curVolume == null) checkAndScheduleUpdateTaskOnce(replace = true, force = true)
                    else runOnce(vm.curVolume!!.allFeeds, doItWanyway = vm.curVolume!!.isNormal)
                },
            )
            refreshing = false
        }) {
            if (vm.subPrefs.prefFeedGridLayout) {
                LazyVerticalGrid(state = rememberLazyGridState(), columns = GridCells.Adaptive(80.dp), modifier = Modifier.fillMaxSize(), verticalArrangement = Arrangement.spacedBy(16.dp), horizontalArrangement = Arrangement.spacedBy(16.dp), contentPadding = PaddingValues(start = 12.dp, top = 16.dp, end = 12.dp, bottom = 16.dp)) {
                    if (feedIdsToUse.isEmpty() && !vm.showAllFeeds && volumes.isNotEmpty()) items(volumes, key = { it.id}) { volume ->
                        Column(Modifier.background(MaterialTheme.colorScheme.surface)
                            .combinedClickable(onClick = { vm.curVolume = volume},
                                onLongClick = {
                                    feedsSelected.clear()
                                    feedsSelected.addAll(volume.allFeeds)
                                    volumeToOperate = volume
                                })) {
                            Icon(imageVector = ImageVector.vectorResource(R.drawable.rounded_books_movies_and_music_24), tint = buttonColor, contentDescription = null, modifier = Modifier.fillMaxWidth())
                            val childrenCount = remember(volume.id) { volume.directChildrenCount }
                            Text("${volume.name}: $childrenCount", color = textColor, maxLines = 2, overflow = TextOverflow.Ellipsis, style = MaterialTheme.typography.bodyMedium)
                        }
                    }
                    items(feedList, key = { it.id}) { feed ->
                        var isSelected by remember(selectMode, selectedSize, feed.id) { mutableStateOf(selectMode && feed in feedsSelected) }
                        fun toggleSelected() {
                            isSelected = !isSelected
                            if (isSelected) feedsSelected.add(feed)
                            else feedsSelected.remove(feed)
                        }
                        Column(Modifier.background(if (isSelected) MaterialTheme.colorScheme.secondaryContainer else MaterialTheme.colorScheme.surface).combinedClickable(
                            onClick = {
                                Logd(TAG, "clicked: ${feed.title}")
                                if (!feed.isBuilding) {
                                    if (selectMode) toggleSelected()
                                    else navTo(FeedDetails(feedId = feed.id))
                                } },
                            onLongClick = {
                                if (!feed.isBuilding) {
                                    selectMode = !selectMode
                                    isSelected = selectMode
                                    feedsSelected.clear()
                                    if (selectMode) {
                                        val index = feedList.indexOfFirst { it.id == feed.id }
                                        feedsSelected.add(feed)
                                        longPressIndex = index
                                    } else {
                                        selectedSize = 0
                                        longPressIndex = -1
                                    }
                                }
                                Logd(TAG, "long clicked: ${feed.title}")
                            })) {
                            ConstraintLayout(Modifier.fillMaxSize()) {
                                val (coverImage, episodeCount, rating, error) = createRefs()
                                AsyncImage(model = ImageRequest.Builder(context).data(feed.imageUrl).memoryCachePolicy(CachePolicy.ENABLED).build(), placeholder = painterResource(R.drawable.ic_launcher_foreground), error = painterResource(R.drawable.ic_launcher_foreground), contentDescription = "coverImage",
                                    colorFilter = if (!feed.inNormalVolume) ColorFilter.tint(color = Color.Gray.copy(alpha = 0.5f), blendMode = BlendMode.SrcAtop) else null,
                                    modifier = Modifier.fillMaxWidth().aspectRatio(1f).constrainAs(coverImage) {
                                        top.linkTo(parent.top)
                                        bottom.linkTo(parent.bottom)
                                        start.linkTo(parent.start)
                                    })
                                val measureString = remember(feed.episodesCount) { formatWithGrouping(feed.episodesCount.toLong()) }
                                if (measureString.isNotBlank()) Text(measureString, color = buttonAltColor, modifier = Modifier.background(MaterialTheme.colorScheme.tertiaryContainer.copy(0.8f)).constrainAs(episodeCount) {
                                    end.linkTo(parent.end)
                                    top.linkTo(coverImage.top)
                                })
                                if (feed.rating != Rating.UNRATED.code)
                                    Icon(imageVector = ImageVector.vectorResource(Rating.fromCode(feed.rating).res), tint = buttonColor, contentDescription = "rating",
                                        modifier = Modifier.background(MaterialTheme.colorScheme.tertiaryContainer.copy(0.8f)).constrainAs(rating) {
                                            start.linkTo(coverImage.start)
                                            bottom.linkTo(coverImage.bottom)
                                        })
                                if (feed.lastUpdateFailed) Icon(imageVector = ImageVector.vectorResource(R.drawable.ic_error), tint = Color.Red, contentDescription = "error",
                                    modifier = Modifier.background(Color.Gray).constrainAs(error) {
                                        end.linkTo(parent.end)
                                        bottom.linkTo(coverImage.bottom)
                                    })
                            }
                            Text(feed.title ?: "No title", color = textColor, style = MaterialTheme.typography.bodyMedium, maxLines = 2, overflow = TextOverflow.Ellipsis)
                        }
                    }
                }
            } else {
                val listState = rememberLazyListState()
                DisposableEffect(LocalLifecycleOwner.current) {
                    onDispose {
                        val index = listState.firstVisibleItemIndex
                        val offset = listState.firstVisibleItemScrollOffset
                        Logd(TAG, "DisposableEffect onDispose save positions: $index $offset")
                        runOnIOScope {
                            vm.subPrefs = upsert(vm.subPrefs) {
                                it.positionIndex = index
                                it.positionOffset = offset
                            }
                        }
                    }
                }
                LazyColumn(state = listState, modifier = Modifier.fillMaxSize().padding(start = 5.dp, end = 5.dp), verticalArrangement = Arrangement.spacedBy(8.dp)) {
                    if (feedIdsToUse.isEmpty() && !vm.showAllFeeds && volumes.isNotEmpty()) items(volumes, key = { v -> v.id}) { volume ->
//                        Logd(TAG, "Volume: ${volume.name} ${volume.id}")
                        Row(verticalAlignment = Alignment.CenterVertically, modifier = Modifier.combinedClickable(
                            onClick = { vm.curVolume = volume },
                            onLongClick = {
                                feedsSelected.clear()
                                feedsSelected.addAll(volume.allFeeds)
                                volumeToOperate = volume
                            })) {
                            Icon(imageVector = ImageVector.vectorResource(R.drawable.rounded_books_movies_and_music_24), tint = buttonColor, contentDescription = null, modifier = Modifier.width(50.dp).height(50.dp))
                            val childrenCount = remember(volume.id) { volume.directChildrenCount }
                            Text("${volume.name}: $childrenCount", color = textColor, maxLines = 2, overflow = TextOverflow.Ellipsis, textAlign = TextAlign.Center, style = MaterialTheme.typography.bodyLarge.copy(fontWeight = FontWeight.Bold), modifier = Modifier.weight(1f).fillMaxHeight().wrapContentHeight(Alignment.CenterVertically))
                        }
                    }
                    items(feedList, key = { feed -> feed.id}) { feed_ ->
                        val feed by rememberUpdatedState(feed_)
                        var isSelected by remember(key1 = selectMode, key2 = selectedSize) { mutableStateOf(selectMode && feed in feedsSelected) }
                        fun toggleSelected() {
                            isSelected = !isSelected
                            if (isSelected) feedsSelected.add(feed)
                            else feedsSelected.remove(feed)
                            Logd(TAG, "toggleSelected: selected: ${feedsSelected.size}")
                        }
                        val imageSize = 60
                        Row(Modifier.height(imageSize.dp).background(if (isSelected) MaterialTheme.colorScheme.secondaryContainer else MaterialTheme.colorScheme.surface)) {
                            Box(modifier = Modifier.size(imageSize.dp)) {
                                AsyncImage(model = ImageRequest.Builder(context).data(feed.imageUrl).memoryCachePolicy(CachePolicy.ENABLED).build(), placeholder = painterResource(R.drawable.ic_launcher_foreground), error = painterResource(R.drawable.ic_launcher_foreground), contentDescription = "imgvCover",
                                    colorFilter = if (!feed.inNormalVolume) ColorFilter.tint(color = Color.Gray.copy(alpha = 0.5f), blendMode = BlendMode.SrcAtop) else null,
                                    modifier = Modifier.fillMaxSize().clickable {
                                        Logd(TAG, "icon clicked!")
                                        if (!feed.isBuilding) {
                                            if (selectMode) toggleSelected()
                                            else navTo(FeedDetails(feedId = feed.id, modeName = FeedScreenMode.Info.name))
                                        }
                                    })
                                if (feed.rating != Rating.UNRATED.code) Icon(imageVector = ImageVector.vectorResource(Rating.fromCode(feed.rating).res), tint = buttonColor, contentDescription = "rating", modifier = Modifier.size((imageSize/4).dp).align(Alignment.BottomStart).background(MaterialTheme.colorScheme.tertiaryContainer.copy(0.8f)))
                            }
                            Box(Modifier.weight(1f).fillMaxHeight().padding(start = 10.dp).combinedClickable(onClick = {
                                if (!feed.isBuilding) {
                                    if (selectMode) toggleSelected()
                                    else navTo(FeedDetails(feedId = feed.id))
                                }
                            }, onLongClick = {
                                if (!feed.isBuilding) {
                                    selectMode = !selectMode
                                    isSelected = selectMode
                                    feedsSelected.clear()
                                    if (selectMode) {
                                        feedsSelected.add(feed)
                                        val index = feedList.indexOfFirst { it.id == feed.id }
                                        longPressIndex = index
                                    } else {
                                        selectedSize = 0
                                        longPressIndex = -1
                                    }
                                }
                            })) {
                                Text(feed.title ?: "No title", color = textColor, maxLines = 2, overflow = TextOverflow.Ellipsis, style = MaterialTheme.typography.bodyMedium.copy(fontWeight = FontWeight.Bold), modifier = Modifier.align(Alignment.TopStart))
                                Row(modifier = Modifier.align(Alignment.BottomStart)) {
//                                    Logd(TAG, "episodesCount: ${feed.id} ${feed.episodesCount} ${feed.totleDuration}")
                                    val measureString = remember(feed.episodesCount, feed.totleDuration) { formatWithGrouping(feed.episodesCount.toLong()) + " : " + durationInHours(feed.totleDuration/1000, false) }
                                    Text(measureString, color = textColor, style = MaterialTheme.typography.bodyMedium)
                                    Spacer(modifier = Modifier.weight(1f))
                                    Text(feed.sortInfo, color = textColor, style = MaterialTheme.typography.bodyMedium)
                                }
                            }
                            if (feed.lastUpdateFailed) Icon(imageVector = ImageVector.vectorResource(R.drawable.ic_error), tint = Color.Red, contentDescription = "error")
                        }
                    }
                }
            }
            if (selectMode) {
                Row(modifier = Modifier.align(Alignment.TopEnd).background(MaterialTheme.colorScheme.tertiaryContainer),
                    horizontalArrangement = Arrangement.spacedBy(15.dp), verticalAlignment = Alignment.CenterVertically) {
                    Icon(imageVector = ImageVector.vectorResource(R.drawable.baseline_arrow_upward_24), tint = buttonColor, contentDescription = null, modifier = Modifier.width(35.dp).height(35.dp).padding(start = 10.dp).clickable {
                        feedsSelected.clear()
                        for (i in 0..longPressIndex) feedsSelected.add(feedList[i])
                        selectedSize = feedsSelected.size
                        Logd(TAG, "selectedIds: ${feedsSelected.size}")
                    })
                    Icon(imageVector = ImageVector.vectorResource(R.drawable.baseline_arrow_downward_24), tint = buttonColor, contentDescription = null, modifier = Modifier.width(35.dp).height(35.dp).clickable {
                        feedsSelected.clear()
                        for (i in longPressIndex..<feedList.size) feedsSelected.add(feedList[i])
                        selectedSize = feedsSelected.size
                        Logd(TAG, "selectedIds: ${feedsSelected.size}")
                    })
                    var selectAllRes by remember { mutableIntStateOf(R.drawable.ic_select_all) }
                    Icon(imageVector = ImageVector.vectorResource(selectAllRes), tint = buttonColor, contentDescription = null, modifier = Modifier.width(35.dp).height(35.dp).clickable {
                        if (selectedSize != feedList.size) {
                            feedsSelected.clear()
                            feedsSelected.addAll(feedList)
                            selectAllRes = R.drawable.ic_select_none
                        } else {
                            feedsSelected.clear()
                            longPressIndex = -1
                            selectAllRes = R.drawable.ic_select_all
                        }
                        selectedSize = feedsSelected.size
                        Logd(TAG, "selectedIds: ${feedsSelected.size}")
                    })
                    @Composable
                    fun FeedsSpeedDial(selected: List<Feed>, modifier: Modifier = Modifier) {
                        val bgColor = MaterialTheme.colorScheme.tertiaryContainer
                        val fgColor = remember { complementaryColorOf(bgColor) }
                        if (isFeedsOptionsExpanded) CommonPopupCard(onDismiss = { isFeedsOptionsExpanded = false }) {
                            Column(modifier = Modifier.padding(8.dp), verticalArrangement = Arrangement.spacedBy(10.dp)) { feedsOptionsMap.entries.forEachIndexed { _, entry -> entry.value() } }
                        }
                        FloatingActionButton(containerColor = bgColor, contentColor = fgColor,
                            onClick = { isFeedsOptionsExpanded = !isFeedsOptionsExpanded }) { Icon(Icons.Filled.Menu, "Menu") }
                    }
                    FeedsSpeedDial(feedsSelected, modifier = Modifier.padding(start = 16.dp))
                }
            }
        }

    }
}

enum class FeedSortIndex(val res: Int) {
    Feed(R.string.feed),
    Date(R.string.date),
    Time(R.string.time),
    Count(R.string.count)
}

enum class FeedPropertySortIndex(val res: Int) {
    Title(R.string.title),
    Author(R.string.author),
    Rating(R.string.rating_label),
    Score(R.string.score),
    ScoreCount(R.string.score_count),
    Updated(R.string.last_update),
    FullUpdate(R.string.last_full_update),
    TotleDuration(R.string.total_duration),
    Commented(R.string.last_commented)
}

enum class FeedDateSortIndex(val res: Int) {
    Publish(R.string.publish_date),
    Downloaded(R.string.downloaded_label),
    Played(R.string.played),
    Commented(R.string.commented)
}

enum class FeedTimeSortIndex(val res: Int) {
    Total(R.string.total_duration),
    Min(R.string.min_duration),
    Max(R.string.max_duration),
    Average(R.string.average_duration)
}