Просмотр исходного кода

feat: 书源管理界面优化

HapeLee 3 месяцев назад
Родитель
Сommit
aeef0872d4

+ 5 - 16
app/src/main/java/io/legado/app/ui/book/source/debug/BookSourceDebugScreen.kt

@@ -9,7 +9,6 @@ import androidx.compose.foundation.layout.fillMaxSize
 import androidx.compose.foundation.layout.fillMaxWidth
 import androidx.compose.foundation.layout.height
 import androidx.compose.foundation.layout.padding
-import androidx.compose.foundation.layout.size
 import androidx.compose.foundation.lazy.LazyColumn
 import androidx.compose.foundation.lazy.LazyListScope
 import androidx.compose.foundation.lazy.LazyRow
@@ -19,7 +18,6 @@ import androidx.compose.material.icons.Icons
 import androidx.compose.material.icons.filled.ClearAll
 import androidx.compose.material.icons.filled.PlayArrow
 import androidx.compose.material.icons.filled.Stop
-import androidx.compose.material3.CircularProgressIndicator
 import androidx.compose.runtime.Composable
 import androidx.compose.runtime.LaunchedEffect
 import androidx.compose.runtime.remember
@@ -29,6 +27,7 @@ import androidx.compose.ui.input.nestedscroll.nestedScroll
 import androidx.compose.ui.text.style.TextOverflow
 import androidx.compose.ui.unit.dp
 import io.legado.app.model.Debug
+import io.legado.app.ui.about.MarkdownSheet
 import io.legado.app.ui.theme.LegadoTheme
 import io.legado.app.ui.theme.fadingEdge
 import io.legado.app.ui.widget.components.AppFloatingActionButton
@@ -37,8 +36,6 @@ import io.legado.app.ui.widget.components.AppTextField
 import io.legado.app.ui.widget.components.EmptyMessage
 import io.legado.app.ui.widget.components.button.ToggleChip
 import io.legado.app.ui.widget.components.card.GlassCard
-import io.legado.app.ui.widget.components.modalBottomSheet.AppModalBottomSheet
-import io.legado.app.ui.widget.components.progressIndicator.AppCircularProgressIndicator
 import io.legado.app.ui.widget.components.text.AppText
 import io.legado.app.ui.widget.components.topbar.GlassMediumFlexibleTopAppBar
 import io.legado.app.ui.widget.components.topbar.GlassTopAppBarDefaults
@@ -143,20 +140,12 @@ fun BookSourceDebugScreen(
         }
     }
     val selectedEntry = state.entries.firstOrNull { it.id == state.selectedEntryId }
-    AppModalBottomSheet(
+    MarkdownSheet(
         show = selectedEntry != null,
+        title = selectedEntry?.kind?.title.orEmpty(),
+        content = selectedEntry?.message.orEmpty(),
         onDismissRequest = { onIntent(BookSourceDebugIntent.DismissEntry) },
-        title = selectedEntry?.kind?.title,
-        animateContentSize = false,
-    ) {
-        selectedEntry?.let {
-            AppText(
-                text = it.message,
-                modifier = Modifier.fillMaxWidth(),
-                style = LegadoTheme.typography.bodyMedium,
-            )
-        }
-    }
+    )
 }
 
 @Composable

+ 0 - 4
app/src/main/java/io/legado/app/ui/book/source/edit/BookSourceEditActivity.kt

@@ -14,7 +14,6 @@ import io.legado.app.base.BaseComposeActivity
 import io.legado.app.data.appDb
 import io.legado.app.data.entities.BookSource
 import io.legado.app.lib.dialogs.alert
-import io.legado.app.ui.about.AppLogDialog
 import io.legado.app.ui.book.search.SearchActivity
 import io.legado.app.ui.book.search.SearchScope
 import io.legado.app.ui.book.source.debug.BookSourceDebugActivity
@@ -26,7 +25,6 @@ import io.legado.app.utils.getClipText
 import io.legado.app.utils.sendToClip
 import io.legado.app.utils.share
 import io.legado.app.utils.showDialogFragment
-import io.legado.app.utils.showHelp
 import io.legado.app.utils.startActivity
 import io.legado.app.utils.toastOnUi
 import kotlinx.coroutines.Dispatchers
@@ -89,8 +87,6 @@ class BookSourceEditActivity : BaseComposeActivity(), VariableDialog.Callback {
                         negativeButton(R.string.no)
                     }
 
-                    BookSourceEditEffect.OpenLog -> showDialogFragment<AppLogDialog>()
-                    BookSourceEditEffect.OpenHelp -> showHelp("ruleHelp")
                     is BookSourceEditEffect.OpenVariable -> openVariable(effect.sourceUrl)
                     is BookSourceEditEffect.ShowMessage -> toastOnUi(effect.message)
                 }

+ 7 - 2
app/src/main/java/io/legado/app/ui/book/source/edit/BookSourceEditContract.kt

@@ -38,8 +38,14 @@ data class BookSourceEditUiState(
     val bookSourceType: Int = 0,
     val autoComplete: Boolean = false,
     val dirty: Boolean = false,
+    val activeSheet: BookSourceEditSheet? = null,
 )
 
+sealed interface BookSourceEditSheet {
+    data object Log : BookSourceEditSheet
+    data class Help(val content: String) : BookSourceEditSheet
+}
+
 sealed interface BookSourceEditIntent {
     data class Load(val sourceUrl: String?) : BookSourceEditIntent
     data class SelectTab(val tab: BookSourceEditTab) : BookSourceEditIntent
@@ -62,6 +68,7 @@ sealed interface BookSourceEditIntent {
     data object ClearCookie : BookSourceEditIntent
     data object ShowLog : BookSourceEditIntent
     data object ShowHelp : BookSourceEditIntent
+    data object DismissSheet : BookSourceEditIntent
     data object SaveAndSetVariable : BookSourceEditIntent
     data object RequestBack : BookSourceEditIntent
     data object DiscardChanges : BookSourceEditIntent
@@ -76,8 +83,6 @@ sealed interface BookSourceEditEffect {
     data class ShareText(val text: String) : BookSourceEditEffect
     data object ReadClipboard : BookSourceEditEffect
     data object ConfirmDiscard : BookSourceEditEffect
-    data object OpenLog : BookSourceEditEffect
-    data object OpenHelp : BookSourceEditEffect
     data class OpenVariable(val sourceUrl: String) : BookSourceEditEffect
     data class ShowMessage(val message: String) : BookSourceEditEffect
 }

+ 13 - 0
app/src/main/java/io/legado/app/ui/book/source/edit/BookSourceEditScreen.kt

@@ -37,6 +37,7 @@ import androidx.compose.ui.res.stringResource
 import androidx.compose.ui.text.style.TextOverflow
 import androidx.compose.ui.unit.dp
 import io.legado.app.R
+import io.legado.app.ui.about.MarkdownSheet
 import io.legado.app.ui.theme.LegadoTheme
 import io.legado.app.ui.widget.components.AppFloatingActionButton
 import io.legado.app.ui.widget.components.AppScaffold
@@ -45,6 +46,7 @@ import io.legado.app.ui.widget.components.card.GlassCard
 import io.legado.app.ui.widget.components.card.TextCard
 import io.legado.app.ui.widget.components.icon.AppIcon
 import io.legado.app.ui.widget.components.icon.AppIcons
+import io.legado.app.ui.widget.components.log.AppLogSheet
 import io.legado.app.ui.widget.components.menuItem.RoundDropdownMenu
 import io.legado.app.ui.widget.components.menuItem.RoundDropdownMenuItem
 import io.legado.app.ui.widget.components.modalBottomSheet.AppModalBottomSheet
@@ -170,6 +172,17 @@ fun BookSourceEditScreen(
             onIntent(BookSourceEditIntent.UpdateField(path, value))
         },
     )
+    AppLogSheet(
+        show = state.activeSheet is BookSourceEditSheet.Log,
+        onDismissRequest = { onIntent(BookSourceEditIntent.DismissSheet) },
+    )
+    val helpSheet = state.activeSheet as? BookSourceEditSheet.Help
+    MarkdownSheet(
+        show = helpSheet != null,
+        title = stringResource(R.string.help),
+        content = helpSheet?.content.orEmpty(),
+        onDismissRequest = { onIntent(BookSourceEditIntent.DismissSheet) },
+    )
 }
 
 @Composable

+ 11 - 2
app/src/main/java/io/legado/app/ui/book/source/edit/BookSourceEditViewModel.kt

@@ -89,8 +89,9 @@ class BookSourceEditViewModel(application: Application) : AndroidViewModel(appli
 
             BookSourceEditIntent.Paste -> _effects.tryEmit(BookSourceEditEffect.ReadClipboard)
             BookSourceEditIntent.ClearCookie -> clearCookie()
-            BookSourceEditIntent.ShowLog -> _effects.tryEmit(BookSourceEditEffect.OpenLog)
-            BookSourceEditIntent.ShowHelp -> _effects.tryEmit(BookSourceEditEffect.OpenHelp)
+            BookSourceEditIntent.ShowLog -> _uiState.update { it.copy(activeSheet = BookSourceEditSheet.Log) }
+            BookSourceEditIntent.ShowHelp -> showHelp()
+            BookSourceEditIntent.DismissSheet -> _uiState.update { it.copy(activeSheet = null) }
             BookSourceEditIntent.SaveAndSetVariable -> save { BookSourceEditEffect.OpenVariable(it) }
             BookSourceEditIntent.RequestBack -> if (_uiState.value.dirty) {
                 _effects.tryEmit(BookSourceEditEffect.ConfirmDiscard)
@@ -108,6 +109,14 @@ class BookSourceEditViewModel(application: Application) : AndroidViewModel(appli
         }
     }
 
+    private fun showHelp() = viewModelScope.launch(Dispatchers.IO) {
+        val content = getApplication<Application>().assets
+            .open("web/help/md/ruleHelp.md")
+            .bufferedReader()
+            .use { it.readText() }
+        _uiState.update { it.copy(activeSheet = BookSourceEditSheet.Help(content)) }
+    }
+
     private fun applySource(source: BookSource, asOriginal: Boolean = false) {
         if (asOriginal) originalSource = source
         draftJson = JsonParser.parseString(GSON.toJson(source)).asJsonObject

+ 2 - 6
app/src/main/java/io/legado/app/ui/book/source/manage/BookSourceScreen.kt

@@ -5,7 +5,6 @@ import androidx.compose.foundation.clickable
 import androidx.compose.foundation.layout.Arrangement
 import androidx.compose.foundation.layout.Box
 import androidx.compose.foundation.layout.Column
-import androidx.compose.foundation.layout.PaddingValues
 import androidx.compose.foundation.layout.Spacer
 import androidx.compose.foundation.layout.fillMaxHeight
 import androidx.compose.foundation.layout.fillMaxSize
@@ -20,7 +19,7 @@ import androidx.compose.foundation.lazy.rememberLazyListState
 import androidx.compose.foundation.shape.RoundedCornerShape
 import androidx.compose.material.icons.Icons
 import androidx.compose.material.icons.filled.Delete
-import androidx.compose.material.icons.filled.Folder
+import androidx.compose.material.icons.outlined.Sell
 import androidx.compose.material3.ExperimentalMaterial3Api
 import androidx.compose.material3.ExperimentalMaterial3ExpressiveApi
 import androidx.compose.material3.SnackbarHostState
@@ -188,7 +187,7 @@ fun BookSourceScreen(
         topBarActions = {
             TopBarActionButton(
                 onClick = { showGroupFilterSheet = true },
-                imageVector = Icons.Default.Folder,
+                imageVector = Icons.Outlined.Sell,
                 contentDescription = stringResource(R.string.menu_action_group),
             )
         },
@@ -437,8 +436,6 @@ private fun BookSourceGroupFilterSheet(
             Spacer(Modifier.height(8.dp))
             LazyVerticalGrid(
                 columns = GridCells.Fixed(2),
-                modifier = Modifier.height(420.dp),
-                contentPadding = PaddingValues(vertical = 4.dp),
                 horizontalArrangement = Arrangement.spacedBy(8.dp),
                 verticalArrangement = Arrangement.spacedBy(6.dp),
             ) {
@@ -461,7 +458,6 @@ private fun BookSourceGroupFilterSheet(
                     GroupFilterItem(label, value, state.activeFilter, onSelect)
                 }
             }
-            Spacer(Modifier.height(12.dp))
         }
     }
 }

+ 8 - 9
app/src/main/java/io/legado/app/ui/rss/source/debug/RssSourceDebugScreen.kt

@@ -18,15 +18,16 @@ import androidx.compose.material.icons.filled.Stop
 import androidx.compose.runtime.Composable
 import androidx.compose.runtime.LaunchedEffect
 import androidx.compose.runtime.remember
-import androidx.compose.ui.Modifier
 import androidx.compose.ui.Alignment
+import androidx.compose.ui.Modifier
 import androidx.compose.ui.input.nestedscroll.nestedScroll
 import androidx.compose.ui.unit.dp
 import io.legado.app.model.Debug
+import io.legado.app.ui.about.MarkdownSheet
 import io.legado.app.ui.book.source.debug.BookSourceDebugFilter
 import io.legado.app.ui.book.source.debug.BookSourceDebugStatus
-import io.legado.app.ui.book.source.debug.DebugEntryCard
 import io.legado.app.ui.book.source.debug.DebugChipRow
+import io.legado.app.ui.book.source.debug.DebugEntryCard
 import io.legado.app.ui.theme.LegadoTheme
 import io.legado.app.ui.widget.components.AppFloatingActionButton
 import io.legado.app.ui.widget.components.AppScaffold
@@ -34,8 +35,6 @@ import io.legado.app.ui.widget.components.AppTextField
 import io.legado.app.ui.widget.components.EmptyMessage
 import io.legado.app.ui.widget.components.button.ToggleChip
 import io.legado.app.ui.widget.components.card.GlassCard
-import io.legado.app.ui.widget.components.modalBottomSheet.AppModalBottomSheet
-import io.legado.app.ui.widget.components.progressIndicator.AppCircularProgressIndicator
 import io.legado.app.ui.widget.components.text.AppText
 import io.legado.app.ui.widget.components.topbar.GlassMediumFlexibleTopAppBar
 import io.legado.app.ui.widget.components.topbar.GlassTopAppBarDefaults
@@ -172,12 +171,12 @@ fun RssSourceDebugScreen(
         }
     }
     val selected = state.entries.firstOrNull { it.id == state.selectedEntryId }
-    AppModalBottomSheet(
-        selected != null,
-        { onIntent(RssSourceDebugIntent.DismissEntry) },
+    MarkdownSheet(
+        show = selected != null,
         title = "日志详情",
-        animateContentSize = false
-    ) { selected?.let { AppText(it.message, Modifier.fillMaxWidth()) } }
+        content = selected?.message.orEmpty(),
+        onDismissRequest = { onIntent(RssSourceDebugIntent.DismissEntry) },
+    )
 }
 
 private val RssSourceDebugTarget.label

+ 11 - 4
app/src/main/java/io/legado/app/ui/rss/source/edit/RssSourceEditActivity.kt

@@ -1,17 +1,22 @@
 package io.legado.app.ui.rss.source.edit
 
 import android.content.Intent
-import androidx.compose.runtime.*
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.LaunchedEffect
 import androidx.lifecycle.compose.collectAsStateWithLifecycle
 import androidx.lifecycle.lifecycleScope
 import io.legado.app.R
 import io.legado.app.base.BaseComposeActivity
 import io.legado.app.data.appDb
-import io.legado.app.ui.about.AppLogDialog
 import io.legado.app.ui.login.SourceLoginActivity
 import io.legado.app.ui.rss.source.debug.RssSourceDebugActivity
 import io.legado.app.ui.widget.dialog.VariableDialog
-import io.legado.app.utils.*
+import io.legado.app.utils.getClipText
+import io.legado.app.utils.sendToClip
+import io.legado.app.utils.share
+import io.legado.app.utils.showDialogFragment
+import io.legado.app.utils.startActivity
+import io.legado.app.utils.toastOnUi
 import kotlinx.coroutines.Dispatchers
 import kotlinx.coroutines.flow.collectLatest
 import kotlinx.coroutines.launch
@@ -25,7 +30,9 @@ class RssSourceEditActivity : BaseComposeActivity(), VariableDialog.Callback {
    is RssSourceEditEffect.Debug->startActivity<RssSourceDebugActivity>{putExtra("key",e.url)}
    is RssSourceEditEffect.Login->startActivity<SourceLoginActivity>{putExtra("type","rssSource");putExtra("key",e.url)}
    is RssSourceEditEffect.Copy->sendToClip(e.text);is RssSourceEditEffect.Share->share(e.text);RssSourceEditEffect.ReadClipboard->{val t=getClipText();if(t.isNullOrBlank())toastOnUi("剪贴板为空")else vm.onIntent(RssSourceEditIntent.Import(t))}
-   is RssSourceEditEffect.Variable->openVariable(e.url);RssSourceEditEffect.Log->showDialogFragment<AppLogDialog>();RssSourceEditEffect.Help->showHelp("ruleHelp");is RssSourceEditEffect.Message->toastOnUi(e.text)
+      is RssSourceEditEffect.Variable -> openVariable(e.url); is RssSourceEditEffect.Message -> toastOnUi(
+          e.text
+      )
   }}}
   RssSourceEditScreen(state,vm::onIntent){vm.onIntent(RssSourceEditIntent.Back)}
  }

+ 17 - 4
app/src/main/java/io/legado/app/ui/rss/source/edit/RssSourceEditContract.kt

@@ -1,9 +1,11 @@
 package io.legado.app.ui.rss.source.edit
 
 import androidx.annotation.StringRes
-import androidx.compose.runtime.*
-import kotlinx.collections.immutable.*
+import androidx.compose.runtime.Stable
 import io.legado.app.ui.book.source.edit.BookSourceEditFieldUi
+import kotlinx.collections.immutable.ImmutableList
+import kotlinx.collections.immutable.ImmutableMap
+import kotlinx.collections.immutable.persistentMapOf
 
 enum class RssSourceEditTab(@StringRes val title: Int) {
     Base(io.legado.app.R.string.source_tab_base),
@@ -17,7 +19,13 @@ enum class RssSourceEditTab(@StringRes val title: Int) {
     val enabled: Boolean = true, val singleUrl: Boolean = false, val cookieJar: Boolean = true,
     val preload: Boolean = false, val type: Int = 0, val articleStyle: Int = 0,
     val autoComplete: Boolean = false, val dirty: Boolean = false, val selectedField: String? = null,
+    val activeSheet: RssSourceEditSheet? = null,
 )
+
+sealed interface RssSourceEditSheet {
+    data object Log : RssSourceEditSheet;
+    data class Help(val content: String) : RssSourceEditSheet
+}
 sealed interface RssSourceEditIntent {
     data class Load(val url: String?) : RssSourceEditIntent; data class SelectTab(val tab: RssSourceEditTab) : RssSourceEditIntent
     data class UpdateField(val key: String, val value: String) : RssSourceEditIntent; data class EditField(val key: String?) : RssSourceEditIntent
@@ -27,10 +35,15 @@ sealed interface RssSourceEditIntent {
     data object ToggleAutoComplete : RssSourceEditIntent; data object Save : RssSourceEditIntent; data object SaveDebug : RssSourceEditIntent
     data object SaveLogin : RssSourceEditIntent; data object Copy : RssSourceEditIntent; data object Paste : RssSourceEditIntent
     data class Import(val text: String) : RssSourceEditIntent; data object Share : RssSourceEditIntent; data object ClearCookie : RssSourceEditIntent
-    data object SetVariable : RssSourceEditIntent; data object ShowLog : RssSourceEditIntent; data object Help : RssSourceEditIntent; data object Back : RssSourceEditIntent
+    data object SetVariable : RssSourceEditIntent;
+    data object ShowLog : RssSourceEditIntent;
+    data object Help : RssSourceEditIntent;
+    data object DismissSheet : RssSourceEditIntent;
+    data object Back : RssSourceEditIntent
 }
 sealed interface RssSourceEditEffect {
     data class Finish(val url: String) : RssSourceEditEffect; data class Debug(val url: String) : RssSourceEditEffect; data class Login(val url: String) : RssSourceEditEffect
     data class Copy(val text: String) : RssSourceEditEffect; data class Share(val text: String) : RssSourceEditEffect; data object ReadClipboard : RssSourceEditEffect
-    data class Variable(val url: String) : RssSourceEditEffect; data object Log : RssSourceEditEffect; data object Help : RssSourceEditEffect; data class Message(val text: String) : RssSourceEditEffect
+    data class Variable(val url: String) : RssSourceEditEffect;
+    data class Message(val text: String) : RssSourceEditEffect
 }

+ 39 - 7
app/src/main/java/io/legado/app/ui/rss/source/edit/RssSourceEditScreen.kt

@@ -1,26 +1,49 @@
 package io.legado.app.ui.rss.source.edit
 
 import androidx.activity.compose.BackHandler
-import androidx.compose.foundation.layout.*
+import androidx.compose.foundation.layout.Arrangement
+import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.PaddingValues
+import androidx.compose.foundation.layout.Row
+import androidx.compose.foundation.layout.fillMaxSize
+import androidx.compose.foundation.layout.fillMaxWidth
 import androidx.compose.foundation.lazy.LazyColumn
 import androidx.compose.foundation.lazy.items
-import androidx.compose.foundation.pager.*
+import androidx.compose.foundation.pager.HorizontalPager
+import androidx.compose.foundation.pager.rememberPagerState
 import androidx.compose.material.icons.Icons
 import androidx.compose.material.icons.filled.BugReport
 import androidx.compose.material.icons.filled.MoreVert
 import androidx.compose.material.icons.filled.Save
-import androidx.compose.runtime.*
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.LaunchedEffect
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.remember
+import androidx.compose.runtime.rememberCoroutineScope
+import androidx.compose.runtime.setValue
+import androidx.compose.runtime.snapshotFlow
 import androidx.compose.ui.Modifier
 import androidx.compose.ui.input.nestedscroll.nestedScroll
 import androidx.compose.ui.res.stringArrayResource
 import androidx.compose.ui.res.stringResource
 import androidx.compose.ui.unit.dp
 import io.legado.app.R
-import io.legado.app.ui.book.source.edit.*
-import io.legado.app.ui.widget.components.*
-import io.legado.app.ui.widget.components.menuItem.*
+import io.legado.app.ui.about.MarkdownSheet
+import io.legado.app.ui.book.source.edit.SourceEditFieldCard
+import io.legado.app.ui.book.source.edit.SourceEditFieldSheet
+import io.legado.app.ui.book.source.edit.SourceEditOptionCard
+import io.legado.app.ui.widget.components.AppFloatingActionButton
+import io.legado.app.ui.widget.components.AppScaffold
+import io.legado.app.ui.widget.components.log.AppLogSheet
+import io.legado.app.ui.widget.components.menuItem.RoundDropdownMenu
+import io.legado.app.ui.widget.components.menuItem.RoundDropdownMenuItem
 import io.legado.app.ui.widget.components.tabRow.AppTabRow
-import io.legado.app.ui.widget.components.topbar.*
+import io.legado.app.ui.widget.components.topbar.GlassMediumFlexibleTopAppBar
+import io.legado.app.ui.widget.components.topbar.GlassTopAppBarDefaults
+import io.legado.app.ui.widget.components.topbar.TopBarActionButton
+import io.legado.app.ui.widget.components.topbar.TopBarNavigationButton
 import kotlinx.coroutines.launch
 
 @Composable fun RssSourceEditScreen(state:RssSourceEditUiState,onIntent:(RssSourceEditIntent)->Unit,onBack:()->Unit){
@@ -37,6 +60,15 @@ import kotlinx.coroutines.launch
             items(state.fields[tab].orEmpty(),key={it.path}){field->SourceEditFieldCard(field,Modifier.fillMaxWidth()){onIntent(RssSourceEditIntent.EditField(field.path))}}
         }} }
     val field=state.fields.values.flatten().firstOrNull{it.path==state.selectedField};SourceEditFieldSheet(field,{onIntent(RssSourceEditIntent.EditField(null))}){k,v->onIntent(RssSourceEditIntent.UpdateField(k,v))}
+    AppLogSheet(
+        show = state.activeSheet is RssSourceEditSheet.Log,
+        onDismissRequest = { onIntent(RssSourceEditIntent.DismissSheet) })
+    val helpSheet = state.activeSheet as? RssSourceEditSheet.Help
+    MarkdownSheet(
+        show = helpSheet != null,
+        title = stringResource(R.string.help),
+        content = helpSheet?.content.orEmpty(),
+        onDismissRequest = { onIntent(RssSourceEditIntent.DismissSheet) })
 }
 @Composable private fun RssEditOptions(s:RssSourceEditUiState,on:(RssSourceEditIntent)->Unit){val types=stringArrayResource(R.array.rss_type);val styles=stringArrayResource(R.array.layout_type);var typeMenu by remember{mutableStateOf(false)};var styleMenu by remember{mutableStateOf(false)}
     Column(verticalArrangement=Arrangement.spacedBy(8.dp)){Row(horizontalArrangement=Arrangement.spacedBy(8.dp)){Box(Modifier.weight(1f)){SourceEditOptionCard("类型:${types.getOrNull(s.type).orEmpty()}",{typeMenu=true});RoundDropdownMenu(typeMenu,{typeMenu=false}){types.forEachIndexed{i,t->RoundDropdownMenuItem(text=t,isSelected=s.type==i,onClick={typeMenu=false;on(RssSourceEditIntent.SetType(i))})}}};Box(Modifier.weight(1f)){SourceEditOptionCard("布局:${styles.getOrNull(s.articleStyle).orEmpty()}",{styleMenu=true});RoundDropdownMenu(styleMenu,{styleMenu=false}){styles.forEachIndexed{i,t->RoundDropdownMenuItem(text=t,isSelected=s.articleStyle==i,onClick={styleMenu=false;on(RssSourceEditIntent.SetArticleStyle(i))})}}};SourceEditOptionCard(stringResource(R.string.is_enable),{on(RssSourceEditIntent.SetEnabled(!s.enabled))},Modifier.weight(1f),checked=s.enabled)}

+ 33 - 7
app/src/main/java/io/legado/app/ui/rss/source/edit/RssSourceEditViewModel.kt

@@ -3,21 +3,27 @@ package io.legado.app.ui.rss.source.edit
 import android.app.Application
 import androidx.lifecycle.AndroidViewModel
 import androidx.lifecycle.viewModelScope
-import com.google.gson.*
+import com.google.gson.JsonObject
+import com.google.gson.JsonParser
 import io.legado.app.R
 import io.legado.app.data.appDb
 import io.legado.app.data.entities.RssSource
 import io.legado.app.exception.NoStackTraceException
-import io.legado.app.help.*
-import io.legado.app.help.config.SourceConfig
+import io.legado.app.help.AppCacheManager
+import io.legado.app.help.RuleComplete
 import io.legado.app.help.http.CookieStore
 import io.legado.app.help.source.removeSortCache
 import io.legado.app.model.SharedJsScope
 import io.legado.app.ui.book.source.edit.BookSourceEditFieldUi
 import io.legado.app.utils.GSON
-import kotlinx.collections.immutable.*
+import kotlinx.collections.immutable.toImmutableList
+import kotlinx.collections.immutable.toImmutableMap
 import kotlinx.coroutines.Dispatchers
-import kotlinx.coroutines.flow.*
+import kotlinx.coroutines.flow.MutableSharedFlow
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.asSharedFlow
+import kotlinx.coroutines.flow.asStateFlow
+import kotlinx.coroutines.flow.update
 import kotlinx.coroutines.launch
 
 class RssSourceEditViewModel(app: Application) : AndroidViewModel(app) {
@@ -35,10 +41,30 @@ class RssSourceEditViewModel(app: Application) : AndroidViewModel(app) {
         RssSourceEditIntent.SaveLogin -> save { RssSourceEditEffect.Login(it) }; RssSourceEditIntent.Copy -> _effects.tryEmit(RssSourceEditEffect.Copy(GSON.toJson(current())))
         RssSourceEditIntent.Paste -> _effects.tryEmit(RssSourceEditEffect.ReadClipboard); is RssSourceEditIntent.Import -> import(i.text)
         RssSourceEditIntent.Share -> _effects.tryEmit(RssSourceEditEffect.Share(GSON.toJson(current()))); RssSourceEditIntent.ClearCookie -> viewModelScope.launch(Dispatchers.IO){ CookieStore.removeCookie(current().sourceUrl) }
-        RssSourceEditIntent.SetVariable -> save { RssSourceEditEffect.Variable(it) }; RssSourceEditIntent.ShowLog -> _effects.tryEmit(RssSourceEditEffect.Log)
-        RssSourceEditIntent.Help -> _effects.tryEmit(RssSourceEditEffect.Help); RssSourceEditIntent.Back -> _effects.tryEmit(RssSourceEditEffect.Finish(""))
+        RssSourceEditIntent.SetVariable -> save { RssSourceEditEffect.Variable(it) }; RssSourceEditIntent.ShowLog -> _uiState.update {
+            it.copy(
+                activeSheet = RssSourceEditSheet.Log
+            )
+        }
+
+        RssSourceEditIntent.Help -> showHelp(); RssSourceEditIntent.DismissSheet -> _uiState.update {
+            it.copy(
+                activeSheet = null
+            )
+        }; RssSourceEditIntent.Back -> _effects.tryEmit(RssSourceEditEffect.Finish(""))
     } }
     private fun load(url:String?)=viewModelScope.launch(Dispatchers.IO){ apply(url?.let{appDb.rssSourceDao.getByKey(it)}?:RssSource(),true) }
+    private fun showHelp() = viewModelScope.launch(Dispatchers.IO) {
+        val content =
+            getApplication<Application>().assets.open("web/help/md/ruleHelp.md").bufferedReader()
+                .use { it.readText() }; _uiState.update {
+        it.copy(
+            activeSheet = RssSourceEditSheet.Help(
+                content
+            )
+        )
+    }
+    }
     private fun apply(s:RssSource, asOriginal:Boolean=false){ if(asOriginal){original=s;baseline=GSON.toJson(s)};json=JsonParser.parseString(GSON.toJson(s)).asJsonObject;_uiState.value=RssSourceEditUiState(false,fields=groups(),enabled=s.enabled,singleUrl=s.singleUrl,cookieJar=s.enabledCookieJar==true,preload=s.preload,type=s.type,articleStyle=s.articleStyle) }
     private fun update(k:String,v:String){ if(v.isBlank())json.remove(k) else json.addProperty(k,v);_uiState.update{st->st.copy(fields=st.fields.mapValues{(_,fs)->fs.map{if(it.path==k)it.copy(value=v)else it}.toImmutableList()}.toImmutableMap(),dirty=true)} }
     private fun flag(f:RssSourceEditUiState.()->RssSourceEditUiState)=_uiState.update{f(it).copy(dirty=true)}