diff --git a/.vscode/launch.json b/.vscode/launch.json new file mode 100644 index 0000000..bc77ead --- /dev/null +++ b/.vscode/launch.json @@ -0,0 +1,22 @@ +{ + "version": "0.2.0", + "configurations": [ + { + "name": "ollama-app", + "request": "launch", + "type": "dart", + }, + { + "name": "ollama-app (profile mode)", + "request": "launch", + "type": "dart", + "flutterMode": "profile" + }, + { + "name": "ollama-app (release mode)", + "request": "launch", + "type": "dart", + "flutterMode": "release" + } + ] +} \ No newline at end of file diff --git a/analysis_options.yaml b/analysis_options.yaml index 5c5ed34..c72735d 100644 --- a/analysis_options.yaml +++ b/analysis_options.yaml @@ -16,19 +16,18 @@ linter: - avoid_multiple_declarations_per_line - eol_at_end_of_file - leading_newlines_in_multiline_strings + - no_wildcard_variable_uses + - prefer_final_locals - unnecessary_breaks - - unnecessary_final - unnecessary_lambdas - unnecessary_parenthesis # Async, Errors, Null Safety - avoid_catching_errors - - avoid_null_checks_in_equality_operators - avoid_void_async - cancel_subscriptions - cast_nullable_to_non_nullable - unnecessary_await_in_return - - use_if_null_to_convert_nulls_to_bools - use_to_and_as_if_applicable # Usage Patterns diff --git a/android/app/build.gradle.kts b/android/app/build.gradle.kts index 88fe025..1ffc3ab 100644 --- a/android/app/build.gradle.kts +++ b/android/app/build.gradle.kts @@ -3,7 +3,6 @@ import java.io.FileInputStream plugins { id("com.android.application") - id("kotlin-android") // The Flutter Gradle Plugin must be applied after the Android and Kotlin Gradle plugins. id("dev.flutter.flutter-gradle-plugin") } @@ -17,15 +16,11 @@ if (keystorePropertiesFile.exists()) { android { namespace = "com.freakurl.apps.ollama" compileSdk = flutter.compileSdkVersion - ndkVersion = "27.0.12077973" // flutter.ndkVersion + ndkVersion = "28.2.13676358" // flutter.ndkVersion compileOptions { - sourceCompatibility = JavaVersion.VERSION_11 - targetCompatibility = JavaVersion.VERSION_11 - } - - kotlinOptions { - jvmTarget = JavaVersion.VERSION_11.toString() + sourceCompatibility = JavaVersion.VERSION_17 + targetCompatibility = JavaVersion.VERSION_17 } defaultConfig { @@ -54,6 +49,13 @@ android { } } +kotlin { + compilerOptions { + jvmTarget = org.jetbrains.kotlin.gradle.dsl.JvmTarget.JVM_17 + } +} + + flutter { source = "../.." } diff --git a/android/gradle.properties b/android/gradle.properties index f018a61..475a628 100644 --- a/android/gradle.properties +++ b/android/gradle.properties @@ -1,3 +1,7 @@ org.gradle.jvmargs=-Xmx8G -XX:MaxMetaspaceSize=4G -XX:ReservedCodeCacheSize=512m -XX:+HeapDumpOnOutOfMemoryError android.useAndroidX=true android.enableJetifier=true +# This builtInKotlin flag was added automatically by Flutter migrator +android.builtInKotlin=false +# This newDsl flag was added automatically by Flutter migrator +android.newDsl=false diff --git a/android/gradle/wrapper/gradle-wrapper.properties b/android/gradle/wrapper/gradle-wrapper.properties index afa1e8e..f587a47 100644 --- a/android/gradle/wrapper/gradle-wrapper.properties +++ b/android/gradle/wrapper/gradle-wrapper.properties @@ -2,4 +2,4 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-8.10.2-all.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-8.14.1-all.zip diff --git a/android/settings.gradle.kts b/android/settings.gradle.kts index 11662c3..da20e6d 100644 --- a/android/settings.gradle.kts +++ b/android/settings.gradle.kts @@ -18,8 +18,8 @@ pluginManagement { plugins { id("dev.flutter.flutter-plugin-loader") version "1.0.0" - id("com.android.application") version "8.7.0" apply false - id("org.jetbrains.kotlin.android") version "2.1.0" apply false + id("com.android.application") version "8.11.1" apply false + id("org.jetbrains.kotlin.android") version "2.2.20" apply false } include(":app") diff --git a/assets/fonts/GoogleSansCode-Italic-VariableFont_MONO,wght.ttf b/assets/fonts/GoogleSansCode-Italic-VariableFont_MONO,wght.ttf new file mode 100644 index 0000000..d07eaf9 Binary files /dev/null and b/assets/fonts/GoogleSansCode-Italic-VariableFont_MONO,wght.ttf differ diff --git a/assets/fonts/GoogleSansCode-VariableFont_MONO,wght.ttf b/assets/fonts/GoogleSansCode-VariableFont_MONO,wght.ttf new file mode 100644 index 0000000..58583b9 Binary files /dev/null and b/assets/fonts/GoogleSansCode-VariableFont_MONO,wght.ttf differ diff --git a/devtools_options.yaml b/devtools_options.yaml index 4dcfde9..2c00e5e 100644 --- a/devtools_options.yaml +++ b/devtools_options.yaml @@ -1,4 +1,5 @@ description: This file stores settings for Dart & Flutter DevTools. documentation: https://docs.flutter.dev/tools/devtools/extensions#configure-extension-enablement-states extensions: - - shared_preferences: true \ No newline at end of file + - shared_preferences: true + - drift: true \ No newline at end of file diff --git a/lib/l10n/app_de.arb b/lib/l10n/app_de.arb index 9308bcd..55297f6 100644 --- a/lib/l10n/app_de.arb +++ b/lib/l10n/app_de.arb @@ -1,10 +1,12 @@ { "@@locale": "de", - "appTitle": "Ollama", + "appTitle": "{env, select, short{Ollama} integrated{{title} - Ollama App} other{Ollama App}}", "optionNewChat": "Neuer Chat", "optionSettings": "Einstellungen", "optionInstallPwa": "Webapp installieren", - "optionNoChatFound": "Keine Chats gefunden", + "optionNoChatFound": "Keine Chats", + "optionNoChatFoundSearch": "Keinen Chat gefunden für Suche ‚{query}‘", + "optionSearchChats": "Chats durchsuchen", "tipPrefix": "Tipp: ", "tip0": "Bearbeite Nachrichten durch langes Tippen", "tip1": "Lösche Nachrichten durch Doppeltippen", @@ -15,37 +17,32 @@ "renameChat": "Umbenennen", "takeImage": "Bild Aufnehmen", "uploadImage": "Bild Hochladen", - "notAValidImage": "Kein gültiges Bild", - "imageOnlyConversation": "Nur Bild Unterhaltung", - "messageInputPlaceholder": "Nachricht", - "tooltipAttachment": "Anhang hinzufügen", + "messageInputPlaceholder": "{model} fragen", + "messageInputPlaceholderModelPlaceholder": "Modell", + "tooltipMessageOptions": "Nachrichtenoptionen", "tooltipSend": "Senden", - "tooltipSave": "Speichern", - "tooltipLetAIThink": "Lass KI denken", + "tooltipLetAIThink": "Mit {model} generieren", "tooltipAddHostHeaders": "Host-Header festlegen", "tooltipReset": "Aktuellen Chat zurücksetzen", "tooltipOptions": "Optionen anzeigen", "noModelSelected": "Kein Modell ausgewählt", - "noHostSelected": "Kein Host ausgewählt, öffne zum Auswählen die Einstellungen", - "noSelectedModel": "", + "noHostSelected": "Kein Host ausgewählt, öffne zum Auswählen die Einstellungen.", + "noSelectedModel": "", "newChatTitle": "Unbenannter Chat", - "modelDialogAddModel": "Hinzufügen", - "modelDialogAddPromptTitle": "Neues Modell hinzufügen", - "modelDialogAddPromptDescription": "Das kann entweder ein normaler Name (z.B. 'llama3') oder Name und Tag (z.B. 'llama3:70b') sein.", - "modelDialogAddPromptAlreadyExists": "Modell existiert bereits", - "modelDialogAddPromptInvalid": "Ungültiger Modellname", - "modelDialogAddAllowanceTitle": "Proxy erlauben", - "modelDialogAddAllowanceDescription": "Ollama App muss überprüfen, ob das eingegebene Modell gültig ist. Dafür senden wir normalerweise eine Webanfrage an die Ollama-Modellliste und überprüfen den Statuscode, aber da gerade der Webclient verwendet wird, können wir das nicht direkt tun. Stattdessen sendet die App die Anfrage an eine andere API, gehostet von JHubi1, um dies für uns zu überprüfen.\nDies ist eine einmalige Anfrage und wird nur gesendet, wenn du ein neues Modell hinzufügst.\nIhre IP-Adresse wird mit der Anfrage gesendet und kann bis zu zehn Minuten gespeichert werden, um Spamming mit potenziell schädlichen Absichten zu verhindern.\nWenn du zustimmst, wird deine Auswahl für die Zukunft gespeichert; wenn nicht, wird nichts gesendet und das Modell wird nicht hinzugefügt.", - "modelDialogAddAllowanceAllow": "Erlauben", - "modelDialogAddAllowanceDeny": "Ablehnen", - "modelDialogAddAssuranceTitle": "{model} hinzufügen?", - "modelDialogAddAssuranceDescription": "Durch Drücken von 'Hinzufügen' wird das Modell '{model}' direkt vom Ollama-Server auf deinen Host heruntergeladen.\nJe nach Internetverbindung kann dies eine Weile dauern. Der Vorgang kann nicht abgebrochen werden.\nWenn die App während des Downloads geschlossen wird, wird der Download fortgesetzt, wenn du den Namen erneut in den Modelldialog eingibst.", - "modelDialogAddAssuranceAdd": "Hinzufügen", - "modelDialogAddAssuranceCancel": "Abbrechen", - "modelDialogAddDownloadPercentLoading": "lade Fortschritt", - "modelDialogAddDownloadPercent": "Download bei {percent}%", + "learnMore": "Mehr erfahren", + "modelDialogAdd": "Hinzufügen", + "modelDialogAddPromptTitle": "Modell hinzufügen", + "modelDialogAddPromptDescription": "Das kann entweder ein normaler Name (z.B. „llama3“) oder ein Name zusammen mit einem Tag (z.B. „llama3:70b“) sein.", "modelDialogAddCurrently": "Wird gerade heruntergeladen:", "modelDialogAddPromptAlreadyExists": "Modell existiert bereits.", + "modelDialogAddPromptInvalid": "Ungültiger Modellname.", + "modelDialogAddPromptNotFound": "Das Modell konnte auf dem Server nicht gefunden werden. Bitte überprüfe die Schreibweise und versuche es erneut.", + "modelDialogAddPromptNetworkError": "Netzwerkfehler beim Überprüfen des Modells. Bitte überprüfe deine Verbindung und versuche es erneut.", + "modelDialogAddPromptCorruptionError": "Die API hat „EOF“ zurückgegeben; dein Server wurde wahrscheinlich neu gestartet, während das Modell zuvor heruntergeladen wurde.", + "modelDialogAddPromptCorruptionErrorSolution": "Alternativ kannst du den folgenden Befehl direkt auf deinem Linux-Host ausführen:\nWenn du eine neue Fehlermeldung siehst, versuche den Befehl mit „sudo“ davor auszuführen.", + "modelDialogAddCancelTitle": "Download abbrechen?", + "modelDialogAddCancelConfirm": "Abbrechen", + "modelDialogAddCancelHide": "Ausblenden", "modelDialogAddDownloadFailed": "Getrennt, versuche es erneut", - "modelDialogAddDownloadSuccess": "Download erfolgreich", + "modelDialogAddDownloadSuccess": "Download erfolgreich. Als aktuelles Modell festlegen.", "deleteDialogTitle": "Chat löschen", "deleteDialogDescription": "Bist du sicher, dass du fortfahren möchtest? Dies wird alle Erinnerungen dieses Chats löschen und kann nicht rückgängig gemacht werden.\nUm diesen Dialog zu deaktivieren, besuche die Einstellungen.", "deleteDialogDelete": "Löschen", @@ -73,12 +70,11 @@ "settingsExperimentalDeprecatedDescription": "Diese Funktion ist veraltet und wird in einer zukünftigen Version entfernt werden.\nEs funktioniert möglicherweise nicht wie beabsichtigt oder erwartet. Benutzung auf eigenes Gefahr.", "settingsExperimentalDeprecatedFeature": "Veraltete Funktion, halte, um mehr zu erfahren", "settingsHost": "Host", + "settingsHostMissing": "Kein Host festgelegt", "settingsHostValid": "Gültiger Host", "settingsHostChecking": "Host wird Überprüft", - "settingsHostInvalid": "Fehler: {type, select, url{Ungültige URL} host{Ungültiger Host} timeout{Request Fehlgeschlagen. Server Fehler} ratelimit{Zu viele Anfragen} other{Request Fehlgeschlagen}}", - "settingsHostHeaderTitle": "Host-Header festlegen", - "settingsHostHeaderInvalid": "Der eingegebene Text ist kein gültiges Header-JSON-Objekt", - "settingsHostInvalidDetailed": "{type, select, url{Die eingegebene URL ist ungültig. Es handelt sich nicht um ein standardisiertes URL-Format.} other{Der eingegebene Host ist ungültig. Er kann nicht erreicht werden. Bitte überprüfe den Host und versuche es erneut.}}", + "settingsHostInvalid": "{type, select, invalidUrl{Ungültige URL} unreachable{Host nicht erreichbar} undetectable{Host kein Ollama-Server} timeout{Verbindung abgelaufen} outdated{Veraltete Ollama-Version} other{Ungültiger Host}}", + "settingsHostInvalidDetailed": "{type, select, invalidUrl{Die eingegebene URL hat kein gültiges Format.} unreachable{Der Host ist ungültig oder nicht erreichbar, möglicherweise aufgrund einer falschen Adresse oder eines Netzwerkproblems.} undetectable{Der Host ist erreichbar, antwortet aber nicht wie von Ollama App erwartet.\nEr ist möglicherweise kein Ollama-Server. Bist du sicher, dass du ihn richtig eingegeben hast?} timeout{Der Host konnte nicht rechtzeitig erreicht werden.\nVersuche, den Timeout-Multiplikator in den Oberflächen-Einstellungen zu erhöhen.} outdated{Der Host verwendet eine Ollama-Version, die mit dieser App nicht kompatibel ist.\nBitte aktualisiere sowohl den Host als auch diese App, wenn möglich.} other{Der eingegebene Host ist ungültig.}}", "settingsSystemMessage": "Systemnachricht", "settingsUseSystem": "Systemnachricht verwenden", "settingsUseSystemDescription": "Deaktiviere das Setzen der obigen Systemnachricht und benutze stattdessen die des Modells. Kann nützlich für Modelle mit Model-Files sein", diff --git a/lib/l10n/app_en.arb b/lib/l10n/app_en.arb index 50b6ad7..63eb52d 100644 --- a/lib/l10n/app_en.arb +++ b/lib/l10n/app_en.arb @@ -1,8 +1,24 @@ { "@@locale": "en", - "appTitle": "Ollama", + "appTitle": "{env, select, short{Ollama} integrated{{title} - Ollama App} other{Ollama App}}", "@appTitle": { - "description": "Title of the application", + "description": "Title of the application. Should stay untranslated in most cases", + "context": "app:sidebar", + "placeholders": { + "env": { + "type": "String", + "description": "The environment the app is running in. Can be 'short' on mobile platforms, 'integrated' on web if the app is currently displaying a page, or 'other' in every other case.", + "example": "integrated" + }, + "title": { + "type": "String", + "description": "Title of the chat or settings page currently visible, if visible" + } + } + }, + "learnMore": "Learn more", + "@learnMore": { + "description": "Text displayed for learn more button", "context": "app:sidebar" }, "optionNewChat": "New Chat", @@ -20,9 +36,35 @@ "description": "Text displayed for install PWA option", "context": "app:sidebar" }, - "optionNoChatFound": "No chats found", + "optionNoChatFound": "No chats", "@optionNoChatFound": { - "description": "Text displayed when no chats are found", + "description": "Text displayed when there are no chats", + "context": "app:sidebar" + }, + "optionNoChatFoundSearch": "No chats found for ‘{query}’", + "@optionNoChatFoundSearch": { + "description": "Text displayed when no chats are found for the search query", + "context": "app:sidebar", + "placeholders": { + "query": { + "type": "String", + "description": "The search query that returned no results" + } + } + }, + "optionSearchChats": "Search through chats", + "@optionSearchChats": { + "description": "Placeholder for search chats input", + "context": "app:sidebar" + }, + "optionChatDetails": "Chat details", + "@optionChatDetails": { + "description": "Text displayed as title for chat details sidebar", + "context": "app:sidebar" + }, + "optionChatDetailsNoChat": "No chat selected", + "@optionChatDetailsNoChat": { + "description": "Text displayed when no chat is selected in the chat details sidebar", "context": "app:sidebar" }, "tipPrefix": "Tip: ", @@ -65,34 +107,100 @@ "description": "Text displayed for rename chat option", "context": "app:sidebar" }, - "takeImage": "Take Image", + "takeImage": "Camera", "@takeImage": { "description": "Text displayed for take image button", "context": "app:chat:attachment" }, - "uploadImage": "Upload Image", + "uploadImage": "Gallery", "@uploadImage": { "description": "Text displayed for image upload button", "context": "app:chat:attachment" }, - "notAValidImage": "Not a valid image", - "@notAValidImage": { - "description": "Text displayed when an image is not valid", - "context": "app:chat:attachment" + "newChatGreeting1": "What’s on your mind, {user}?", + "@newChatGreeting1": { + "description": "First greeting displayed in a new chat", + "context": "app:chat:greeting", + "placeholders": { + "user": { + "type": "String", + "description": "Name of the user. If unavailable, 'user' will be used instead.", + "example": "User" + } + } }, - "imageOnlyConversation": "Image Only Conversation", - "@imageOnlyConversation": { - "description": "Title, if 'Generate Title' is executed on a conversation with no text messages", - "context": "app:chat:title" + "newChatGreeting2": "Good to see you, {user}.", + "@newChatGreeting2": { + "description": "Second greeting displayed in a new chat", + "context": "app:chat:greeting", + "placeholders": { + "user": { + "type": "String", + "description": "Name of the user. If unavailable, 'user' will be used instead.", + "example": "User" + } + } }, - "messageInputPlaceholder": "Message", + "newChatGreeting3": "What can I do for you, {user}?", + "@newChatGreeting3": { + "description": "Third greeting displayed in a new chat", + "context": "app:chat:greeting", + "placeholders": { + "user": { + "type": "String", + "description": "Name of the user. If unavailable, 'user' will be used instead.", + "example": "User" + } + } + }, + "newChatGreeting4": "How can I help you today, {user}?", + "@newChatGreeting4": { + "description": "Fourth greeting displayed in a new chat", + "context": "app:chat:greeting", + "placeholders": { + "user": { + "type": "String", + "description": "Name of the user. If unavailable, 'user' will be used instead.", + "example": "User" + } + } + }, + "newChatGreeting5": "What would you like to talk about, {user}?", + "@newChatGreeting5": { + "description": "Fifth greeting displayed in a new chat", + "context": "app:chat:greeting", + "placeholders": { + "user": { + "type": "String", + "description": "Name of the user. If unavailable, 'user' will be used instead.", + "example": "User" + } + } + }, + "newChatGreetingNameFallback": "user", + "@newChatGreetingNameFallback": { + "description": "Fallback name for the user if no name is set", + "context": "app:chat:greeting" + }, + "messageInputPlaceholder": "Ask {model}", "@messageInputPlaceholder": { "description": "Placeholder text for message input", + "context": "app:chat:input", + "placeholders": { + "model": { + "type": "String", + "description": "Friendly name of the model without its tag, so 'gemma3' or 'gpt-oss' for example" + } + } + }, + "messageInputPlaceholderModelPlaceholder": "model", + "@messageInputPlaceholderModelPlaceholder": { + "description": "Model name used in 'messageInputPlaceholder' if no model is available, should fit into capitalization and context", "context": "app:chat:input" }, - "tooltipAttachment": "Add attachment", - "@tooltipAttachment": { - "description": "Tooltip for attachment button", + "tooltipMessageOptions": "Message options", + "@tooltipMessageOptions": { + "description": "Tooltip for message options button; options here being attachments, etc.", "context": "app:chat:tooltips" }, "tooltipSend": "Send", @@ -100,14 +208,20 @@ "description": "Tooltip for send button", "context": "app:chat:tooltips" }, - "tooltipSave": "Save", - "@tooltipSave": { - "description": "Tooltip for save button", - "context": "app:chat:tooltips" - }, - "tooltipLetAIThink": "Let AI think", + "tooltipLetAIThink": "Generate with {model}", "@tooltipLetAIThink": { - "description": "Tooltip for let AI think button", + "description": "Tooltip for generate with model button", + "context": "app:chat:tooltips", + "placeholders": { + "model": { + "type": "String", + "description": "Friendly name of the model without its tag, so 'gemma3' or 'gpt-oss' for example" + } + } + }, + "tooltipAddHostHeaders": "Add host headers", + "@tooltipAddHostHeaders": { + "description": "Tooltip for add host headers button", "context": "app:chat:tooltips" }, "tooltipReset": "Reset current chat", @@ -125,120 +239,97 @@ "description": "Text displayed when no model is selected", "context": "app:chat" }, - "noHostSelected": "No host selected, open settings to set one", + "noHostSelected": "No host selected, open settings to set one.", "@noHostSelected": { "description": "Text displayed when no host is selected", "context": "app:chat" }, - "noSelectedModel": "", + "noSelectedModel": "", "@noSelectedModel": { - "description": "Text displayed when no model is selected", + "description": "Text displayed when no model is selected. Should be lower case even if the language would capitalize it, to fit better into the app's design.", "context": "app:chat" }, - "newChatTitle": "Unnamed Chat", + "newChatTitle": "Untitled Chat", "@newChatTitle": { "description": "Title of a new chat", "context": "app:chat:title" }, - "modelDialogAddModel": "Add", - "@modelDialogAddModel": { + "modelDialogTitle": "Select a model", + "@modelDialogTitle": { + "description": "Title of the model selection dialog", + "context": "app:modelDialog" + }, + "modelDialogAdd": "Add", + "@modelDialogAdd": { "description": "Text displayed for add model button", "context": "app:modelDialog" }, - "modelDialogAddPromptTitle": "Add new model", + "modelDialogRefresh": "Refresh", + "@modelDialogRefresh": { + "description": "Text displayed for refresh models button", + "context": "app:modelDialog" + }, + "modelDialogAddPromptTitle": "Add model", "@modelDialogAddPromptTitle": { "description": "Title of the add model dialog", "context": "app:modelDialog" }, - "modelDialogAddPromptDescription": "This can either be a normal name (e.g. 'llama3') or a name and tag (e.g. 'llama3:70b').", + "modelDialogAddPromptDescription": "This can either be a normal name (e.g. “llama3”) or a name together with a tag (e.g. “llama3:70b”).", "@modelDialogAddPromptDescription": { "description": "Description of the add model dialog", "context": "app:modelDialog" }, - "modelDialogAddPromptAlreadyExists": "Model already exists", + "modelDialogAddCurrently": "Currently downloading:", + "@modelDialogAddCurrently": { + "description": "Text displayed when there are currently downloading models", + "context": "app:modelDialog" + }, + "modelDialogAddPromptAlreadyExists": "Model already exists. Try refreshing if it was deleted.", "@modelDialogAddPromptAlreadyExists": { "description": "Text displayed when the model already exists", "context": "app:modelDialog" }, - "modelDialogAddPromptInvalid": "Invalid model name", + "modelDialogAddPromptInvalid": "Couldn’t read your model name correctly. Please check the spelling and try again.", "@modelDialogAddPromptInvalid": { "description": "Text displayed when the model name is invalid", "context": "app:modelDialog" }, - "modelDialogAddAllowanceTitle": "Allow Proxy", - "@modelDialogAddAllowanceTitle": { - "description": "Title of the allow proxy dialog", + "modelDialogAddPromptNotFound": "Couldn’t find your model on the server. Please check the spelling and try again.", + "@modelDialogAddPromptNotFound": { + "description": "Text displayed when the model could not be found", "context": "app:modelDialog" }, - "modelDialogAddAllowanceDescription": "Ollama App must check if the entered model is valid. To do that, we normally send a web request to the Ollama model list and check the status code, but because you're using the web client, we can't do that directly. Instead, the app will send the request to a different API, hosted by JHubi1, to check for us.\nThis is a one-time request and will only be sent when you add a new model.\nYour IP address will be sent with the request and might be stored for up to ten minutes to prevent spamming with potentially harmful intentions.\nIf you accept, your selection will be remembered for the future; if not, nothing will be sent and the model won't be added.", - "@modelDialogAddAllowanceDescription": { - "description": "Description of the allow proxy dialog", + "modelDialogAddPromptNetworkError": "Network error while checking model. Please check your connection and try again.", + "@modelDialogAddPromptNetworkError": { + "description": "Text displayed when there was a network error while checking the model", "context": "app:modelDialog" }, - "modelDialogAddAllowanceAllow": "Allow", - "@modelDialogAddAllowanceAllow": { - "description": "Text displayed for allow button, should be capitalized", + "modelDialogAddPromptCorruptionError": "The API returned “EOF”; your server was likely restarted while downloading the model previously.", + "@modelDialogAddPromptCorruptionError": { + "description": "Text displayed when the model is corrupted on the server", "context": "app:modelDialog" }, - "modelDialogAddAllowanceDeny": "Deny", - "@modelDialogAddAllowanceDeny": { - "description": "Text displayed for deny button, should be capitalized", + "modelDialogAddPromptCorruptionErrorSolution": "Alternatively, you can run the following command on your Linux host directly:\nIf you see a new error, try running the command with “sudo” placed in front of it.", + "@modelDialogAddPromptCorruptionErrorSolution": { + "description": "Text displayed as solution for the corrupted model error", "context": "app:modelDialog" }, - "modelDialogAddAssuranceTitle": "Add {model}?", - "@modelDialogAddAssuranceTitle": { - "description": "Title of the add model assurance dialog", - "context": "app:modelDialog", - "placeholders": { - "model": { - "type": "String", - "description": "Name of the model to be added" - } - } - }, - "modelDialogAddAssuranceDescription": "Pressing 'Add' will download the model '{model}' directly from the Ollama server to your host.\nThis can take a while depending on your internet connection. The action cannot be canceled.\nIf the app is closed during the download, it will resume if you enter the name into the model dialog again.", - "@modelDialogAddAssuranceDescription": { - "description": "Description of the add model assurance dialog", - "context": "app:modelDialog", - "placeholders": { - "model": { - "type": "String", - "description": "Name of the model to be added" - } - } - }, - "modelDialogAddAssuranceAdd": "Add", - "@modelDialogAddAssuranceAdd": { - "description": "Text displayed for add button, should be capitalized", + "modelDialogAddCancelTitle": "Cancel download?", + "@modelDialogAddCancelTitle": { + "description": "Title of the cancel download dialog", "context": "app:modelDialog" }, - "modelDialogAddAssuranceCancel": "Cancel", - "@modelDialogAddAssuranceCancel": { - "description": "Text displayed for cancel button, should be capitalized", + "modelDialogAddCancelConfirm": "Cancel", + "@modelDialogAddCancelConfirm": { + "description": "Text displayed for cancel download button", "context": "app:modelDialog" }, - "modelDialogAddDownloadPercentLoading": "loading progress", - "@modelDialogAddDownloadPercentLoading": { - "description": "Text displayed while loading the download progress", - "context": "app:modelDialog:download" + "modelDialogAddCancelHide": "Hide", + "@modelDialogAddCancelHide": { + "description": "Text displayed for hide button in cancel download dialog. Hide in the sense of 'hide this dialog'", + "context": "app:modelDialog" }, - "modelDialogAddDownloadPercent": "download at {percent}%", - "@modelDialogAddDownloadPercent": { - "description": "Text displayed while downloading a model", - "context": "app:modelDialog:download", - "placeholders": { - "percent": { - "type": "String", - "description": "Percentage of the download" - } - } - }, - "modelDialogAddDownloadFailed": "Disconnected, try again", - "@modelDialogAddDownloadFailed": { - "description": "Text displayed when the download of a model fails", - "context": "app:modelDialog:download" - }, - "modelDialogAddDownloadSuccess": "Download successful", + "modelDialogAddDownloadSuccess": "Download successful. Set as current model.", "@modelDialogAddDownloadSuccess": { "description": "Text displayed when the download of a model is successful", "context": "app:modelDialog:download" @@ -298,6 +389,16 @@ "description": "Title of the edit message dialog", "context": "app:editMessageDialog" }, + "settingsTitleOverview": "Overview", + "@settingsTitleOverview": { + "description": "Title of the overview settings section", + "context": "app:settings" + }, + "settingsDescriptionOverview": "General settings regarding your host.", + "@settingsDescriptionOverview": { + "description": "Description of the overview settings section", + "context": "app:settings" + }, "settingsTitleBehavior": "Behavior", "@settingsTitleBehavior": { "description": "Title of the behavior settings section", @@ -348,17 +449,12 @@ "description": "Description of the about settings section", "context": "app:settings" }, - "settingsSavedAutomatically": "Settings are saved automatically", - "@settingsSavedAutomatically": { - "description": "Text displayed when settings are saved automatically", - "context": "app:settings" - }, "settingsExperimentalAlpha": "alpha", "@settingsExperimentalAlpha": { "description": "Text displayed when a feature is in alpha phase", "context": "app:settings" }, - "settingsExperimentalAlphaDescription": "This feature is in alpha and may not work as intended or expected.\nCritical issues and/or permanent critical damage to device and/or used services cannot be ruled out.\nUse at your own risk. No liability on the part of the app author.", + "settingsExperimentalAlphaDescription": "This feature is in alpha and may not work as intended or expected.\nCritical issues and/or permanent critical damage to device and/or connected services cannot be ruled out.\nUse at your own risk. No liability on the part of the app author.", "@settingsExperimentalAlphaDescription": { "description": "Description of the alpha feature", "context": "app:settings" @@ -373,7 +469,7 @@ "description": "Text displayed when a feature is in beta", "context": "app:settings" }, - "settingsExperimentalBetaDescription": "This feature is in beta and may not work as intended or expected.\nLess severe issues may or may not occur. Damage shouldn't be critical.\nUse at your own risk.", + "settingsExperimentalBetaDescription": "This feature is in beta and may not work as intended or expected.\nLess severe issues may or may not occur. Damage shouldn’t be critical.\nUse at your own risk.", "@settingsExperimentalBetaDescription": { "description": "Description of the beta feature", "context": "app:settings" @@ -403,6 +499,11 @@ "description": "Text displayed as description for host input", "context": "app:settings:host" }, + "settingsHostMissing": "No host set", + "@settingsHostMissing": { + "description": "Text displayed when no host is set", + "context": "app:settings:host" + }, "settingsHostValid": "Valid Host", "@settingsHostValid": { "description": "Text displayed when the host is valid", @@ -413,40 +514,57 @@ "description": "Text displayed when the host is being checked", "context": "app:settings:host" }, - "settingsHostInvalid": "Issue: {type, select, url{Invalid URL} host{Invalid Host} timeout{Request failed. Server issues} ratelimit{Too many requests} other{Request Failed}}", + "settingsHostInvalid": "{type, select, invalidUrl{Invalid URL} unreachable{Host unreachable} undetectable{Host not an Ollama server} timeout{Connection timed out} outdated{Outdated Ollama version} other{Invalid host}}", "@settingsHostInvalid": { - "description": "Text displayed when the host is invalid", + "description": "Short title displayed when the host is invalid", "context": "app:settings:host", "placeholders": { "type": { "type": "String", - "description": "Type of the issue, either 'url', 'host', 'timeout', 'ratelimit' or 'other' (preferably 'host')" + "description": "Type of the issue, either 'invalidUrl', 'unreachable', 'undetectable', 'timeout' or 'outdated'. See `lib/services/host.dart` for more information." } } }, - "tooltipAddHostHeaders": "Add host headers", - "@tooltipAddHostHeaders": { - "description": "Tooltip for add host headers button", - "context": "app:settings:host" - }, - "settingsHostHeaderTitle": "Set host header", - "@settingsHostHeaderTitle": { - "description": "Text displayed as description for host header input", - "context": "app:settings:host" - }, - "settingsHostHeaderInvalid": "The entered text isn't a valid header JSON object", - "@settingsHostHeaderInvalid": { - "description": "Text displayed when the host header is invalid", - "context": "app:settings:host" - }, - "settingsHostInvalidDetailed": "{type, select, url{The URL you entered is invalid. It isn't in a standardized URL format.} other{The host you entered is invalid. It cannot be reached. Please check the host and try again.}}", + "settingsHostInvalidDetailed": "{type, select, invalidUrl{The URL you entered isn’t in a valid format.} unreachable{The host is invalid or unreachable, possibly due to a wrong address or network issue.} undetectable{The host is reachable but doesn’t respond as expected by Ollama App.\nIt may not be an Ollama server. Are you sure you entered it correctly?} timeout{The host couldn’t be reached in time.\nTry increasing the timeout multiplier in interface settings.} outdated{The host is running an Ollama version incompatible with this app.\nPlease update both the host and this app, if possible.} other{The host you entered is invalid.}}", "@settingsHostInvalidDetailed": { - "description": "Text displayed when the host is invalid", + "description": "Detailed text displayed when the host is invalid", "context": "app:settings:host", "placeholders": { "type": { "type": "String", - "description": "Type of the issue, either 'url' or 'other' (preferably 'host')" + "description": "Type of the issue, either 'invalidUrl', 'unreachable', 'undetectable', 'timeout' or 'outdated'. See `lib/services/host.dart` for more information." + } + } + }, + "settingsHostHeaderHeaderName": "Header", + "@settingsHostHeaderHeaderName": { + "description": "Text displayed as description for host header name input", + "context": "app:settings:host" + }, + "settingsHostHeaderHeaderValue": "Value", + "@settingsHostHeaderHeaderValue": { + "description": "Text displayed as description for host header value input", + "context": "app:settings:host" + }, + "settingsHostHeaderUnsupported": "The header “{header}” may not be supported.", + "@settingsHostHeaderUnsupported": { + "description": "Text displayed when a host header is unsupported", + "context": "app:settings:host", + "placeholders": { + "header": { + "type": "String", + "description": "The name of the unsupported header" + } + } + }, + "settingsHostHeaderDuplicate": "The header “{header}” is already set.", + "@settingsHostHeaderDuplicate": { + "description": "Text displayed when a host header is a duplicate", + "context": "app:settings:host", + "placeholders": { + "header": { + "type": "String", + "description": "The name of the duplicate header" } } }, @@ -460,7 +578,7 @@ "description": "Text displayed as description for use system message toggle", "context": "app:settings:behavior" }, - "settingsUseSystemDescription": "Disables setting the system message above and uses the one from the model's Modelfile instead. Can be useful for models with model files.", + "settingsUseSystemDescription": "Disables setting the system message above and uses the one from the model’s Modelfile instead. Can be useful for models with model files.", "@settingsUseSystemDescription": { "description": "Description of the use system message toggle", "context": "app:settings:behavior" @@ -525,7 +643,7 @@ "description": "Text displayed as description for keep model loaded always toggle", "context": "app:settings:interface:keepModelLoaded" }, - "settingsKeepModelLoadedNever": "Don't keep model loaded", + "settingsKeepModelLoadedNever": "Don’t keep model loaded", "@settingsKeepModelLoadedNever": { "description": "Text displayed as description for don't keep model loaded toggle", "context": "app:settings:interface:keepModelLoaded" @@ -606,7 +724,7 @@ "description": "Description of the temporary fixes section", "context": "app:settings:interface:temporaryFixes" }, - "settingsTemporaryFixesInstructions": "Do not toggle any of these settings unless you know what you are doing! The given solutions might not work as expected.\nThey cannot be seen as final or should be judged as such. Issues might occur.", + "settingsTemporaryFixesInstructions": "Do not toggle any of these settings unless you know what you are doing! The given solutions might not work as expected.\nThey should not be considered final or judged as such. Issues might occur.", "@settingsTemporaryFixesInstructions": { "description": "Instructions and warnings for the temporary fixes", "context": "app:settings:interface:temporaryFixes" @@ -616,7 +734,7 @@ "description": "Text displayed when no fixes are available", "context": "app:settings:interface:temporaryFixes" }, - "settingsVoicePermissionLoading": "Loading voice permissions ...", + "settingsVoicePermissionLoading": "Loading voice permissions...", "@settingsVoicePermissionLoading": { "description": "Text displayed while loading voice permissions", "context": "app:settings:voice" @@ -706,12 +824,12 @@ "description": "Text displayed when chats are imported successfully", "context": "app:settings:export" }, - "settingsExportInfo": "These options allow you to export and import your chat history. This can be useful if you want to transfer your chat history to another device or back up your chat history", + "settingsExportInfo": "These options allow you to export and import your chat history. This can be useful if you want to transfer your chat history to another device or back up your chat history.", "@settingsExportInfo": { "description": "Information displayed for export and import options", "context": "app:settings:export" }, - "settingsExportWarning": "Multiple chat histories won't be merged! You'll lose your current chat history if you import a new one.", + "settingsExportWarning": "Multiple chat histories won’t be merged! You’ll lose your current chat history if you import a new one.", "@settingsExportWarning": { "description": "Warning displayed for export and import options", "context": "app:settings:export" @@ -721,7 +839,7 @@ "description": "Text displayed as description for check for updates button", "context": "app:settings:info" }, - "settingsUpdateChecking": "Checking for updates ...", + "settingsUpdateChecking": "Checking for updates...", "@settingsUpdateChecking": { "description": "Text displayed while looking for updates", "context": "app:settings:info" @@ -742,7 +860,7 @@ } } }, - "settingsUpdateRateLimit": "Can't check, API rate limit exceeded", + "settingsUpdateRateLimit": "Can’t check, API rate limit exceeded", "@settingsUpdateRateLimit": { "description": "Text displayed when the API rate limit is exceeded", "context": "app:settings:info" @@ -777,7 +895,7 @@ "description": "Text displayed for cancel button, should be capitalized", "context": "app:settings:info" }, - "settingsCheckForUpdates": "Check for updates on open", + "settingsCheckForUpdates": "Check for updates on startup", "@settingsCheckForUpdates": { "description": "Text displayed as description for check for updates toggle", "context": "app:settings:info" diff --git a/lib/l10n/gen/app_localizations.dart b/lib/l10n/gen/app_localizations.dart index 073285e..abe8d0a 100644 --- a/lib/l10n/gen/app_localizations.dart +++ b/lib/l10n/gen/app_localizations.dart @@ -106,11 +106,17 @@ abstract class AppLocalizations { Locale('zh'), ]; - /// Title of the application + /// Title of the application. Should stay untranslated in most cases /// /// In en, this message translates to: - /// **'Ollama'** - String get appTitle; + /// **'{env, select, short{Ollama} integrated{{title} - Ollama App} other{Ollama App}}'** + String appTitle(String env, String title); + + /// Text displayed for learn more button + /// + /// In en, this message translates to: + /// **'Learn more'** + String get learnMore; /// Text displayed for new chat option /// @@ -130,12 +136,36 @@ abstract class AppLocalizations { /// **'Install Webapp'** String get optionInstallPwa; - /// Text displayed when no chats are found + /// Text displayed when there are no chats /// /// In en, this message translates to: - /// **'No chats found'** + /// **'No chats'** String get optionNoChatFound; + /// Text displayed when no chats are found for the search query + /// + /// In en, this message translates to: + /// **'No chats found for ‘{query}’'** + String optionNoChatFoundSearch(String query); + + /// Placeholder for search chats input + /// + /// In en, this message translates to: + /// **'Search through chats'** + String get optionSearchChats; + + /// Text displayed as title for chat details sidebar + /// + /// In en, this message translates to: + /// **'Chat details'** + String get optionChatDetails; + + /// Text displayed when no chat is selected in the chat details sidebar + /// + /// In en, this message translates to: + /// **'No chat selected'** + String get optionChatDetailsNoChat; + /// Prefix for tips /// /// In en, this message translates to: @@ -187,38 +217,68 @@ abstract class AppLocalizations { /// Text displayed for take image button /// /// In en, this message translates to: - /// **'Take Image'** + /// **'Camera'** String get takeImage; /// Text displayed for image upload button /// /// In en, this message translates to: - /// **'Upload Image'** + /// **'Gallery'** String get uploadImage; - /// Text displayed when an image is not valid + /// First greeting displayed in a new chat /// /// In en, this message translates to: - /// **'Not a valid image'** - String get notAValidImage; + /// **'What’s on your mind, {user}?'** + String newChatGreeting1(String user); - /// Title, if 'Generate Title' is executed on a conversation with no text messages + /// Second greeting displayed in a new chat /// /// In en, this message translates to: - /// **'Image Only Conversation'** - String get imageOnlyConversation; + /// **'Good to see you, {user}.'** + String newChatGreeting2(String user); + + /// Third greeting displayed in a new chat + /// + /// In en, this message translates to: + /// **'What can I do for you, {user}?'** + String newChatGreeting3(String user); + + /// Fourth greeting displayed in a new chat + /// + /// In en, this message translates to: + /// **'How can I help you today, {user}?'** + String newChatGreeting4(String user); + + /// Fifth greeting displayed in a new chat + /// + /// In en, this message translates to: + /// **'What would you like to talk about, {user}?'** + String newChatGreeting5(String user); + + /// Fallback name for the user if no name is set + /// + /// In en, this message translates to: + /// **'user'** + String get newChatGreetingNameFallback; /// Placeholder text for message input /// /// In en, this message translates to: - /// **'Message'** - String get messageInputPlaceholder; + /// **'Ask {model}'** + String messageInputPlaceholder(String model); - /// Tooltip for attachment button + /// Model name used in 'messageInputPlaceholder' if no model is available, should fit into capitalization and context /// /// In en, this message translates to: - /// **'Add attachment'** - String get tooltipAttachment; + /// **'model'** + String get messageInputPlaceholderModelPlaceholder; + + /// Tooltip for message options button; options here being attachments, etc. + /// + /// In en, this message translates to: + /// **'Message options'** + String get tooltipMessageOptions; /// Tooltip for send button /// @@ -226,17 +286,17 @@ abstract class AppLocalizations { /// **'Send'** String get tooltipSend; - /// Tooltip for save button + /// Tooltip for generate with model button /// /// In en, this message translates to: - /// **'Save'** - String get tooltipSave; + /// **'Generate with {model}'** + String tooltipLetAIThink(String model); - /// Tooltip for let AI think button + /// Tooltip for add host headers button /// /// In en, this message translates to: - /// **'Let AI think'** - String get tooltipLetAIThink; + /// **'Add host headers'** + String get tooltipAddHostHeaders; /// Tooltip for reset button /// @@ -259,121 +319,115 @@ abstract class AppLocalizations { /// Text displayed when no host is selected /// /// In en, this message translates to: - /// **'No host selected, open settings to set one'** + /// **'No host selected, open settings to set one.'** String get noHostSelected; - /// Text displayed when no model is selected + /// Text displayed when no model is selected. Should be lower case even if the language would capitalize it, to fit better into the app's design. /// /// In en, this message translates to: - /// **''** + /// **''** String get noSelectedModel; /// Title of a new chat /// /// In en, this message translates to: - /// **'Unnamed Chat'** + /// **'Untitled Chat'** String get newChatTitle; + /// Title of the model selection dialog + /// + /// In en, this message translates to: + /// **'Select a model'** + String get modelDialogTitle; + /// Text displayed for add model button /// /// In en, this message translates to: /// **'Add'** - String get modelDialogAddModel; + String get modelDialogAdd; + + /// Text displayed for refresh models button + /// + /// In en, this message translates to: + /// **'Refresh'** + String get modelDialogRefresh; /// Title of the add model dialog /// /// In en, this message translates to: - /// **'Add new model'** + /// **'Add model'** String get modelDialogAddPromptTitle; /// Description of the add model dialog /// /// In en, this message translates to: - /// **'This can either be a normal name (e.g. \'llama3\') or a name and tag (e.g. \'llama3:70b\').'** + /// **'This can either be a normal name (e.g. “llama3”) or a name together with a tag (e.g. “llama3:70b”).'** String get modelDialogAddPromptDescription; + /// Text displayed when there are currently downloading models + /// + /// In en, this message translates to: + /// **'Currently downloading:'** + String get modelDialogAddCurrently; + /// Text displayed when the model already exists /// /// In en, this message translates to: - /// **'Model already exists'** + /// **'Model already exists. Try refreshing if it was deleted.'** String get modelDialogAddPromptAlreadyExists; /// Text displayed when the model name is invalid /// /// In en, this message translates to: - /// **'Invalid model name'** + /// **'Couldn’t read your model name correctly. Please check the spelling and try again.'** String get modelDialogAddPromptInvalid; - /// Title of the allow proxy dialog + /// Text displayed when the model could not be found /// /// In en, this message translates to: - /// **'Allow Proxy'** - String get modelDialogAddAllowanceTitle; + /// **'Couldn’t find your model on the server. Please check the spelling and try again.'** + String get modelDialogAddPromptNotFound; - /// Description of the allow proxy dialog + /// Text displayed when there was a network error while checking the model /// /// In en, this message translates to: - /// **'Ollama App must check if the entered model is valid. To do that, we normally send a web request to the Ollama model list and check the status code, but because you\'re using the web client, we can\'t do that directly. Instead, the app will send the request to a different API, hosted by JHubi1, to check for us.\nThis is a one-time request and will only be sent when you add a new model.\nYour IP address will be sent with the request and might be stored for up to ten minutes to prevent spamming with potentially harmful intentions.\nIf you accept, your selection will be remembered for the future; if not, nothing will be sent and the model won\'t be added.'** - String get modelDialogAddAllowanceDescription; + /// **'Network error while checking model. Please check your connection and try again.'** + String get modelDialogAddPromptNetworkError; - /// Text displayed for allow button, should be capitalized + /// Text displayed when the model is corrupted on the server /// /// In en, this message translates to: - /// **'Allow'** - String get modelDialogAddAllowanceAllow; + /// **'The API returned “EOF”; your server was likely restarted while downloading the model previously.'** + String get modelDialogAddPromptCorruptionError; - /// Text displayed for deny button, should be capitalized + /// Text displayed as solution for the corrupted model error /// /// In en, this message translates to: - /// **'Deny'** - String get modelDialogAddAllowanceDeny; + /// **'Alternatively, you can run the following command on your Linux host directly:\nIf you see a new error, try running the command with “sudo” placed in front of it.'** + String get modelDialogAddPromptCorruptionErrorSolution; - /// Title of the add model assurance dialog + /// Title of the cancel download dialog /// /// In en, this message translates to: - /// **'Add {model}?'** - String modelDialogAddAssuranceTitle(String model); + /// **'Cancel download?'** + String get modelDialogAddCancelTitle; - /// Description of the add model assurance dialog - /// - /// In en, this message translates to: - /// **'Pressing \'Add\' will download the model \'{model}\' directly from the Ollama server to your host.\nThis can take a while depending on your internet connection. The action cannot be canceled.\nIf the app is closed during the download, it will resume if you enter the name into the model dialog again.'** - String modelDialogAddAssuranceDescription(String model); - - /// Text displayed for add button, should be capitalized - /// - /// In en, this message translates to: - /// **'Add'** - String get modelDialogAddAssuranceAdd; - - /// Text displayed for cancel button, should be capitalized + /// Text displayed for cancel download button /// /// In en, this message translates to: /// **'Cancel'** - String get modelDialogAddAssuranceCancel; + String get modelDialogAddCancelConfirm; - /// Text displayed while loading the download progress + /// Text displayed for hide button in cancel download dialog. Hide in the sense of 'hide this dialog' /// /// In en, this message translates to: - /// **'loading progress'** - String get modelDialogAddDownloadPercentLoading; - - /// Text displayed while downloading a model - /// - /// In en, this message translates to: - /// **'download at {percent}%'** - String modelDialogAddDownloadPercent(String percent); - - /// Text displayed when the download of a model fails - /// - /// In en, this message translates to: - /// **'Disconnected, try again'** - String get modelDialogAddDownloadFailed; + /// **'Hide'** + String get modelDialogAddCancelHide; /// Text displayed when the download of a model is successful /// /// In en, this message translates to: - /// **'Download successful'** + /// **'Download successful. Set as current model.'** String get modelDialogAddDownloadSuccess; /// Title of the delete dialog @@ -442,6 +496,18 @@ abstract class AppLocalizations { /// **'Edit message'** String get dialogEditMessageTitle; + /// Title of the overview settings section + /// + /// In en, this message translates to: + /// **'Overview'** + String get settingsTitleOverview; + + /// Description of the overview settings section + /// + /// In en, this message translates to: + /// **'General settings regarding your host.'** + String get settingsDescriptionOverview; + /// Title of the behavior settings section /// /// In en, this message translates to: @@ -502,12 +568,6 @@ abstract class AppLocalizations { /// **'Check for updates and learn more about Ollama App.'** String get settingsDescriptionAbout; - /// Text displayed when settings are saved automatically - /// - /// In en, this message translates to: - /// **'Settings are saved automatically'** - String get settingsSavedAutomatically; - /// Text displayed when a feature is in alpha phase /// /// In en, this message translates to: @@ -517,7 +577,7 @@ abstract class AppLocalizations { /// Description of the alpha feature /// /// In en, this message translates to: - /// **'This feature is in alpha and may not work as intended or expected.\nCritical issues and/or permanent critical damage to device and/or used services cannot be ruled out.\nUse at your own risk. No liability on the part of the app author.'** + /// **'This feature is in alpha and may not work as intended or expected.\nCritical issues and/or permanent critical damage to device and/or connected services cannot be ruled out.\nUse at your own risk. No liability on the part of the app author.'** String get settingsExperimentalAlphaDescription; /// Text displayed when a feature is in alpha @@ -535,7 +595,7 @@ abstract class AppLocalizations { /// Description of the beta feature /// /// In en, this message translates to: - /// **'This feature is in beta and may not work as intended or expected.\nLess severe issues may or may not occur. Damage shouldn\'t be critical.\nUse at your own risk.'** + /// **'This feature is in beta and may not work as intended or expected.\nLess severe issues may or may not occur. Damage shouldn’t be critical.\nUse at your own risk.'** String get settingsExperimentalBetaDescription; /// Text displayed when a feature is in beta @@ -568,6 +628,12 @@ abstract class AppLocalizations { /// **'Host'** String get settingsHost; + /// Text displayed when no host is set + /// + /// In en, this message translates to: + /// **'No host set'** + String get settingsHostMissing; + /// Text displayed when the host is valid /// /// In en, this message translates to: @@ -580,36 +646,42 @@ abstract class AppLocalizations { /// **'Checking Host'** String get settingsHostChecking; - /// Text displayed when the host is invalid + /// Short title displayed when the host is invalid /// /// In en, this message translates to: - /// **'Issue: {type, select, url{Invalid URL} host{Invalid Host} timeout{Request failed. Server issues} ratelimit{Too many requests} other{Request Failed}}'** + /// **'{type, select, invalidUrl{Invalid URL} unreachable{Host unreachable} undetectable{Host not an Ollama server} timeout{Connection timed out} outdated{Outdated Ollama version} other{Invalid host}}'** String settingsHostInvalid(String type); - /// Tooltip for add host headers button + /// Detailed text displayed when the host is invalid /// /// In en, this message translates to: - /// **'Add host headers'** - String get tooltipAddHostHeaders; - - /// Text displayed as description for host header input - /// - /// In en, this message translates to: - /// **'Set host header'** - String get settingsHostHeaderTitle; - - /// Text displayed when the host header is invalid - /// - /// In en, this message translates to: - /// **'The entered text isn\'t a valid header JSON object'** - String get settingsHostHeaderInvalid; - - /// Text displayed when the host is invalid - /// - /// In en, this message translates to: - /// **'{type, select, url{The URL you entered is invalid. It isn\'t in a standardized URL format.} other{The host you entered is invalid. It cannot be reached. Please check the host and try again.}}'** + /// **'{type, select, invalidUrl{The URL you entered isn’t in a valid format.} unreachable{The host is invalid or unreachable, possibly due to a wrong address or network issue.} undetectable{The host is reachable but doesn’t respond as expected by Ollama App.\nIt may not be an Ollama server. Are you sure you entered it correctly?} timeout{The host couldn’t be reached in time.\nTry increasing the timeout multiplier in interface settings.} outdated{The host is running an Ollama version incompatible with this app.\nPlease update both the host and this app, if possible.} other{The host you entered is invalid.}}'** String settingsHostInvalidDetailed(String type); + /// Text displayed as description for host header name input + /// + /// In en, this message translates to: + /// **'Header'** + String get settingsHostHeaderHeaderName; + + /// Text displayed as description for host header value input + /// + /// In en, this message translates to: + /// **'Value'** + String get settingsHostHeaderHeaderValue; + + /// Text displayed when a host header is unsupported + /// + /// In en, this message translates to: + /// **'The header “{header}” may not be supported.'** + String settingsHostHeaderUnsupported(String header); + + /// Text displayed when a host header is a duplicate + /// + /// In en, this message translates to: + /// **'The header “{header}” is already set.'** + String settingsHostHeaderDuplicate(String header); + /// Text displayed as description for system message input /// /// In en, this message translates to: @@ -625,7 +697,7 @@ abstract class AppLocalizations { /// Description of the use system message toggle /// /// In en, this message translates to: - /// **'Disables setting the system message above and uses the one from the model\'s Modelfile instead. Can be useful for models with model files.'** + /// **'Disables setting the system message above and uses the one from the model’s Modelfile instead. Can be useful for models with model files.'** String get settingsUseSystemDescription; /// Text displayed as description for disable markdown toggle @@ -703,7 +775,7 @@ abstract class AppLocalizations { /// Text displayed as description for don't keep model loaded toggle /// /// In en, this message translates to: - /// **'Don\'t keep model loaded'** + /// **'Don’t keep model loaded'** String get settingsKeepModelLoadedNever; /// Text displayed as description for keep model loaded for toggle @@ -793,7 +865,7 @@ abstract class AppLocalizations { /// Instructions and warnings for the temporary fixes /// /// In en, this message translates to: - /// **'Do not toggle any of these settings unless you know what you are doing! The given solutions might not work as expected.\nThey cannot be seen as final or should be judged as such. Issues might occur.'** + /// **'Do not toggle any of these settings unless you know what you are doing! The given solutions might not work as expected.\nThey should not be considered final or judged as such. Issues might occur.'** String get settingsTemporaryFixesInstructions; /// Text displayed when no fixes are available @@ -805,7 +877,7 @@ abstract class AppLocalizations { /// Text displayed while loading voice permissions /// /// In en, this message translates to: - /// **'Loading voice permissions ...'** + /// **'Loading voice permissions...'** String get settingsVoicePermissionLoading; /// Text displayed when text-to-speech is not supported @@ -913,13 +985,13 @@ abstract class AppLocalizations { /// Information displayed for export and import options /// /// In en, this message translates to: - /// **'These options allow you to export and import your chat history. This can be useful if you want to transfer your chat history to another device or back up your chat history'** + /// **'These options allow you to export and import your chat history. This can be useful if you want to transfer your chat history to another device or back up your chat history.'** String get settingsExportInfo; /// Warning displayed for export and import options /// /// In en, this message translates to: - /// **'Multiple chat histories won\'t be merged! You\'ll lose your current chat history if you import a new one.'** + /// **'Multiple chat histories won’t be merged! You’ll lose your current chat history if you import a new one.'** String get settingsExportWarning; /// Text displayed as description for check for updates button @@ -931,7 +1003,7 @@ abstract class AppLocalizations { /// Text displayed while looking for updates /// /// In en, this message translates to: - /// **'Checking for updates ...'** + /// **'Checking for updates...'** String get settingsUpdateChecking; /// Text displayed when the app is up to date @@ -949,7 +1021,7 @@ abstract class AppLocalizations { /// Text displayed when the API rate limit is exceeded /// /// In en, this message translates to: - /// **'Can\'t check, API rate limit exceeded'** + /// **'Can’t check, API rate limit exceeded'** String get settingsUpdateRateLimit; /// Text displayed when an issue occurs while checking for updates @@ -991,7 +1063,7 @@ abstract class AppLocalizations { /// Text displayed as description for check for updates toggle /// /// In en, this message translates to: - /// **'Check for updates on open'** + /// **'Check for updates on startup'** String get settingsCheckForUpdates; /// Text displayed as description for GitHub button diff --git a/lib/l10n/gen/app_localizations_de.dart b/lib/l10n/gen/app_localizations_de.dart index 606409f..3bc0f00 100644 --- a/lib/l10n/gen/app_localizations_de.dart +++ b/lib/l10n/gen/app_localizations_de.dart @@ -9,7 +9,17 @@ class AppLocalizationsDe extends AppLocalizations { AppLocalizationsDe([String locale = 'de']) : super(locale); @override - String get appTitle => 'Ollama'; + String appTitle(String env, String title) { + String _temp0 = intl.Intl.selectLogic(env, { + 'short': 'Ollama', + 'integrated': '$title - Ollama App', + 'other': 'Ollama App', + }); + return '$_temp0'; + } + + @override + String get learnMore => 'Mehr erfahren'; @override String get optionNewChat => 'Neuer Chat'; @@ -21,7 +31,21 @@ class AppLocalizationsDe extends AppLocalizations { String get optionInstallPwa => 'Webapp installieren'; @override - String get optionNoChatFound => 'Keine Chats gefunden'; + String get optionNoChatFound => 'Keine Chats'; + + @override + String optionNoChatFoundSearch(String query) { + return 'Keinen Chat gefunden für Suche ‚$query‘'; + } + + @override + String get optionSearchChats => 'Chats durchsuchen'; + + @override + String get optionChatDetails => 'Chat details'; + + @override + String get optionChatDetailsNoChat => 'No chat selected'; @override String get tipPrefix => 'Tipp: '; @@ -54,25 +78,54 @@ class AppLocalizationsDe extends AppLocalizations { String get uploadImage => 'Bild Hochladen'; @override - String get notAValidImage => 'Kein gültiges Bild'; + String newChatGreeting1(String user) { + return 'What’s on your mind, $user?'; + } @override - String get imageOnlyConversation => 'Nur Bild Unterhaltung'; + String newChatGreeting2(String user) { + return 'Good to see you, $user.'; + } @override - String get messageInputPlaceholder => 'Nachricht'; + String newChatGreeting3(String user) { + return 'What can I do for you, $user?'; + } @override - String get tooltipAttachment => 'Anhang hinzufügen'; + String newChatGreeting4(String user) { + return 'How can I help you today, $user?'; + } + + @override + String newChatGreeting5(String user) { + return 'What would you like to talk about, $user?'; + } + + @override + String get newChatGreetingNameFallback => 'user'; + + @override + String messageInputPlaceholder(String model) { + return '$model fragen'; + } + + @override + String get messageInputPlaceholderModelPlaceholder => 'Modell'; + + @override + String get tooltipMessageOptions => 'Nachrichtenoptionen'; @override String get tooltipSend => 'Senden'; @override - String get tooltipSave => 'Speichern'; + String tooltipLetAIThink(String model) { + return 'Mit $model generieren'; + } @override - String get tooltipLetAIThink => 'Lass KI denken'; + String get tooltipAddHostHeaders => 'Host-Header festlegen'; @override String get tooltipReset => 'Aktuellen Chat zurücksetzen'; @@ -85,72 +138,67 @@ class AppLocalizationsDe extends AppLocalizations { @override String get noHostSelected => - 'Kein Host ausgewählt, öffne zum Auswählen die Einstellungen'; + 'Kein Host ausgewählt, öffne zum Auswählen die Einstellungen.'; @override - String get noSelectedModel => ''; + String get noSelectedModel => ''; @override String get newChatTitle => 'Unbenannter Chat'; @override - String get modelDialogAddModel => 'Hinzufügen'; + String get modelDialogTitle => 'Select a model'; @override - String get modelDialogAddPromptTitle => 'Neues Modell hinzufügen'; + String get modelDialogAdd => 'Hinzufügen'; + + @override + String get modelDialogRefresh => 'Refresh'; + + @override + String get modelDialogAddPromptTitle => 'Modell hinzufügen'; @override String get modelDialogAddPromptDescription => - 'Das kann entweder ein normaler Name (z.B. \'llama3\') oder Name und Tag (z.B. \'llama3:70b\') sein.'; + 'Das kann entweder ein normaler Name (z.B. „llama3“) oder ein Name zusammen mit einem Tag (z.B. „llama3:70b“) sein.'; @override - String get modelDialogAddPromptAlreadyExists => 'Modell existiert bereits'; + String get modelDialogAddCurrently => 'Wird gerade heruntergeladen:'; @override - String get modelDialogAddPromptInvalid => 'Ungültiger Modellname'; + String get modelDialogAddPromptAlreadyExists => 'Modell existiert bereits.'; @override - String get modelDialogAddAllowanceTitle => 'Proxy erlauben'; + String get modelDialogAddPromptInvalid => 'Ungültiger Modellname.'; @override - String get modelDialogAddAllowanceDescription => - 'Ollama App muss überprüfen, ob das eingegebene Modell gültig ist. Dafür senden wir normalerweise eine Webanfrage an die Ollama-Modellliste und überprüfen den Statuscode, aber da gerade der Webclient verwendet wird, können wir das nicht direkt tun. Stattdessen sendet die App die Anfrage an eine andere API, gehostet von JHubi1, um dies für uns zu überprüfen.\nDies ist eine einmalige Anfrage und wird nur gesendet, wenn du ein neues Modell hinzufügst.\nIhre IP-Adresse wird mit der Anfrage gesendet und kann bis zu zehn Minuten gespeichert werden, um Spamming mit potenziell schädlichen Absichten zu verhindern.\nWenn du zustimmst, wird deine Auswahl für die Zukunft gespeichert; wenn nicht, wird nichts gesendet und das Modell wird nicht hinzugefügt.'; + String get modelDialogAddPromptNotFound => + 'Das Modell konnte auf dem Server nicht gefunden werden. Bitte überprüfe die Schreibweise und versuche es erneut.'; @override - String get modelDialogAddAllowanceAllow => 'Erlauben'; + String get modelDialogAddPromptNetworkError => + 'Netzwerkfehler beim Überprüfen des Modells. Bitte überprüfe deine Verbindung und versuche es erneut.'; @override - String get modelDialogAddAllowanceDeny => 'Ablehnen'; + String get modelDialogAddPromptCorruptionError => + 'Die API hat „EOF“ zurückgegeben; dein Server wurde wahrscheinlich neu gestartet, während das Modell zuvor heruntergeladen wurde.'; @override - String modelDialogAddAssuranceTitle(String model) { - return '$model hinzufügen?'; - } + String get modelDialogAddPromptCorruptionErrorSolution => + 'Alternativ kannst du den folgenden Befehl direkt auf deinem Linux-Host ausführen:\nWenn du eine neue Fehlermeldung siehst, versuche den Befehl mit „sudo“ davor auszuführen.'; @override - String modelDialogAddAssuranceDescription(String model) { - return 'Durch Drücken von \'Hinzufügen\' wird das Modell \'$model\' direkt vom Ollama-Server auf deinen Host heruntergeladen.\nJe nach Internetverbindung kann dies eine Weile dauern. Der Vorgang kann nicht abgebrochen werden.\nWenn die App während des Downloads geschlossen wird, wird der Download fortgesetzt, wenn du den Namen erneut in den Modelldialog eingibst.'; - } + String get modelDialogAddCancelTitle => 'Download abbrechen?'; @override - String get modelDialogAddAssuranceAdd => 'Hinzufügen'; + String get modelDialogAddCancelConfirm => 'Abbrechen'; @override - String get modelDialogAddAssuranceCancel => 'Abbrechen'; + String get modelDialogAddCancelHide => 'Ausblenden'; @override - String get modelDialogAddDownloadPercentLoading => 'lade Fortschritt'; - - @override - String modelDialogAddDownloadPercent(String percent) { - return 'Download bei $percent%'; - } - - @override - String get modelDialogAddDownloadFailed => 'Getrennt, versuche es erneut'; - - @override - String get modelDialogAddDownloadSuccess => 'Download erfolgreich'; + String get modelDialogAddDownloadSuccess => + 'Download erfolgreich. Als aktuelles Modell festlegen.'; @override String get deleteDialogTitle => 'Chat löschen'; @@ -186,6 +234,13 @@ class AppLocalizationsDe extends AppLocalizations { @override String get dialogEditMessageTitle => 'Nachricht bearbeiten'; + @override + String get settingsTitleOverview => 'Overview'; + + @override + String get settingsDescriptionOverview => + 'General settings regarding your host.'; + @override String get settingsTitleBehavior => 'Verhalten'; @@ -221,10 +276,6 @@ class AppLocalizationsDe extends AppLocalizations { String get settingsDescriptionAbout => 'Suche nach Updates und erfahre mehr über Ollama App.'; - @override - String get settingsSavedAutomatically => - 'Einstellungen werden automatisch gespeichert'; - @override String get settingsExperimentalAlpha => 'alpha'; @@ -261,6 +312,9 @@ class AppLocalizationsDe extends AppLocalizations { @override String get settingsHost => 'Host'; + @override + String get settingsHostMissing => 'Kein Host festgelegt'; + @override String get settingsHostValid => 'Gültiger Host'; @@ -270,36 +324,49 @@ class AppLocalizationsDe extends AppLocalizations { @override String settingsHostInvalid(String type) { String _temp0 = intl.Intl.selectLogic(type, { - 'url': 'Ungültige URL', - 'host': 'Ungültiger Host', - 'timeout': 'Request Fehlgeschlagen. Server Fehler', - 'ratelimit': 'Zu viele Anfragen', - 'other': 'Request Fehlgeschlagen', + 'invalidUrl': 'Ungültige URL', + 'unreachable': 'Host nicht erreichbar', + 'undetectable': 'Host kein Ollama-Server', + 'timeout': 'Verbindung abgelaufen', + 'outdated': 'Veraltete Ollama-Version', + 'other': 'Ungültiger Host', }); - return 'Fehler: $_temp0'; + return '$_temp0'; } - @override - String get tooltipAddHostHeaders => 'Host-Header festlegen'; - - @override - String get settingsHostHeaderTitle => 'Host-Header festlegen'; - - @override - String get settingsHostHeaderInvalid => - 'Der eingegebene Text ist kein gültiges Header-JSON-Objekt'; - @override String settingsHostInvalidDetailed(String type) { String _temp0 = intl.Intl.selectLogic(type, { - 'url': - 'Die eingegebene URL ist ungültig. Es handelt sich nicht um ein standardisiertes URL-Format.', - 'other': - 'Der eingegebene Host ist ungültig. Er kann nicht erreicht werden. Bitte überprüfe den Host und versuche es erneut.', + 'invalidUrl': 'Die eingegebene URL hat kein gültiges Format.', + 'unreachable': + 'Der Host ist ungültig oder nicht erreichbar, möglicherweise aufgrund einer falschen Adresse oder eines Netzwerkproblems.', + 'undetectable': + 'Der Host ist erreichbar, antwortet aber nicht wie von Ollama App erwartet.\nEr ist möglicherweise kein Ollama-Server. Bist du sicher, dass du ihn richtig eingegeben hast?', + 'timeout': + 'Der Host konnte nicht rechtzeitig erreicht werden.\nVersuche, den Timeout-Multiplikator in den Oberflächen-Einstellungen zu erhöhen.', + 'outdated': + 'Der Host verwendet eine Ollama-Version, die mit dieser App nicht kompatibel ist.\nBitte aktualisiere sowohl den Host als auch diese App, wenn möglich.', + 'other': 'Der eingegebene Host ist ungültig.', }); return '$_temp0'; } + @override + String get settingsHostHeaderHeaderName => 'Header'; + + @override + String get settingsHostHeaderHeaderValue => 'Value'; + + @override + String settingsHostHeaderUnsupported(String header) { + return 'The header “$header” may not be supported.'; + } + + @override + String settingsHostHeaderDuplicate(String header) { + return 'The header “$header” is already set.'; + } + @override String get settingsSystemMessage => 'Systemnachricht'; diff --git a/lib/l10n/gen/app_localizations_en.dart b/lib/l10n/gen/app_localizations_en.dart index 9a1ab3d..e22d30d 100644 --- a/lib/l10n/gen/app_localizations_en.dart +++ b/lib/l10n/gen/app_localizations_en.dart @@ -9,7 +9,17 @@ class AppLocalizationsEn extends AppLocalizations { AppLocalizationsEn([String locale = 'en']) : super(locale); @override - String get appTitle => 'Ollama'; + String appTitle(String env, String title) { + String _temp0 = intl.Intl.selectLogic(env, { + 'short': 'Ollama', + 'integrated': '$title - Ollama App', + 'other': 'Ollama App', + }); + return '$_temp0'; + } + + @override + String get learnMore => 'Learn more'; @override String get optionNewChat => 'New Chat'; @@ -21,7 +31,21 @@ class AppLocalizationsEn extends AppLocalizations { String get optionInstallPwa => 'Install Webapp'; @override - String get optionNoChatFound => 'No chats found'; + String get optionNoChatFound => 'No chats'; + + @override + String optionNoChatFoundSearch(String query) { + return 'No chats found for ‘$query’'; + } + + @override + String get optionSearchChats => 'Search through chats'; + + @override + String get optionChatDetails => 'Chat details'; + + @override + String get optionChatDetailsNoChat => 'No chat selected'; @override String get tipPrefix => 'Tip: '; @@ -48,31 +72,60 @@ class AppLocalizationsEn extends AppLocalizations { String get renameChat => 'Rename'; @override - String get takeImage => 'Take Image'; + String get takeImage => 'Camera'; @override - String get uploadImage => 'Upload Image'; + String get uploadImage => 'Gallery'; @override - String get notAValidImage => 'Not a valid image'; + String newChatGreeting1(String user) { + return 'What’s on your mind, $user?'; + } @override - String get imageOnlyConversation => 'Image Only Conversation'; + String newChatGreeting2(String user) { + return 'Good to see you, $user.'; + } @override - String get messageInputPlaceholder => 'Message'; + String newChatGreeting3(String user) { + return 'What can I do for you, $user?'; + } @override - String get tooltipAttachment => 'Add attachment'; + String newChatGreeting4(String user) { + return 'How can I help you today, $user?'; + } + + @override + String newChatGreeting5(String user) { + return 'What would you like to talk about, $user?'; + } + + @override + String get newChatGreetingNameFallback => 'user'; + + @override + String messageInputPlaceholder(String model) { + return 'Ask $model'; + } + + @override + String get messageInputPlaceholderModelPlaceholder => 'model'; + + @override + String get tooltipMessageOptions => 'Message options'; @override String get tooltipSend => 'Send'; @override - String get tooltipSave => 'Save'; + String tooltipLetAIThink(String model) { + return 'Generate with $model'; + } @override - String get tooltipLetAIThink => 'Let AI think'; + String get tooltipAddHostHeaders => 'Add host headers'; @override String get tooltipReset => 'Reset current chat'; @@ -84,72 +137,69 @@ class AppLocalizationsEn extends AppLocalizations { String get noModelSelected => 'No model selected'; @override - String get noHostSelected => 'No host selected, open settings to set one'; + String get noHostSelected => 'No host selected, open settings to set one.'; @override - String get noSelectedModel => ''; + String get noSelectedModel => ''; @override - String get newChatTitle => 'Unnamed Chat'; + String get newChatTitle => 'Untitled Chat'; @override - String get modelDialogAddModel => 'Add'; + String get modelDialogTitle => 'Select a model'; @override - String get modelDialogAddPromptTitle => 'Add new model'; + String get modelDialogAdd => 'Add'; + + @override + String get modelDialogRefresh => 'Refresh'; + + @override + String get modelDialogAddPromptTitle => 'Add model'; @override String get modelDialogAddPromptDescription => - 'This can either be a normal name (e.g. \'llama3\') or a name and tag (e.g. \'llama3:70b\').'; + 'This can either be a normal name (e.g. “llama3”) or a name together with a tag (e.g. “llama3:70b”).'; @override - String get modelDialogAddPromptAlreadyExists => 'Model already exists'; + String get modelDialogAddCurrently => 'Currently downloading:'; @override - String get modelDialogAddPromptInvalid => 'Invalid model name'; + String get modelDialogAddPromptAlreadyExists => + 'Model already exists. Try refreshing if it was deleted.'; @override - String get modelDialogAddAllowanceTitle => 'Allow Proxy'; + String get modelDialogAddPromptInvalid => + 'Couldn’t read your model name correctly. Please check the spelling and try again.'; @override - String get modelDialogAddAllowanceDescription => - 'Ollama App must check if the entered model is valid. To do that, we normally send a web request to the Ollama model list and check the status code, but because you\'re using the web client, we can\'t do that directly. Instead, the app will send the request to a different API, hosted by JHubi1, to check for us.\nThis is a one-time request and will only be sent when you add a new model.\nYour IP address will be sent with the request and might be stored for up to ten minutes to prevent spamming with potentially harmful intentions.\nIf you accept, your selection will be remembered for the future; if not, nothing will be sent and the model won\'t be added.'; + String get modelDialogAddPromptNotFound => + 'Couldn’t find your model on the server. Please check the spelling and try again.'; @override - String get modelDialogAddAllowanceAllow => 'Allow'; + String get modelDialogAddPromptNetworkError => + 'Network error while checking model. Please check your connection and try again.'; @override - String get modelDialogAddAllowanceDeny => 'Deny'; + String get modelDialogAddPromptCorruptionError => + 'The API returned “EOF”; your server was likely restarted while downloading the model previously.'; @override - String modelDialogAddAssuranceTitle(String model) { - return 'Add $model?'; - } + String get modelDialogAddPromptCorruptionErrorSolution => + 'Alternatively, you can run the following command on your Linux host directly:\nIf you see a new error, try running the command with “sudo” placed in front of it.'; @override - String modelDialogAddAssuranceDescription(String model) { - return 'Pressing \'Add\' will download the model \'$model\' directly from the Ollama server to your host.\nThis can take a while depending on your internet connection. The action cannot be canceled.\nIf the app is closed during the download, it will resume if you enter the name into the model dialog again.'; - } + String get modelDialogAddCancelTitle => 'Cancel download?'; @override - String get modelDialogAddAssuranceAdd => 'Add'; + String get modelDialogAddCancelConfirm => 'Cancel'; @override - String get modelDialogAddAssuranceCancel => 'Cancel'; + String get modelDialogAddCancelHide => 'Hide'; @override - String get modelDialogAddDownloadPercentLoading => 'loading progress'; - - @override - String modelDialogAddDownloadPercent(String percent) { - return 'download at $percent%'; - } - - @override - String get modelDialogAddDownloadFailed => 'Disconnected, try again'; - - @override - String get modelDialogAddDownloadSuccess => 'Download successful'; + String get modelDialogAddDownloadSuccess => + 'Download successful. Set as current model.'; @override String get deleteDialogTitle => 'Delete Chat'; @@ -185,6 +235,13 @@ class AppLocalizationsEn extends AppLocalizations { @override String get dialogEditMessageTitle => 'Edit message'; + @override + String get settingsTitleOverview => 'Overview'; + + @override + String get settingsDescriptionOverview => + 'General settings regarding your host.'; + @override String get settingsTitleBehavior => 'Behavior'; @@ -220,15 +277,12 @@ class AppLocalizationsEn extends AppLocalizations { String get settingsDescriptionAbout => 'Check for updates and learn more about Ollama App.'; - @override - String get settingsSavedAutomatically => 'Settings are saved automatically'; - @override String get settingsExperimentalAlpha => 'alpha'; @override String get settingsExperimentalAlphaDescription => - 'This feature is in alpha and may not work as intended or expected.\nCritical issues and/or permanent critical damage to device and/or used services cannot be ruled out.\nUse at your own risk. No liability on the part of the app author.'; + 'This feature is in alpha and may not work as intended or expected.\nCritical issues and/or permanent critical damage to device and/or connected services cannot be ruled out.\nUse at your own risk. No liability on the part of the app author.'; @override String get settingsExperimentalAlphaFeature => @@ -239,7 +293,7 @@ class AppLocalizationsEn extends AppLocalizations { @override String get settingsExperimentalBetaDescription => - 'This feature is in beta and may not work as intended or expected.\nLess severe issues may or may not occur. Damage shouldn\'t be critical.\nUse at your own risk.'; + 'This feature is in beta and may not work as intended or expected.\nLess severe issues may or may not occur. Damage shouldn’t be critical.\nUse at your own risk.'; @override String get settingsExperimentalBetaFeature => @@ -259,6 +313,9 @@ class AppLocalizationsEn extends AppLocalizations { @override String get settingsHost => 'Host'; + @override + String get settingsHostMissing => 'No host set'; + @override String get settingsHostValid => 'Valid Host'; @@ -268,36 +325,49 @@ class AppLocalizationsEn extends AppLocalizations { @override String settingsHostInvalid(String type) { String _temp0 = intl.Intl.selectLogic(type, { - 'url': 'Invalid URL', - 'host': 'Invalid Host', - 'timeout': 'Request failed. Server issues', - 'ratelimit': 'Too many requests', - 'other': 'Request Failed', + 'invalidUrl': 'Invalid URL', + 'unreachable': 'Host unreachable', + 'undetectable': 'Host not an Ollama server', + 'timeout': 'Connection timed out', + 'outdated': 'Outdated Ollama version', + 'other': 'Invalid host', }); - return 'Issue: $_temp0'; + return '$_temp0'; } - @override - String get tooltipAddHostHeaders => 'Add host headers'; - - @override - String get settingsHostHeaderTitle => 'Set host header'; - - @override - String get settingsHostHeaderInvalid => - 'The entered text isn\'t a valid header JSON object'; - @override String settingsHostInvalidDetailed(String type) { String _temp0 = intl.Intl.selectLogic(type, { - 'url': - 'The URL you entered is invalid. It isn\'t in a standardized URL format.', - 'other': - 'The host you entered is invalid. It cannot be reached. Please check the host and try again.', + 'invalidUrl': 'The URL you entered isn’t in a valid format.', + 'unreachable': + 'The host is invalid or unreachable, possibly due to a wrong address or network issue.', + 'undetectable': + 'The host is reachable but doesn’t respond as expected by Ollama App.\nIt may not be an Ollama server. Are you sure you entered it correctly?', + 'timeout': + 'The host couldn’t be reached in time.\nTry increasing the timeout multiplier in interface settings.', + 'outdated': + 'The host is running an Ollama version incompatible with this app.\nPlease update both the host and this app, if possible.', + 'other': 'The host you entered is invalid.', }); return '$_temp0'; } + @override + String get settingsHostHeaderHeaderName => 'Header'; + + @override + String get settingsHostHeaderHeaderValue => 'Value'; + + @override + String settingsHostHeaderUnsupported(String header) { + return 'The header “$header” may not be supported.'; + } + + @override + String settingsHostHeaderDuplicate(String header) { + return 'The header “$header” is already set.'; + } + @override String get settingsSystemMessage => 'System message'; @@ -306,7 +376,7 @@ class AppLocalizationsEn extends AppLocalizations { @override String get settingsUseSystemDescription => - 'Disables setting the system message above and uses the one from the model\'s Modelfile instead. Can be useful for models with model files.'; + 'Disables setting the system message above and uses the one from the model’s Modelfile instead. Can be useful for models with model files.'; @override String get settingsDisableMarkdown => 'Disable markdown'; @@ -346,7 +416,7 @@ class AppLocalizationsEn extends AppLocalizations { String get settingsKeepModelLoadedAlways => 'Keep model always loaded'; @override - String get settingsKeepModelLoadedNever => 'Don\'t keep model loaded'; + String get settingsKeepModelLoadedNever => 'Don’t keep model loaded'; @override String get settingsKeepModelLoadedFor => @@ -397,13 +467,13 @@ class AppLocalizationsEn extends AppLocalizations { @override String get settingsTemporaryFixesInstructions => - 'Do not toggle any of these settings unless you know what you are doing! The given solutions might not work as expected.\nThey cannot be seen as final or should be judged as such. Issues might occur.'; + 'Do not toggle any of these settings unless you know what you are doing! The given solutions might not work as expected.\nThey should not be considered final or judged as such. Issues might occur.'; @override String get settingsTemporaryFixesNoFixes => 'No fixes available'; @override - String get settingsVoicePermissionLoading => 'Loading voice permissions ...'; + String get settingsVoicePermissionLoading => 'Loading voice permissions...'; @override String get settingsVoiceTtsNotSupported => 'Text-to-speech not supported'; @@ -460,17 +530,17 @@ class AppLocalizationsEn extends AppLocalizations { @override String get settingsExportInfo => - 'These options allow you to export and import your chat history. This can be useful if you want to transfer your chat history to another device or back up your chat history'; + 'These options allow you to export and import your chat history. This can be useful if you want to transfer your chat history to another device or back up your chat history.'; @override String get settingsExportWarning => - 'Multiple chat histories won\'t be merged! You\'ll lose your current chat history if you import a new one.'; + 'Multiple chat histories won’t be merged! You’ll lose your current chat history if you import a new one.'; @override String get settingsUpdateCheck => 'Check for updates'; @override - String get settingsUpdateChecking => 'Checking for updates ...'; + String get settingsUpdateChecking => 'Checking for updates...'; @override String get settingsUpdateLatest => 'You are on the latest version'; @@ -481,7 +551,7 @@ class AppLocalizationsEn extends AppLocalizations { } @override - String get settingsUpdateRateLimit => 'Can\'t check, API rate limit exceeded'; + String get settingsUpdateRateLimit => 'Can’t check, API rate limit exceeded'; @override String get settingsUpdateIssue => 'An issue occurred'; @@ -503,7 +573,7 @@ class AppLocalizationsEn extends AppLocalizations { String get settingsUpdateDialogCancel => 'Cancel'; @override - String get settingsCheckForUpdates => 'Check for updates on open'; + String get settingsCheckForUpdates => 'Check for updates on startup'; @override String get settingsGithub => 'GitHub'; diff --git a/lib/l10n/gen/app_localizations_fa.dart b/lib/l10n/gen/app_localizations_fa.dart index efb6df1..5f89a07 100644 --- a/lib/l10n/gen/app_localizations_fa.dart +++ b/lib/l10n/gen/app_localizations_fa.dart @@ -9,7 +9,12 @@ class AppLocalizationsFa extends AppLocalizations { AppLocalizationsFa([String locale = 'fa']) : super(locale); @override - String get appTitle => 'Ollama'; + String appTitle(String env, String title) { + return 'Ollama'; + } + + @override + String get learnMore => 'Learn more'; @override String get optionNewChat => 'New Chat'; @@ -23,6 +28,20 @@ class AppLocalizationsFa extends AppLocalizations { @override String get optionNoChatFound => 'No chats found'; + @override + String optionNoChatFoundSearch(String query) { + return 'No chats found for ‘$query’'; + } + + @override + String get optionSearchChats => 'Search through chats'; + + @override + String get optionChatDetails => 'Chat details'; + + @override + String get optionChatDetailsNoChat => 'No chat selected'; + @override String get tipPrefix => 'Tip: '; @@ -54,25 +73,54 @@ class AppLocalizationsFa extends AppLocalizations { String get uploadImage => 'Upload Image'; @override - String get notAValidImage => 'Not a valid image'; + String newChatGreeting1(String user) { + return 'What’s on your mind, $user?'; + } @override - String get imageOnlyConversation => 'Image Only Conversation'; + String newChatGreeting2(String user) { + return 'Good to see you, $user.'; + } @override - String get messageInputPlaceholder => 'Message'; + String newChatGreeting3(String user) { + return 'What can I do for you, $user?'; + } @override - String get tooltipAttachment => 'Add attachment'; + String newChatGreeting4(String user) { + return 'How can I help you today, $user?'; + } + + @override + String newChatGreeting5(String user) { + return 'What would you like to talk about, $user?'; + } + + @override + String get newChatGreetingNameFallback => 'user'; + + @override + String messageInputPlaceholder(String model) { + return 'Message'; + } + + @override + String get messageInputPlaceholderModelPlaceholder => 'model'; + + @override + String get tooltipMessageOptions => 'Message options'; @override String get tooltipSend => 'Send'; @override - String get tooltipSave => 'Save'; + String tooltipLetAIThink(String model) { + return 'Let AI think'; + } @override - String get tooltipLetAIThink => 'Let AI think'; + String get tooltipAddHostHeaders => 'Add host headers'; @override String get tooltipReset => 'Reset current chat'; @@ -93,7 +141,13 @@ class AppLocalizationsFa extends AppLocalizations { String get newChatTitle => 'Unnamed Chat'; @override - String get modelDialogAddModel => 'Add'; + String get modelDialogTitle => 'Select a model'; + + @override + String get modelDialogAdd => 'Add'; + + @override + String get modelDialogRefresh => 'Refresh'; @override String get modelDialogAddPromptTitle => 'Add new model'; @@ -102,6 +156,9 @@ class AppLocalizationsFa extends AppLocalizations { String get modelDialogAddPromptDescription => 'This can have either be a normal name (e.g. \'llama3\') or name and tag (e.g. \'llama3:70b\').'; + @override + String get modelDialogAddCurrently => 'Currently downloading:'; + @override String get modelDialogAddPromptAlreadyExists => 'Model already exists'; @@ -109,44 +166,29 @@ class AppLocalizationsFa extends AppLocalizations { String get modelDialogAddPromptInvalid => 'Invalid model name'; @override - String get modelDialogAddAllowanceTitle => 'Allow Proxy'; + String get modelDialogAddPromptNotFound => + 'Couldn’t find your model on the server. Please check the spelling and try again.'; @override - String get modelDialogAddAllowanceDescription => - 'Ollama App must check if the entered model is valid. For that, we normally send a web request to the Ollama model list and check the status code, but because you\'re using the web client, we can\'t do that directly. Instead, the app will send the request to a different api, hosted by JHubi1, to check for us.\nThis is a one-time request and will only be sent when you add a new model.\nYour IP address will be sent with the request and might be stored for up to ten minutes to prevent spamming with potential harmful intentions.\nIf you accept, your selection will be remembered in the future; if not, nothing will be sent and the model won\'t be added.'; + String get modelDialogAddPromptNetworkError => + 'Network error while checking model. Please check your connection and try again.'; @override - String get modelDialogAddAllowanceAllow => 'Allow'; + String get modelDialogAddPromptCorruptionError => + 'The API returned “EOF”; your server was likely restarted while downloading the model previously.'; @override - String get modelDialogAddAllowanceDeny => 'Deny'; + String get modelDialogAddPromptCorruptionErrorSolution => + 'Alternatively, you can run the following command on your Linux host directly:\nIf you see a new error, try running the command with “sudo” placed in front of it.'; @override - String modelDialogAddAssuranceTitle(String model) { - return 'Add $model?'; - } + String get modelDialogAddCancelTitle => 'Cancel download?'; @override - String modelDialogAddAssuranceDescription(String model) { - return 'Pressing \'Add\' will download the model \'$model\' directly from the Ollama server to your host.\nThis can take a while depending on your internet connection. The action cannot be canceled.\nIf the app is closed during the download, it\'ll resume if you enter the name into the model dialog again.'; - } + String get modelDialogAddCancelConfirm => 'Cancel'; @override - String get modelDialogAddAssuranceAdd => 'Add'; - - @override - String get modelDialogAddAssuranceCancel => 'Cancel'; - - @override - String get modelDialogAddDownloadPercentLoading => 'loading progress'; - - @override - String modelDialogAddDownloadPercent(String percent) { - return 'download at $percent%'; - } - - @override - String get modelDialogAddDownloadFailed => 'Disconnected, try again'; + String get modelDialogAddCancelHide => 'Hide'; @override String get modelDialogAddDownloadSuccess => 'Download successful'; @@ -185,6 +227,13 @@ class AppLocalizationsFa extends AppLocalizations { @override String get dialogEditMessageTitle => 'Edit message'; + @override + String get settingsTitleOverview => 'Overview'; + + @override + String get settingsDescriptionOverview => + 'General settings regarding your host.'; + @override String get settingsTitleBehavior => 'Behavior'; @@ -220,9 +269,6 @@ class AppLocalizationsFa extends AppLocalizations { String get settingsDescriptionAbout => 'Check for updates and learn more about Ollama App.'; - @override - String get settingsSavedAutomatically => 'Settings are saved automatically'; - @override String get settingsExperimentalAlpha => 'alpha'; @@ -259,6 +305,9 @@ class AppLocalizationsFa extends AppLocalizations { @override String get settingsHost => 'Host'; + @override + String get settingsHostMissing => 'No host set'; + @override String get settingsHostValid => 'Valid Host'; @@ -277,16 +326,6 @@ class AppLocalizationsFa extends AppLocalizations { return 'Issue: $_temp0'; } - @override - String get tooltipAddHostHeaders => 'Add host headers'; - - @override - String get settingsHostHeaderTitle => 'Set host header'; - - @override - String get settingsHostHeaderInvalid => - 'The entered text isn\'t a valid header JSON object'; - @override String settingsHostInvalidDetailed(String type) { String _temp0 = intl.Intl.selectLogic(type, { @@ -298,6 +337,22 @@ class AppLocalizationsFa extends AppLocalizations { return '$_temp0'; } + @override + String get settingsHostHeaderHeaderName => 'Header'; + + @override + String get settingsHostHeaderHeaderValue => 'Value'; + + @override + String settingsHostHeaderUnsupported(String header) { + return 'The header “$header” may not be supported.'; + } + + @override + String settingsHostHeaderDuplicate(String header) { + return 'The header “$header” is already set.'; + } + @override String get settingsSystemMessage => 'System message'; diff --git a/lib/l10n/gen/app_localizations_it.dart b/lib/l10n/gen/app_localizations_it.dart index 5b7c52d..6f7ac0a 100644 --- a/lib/l10n/gen/app_localizations_it.dart +++ b/lib/l10n/gen/app_localizations_it.dart @@ -9,7 +9,12 @@ class AppLocalizationsIt extends AppLocalizations { AppLocalizationsIt([String locale = 'it']) : super(locale); @override - String get appTitle => 'Ollama'; + String appTitle(String env, String title) { + return 'Ollama'; + } + + @override + String get learnMore => 'Learn more'; @override String get optionNewChat => 'Nuova Chat'; @@ -23,6 +28,20 @@ class AppLocalizationsIt extends AppLocalizations { @override String get optionNoChatFound => 'Nessuna Chat trovata'; + @override + String optionNoChatFoundSearch(String query) { + return 'No chats found for ‘$query’'; + } + + @override + String get optionSearchChats => 'Search through chats'; + + @override + String get optionChatDetails => 'Chat details'; + + @override + String get optionChatDetailsNoChat => 'No chat selected'; + @override String get tipPrefix => 'Suggerimento: '; @@ -55,25 +74,54 @@ class AppLocalizationsIt extends AppLocalizations { String get uploadImage => 'Carica immagine'; @override - String get notAValidImage => 'Immagine non valida'; + String newChatGreeting1(String user) { + return 'What’s on your mind, $user?'; + } @override - String get imageOnlyConversation => 'Conversazione di sole immagini'; + String newChatGreeting2(String user) { + return 'Good to see you, $user.'; + } @override - String get messageInputPlaceholder => 'Messaggio'; + String newChatGreeting3(String user) { + return 'What can I do for you, $user?'; + } @override - String get tooltipAttachment => 'Aggiungi allegato'; + String newChatGreeting4(String user) { + return 'How can I help you today, $user?'; + } + + @override + String newChatGreeting5(String user) { + return 'What would you like to talk about, $user?'; + } + + @override + String get newChatGreetingNameFallback => 'user'; + + @override + String messageInputPlaceholder(String model) { + return 'Messaggio'; + } + + @override + String get messageInputPlaceholderModelPlaceholder => 'model'; + + @override + String get tooltipMessageOptions => 'Message options'; @override String get tooltipSend => 'Invia'; @override - String get tooltipSave => 'Salva'; + String tooltipLetAIThink(String model) { + return 'Lasciamo che sia IA a pensare'; + } @override - String get tooltipLetAIThink => 'Lasciamo che sia IA a pensare'; + String get tooltipAddHostHeaders => 'Aggiungi host headers'; @override String get tooltipReset => 'Reimposta la chat corrente'; @@ -95,7 +143,13 @@ class AppLocalizationsIt extends AppLocalizations { String get newChatTitle => 'Chat senza nome'; @override - String get modelDialogAddModel => 'Aggiungi'; + String get modelDialogTitle => 'Select a model'; + + @override + String get modelDialogAdd => 'Add'; + + @override + String get modelDialogRefresh => 'Refresh'; @override String get modelDialogAddPromptTitle => 'Aggiungi nuovo modello'; @@ -104,6 +158,9 @@ class AppLocalizationsIt extends AppLocalizations { String get modelDialogAddPromptDescription => 'Questo può essere un nome normale (ad es. \'llama3\') o nome e tag (ad es. \'llama3:70b\').'; + @override + String get modelDialogAddCurrently => 'Currently downloading:'; + @override String get modelDialogAddPromptAlreadyExists => 'Il modello esiste già'; @@ -111,44 +168,29 @@ class AppLocalizationsIt extends AppLocalizations { String get modelDialogAddPromptInvalid => 'Nome del modello non valido'; @override - String get modelDialogAddAllowanceTitle => 'Abilita Proxy'; + String get modelDialogAddPromptNotFound => + 'Couldn’t find your model on the server. Please check the spelling and try again.'; @override - String get modelDialogAddAllowanceDescription => - 'Ollama App deve controllare se il modello inserito è valido. Per questo, normalmente inviamo una richiesta web alla lista dei modelli Ollama e controlliamo il codice di stato, ma perché stai usando il web client, non possiamo farlo direttamente. Invece, l\'app invierà la richiesta a un altro api, ospitato da JHubi1, per eseguire il controllo.\nQuesta è una richiesta verrà inviata solo quando aggiungi un nuovo modello.\nIl tuo indirizzo IP verrà inviato con la richiesta e potrebbe essere memorizzato per un massimo di dieci minuti per evitare lo spamming con potenziali intenzioni nocive.\nSe accetti, la tua selezione sarà ricordata in futuro; in caso contrario, non verrà inviato nulla e il modello non verrà aggiunto.'; + String get modelDialogAddPromptNetworkError => + 'Network error while checking model. Please check your connection and try again.'; @override - String get modelDialogAddAllowanceAllow => 'Consenti'; + String get modelDialogAddPromptCorruptionError => + 'The API returned “EOF”; your server was likely restarted while downloading the model previously.'; @override - String get modelDialogAddAllowanceDeny => 'Nega'; + String get modelDialogAddPromptCorruptionErrorSolution => + 'Alternatively, you can run the following command on your Linux host directly:\nIf you see a new error, try running the command with “sudo” placed in front of it.'; @override - String modelDialogAddAssuranceTitle(String model) { - return 'Aggiungi $model?'; - } + String get modelDialogAddCancelTitle => 'Cancel download?'; @override - String modelDialogAddAssuranceDescription(String model) { - return 'Premendo \'Aggiungi\' scaricherà il modello \'$model\' direttamente dal server Ollama al tuo host.\nQuesto può richiedere un po\' di tempo a seconda della tua connessione internet. L\'azione non può essere annullata.\nSe l\'app è chiusa durante il download, riprenderà se inserisci di nuovo il nome nella finestra del modello.'; - } + String get modelDialogAddCancelConfirm => 'Cancel'; @override - String get modelDialogAddAssuranceAdd => 'Aggiungi'; - - @override - String get modelDialogAddAssuranceCancel => 'Annulla'; - - @override - String get modelDialogAddDownloadPercentLoading => 'Caricamento in corso'; - - @override - String modelDialogAddDownloadPercent(String percent) { - return 'scarica al $percent%'; - } - - @override - String get modelDialogAddDownloadFailed => 'Disconnesso, riprova'; + String get modelDialogAddCancelHide => 'Hide'; @override String get modelDialogAddDownloadSuccess => @@ -188,6 +230,13 @@ class AppLocalizationsIt extends AppLocalizations { @override String get dialogEditMessageTitle => 'Modifica Messaggio'; + @override + String get settingsTitleOverview => 'Overview'; + + @override + String get settingsDescriptionOverview => + 'General settings regarding your host.'; + @override String get settingsTitleBehavior => 'Comportamento'; @@ -223,10 +272,6 @@ class AppLocalizationsIt extends AppLocalizations { String get settingsDescriptionAbout => 'Controlla gli aggiornamenti e scopri di più su Ollama App.'; - @override - String get settingsSavedAutomatically => - 'Le impostazioni vengono salvate automaticamente'; - @override String get settingsExperimentalAlpha => 'alpha'; @@ -263,6 +308,9 @@ class AppLocalizationsIt extends AppLocalizations { @override String get settingsHost => 'Host'; + @override + String get settingsHostMissing => 'No host set'; + @override String get settingsHostValid => 'Host valido'; @@ -281,16 +329,6 @@ class AppLocalizationsIt extends AppLocalizations { return 'Problema: $_temp0'; } - @override - String get tooltipAddHostHeaders => 'Aggiungi host headers'; - - @override - String get settingsHostHeaderTitle => 'Imposta header host'; - - @override - String get settingsHostHeaderInvalid => - 'Il testo immesso non è un valido oggetto JSON'; - @override String settingsHostInvalidDetailed(String type) { String _temp0 = intl.Intl.selectLogic(type, { @@ -302,6 +340,22 @@ class AppLocalizationsIt extends AppLocalizations { return '$_temp0'; } + @override + String get settingsHostHeaderHeaderName => 'Header'; + + @override + String get settingsHostHeaderHeaderValue => 'Value'; + + @override + String settingsHostHeaderUnsupported(String header) { + return 'The header “$header” may not be supported.'; + } + + @override + String settingsHostHeaderDuplicate(String header) { + return 'The header “$header” is already set.'; + } + @override String get settingsSystemMessage => 'Messaggio di sistema'; diff --git a/lib/l10n/gen/app_localizations_tr.dart b/lib/l10n/gen/app_localizations_tr.dart index 2549b11..7c5729f 100644 --- a/lib/l10n/gen/app_localizations_tr.dart +++ b/lib/l10n/gen/app_localizations_tr.dart @@ -9,7 +9,12 @@ class AppLocalizationsTr extends AppLocalizations { AppLocalizationsTr([String locale = 'tr']) : super(locale); @override - String get appTitle => 'Ollama'; + String appTitle(String env, String title) { + return 'Ollama'; + } + + @override + String get learnMore => 'Learn more'; @override String get optionNewChat => 'Yeni Sohbet'; @@ -23,6 +28,20 @@ class AppLocalizationsTr extends AppLocalizations { @override String get optionNoChatFound => 'Sohbet bulunamadı'; + @override + String optionNoChatFoundSearch(String query) { + return 'No chats found for ‘$query’'; + } + + @override + String get optionSearchChats => 'Search through chats'; + + @override + String get optionChatDetails => 'Chat details'; + + @override + String get optionChatDetailsNoChat => 'No chat selected'; + @override String get tipPrefix => 'İpucu: '; @@ -54,25 +73,54 @@ class AppLocalizationsTr extends AppLocalizations { String get uploadImage => 'Görsel Yükle'; @override - String get notAValidImage => 'Geçerli bir görsel değil'; + String newChatGreeting1(String user) { + return 'What’s on your mind, $user?'; + } @override - String get imageOnlyConversation => 'Sadece Görsel İçeren Konuşma'; + String newChatGreeting2(String user) { + return 'Good to see you, $user.'; + } @override - String get messageInputPlaceholder => 'Mesaj'; + String newChatGreeting3(String user) { + return 'What can I do for you, $user?'; + } @override - String get tooltipAttachment => 'Ek ekle'; + String newChatGreeting4(String user) { + return 'How can I help you today, $user?'; + } + + @override + String newChatGreeting5(String user) { + return 'What would you like to talk about, $user?'; + } + + @override + String get newChatGreetingNameFallback => 'user'; + + @override + String messageInputPlaceholder(String model) { + return 'Mesaj'; + } + + @override + String get messageInputPlaceholderModelPlaceholder => 'model'; + + @override + String get tooltipMessageOptions => 'Message options'; @override String get tooltipSend => 'Gönder'; @override - String get tooltipSave => 'Kaydet'; + String tooltipLetAIThink(String model) { + return 'AI\'\'nın düşünmesine izin ver'; + } @override - String get tooltipLetAIThink => 'AI\'\'nın düşünmesine izin ver'; + String get tooltipAddHostHeaders => 'Ana bilgisayar başlıkları ekle'; @override String get tooltipReset => 'Mevcut sohbeti sıfırla'; @@ -94,7 +142,13 @@ class AppLocalizationsTr extends AppLocalizations { String get newChatTitle => 'İsimsiz Sohbet'; @override - String get modelDialogAddModel => 'Ekle'; + String get modelDialogTitle => 'Select a model'; + + @override + String get modelDialogAdd => 'Add'; + + @override + String get modelDialogRefresh => 'Refresh'; @override String get modelDialogAddPromptTitle => 'Yeni model ekle'; @@ -103,6 +157,9 @@ class AppLocalizationsTr extends AppLocalizations { String get modelDialogAddPromptDescription => 'Bu normal bir isim (örneğin \'llama3\') ya da isim ve etiket (örneğin \'llama3:70b\') olabilir.'; + @override + String get modelDialogAddCurrently => 'Currently downloading:'; + @override String get modelDialogAddPromptAlreadyExists => 'Model zaten mevcut'; @@ -110,45 +167,29 @@ class AppLocalizationsTr extends AppLocalizations { String get modelDialogAddPromptInvalid => 'Geçersiz model adı'; @override - String get modelDialogAddAllowanceTitle => 'Proxy\'e İzin Ver'; + String get modelDialogAddPromptNotFound => + 'Couldn’t find your model on the server. Please check the spelling and try again.'; @override - String get modelDialogAddAllowanceDescription => - 'Ollama Uygulaması, girilen modelin geçerli olup olmadığını kontrol etmelidir. Bunun için normalde Ollama model listesine bir web isteği gönderir ve durum kodunu kontrol ederiz, ancak siz web istemcisini kullandığınız için bunu doğrudan yapamayız. Bunun yerine, uygulama bizim için kontrol etmek amacıyla JHubi1 tarafından barındırılan farklı bir API\'ye istek gönderecek. \nBu, yalnızca bir kez yapılan bir istektir ve yalnızca yeni bir model eklediğinizde gönderilecektir. \nIP adresiniz istekle birlikte gönderilecek ve olası zararlı niyetlerle spam yapılmasını önlemek amacıyla on dakikaya kadar saklanabilir. \nKabul ederseniz, seçiminiz gelecekte hatırlanacaktır; kabul etmezseniz, hiçbir şey gönderilmeyecek ve model eklenmeyecektir.'; + String get modelDialogAddPromptNetworkError => + 'Network error while checking model. Please check your connection and try again.'; @override - String get modelDialogAddAllowanceAllow => 'İzin ver'; + String get modelDialogAddPromptCorruptionError => + 'The API returned “EOF”; your server was likely restarted while downloading the model previously.'; @override - String get modelDialogAddAllowanceDeny => 'Reddet'; + String get modelDialogAddPromptCorruptionErrorSolution => + 'Alternatively, you can run the following command on your Linux host directly:\nIf you see a new error, try running the command with “sudo” placed in front of it.'; @override - String modelDialogAddAssuranceTitle(String model) { - return '$model Ekle?'; - } + String get modelDialogAddCancelTitle => 'Cancel download?'; @override - String modelDialogAddAssuranceDescription(String model) { - return '\'Ekle\' tuşuna basmak, \'$model\' modelini doğrudan Ollama sunucusundan bilgisayarınıza indirecektir. İnternet bağlantınıza bağlı olarak bu işlem biraz zaman alabilir. Bu işlem iptal edilemez. Uygulama indirme sırasında kapatılırsa, model adını tekrar model diyaloguna girerseniz indirme işlemi kaldığı yerden devam eder.'; - } + String get modelDialogAddCancelConfirm => 'Cancel'; @override - String get modelDialogAddAssuranceAdd => 'Ekle'; - - @override - String get modelDialogAddAssuranceCancel => 'İptal'; - - @override - String get modelDialogAddDownloadPercentLoading => 'yükleme ilerleme durumu'; - - @override - String modelDialogAddDownloadPercent(String percent) { - return '%$percent oranında indir'; - } - - @override - String get modelDialogAddDownloadFailed => - 'Bağlantı kesildi, yeniden deneyin'; + String get modelDialogAddCancelHide => 'Hide'; @override String get modelDialogAddDownloadSuccess => 'İndirme tamamlandı'; @@ -187,6 +228,13 @@ class AppLocalizationsTr extends AppLocalizations { @override String get dialogEditMessageTitle => 'Mesajı düzenle'; + @override + String get settingsTitleOverview => 'Overview'; + + @override + String get settingsDescriptionOverview => + 'General settings regarding your host.'; + @override String get settingsTitleBehavior => 'Davranış'; @@ -222,9 +270,6 @@ class AppLocalizationsTr extends AppLocalizations { String get settingsDescriptionAbout => 'Güncellemeleri kontrol edin ve Ollama Uygulaması hakkında daha fazla bilgi edinin.'; - @override - String get settingsSavedAutomatically => 'Ayarlar otomatik olarak kaydedilir'; - @override String get settingsExperimentalAlpha => 'alfa'; @@ -261,6 +306,9 @@ class AppLocalizationsTr extends AppLocalizations { @override String get settingsHost => 'Ana bilgisayar'; + @override + String get settingsHostMissing => 'No host set'; + @override String get settingsHostValid => 'Geçerli Ana Bilgisayar'; @@ -279,16 +327,6 @@ class AppLocalizationsTr extends AppLocalizations { return 'Sorun: $_temp0'; } - @override - String get tooltipAddHostHeaders => 'Ana bilgisayar başlıkları ekle'; - - @override - String get settingsHostHeaderTitle => 'Ana bilgisayar başlığını ayarla'; - - @override - String get settingsHostHeaderInvalid => - 'Girilen metin geçerli bir başlık JSON nesnesi değil'; - @override String settingsHostInvalidDetailed(String type) { String _temp0 = intl.Intl.selectLogic(type, { @@ -299,6 +337,22 @@ class AppLocalizationsTr extends AppLocalizations { return '$_temp0'; } + @override + String get settingsHostHeaderHeaderName => 'Header'; + + @override + String get settingsHostHeaderHeaderValue => 'Value'; + + @override + String settingsHostHeaderUnsupported(String header) { + return 'The header “$header” may not be supported.'; + } + + @override + String settingsHostHeaderDuplicate(String header) { + return 'The header “$header” is already set.'; + } + @override String get settingsSystemMessage => 'Sistem mesajı'; diff --git a/lib/l10n/gen/app_localizations_zh.dart b/lib/l10n/gen/app_localizations_zh.dart index ad60958..a4618cb 100644 --- a/lib/l10n/gen/app_localizations_zh.dart +++ b/lib/l10n/gen/app_localizations_zh.dart @@ -9,7 +9,12 @@ class AppLocalizationsZh extends AppLocalizations { AppLocalizationsZh([String locale = 'zh']) : super(locale); @override - String get appTitle => 'Ollama'; + String appTitle(String env, String title) { + return 'Ollama'; + } + + @override + String get learnMore => 'Learn more'; @override String get optionNewChat => '新建聊天'; @@ -23,6 +28,20 @@ class AppLocalizationsZh extends AppLocalizations { @override String get optionNoChatFound => '暂无聊天消息'; + @override + String optionNoChatFoundSearch(String query) { + return 'No chats found for ‘$query’'; + } + + @override + String get optionSearchChats => 'Search through chats'; + + @override + String get optionChatDetails => 'Chat details'; + + @override + String get optionChatDetailsNoChat => 'No chat selected'; + @override String get tipPrefix => '提示: '; @@ -54,25 +73,54 @@ class AppLocalizationsZh extends AppLocalizations { String get uploadImage => '上传图像'; @override - String get notAValidImage => '不是一个有效的图片文件.'; + String newChatGreeting1(String user) { + return 'What’s on your mind, $user?'; + } @override - String get imageOnlyConversation => '仅图片对话'; + String newChatGreeting2(String user) { + return 'Good to see you, $user.'; + } @override - String get messageInputPlaceholder => '消息'; + String newChatGreeting3(String user) { + return 'What can I do for you, $user?'; + } @override - String get tooltipAttachment => '添加附件'; + String newChatGreeting4(String user) { + return 'How can I help you today, $user?'; + } + + @override + String newChatGreeting5(String user) { + return 'What would you like to talk about, $user?'; + } + + @override + String get newChatGreetingNameFallback => 'user'; + + @override + String messageInputPlaceholder(String model) { + return '消息'; + } + + @override + String get messageInputPlaceholderModelPlaceholder => 'model'; + + @override + String get tooltipMessageOptions => 'Message options'; @override String get tooltipSend => '发送'; @override - String get tooltipSave => '保存'; + String tooltipLetAIThink(String model) { + return '让AI思考'; + } @override - String get tooltipLetAIThink => '让AI思考'; + String get tooltipAddHostHeaders => '设置主机请求头'; @override String get tooltipReset => '重置当前聊天'; @@ -93,7 +141,13 @@ class AppLocalizationsZh extends AppLocalizations { String get newChatTitle => '未命名的聊天'; @override - String get modelDialogAddModel => '添加'; + String get modelDialogTitle => 'Select a model'; + + @override + String get modelDialogAdd => 'Add'; + + @override + String get modelDialogRefresh => 'Refresh'; @override String get modelDialogAddPromptTitle => '添加新模型'; @@ -102,6 +156,9 @@ class AppLocalizationsZh extends AppLocalizations { String get modelDialogAddPromptDescription => '可以是一个普通名称(如:\'llama3\'),也可以是名称加标签(如:\'llama3:70b\')。'; + @override + String get modelDialogAddCurrently => 'Currently downloading:'; + @override String get modelDialogAddPromptAlreadyExists => '模型已存在'; @@ -109,44 +166,29 @@ class AppLocalizationsZh extends AppLocalizations { String get modelDialogAddPromptInvalid => '无效的模型名称'; @override - String get modelDialogAddAllowanceTitle => '允许代理服务器'; + String get modelDialogAddPromptNotFound => + 'Couldn’t find your model on the server. Please check the spelling and try again.'; @override - String get modelDialogAddAllowanceDescription => - 'Ollama 应用程序必须检查输入的模型是否有效。 为此,我们通常向Ollama模型列表发送一个网络请求并检查状态。 由于您正在使用 Web 客户端,我们不能直接做到这一点。 因此,应用将把请求发送到另一个由 JHubi 1 部署的api 上进行检查。\n这是一个一次性请求,只有当您添加一个新模型时才会发送。\n您的IP地址将与请求一起发送,可能会被存储长达10分钟,以防止潜在的有害故障。\n如果您接受,您的选择将在将来被记住;如果不接受,将不会发送任何内容,也不会添加模型。'; + String get modelDialogAddPromptNetworkError => + 'Network error while checking model. Please check your connection and try again.'; @override - String get modelDialogAddAllowanceAllow => '允许'; + String get modelDialogAddPromptCorruptionError => + 'The API returned “EOF”; your server was likely restarted while downloading the model previously.'; @override - String get modelDialogAddAllowanceDeny => '拒绝'; + String get modelDialogAddPromptCorruptionErrorSolution => + 'Alternatively, you can run the following command on your Linux host directly:\nIf you see a new error, try running the command with “sudo” placed in front of it.'; @override - String modelDialogAddAssuranceTitle(String model) { - return '添加$model?'; - } + String get modelDialogAddCancelTitle => 'Cancel download?'; @override - String modelDialogAddAssuranceDescription(String model) { - return '按下“添加”将直接从 Ollama 服务器下载模型“$model”到您的主机。\n这可能需要一些时间,取决于您的互联网连接。该操作不能被取消。\n如果在下载过程中关闭应用,当您再次在模型对话框中输入名称,它将恢复之前的下载。'; - } + String get modelDialogAddCancelConfirm => 'Cancel'; @override - String get modelDialogAddAssuranceAdd => '添加'; - - @override - String get modelDialogAddAssuranceCancel => '取消'; - - @override - String get modelDialogAddDownloadPercentLoading => '加载进度'; - - @override - String modelDialogAddDownloadPercent(String percent) { - return '已下载 $percent%'; - } - - @override - String get modelDialogAddDownloadFailed => '连接断开,请重试'; + String get modelDialogAddCancelHide => 'Hide'; @override String get modelDialogAddDownloadSuccess => '下载成功'; @@ -185,6 +227,13 @@ class AppLocalizationsZh extends AppLocalizations { @override String get dialogEditMessageTitle => '编辑消息'; + @override + String get settingsTitleOverview => 'Overview'; + + @override + String get settingsDescriptionOverview => + 'General settings regarding your host.'; + @override String get settingsTitleBehavior => '行为'; @@ -215,9 +264,6 @@ class AppLocalizationsZh extends AppLocalizations { @override String get settingsDescriptionAbout => '检查更新并了解更多关于Ollama App的信息。'; - @override - String get settingsSavedAutomatically => '设置已自动保存'; - @override String get settingsExperimentalAlpha => 'alpha'; @@ -251,6 +297,9 @@ class AppLocalizationsZh extends AppLocalizations { @override String get settingsHost => '主机地址'; + @override + String get settingsHostMissing => 'No host set'; + @override String get settingsHostValid => '有效主机地址'; @@ -268,15 +317,6 @@ class AppLocalizationsZh extends AppLocalizations { return '问题:$_temp0'; } - @override - String get tooltipAddHostHeaders => '设置主机请求头'; - - @override - String get settingsHostHeaderTitle => '设置主机请求头'; - - @override - String get settingsHostHeaderInvalid => '输入的文本不是有效的标题 JSON 对象'; - @override String settingsHostInvalidDetailed(String type) { String _temp0 = intl.Intl.selectLogic(type, { @@ -286,6 +326,22 @@ class AppLocalizationsZh extends AppLocalizations { return '$_temp0'; } + @override + String get settingsHostHeaderHeaderName => 'Header'; + + @override + String get settingsHostHeaderHeaderValue => 'Value'; + + @override + String settingsHostHeaderUnsupported(String header) { + return 'The header “$header” may not be supported.'; + } + + @override + String settingsHostHeaderDuplicate(String header) { + return 'The header “$header” is already set.'; + } + @override String get settingsSystemMessage => '系统信息'; diff --git a/lib/main.dart b/lib/main.dart index 5f0b2cd..9de5634 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -1,81 +1,123 @@ import 'dart:async'; import 'dart:io'; +import 'package:auto_route/auto_route.dart'; import 'package:bitsdojo_window/bitsdojo_window.dart'; +import 'package:dartx/dartx.dart'; import 'package:dynamic_system_colors/dynamic_system_colors.dart'; import 'package:flutter/foundation.dart'; -import 'package:flutter/material.dart'; -import 'package:flutter_chat_types/flutter_chat_types.dart' as types; +import 'package:flutter/material.dart' hide RouteSettings; // import 'package:flutter_displaymode/flutter_displaymode.dart'; import 'package:flutter_tts/flutter_tts.dart'; +import 'package:flutter_web_plugins/url_strategy.dart' show usePathUrlStrategy; +import 'package:intl/date_symbol_data_local.dart'; import 'package:permission_handler/permission_handler.dart'; import 'package:pwa_install/pwa_install.dart' as pwa; import 'package:shared_preferences/shared_preferences.dart'; import 'package:shared_preferences/util/legacy_to_async_migration_util.dart'; import 'package:speech_to_text/speech_to_text.dart'; import 'package:universal_html/html.dart' as html; -import 'package:uuid/uuid.dart'; import 'l10n/gen/app_localizations.dart'; -import 'screens/main.dart'; -import 'services/clients.dart'; -import 'services/responsive.dart'; -import 'services/theme.dart'; +import 'main.gr.dart'; +import 'services/services.dart'; // client configuration -// use host or not, if false dialog is shown +/// forces usage of [fixedHost] as the host const bool useHost = false; -// host of ollama, must be accessible from the client, without trailing slash -// ! will always be accepted as valid, even if [useHost] is false + +/// the host used when [useHost] is true; not validated! const String fixedHost = "http://example.com:11434"; -// use model or not, if false selector is shown + +/// forces usage of [fixedModel] as the model const bool useModel = false; -// model name as string, must be valid ollama model! -const String fixedModel = "gemma3"; -// recommended models, shown with a star in model selector -const List recommendedModels = ["gemma3", "llama3.3"]; -// allow opening of settings -const bool allowSettings = true; -// allow multiple chats -const bool allowMultipleChats = true; + +/// the model used when [useModel] is true; not validated! +const String fixedModel = "gemma3:latest"; // client configuration end Completer prefsReady = Completer(); SharedPreferencesWithCache? prefs; -String? host; - -bool chatAllowed = true; -String hoveredChat = ""; - -final user = types.User(id: const Uuid().v4()); -final assistant = types.User(id: const Uuid().v4()); - -bool settingsOpen = false; -bool desktopTitleVisible = true; -bool logoVisible = true; -bool menuVisible = false; -bool sendable = false; -bool updateDetectedOnStart = false; -double sidebarIconSize = 1; - SpeechToText speech = SpeechToText(); FlutterTts voice = FlutterTts(); bool voiceSupported = false; -BuildContext? mainContext; -void Function(void Function())? setGlobalState; -void Function(void Function())? setMainAppState; +Color adaptedSurfaceFromColorScheme(ColorScheme cs) => cs.surface; +Color adaptedOnSurfaceFromColorScheme(ColorScheme cs) => cs.surfaceContainerLow; -void main() { +const kDisabledOpacity = 0.38; + +const kImageLogo = AssetImage("assets/logo512.png"); +const kImageLogoError = AssetImage("assets/logo512error.png"); + +final uuidRegex = RegExp( + r"^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$", + caseSensitive: false, +); + +@AutoRouterConfig() +class AppRouter extends RootStackRouter { + @override + RouteType get defaultRouteType => const RouteType.material(); + + @override + List get routes => [ + AutoRoute( + page: RouteMain.page, + path: "/", + children: [ + AutoRoute(page: RouteNewChat.page, path: ""), + AutoRoute(page: RouteChat.page, path: "c/:id"), + + AutoRoute( + page: RouteSettingsShell.page, + path: "settings", + children: [ + AutoRoute(page: RouteSettings.page, path: "", initial: true), + AutoRoute(page: RouteSettingsOverview.page, path: "overview"), + + AutoRoute(page: RouteSettingsBehavior.page, path: "behavior"), + AutoRoute(page: RouteSettingsInterface.page, path: "interface"), + AutoRoute(page: RouteSettingsVoice.page, path: "voice"), + AutoRoute(page: RouteSettingsExport.page, path: "export"), + AutoRoute(page: RouteSettingsAbout.page, path: "about"), + ], + ), + ], + ), + RedirectRoute(path: "*", redirectTo: "/"), + ]; +} + +void main() async { + WidgetsFlutterBinding.ensureInitialized(); + usePathUrlStrategy(); + + await initializeDateFormatting(null, null); pwa.PWAInstall().setup(); try { HttpOverrides.global = OllamaHttpOverrides(); } catch (_) {} + SharedPreferences.setPrefix("ollama."); + await migrateLegacySharedPreferencesToSharedPreferencesAsyncIfNecessary( + legacySharedPreferencesInstance: await SharedPreferences.getInstance(), + sharedPreferencesAsyncOptions: const SharedPreferencesOptions(), + migrationCompletedKey: "migrationCompleted", + ); + prefs = await SharedPreferencesWithCache.create( + cacheOptions: const SharedPreferencesWithCacheOptions(), + ); + prefsReady.complete(); + Preferences.instance; + + chatDb = ChatDatabase(); + await ChatManager.instance.loadChats(); + runApp(const App()); if (LayoutFeature.desktop()) { @@ -96,6 +138,9 @@ class App extends StatefulWidget { } class _AppState extends State { + final _appRouter = AppRouter(); + Chat? _currentChat; + @override void initState() { super.initState(); @@ -104,17 +149,6 @@ class _AppState extends State { // FlutterDisplayMode.setHighRefreshRate().catchError((_) {}); Future load() async { - SharedPreferences.setPrefix("ollama."); - await migrateLegacySharedPreferencesToSharedPreferencesAsyncIfNecessary( - legacySharedPreferencesInstance: await SharedPreferences.getInstance(), - sharedPreferencesAsyncOptions: const SharedPreferencesOptions(), - migrationCompletedKey: "migrationCompleted", - ); - prefs = await SharedPreferencesWithCache.create( - cacheOptions: const SharedPreferencesWithCacheOptions(), - ); - prefsReady.complete(); - try { if ((await Permission.bluetoothConnect.isGranted) && (await Permission.microphone.isGranted)) { @@ -130,6 +164,34 @@ class _AppState extends State { } load(); + + ChatManager.instance.addListener(onChatUpdate); + WidgetsBinding.instance.addPostFrameCallback((_) { + precacheImage(kImageLogo, context); + precacheImage(kImageLogoError, context); + if (mounted) setState(() {}); + }); + } + + @override + void dispose() { + ChatManager.instance.removeListener(onChatUpdate); + super.dispose(); + } + + void onUpdate() { + if (mounted) setState(() {}); + } + + void onChatUpdate() { + if (ChatManager.instance.currentChat != _currentChat) { + _currentChat?.removeListener(onUpdate); + _currentChat = ChatManager.instance.currentChat; + _currentChat?.addListener(onUpdate); + WidgetsBinding.instance.addPostFrameCallback((_) { + if (mounted) setState(() {}); + }); + } } @override @@ -142,15 +204,38 @@ class _AppState extends State { dynamicDark: dynamicDark, ), builder: (themeMode, themeLight, themeDark) { - return MaterialApp( + return MaterialApp.router( localizationsDelegates: AppLocalizations.localizationsDelegates, supportedLocales: AppLocalizations.supportedLocales, - onGenerateTitle: (context) => - AppLocalizations.of(context).appTitle, + onGenerateTitle: (context) { + final currentId = _appRouter.currentPath + .split("/") + .where(uuidRegex.hasMatch) + .lastOrNull; + final currentTitle = currentId != null + ? ChatManager.instance.chats + .firstOrNullWhere((chat) => chat.id == currentId) + ?.title ?? + AppLocalizations.of(context).newChatTitle + : null; + + return AppLocalizations.of(context).appTitle(switch (kIsWeb + ? "web" + : Platform.operatingSystem) { + "android" || "ios" => "short", + "web" when (currentTitle != null) => "integrated", + _ => "other", + }, currentTitle ?? ""); + }, theme: themeLight, darkTheme: themeDark, themeMode: themeMode, - home: const ScreenMain(), + routerConfig: _appRouter.config( + navigatorObservers: () => [ + GlobalNavigationObserver(), + HeroController(), + ], + ), ); }, ); @@ -158,3 +243,30 @@ class _AppState extends State { ); } } + +class GlobalNavigationObserver extends NavigatorObserver { + static String? _path; + static String? get path => _path; + + static final StreamController _routerStream = + StreamController.broadcast(); + static Stream get routerStream => _routerStream.stream; + + @override + void didChangeTop(Route topRoute, Route? previousTopRoute) { + super.didChangeTop(topRoute, previousTopRoute); + + var node = topRoute.data; + var path = node?.path ?? ""; + while (node?.parent != null) { + node = node!.parent; + if (node?.path != null && node!.path.isNotEmpty) { + path = "${node.path}/$path"; + } + } + path = "/${path.replaceAll(RegExp(r"(^/+)|(/+$)"), "")}"; + + _path = path; + _routerStream.add(null); + } +} diff --git a/lib/main.gr.dart b/lib/main.gr.dart new file mode 100644 index 0000000..83b8fb9 --- /dev/null +++ b/lib/main.gr.dart @@ -0,0 +1,224 @@ +// dart format width=80 +// GENERATED CODE - DO NOT MODIFY BY HAND + +// ************************************************************************** +// AutoRouterGenerator +// ************************************************************************** + +// ignore_for_file: type=lint +// coverage:ignore-file + +// ignore_for_file: no_leading_underscores_for_library_prefixes +import 'package:auto_route/auto_route.dart' as _i4; +import 'package:flutter/material.dart' as _i5; +import 'package:ollama_app/screens/chat.dart' as _i1; +import 'package:ollama_app/screens/main.dart' as _i2; +import 'package:ollama_app/screens/settings.dart' as _i3; + +/// generated route for +/// [_i1.ScreenChat] +class RouteChat extends _i4.PageRouteInfo { + RouteChat({_i5.Key? key, String? chatId, List<_i4.PageRouteInfo>? children}) + : super( + RouteChat.name, + args: RouteChatArgs(key: key, chatId: chatId), + rawPathParams: {'id': chatId}, + initialChildren: children, + ); + + static const String name = 'RouteChat'; + + static _i4.PageInfo page = _i4.PageInfo( + name, + builder: (data) { + final pathParams = data.inheritedPathParams; + final args = data.argsAs( + orElse: () => RouteChatArgs(chatId: pathParams.optString('id')), + ); + return _i1.ScreenChat(key: args.key, chatId: args.chatId); + }, + ); +} + +class RouteChatArgs { + const RouteChatArgs({this.key, this.chatId}); + + final _i5.Key? key; + + final String? chatId; + + @override + String toString() { + return 'RouteChatArgs{key: $key, chatId: $chatId}'; + } + + @override + bool operator ==(Object other) { + if (identical(this, other)) return true; + if (other is! RouteChatArgs) return false; + return key == other.key && chatId == other.chatId; + } + + @override + int get hashCode => key.hashCode ^ chatId.hashCode; +} + +/// generated route for +/// [_i2.ScreenMain] +class RouteMain extends _i4.PageRouteInfo { + const RouteMain({List<_i4.PageRouteInfo>? children}) + : super(RouteMain.name, initialChildren: children); + + static const String name = 'RouteMain'; + + static _i4.PageInfo page = _i4.PageInfo( + name, + builder: (data) { + return const _i2.ScreenMain(); + }, + ); +} + +/// generated route for +/// [_i1.ScreenNewChat] +class RouteNewChat extends _i4.PageRouteInfo { + const RouteNewChat({List<_i4.PageRouteInfo>? children}) + : super(RouteNewChat.name, initialChildren: children); + + static const String name = 'RouteNewChat'; + + static _i4.PageInfo page = _i4.PageInfo( + name, + builder: (data) { + return const _i1.ScreenNewChat(); + }, + ); +} + +/// generated route for +/// [_i3.ScreenSettings] +class RouteSettings extends _i4.PageRouteInfo { + const RouteSettings({List<_i4.PageRouteInfo>? children}) + : super(RouteSettings.name, initialChildren: children); + + static const String name = 'RouteSettings'; + + static _i4.PageInfo page = _i4.PageInfo( + name, + builder: (data) { + return const _i3.ScreenSettings(); + }, + ); +} + +/// generated route for +/// [_i3.ScreenSettingsAbout] +class RouteSettingsAbout extends _i4.PageRouteInfo { + const RouteSettingsAbout({List<_i4.PageRouteInfo>? children}) + : super(RouteSettingsAbout.name, initialChildren: children); + + static const String name = 'RouteSettingsAbout'; + + static _i4.PageInfo page = _i4.PageInfo( + name, + builder: (data) { + return const _i3.ScreenSettingsAbout(); + }, + ); +} + +/// generated route for +/// [_i3.ScreenSettingsBehavior] +class RouteSettingsBehavior extends _i4.PageRouteInfo { + const RouteSettingsBehavior({List<_i4.PageRouteInfo>? children}) + : super(RouteSettingsBehavior.name, initialChildren: children); + + static const String name = 'RouteSettingsBehavior'; + + static _i4.PageInfo page = _i4.PageInfo( + name, + builder: (data) { + return const _i3.ScreenSettingsBehavior(); + }, + ); +} + +/// generated route for +/// [_i3.ScreenSettingsExport] +class RouteSettingsExport extends _i4.PageRouteInfo { + const RouteSettingsExport({List<_i4.PageRouteInfo>? children}) + : super(RouteSettingsExport.name, initialChildren: children); + + static const String name = 'RouteSettingsExport'; + + static _i4.PageInfo page = _i4.PageInfo( + name, + builder: (data) { + return const _i3.ScreenSettingsExport(); + }, + ); +} + +/// generated route for +/// [_i3.ScreenSettingsInterface] +class RouteSettingsInterface extends _i4.PageRouteInfo { + const RouteSettingsInterface({List<_i4.PageRouteInfo>? children}) + : super(RouteSettingsInterface.name, initialChildren: children); + + static const String name = 'RouteSettingsInterface'; + + static _i4.PageInfo page = _i4.PageInfo( + name, + builder: (data) { + return const _i3.ScreenSettingsInterface(); + }, + ); +} + +/// generated route for +/// [_i3.ScreenSettingsOverview] +class RouteSettingsOverview extends _i4.PageRouteInfo { + const RouteSettingsOverview({List<_i4.PageRouteInfo>? children}) + : super(RouteSettingsOverview.name, initialChildren: children); + + static const String name = 'RouteSettingsOverview'; + + static _i4.PageInfo page = _i4.PageInfo( + name, + builder: (data) { + return const _i3.ScreenSettingsOverview(); + }, + ); +} + +/// generated route for +/// [_i3.ScreenSettingsShell] +class RouteSettingsShell extends _i4.PageRouteInfo { + const RouteSettingsShell({List<_i4.PageRouteInfo>? children}) + : super(RouteSettingsShell.name, initialChildren: children); + + static const String name = 'RouteSettingsShell'; + + static _i4.PageInfo page = _i4.PageInfo( + name, + builder: (data) { + return const _i3.ScreenSettingsShell(); + }, + ); +} + +/// generated route for +/// [_i3.ScreenSettingsVoice] +class RouteSettingsVoice extends _i4.PageRouteInfo { + const RouteSettingsVoice({List<_i4.PageRouteInfo>? children}) + : super(RouteSettingsVoice.name, initialChildren: children); + + static const String name = 'RouteSettingsVoice'; + + static _i4.PageInfo page = _i4.PageInfo( + name, + builder: (data) { + return const _i3.ScreenSettingsVoice(); + }, + ); +} diff --git a/lib/screens/chat.dart b/lib/screens/chat.dart new file mode 100644 index 0000000..d3c5c3c --- /dev/null +++ b/lib/screens/chat.dart @@ -0,0 +1,520 @@ +import 'dart:convert'; +import 'dart:math' as math; +import 'dart:ui'; + +import 'package:auto_route/auto_route.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; +import 'package:ollama_dart/ollama_dart.dart' as llama; +import 'package:visibility_detector/visibility_detector.dart'; + +import '../l10n/gen/app_localizations.dart'; +import '../main.dart'; +import '../main.gr.dart'; +import '../services/services.dart'; +import '../widgets/two_state_widget.dart'; + +@RoutePage() +class ScreenNewChat extends StatefulWidget { + const ScreenNewChat({super.key}); + + @override + State createState() => _ScreenNewChatState(); +} + +class _ScreenNewChatState extends State { + late int greetingId; + + @override + void initState() { + super.initState(); + ChatManager.instance.addListener(onUpdate); + shuffleGreeting(); + } + + @override + void dispose() { + ChatManager.instance.removeListener(onUpdate); + super.dispose(); + } + + void onUpdate() { + if (ChatManager.instance.currentChatId != null) { + context.router.navigate( + RouteChat(chatId: ChatManager.instance.currentChatId!), + ); + } + } + + void shuffleGreeting() => greetingId = math.Random().nextInt(5) + 1; + + @override + Widget build(BuildContext context) { + final textTheme = TextTheme.of(context); + + final breakpoint = Breakpoint.of(context); + final embeddedNavigation = breakpoint.panesRecommended >= 2; + + final appLocalizations = AppLocalizations.of(context); + final user = + Preferences.instance.user ?? + appLocalizations.newChatGreetingNameFallback; + final greeting = switch (greetingId) { + 1 => appLocalizations.newChatGreeting1(user), + 2 => appLocalizations.newChatGreeting2(user), + 3 => appLocalizations.newChatGreeting3(user), + 4 => appLocalizations.newChatGreeting4(user), + _ => appLocalizations.newChatGreeting5(user), + }; + + return _ChatFrame( + placeSearchBar: !embeddedNavigation, + builder: (context, safeAreaPadding, input) => Padding( + padding: safeAreaPadding, + child: VisibilityDetector( + key: const Key("newChatGreeting"), + onVisibilityChanged: (i) { + if (i.visibleFraction <= 0) { + shuffleGreeting(); + if (mounted) setState(() {}); + } + }, + child: Center( + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + Padding( + padding: const EdgeInsets.symmetric(horizontal: 32), + child: Text( + greeting, + style: textTheme.titleLarge, + textAlign: TextAlign.center, + ), + ), + if (input != null) ...[const SizedBox(height: 12), input], + ], + ), + ), + ), + ), + ); + } +} + +@RoutePage() +class ScreenChat extends StatefulWidget { + final String? chatId; + const ScreenChat({super.key, @PathParam("id") this.chatId}); + + @override + State createState() => _ScreenChatState(); +} + +class _ScreenChatState extends State { + Chat? _chat; + + @override + void initState() { + super.initState(); + ChatManager.instance.addListener(onUpdate); + checkChatId(); + } + + @override + void dispose() { + ChatManager.instance.removeListener(onUpdate); + _chat?.removeListener(onUpdate); + super.dispose(); + } + + void onUpdate() { + if (ChatManager.instance.currentChatId != widget.chatId) { + context.router.navigate( + ChatManager.instance.currentChatId != null + ? RouteChat(chatId: ChatManager.instance.currentChatId!) + : const RouteNewChat(), + ); + } + if (mounted) setState(() {}); + } + + @override + void didUpdateWidget(covariant ScreenChat oldWidget) { + super.didUpdateWidget(oldWidget); + checkChatId(); + } + + void checkChatId() { + if ((widget.chatId?.trim() ?? "").isEmpty || + !uuidRegex.hasMatch(widget.chatId!) || + !ChatManager.instance.chats.any((chat) => chat.id == widget.chatId)) { + context.router.navigate(const RouteNewChat()); + } else if (widget.chatId != ChatManager.instance.currentChatId) { + ChatManager.instance.currentChatId = widget.chatId!; + } + + _chat?.removeListener(onUpdate); + _chat = ChatManager.instance.chats + .where((chat) => chat.id == widget.chatId) + .first; + _chat?.addListener(onUpdate); + } + + @override + Widget build(BuildContext context) { + final colorScheme = ColorScheme.of(context); + final adaptedOnSurface = adaptedOnSurfaceFromColorScheme(colorScheme); + + return _ChatFrame( + builder: (context, safeAreaPadding, input) { + final msg = _chat?.messages + .where((m) => m.sender == .assistant) + .whereType() + .lastOrNull; + return Text( + "${msg?.thinking ?? ""}\n\n${msg?.content ?? ""}\n\n${msg?.stats?.toJson() ?? ""}", + ); + }, + ); + } +} + +class _ChatFrame extends StatelessWidget { + final Widget Function( + BuildContext context, + EdgeInsetsGeometry safeAreaPadding, + Widget? input, + ) + builder; + final bool placeSearchBar; + const _ChatFrame({required this.builder, this.placeSearchBar = true}); + + @override + Widget build(BuildContext context) { + final breakpoint = Breakpoint.of(context); + final halfSpacing = breakpoint.spacing / 2; + + final input = Hero( + tag: "chatScreenInput", + child: Align( + alignment: AlignmentGeometry.bottomCenter, + child: AnimatedPadding( + duration: ExpressiveCurves.standardSpatial.fastDuration, + curve: ExpressiveCurves.standardEffects.fast, + padding: breakpoint.panesRecommended >= 2 + ? EdgeInsetsGeometry.only( + left: halfSpacing, + right: halfSpacing, + bottom: halfSpacing, + ) + : EdgeInsetsGeometry.only(bottom: breakpoint.spacing), + child: const _ChatInput(), + ), + ), + ); + + final child = builder.call( + context, + EdgeInsetsGeometry.zero, + placeSearchBar ? null : input, + ); + final stack = Stack(children: [child, if (placeSearchBar) input]); + return Center( + child: ConstrainedBox( + constraints: BoxConstraints(maxWidth: 720 - halfSpacing), + child: SizedBox.expand(child: stack), + ), + ); + } +} + +class _ChatInput extends StatefulWidget { + const _ChatInput(); + + @override + State<_ChatInput> createState() => _ChatInputState(); +} + +class _ChatInputState extends State<_ChatInput> { + late final TextEditingController _controller = TextEditingController(); + late final FocusNode _focusNode = FocusNode( + onKeyEvent: (_, event) { + if (!HardwareKeyboard.instance.isShiftPressed && + event.logicalKey == LogicalKeyboardKey.enter) { + if (event is KeyDownEvent) _submit(); + return KeyEventResult.handled; + } else { + return KeyEventResult.ignored; + } + }, + ); + + Chat? _chat; + + @override + void initState() { + super.initState(); + ModelManager.instance.addListener(onUpdate); + ChatManager.instance.addListener(onChatUpdate); + onChatUpdate(); + } + + @override + void dispose() { + _controller.dispose(); + _focusNode.dispose(); + ModelManager.instance.removeListener(onUpdate); + ChatManager.instance.removeListener(onChatUpdate); + super.dispose(); + } + + void onUpdate() { + if (mounted) setState(() {}); + } + + void onChatUpdate() { + final chat = ChatManager.instance.currentChat; + if (chat != _chat) { + _chat?.removeListener(onUpdate); + _chat = chat; + _chat?.addListener(onUpdate); + } + WidgetsBinding.instance.addPostFrameCallback((_) { + if (mounted) setState(() {}); + }); + } + + Future _submit() async { + final text = _controller.text.trim(); + if (text.isEmpty) return; + + var chat = ChatManager.instance.currentChat; + final isNewChat = chat == null; + if (isNewChat) { + chat = ChatManager.instance.createChat(context: context); + context.router.navigate(RouteChat(chatId: chat.id)); + } + + _controller.clear(); + + await errorGuard( + context, + "N89P97ND", + () async => chat!.send(TextMessage(text, sender: .user)), + errorMessage: errorGuardErrorMessageWithFallbackSingle( + llama.OllamaException, + "Unable to send message", + ), + enableReporting: false, + ); + + if (!mounted || !chat.alive || !isNewChat) return; + await errorGuard( + context, + "A02A05PB", + () async => chat!.generateTitle(context: context, think: true), + errorMessage: errorGuardErrorMessageWithFallbackSingle( + llama.OllamaException, + "Unable to generate chat title", + ), + ); + } + + @override + Widget build(BuildContext context) { + final colorScheme = ColorScheme.of(context); + final textTheme = TextTheme.of(context); + final appLocalizations = AppLocalizations.of(context); + + final adaptiveOnSurface = adaptedOnSurfaceFromColorScheme(colorScheme); + + final enabled = ModelManager.instance.currentModel != null; + final opacity = enabled ? 1.0 : kDisabledOpacity; + + Widget enabledWidget({required Widget child}) => IgnorePointer( + ignoring: !enabled, + child: Opacity(opacity: opacity, child: child), + ); + + Padding leadingTrailingPaddingMap(e) => Padding( + padding: const EdgeInsetsGeometry.symmetric(vertical: 8), + child: e, + ); + final leading = [ + enabledWidget( + child: IconButton( + onPressed: () {}, + icon: const Icon(Icons.add_rounded), + ), + ), + ]; + final trailing = [ + TwoStateWidgetManaged( + state: _chat?.completer.isCompleted ?? true, + from: IconButton.outlined( + onPressed: () => _chat?.completer.complete(), + icon: const Icon(Icons.stop_rounded), + ), + to: enabledWidget( + child: IconButton.filled( + onPressed: () {}, + icon: const Icon(Icons.arrow_upward_rounded), + ), + ), + ), + ]; + + return ConstrainedBox( + constraints: const BoxConstraints(minHeight: 56.0), + child: Material( + shadowColor: colorScheme.shadow, + elevation: 1, + shape: RoundedRectangleBorder( + side: BorderSide(color: adaptiveOnSurface, width: 1), + borderRadius: const BorderRadius.all(Radius.circular(56 / 2)), + ), + child: InkWell( + onTap: enabled + ? () { + if (!_focusNode.hasFocus) _focusNode.requestFocus(); + } + : null, + overlayColor: const WidgetStatePropertyAll(Colors.transparent), + customBorder: const RoundedRectangleBorder( + borderRadius: BorderRadius.all(Radius.circular(56 / 2)), + ), + mouseCursor: enabled + ? SystemMouseCursors.text + : SystemMouseCursors.basic, + child: Padding( + padding: const EdgeInsetsGeometry.symmetric(horizontal: 8.0), + child: Row( + crossAxisAlignment: CrossAxisAlignment.end, + children: [ + ...leading.map(leadingTrailingPaddingMap), + Expanded( + child: Padding( + padding: const EdgeInsetsGeometry.symmetric( + horizontal: 8.0, + vertical: (56 - 24) / 2, + ), + child: Semantics( + inputType: SemanticsInputType.text, + child: Opacity( + opacity: opacity, + child: TextField( + autofocus: true, + onTapAlwaysCalled: true, + focusNode: _focusNode, + onSubmitted: (value) => _submit(), + controller: _controller, + style: textTheme.bodyLarge?.copyWith( + color: colorScheme.onSurface, + ), + enabled: enabled, + minLines: 1, + maxLines: 9, + decoration: + InputDecoration( + hintText: appLocalizations.messageInputPlaceholder( + Model.nameColoredStatic( + name: ModelManager + .instance + .currentModelName, + context: context, + )?.first.text ?? + appLocalizations + .messageInputPlaceholderModelPlaceholder, + ), + ).applyDefaults( + InputDecorationThemeData( + hintStyle: textTheme.bodyLarge?.copyWith( + color: colorScheme.onSurfaceVariant, + ), + enabledBorder: InputBorder.none, + border: InputBorder.none, + focusedBorder: InputBorder.none, + contentPadding: EdgeInsets.zero, + isDense: true, + ), + ), + textInputAction: TextInputAction.newline, + keyboardType: TextInputType.multiline, + ), + ), + ), + ), + ), + ...trailing.map(leadingTrailingPaddingMap), + ], + ), + ), + ), + ), + ); + } +} + +class ChatDetails extends StatelessWidget { + final VoidCallback? onClose; + final EdgeInsetsGeometry padding; + const ChatDetails({ + super.key, + this.onClose, + this.padding = const EdgeInsetsGeometry.directional( + start: 24, + end: 16, + top: 16, + bottom: 24, + ), + }); + + @override + Widget build(BuildContext context) { + final colorScheme = ColorScheme.of(context); + final appLocalizations = AppLocalizations.of(context); + + final currentChat = ChatManager.instance.currentChat; + + return Padding( + padding: padding, + child: Column( + mainAxisSize: MainAxisSize.max, + mainAxisAlignment: MainAxisAlignment.start, + children: [ + Row( + mainAxisSize: MainAxisSize.max, + children: [ + Expanded( + child: Text( + appLocalizations.optionChatDetails, + style: TextStyle( + color: colorScheme.onSurfaceVariant, + fontSize: 22, + ), + ), + ), + const SizedBox(width: 12), + IconButton(onPressed: onClose, icon: const Icon(Icons.close)), + ], + ), + const SizedBox(height: 16), + Padding( + padding: const EdgeInsetsGeometry.directional(end: 8), + child: SizedBox( + width: double.infinity, + // TODO: implement proper details view + child: SingleChildScrollView( + child: Text( + currentChat != null + ? "${currentChat.title}\n\nstats:\n${JsonEncoder.withIndent(" " * 2).convert(currentChat.messages.whereType().lastOrNull?.stats?.toJson() ?? {})}" + : appLocalizations.optionChatDetailsNoChat, + ), + ), + ), + ), + ], + ), + ); + } +} diff --git a/lib/screens/main.dart b/lib/screens/main.dart index 9e54c5d..893966a 100644 --- a/lib/screens/main.dart +++ b/lib/screens/main.dart @@ -1,6 +1,18 @@ -import 'package:flutter/material.dart'; -import 'package:ollama_dart/ollama_dart.dart'; -import 'package:scroll_to_index/scroll_to_index.dart'; +import 'dart:async'; +import 'dart:ui'; + +import 'package:auto_route/auto_route.dart'; +import 'package:flutter/material.dart' hide RouteSettings; +import 'package:ollama_dart/ollama_dart.dart' as llama; + +import '../l10n/gen/app_localizations.dart'; +import '../main.dart'; +import '../main.gr.dart'; +import '../services/services.dart'; +import '../widgets/model_selector.dart'; +import '../widgets/two_state_widget.dart'; +import 'chat.dart'; +import 'settings.dart'; // import 'package:flutter_chat_types/flutter_chat_types.dart' as types; // import 'package:flutter_chat_ui/flutter_chat_ui.dart' as chat_ui; @@ -15,12 +27,6 @@ import 'package:scroll_to_index/scroll_to_index.dart'; // import '../l10n/gen/app_localizations.dart'; // import '../main.dart'; -import '../l10n/gen/app_localizations.dart'; -import '../main.dart'; -import '../services/chat.dart'; -import '../services/error.dart'; -import '../services/markdown.dart'; -import '../services/model.dart'; // import '../services/model.dart'; // import '../services/preferences.dart'; // import '../worker/desktop.dart'; @@ -1770,6 +1776,7 @@ import '../services/model.dart'; // } // } +@RoutePage() class ScreenMain extends StatefulWidget { const ScreenMain({super.key}); @@ -1777,42 +1784,64 @@ class ScreenMain extends StatefulWidget { State createState() => _ScreenMainState(); } -class _ScreenMainState extends State { +class _ScreenMainState extends State + with SingleTickerProviderStateMixin { + bool _thirdPaneOpen = false; + + bool isSettingsRoute = false; + late final bool startedAtSettingsRoute; + PageController? _sidebarController; + + final FocusNode _searchBarFocusNode = FocusNode(); + final TextEditingController _searchBarController = TextEditingController(); + + late final AnimationController _searchBarFocusController; + late final CurvedAnimation _searchBarFocusCurved; + + final _modelSelectorKey = GlobalKey(); + @override void initState() { super.initState(); + setIsSettingsRoute(); + startedAtSettingsRoute = isSettingsRoute; - host = "https://raspimainollama.tunler.net"; ChatManager.instance.addListener(onUpdate); ModelManager.instance.addListener(onUpdate); + HostManager.instance.addListener(onUpdate); - prefsReady.future.then((_) async { - if (!mounted) return; + if (HostManager.instance.host != null) { errorGuard( context, "Q3L4Z1X6", - () async { - await ModelManager.instance.loadModels(); - - if (!mounted) return; - ScaffoldMessenger.of(context).showSnackBar( - SnackBar( - content: Text("Models: ${ModelManager.instance.models.length}"), - ), - ); - }, + () async => ModelManager.instance.loadModels(), errorMessage: errorGuardErrorMessageWithFallbackSingle( - OllamaClientException, + llama.OllamaException, "Unable to load models", ), enableReporting: false, ); + } - await ChatManager.instance.loadChats(); + _searchBarFocusController = AnimationController( + vsync: this, + duration: ExpressiveCurves.expressiveSpatial.fastDuration, + reverseDuration: ExpressiveCurves.expressiveSpatial.fastDuration, + ); + _searchBarFocusCurved = CurvedAnimation( + parent: _searchBarFocusController, + curve: ExpressiveCurves.expressiveSpatial.fast, + reverseCurve: ExpressiveCurves.expressiveSpatial.fast.flipped, + ); + + _searchBarFocusNode.addListener(() { if (!mounted) return; - // ScaffoldMessenger.of(context).showSnackBar( - // SnackBar(content: Text("Chats: ${ChatManager.instance.chats.length}")), - // ); + if (_searchBarFocusNode.hasFocus) { + _searchBarFocusController.forward(); + } else { + _searchBarFocusController.reverse(); + } + setState(() {}); }); } @@ -1820,121 +1849,779 @@ class _ScreenMainState extends State { void dispose() { ChatManager.instance.removeListener(onUpdate); ModelManager.instance.removeListener(onUpdate); + HostManager.instance.removeListener(onUpdate); + + _searchBarFocusNode.dispose(); + _searchBarController.dispose(); + _searchBarFocusCurved.dispose(); + _searchBarFocusController.dispose(); + _sidebarController?.dispose(); + super.dispose(); } void onUpdate() { - if (mounted) setState(() {}); + WidgetsBinding.instance.addPostFrameCallback((_) { + if (mounted) setState(() {}); + }); } - bool test = false; - double testSlider = 0.5; + void setIsSettingsRoute() { + isSettingsRoute = context.router.currentPath.contains("/settings"); + + var page = isSettingsRoute ? 1 : 0; + if (_sidebarController != null && _sidebarController!.hasClients) { + if (startedAtSettingsRoute) page = page == 0 ? 1 : 0; + _sidebarController!.animateToPage( + page, + duration: ExpressiveCurves.expressiveEffects.slowDuration, + curve: ExpressiveCurves.expressiveEffects.slow, + ); + } else { + _sidebarController = PageController(initialPage: 0); + } + } @override Widget build(BuildContext context) { - return Scaffold( - body: ListView( - children: [ - ...ChatManager.instance.chats.map( - (chat) => ScreenMainChatTile(chat: chat), + final theme = Theme.of(context); + final colorScheme = theme.colorScheme; + final textTheme = theme.textTheme; + final appLocalizations = AppLocalizations.of(context); + + final adaptedSurface = adaptedSurfaceFromColorScheme(colorScheme); + final adaptedOnSurface = adaptedOnSurfaceFromColorScheme(colorScheme); + final breakpoint = Breakpoint.of(context); + + final chatsSearched = ChatManager.instance.chats.where((chat) { + final searchTerm = _searchBarController.text.toLowerCase(); + return chat.title.toLowerCase().contains(searchTerm) || + chat.messages.any( + (message) => + message is TextMessage && + message.content.toLowerCase().contains(searchTerm), + ); + }).toList(); + + if (breakpoint != Breakpoint.extraLarge) _thirdPaneOpen = false; + final embeddedNavigation = breakpoint.panesRecommended >= 2; + + final navigationRail = Transform.translate( + offset: const Offset(0, -8), + child: _ScreenMainNavigationBarWide( + adaptedSurface: adaptedSurface, + isSettingsRoute: isSettingsRoute, + onDestinationSelected: () { + setIsSettingsRoute(); + if (mounted) setState(() {}); + }, + ), + ); + + final children = [ + AnimatedSwitcher( + duration: ExpressiveCurves.expressiveSpatial.normalDuration, + switchInCurve: ExpressiveCurves.expressiveSpatial.normal, + switchOutCurve: ExpressiveCurves.expressiveSpatial.normal.flipped, + transitionBuilder: (child, animation) => SizeTransition( + sizeFactor: animation, + axis: Axis.horizontal, + alignment: const AlignmentDirectional(0.75, 0.5), + child: child, + ), + child: embeddedNavigation + ? navigationRail + : SizedBox(width: breakpoint.spacing), + ), + AnimatedContainer( + duration: ExpressiveCurves.standardSpatial.fastDuration, + curve: ExpressiveCurves.standardEffects.fast, + padding: breakpoint.panesRecommended >= 2 + ? EdgeInsetsDirectional.only(end: breakpoint.spacing) + : EdgeInsetsDirectional.zero, + child: AnimatedSwitcher( + duration: ExpressiveCurves.expressiveSpatial.normalDuration, + switchInCurve: ExpressiveCurves.expressiveSpatial.normal, + switchOutCurve: ExpressiveCurves.expressiveSpatial.normal.flipped, + transitionBuilder: (child, animation) => SizeTransition( + sizeFactor: animation, + axis: Axis.horizontal, + alignment: const AlignmentDirectional(0.25, 0.5), + child: child, ), - ListTile( - title: const Text("Add chat"), - onTap: () async { - if (ModelManager.instance.models.isEmpty) return; - var chat = ChatManager.instance.createChat( - context: context, - model: ModelManager.instance.models.first, - ); + child: breakpoint.panesRecommended >= 2 + ? Builder( + builder: (context) { + final children = [ + ClipRRect( + borderRadius: const BorderRadius.vertical( + top: Radius.circular(56 * (2 / 3)), + ), + child: chatsSearched.isEmpty + ? Padding( + padding: EdgeInsetsGeometry.only( + top: 64, + bottom: breakpoint.spacing, + ), + child: Center( + child: Padding( + padding: const EdgeInsets.all(24), + child: Text( + _searchBarController.text.isEmpty + ? appLocalizations.optionNoChatFound + : appLocalizations + .optionNoChatFoundSearch( + _searchBarController.text, + ), + style: textTheme.labelLarge!.copyWith( + color: colorScheme.outline, + ), + textAlign: TextAlign.center, + maxLines: 2, + overflow: TextOverflow.ellipsis, + ), + ), + ), + ) + : ListView.builder( + padding: EdgeInsetsGeometry.only( + top: 64, + bottom: breakpoint.spacing, + ), + itemBuilder: (context, index) { + final chat = chatsSearched.elementAtOrNull( + index, + ); + if (chat == null) return null; + return ListTile( + title: Text(chat.title), + trailing: IconButton( + onPressed: () => + ChatManager.instance.deleteChat(chat), + icon: const Icon(Icons.delete_outline), + ), + ); + }, + ), + ), + DecoratedBox( + decoration: BoxDecoration( + gradient: LinearGradient( + colors: [ + adaptedSurface, + adaptedSurface.withAlpha(0), + ], + begin: Alignment.topCenter, + end: Alignment.bottomCenter, + stops: const [0.25, 1.0], + ), + ), + child: const SizedBox( + width: double.infinity, + height: 56, + ), + ), + AnimatedBuilder( + animation: _searchBarFocusCurved, + builder: (context, child) => LayoutBuilder( + builder: (context, constraints) { + final padding = lerpDouble( + 12.0, + 0.0, + _searchBarFocusCurved.value, + )!; + return OverflowBox( + maxWidth: double.infinity, + alignment: AlignmentGeometry.topCenter, + child: SizedBox( + width: constraints.maxWidth - padding * 2, + child: child, + ), + ); + }, + ), + child: SearchBar( + hintText: appLocalizations.optionSearchChats, + leading: TwoStateWidget( + animation: _searchBarFocusCurved, + from: IconButton( + onPressed: null, + disabledColor: IconTheme.of(context).color, + icon: const Icon(Icons.search), + ), + to: IconButton( + onPressed: () { + _searchBarFocusNode.unfocus(); + onUpdate(); + }, + icon: const Icon(Icons.chevron_left), + ), + ), + elevation: WidgetStateProperty.all(0), + focusNode: _searchBarFocusNode, + controller: _searchBarController, + onChanged: (value) => onUpdate(), + ), + ), + ]; - await errorGuard( - context, - "M49WC9CW", - () async { - var msg = TextMessage( - "Write a long demo message for all GitHub Flavored Markdown features.", - sender: MessageSender.user, - ); - return chat.send(msg); - }, - errorMessage: errorGuardErrorMessageWithFallbackSingle( - OllamaClientException, - "Unable to send message", - ), - enableReporting: false, - ); + var pageViewChildren = [ + Stack(children: children), + const SettingsOptions(embeddedNavigation: true), + ]; + if (startedAtSettingsRoute) { + pageViewChildren = pageViewChildren.reversed.toList(); + } - if (!context.mounted || !chat.alive) return; - await errorGuard( - context, - "W97BM0DJ", - () async => chat.generateTitle(context: context), - errorMessage: errorGuardErrorMessageWithFallbackSingle( - OllamaClientException, - "Unable to generate chat title", - ), - ); + return SizedBox( + width: breakpoint.panesFixedWidth, + child: PageView( + controller: _sidebarController, + physics: const NeverScrollableScrollPhysics(), + scrollDirection: Axis.horizontal, + clipBehavior: Clip.antiAliasWithSaveLayer, + children: pageViewChildren, + ), + ); + }, + ) + : null, + ), + ), + Expanded( + child: Builder( + builder: (context) { + const radius = BorderRadius.vertical(top: Radius.circular(12)); + final border = BorderSide( + width: 2, + color: colorScheme.surfaceContainerHigh, + ); + + return AnimatedContainer( + duration: ExpressiveCurves.standardSpatial.fastDuration, + curve: ExpressiveCurves.standardEffects.fast, + decoration: breakpoint.panesRecommended >= 2 + ? BoxDecoration( + borderRadius: radius, + border: Border(top: border, left: border, right: border), + ) + : const BoxDecoration(), + child: const ClipRRect(borderRadius: radius, child: AutoRouter()), + ); + }, + ), + ), + AnimatedContainer( + duration: ExpressiveCurves.standardSpatial.fastDuration, + curve: ExpressiveCurves.standardEffects.fast, + padding: breakpoint.panes.contains(3) && _thirdPaneOpen + ? EdgeInsetsDirectional.only(start: breakpoint.spacing) + : EdgeInsetsDirectional.zero, + child: Card.filled( + margin: EdgeInsets.zero, + color: adaptedOnSurface, + child: AnimatedSwitcher( + duration: ExpressiveCurves.expressiveSpatial.normalDuration, + switchInCurve: ExpressiveCurves.expressiveSpatial.normal, + switchOutCurve: ExpressiveCurves.expressiveSpatial.normal.flipped, + transitionBuilder: (child, animation) => SizeTransition( + sizeFactor: animation, + axis: Axis.horizontal, + alignment: const AlignmentDirectional(0.25, 0.5), + child: child, + ), + child: breakpoint.panes.contains(3) && _thirdPaneOpen + ? SizedBox( + width: breakpoint.panesThirdFixedWidth, + child: ChatDetails( + onClose: () { + _thirdPaneOpen = false; + if (mounted) setState(() {}); + }, + ), + ) + : null, + ), + ), + ), + SizedBox(width: breakpoint.spacing), + ]; + + final modelSelectorAnimationDuration = + ExpressiveCurves.standardEffects.fastDuration; + final modelSelectorAnimationCurve = ExpressiveCurves.standardEffects.fast; + + final modelHostSelected = HostManager.instance.host != null; + final modelSelector = GestureDetector( + key: _modelSelectorKey, + onTap: !modelHostSelected + ? null + : () { + showModelSelector(context: context, anchorKey: _modelSelectorKey); + }, + child: Opacity( + opacity: !modelHostSelected ? kDisabledOpacity : 1.0, + child: Row( + mainAxisSize: MainAxisSize.min, + children: [ + Text.rich( + TextSpan( + children: + Model.nameColoredStatic( + name: ModelManager.instance.currentModelName, + context: context, + ) ?? + [TextSpan(text: appLocalizations.noSelectedModel)], + ), + style: textTheme.titleMedium!.copyWith( + fontFamily: "GoogleSansCode", + ), + ), + SizedBox(width: breakpoint.spacing / 8), + const Icon(Icons.keyboard_arrow_down), + ], + ), + ), + ); + final modelSelectorShowSubtitle = breakpoint.panesRecommended >= 2; + final modelSelectorOffset = modelSelectorShowSubtitle + ? 80.0 + + (breakpoint.panesFixedWidth ?? 0) + + breakpoint.spacing - + NavigationToolbar.kMiddleSpacing - + kToolbarHeight + : 0.0; + + return Scaffold( + backgroundColor: adaptedSurface, + endDrawer: ConstrainedBox( + constraints: const BoxConstraints(maxWidth: 400), + child: DecoratedBox( + decoration: BoxDecoration( + color: colorScheme.surfaceContainerLow, + borderRadius: const BorderRadiusDirectional.only( + topStart: Radius.circular(16), + bottomStart: Radius.circular(16), + ), + ), + child: ChatDetails( + onClose: () { + if (Navigator.of(context).canPop()) { + Navigator.of(context).pop(); + } }, ), - ], + ), ), + appBar: AppBar( + clipBehavior: Clip.none, + leading: TwoStateWidgetManaged( + state: breakpoint.panesRecommended >= 2, + dimension: kToolbarHeight, + from: const DrawerBackButton(), + to: Transform.translate( + offset: const Offset(12, 2), + child: const Padding( + padding: EdgeInsetsGeometry.only(top: 8), + child: ImageIcon(kImageLogo), + ), + ), + ), + actions: [ + TwoStateWidgetManaged( + state: isSettingsRoute, + from: SizedBox.square( + dimension: kToolbarHeight, + child: Builder( + builder: (context) => IconButton( + icon: const Icon(Icons.info_outline), + tooltip: appLocalizations.optionChatDetails, + onPressed: () { + if (breakpoint.panes.contains(3)) { + _thirdPaneOpen = !_thirdPaneOpen; + if (mounted) setState(() {}); + } else { + Scaffold.of(context).openEndDrawer(); + } + }, + ), + ), + ), + ), + ], + title: SizedBox( + width: double.infinity, + height: theme.appBarTheme.toolbarHeight ?? kToolbarHeight, + child: Stack( + alignment: AlignmentDirectional.center, + children: [ + AnimatedPositionedDirectional( + key: const ValueKey("modelSelectorSubtitle"), + duration: modelSelectorAnimationDuration, + curve: modelSelectorAnimationCurve, + start: modelSelectorShowSubtitle ? modelSelectorOffset : 64, + child: AnimatedSlide( + offset: Offset( + 0, + modelHostSelected || !modelSelectorShowSubtitle ? 0 : 0.7, + ), + duration: modelSelectorAnimationDuration, + curve: modelSelectorAnimationCurve, + child: IgnorePointer( + child: AnimatedOpacity( + opacity: modelHostSelected || !modelSelectorShowSubtitle + ? 0 + : 1, + duration: modelSelectorAnimationDuration, + curve: modelSelectorAnimationCurve, + child: Text( + appLocalizations.noHostSelected, + style: textTheme.labelSmall, + overflow: TextOverflow.ellipsis, + maxLines: 1, + softWrap: false, + ), + ), + ), + ), + ), + AnimatedPositionedDirectional( + key: const ValueKey("modelSelectorButton"), + duration: modelSelectorAnimationDuration, + curve: modelSelectorAnimationCurve, + start: modelSelectorShowSubtitle ? modelSelectorOffset : null, + child: AnimatedSlide( + offset: Offset( + 0, + modelHostSelected || !modelSelectorShowSubtitle ? 0 : -0.2, + ), + duration: modelSelectorAnimationDuration, + curve: modelSelectorAnimationCurve, + child: modelSelector, + ), + ), + ], + ), + ), + centerTitle: breakpoint.panesRecommended < 2, + backgroundColor: adaptedSurface, + scrolledUnderElevation: 0, + ), + body: Builder( + builder: (context) { + if (breakpoint == Breakpoint.extraLarge && + Scaffold.of(context).isEndDrawerOpen) { + _thirdPaneOpen = true; + WidgetsBinding.instance.addPostFrameCallback((_) { + if (!mounted) return; + setState(() {}); + Navigator.of(context).pop(); + }); + } + + return Row(mainAxisSize: MainAxisSize.max, children: children); + }, + ), + ); + + // return Scaffold( + // body: ListView( + // children: [ + // Padding( + // padding: const EdgeInsetsGeometry.only( + // top: 16, + // left: 16, + // right: 16, + // bottom: 8, + // ), + // child: Card.filled( + // margin: EdgeInsets.zero, + // clipBehavior: Clip.antiAlias, + // child: Column( + // mainAxisSize: MainAxisSize.min, + // children: [ + // ListTile( + // title: Text( + // HostManager.instance.host?.toString() ?? "", + // ), + // onTap: loadingModels + // ? null + // : () async { + // loadingModels = true; + // HostManager.instance.host = Uri.parse( + // HostManager.instance.host.toString() == + // "https://ollama.tunler.net" + // ? "https://raspimainollama.tunler.net" + // : "https://ollama.tunler.net", + // ); + // setState(() {}); + + // await loadModels(); + // }, + // dense: true, + // ), + // const Divider(height: 1), + // AnimatedSize( + // duration: Durations.medium1, + // curve: Curves.easeInOutCubic, + // child: ListTile( + // title: ModelManager.instance.models.isEmpty + // ? const Text("No models available") + // : Text.rich( + // TextSpan( + // children: [ + // const TextSpan( + // text: "Models: ", + // style: TextStyle( + // fontStyle: FontStyle.italic, + // ), + // ), + // for ( + // var i = 0; + // i < + // ModelManager.instance.models.length * + // 2 - + // 1; + // i++ + // ) + // if (i.isEven) + // TextSpan( + // text: ModelManager.instance.models + // .elementAt(i ~/ 2) + // .name, + // style: i ~/ 2 == modelIndex + // ? const TextStyle( + // fontWeight: FontWeight.bold, + // ) + // : null, + // ) + // else + // const TextSpan(text: ", "), + // ], + // ), + // ), + // onTap: + // loadingModels || ModelManager.instance.models.isEmpty + // ? null + // : () async { + // final currentIndex = modelIndex; + // if (currentIndex == null) return; + + // final nextIndex = + // (currentIndex + 1) % + // ModelManager.instance.models.length; + // modelIndex = nextIndex; + // ModelManager.instance.currentModel = ModelManager + // .instance + // .models + // .elementAt(nextIndex); + // setState(() {}); + // }, + // dense: true, + // ), + // ), + // ], + // ), + // ), + // ), + + // ...ChatManager.instance.chats.map( + // (chat) => ScreenMainChatTile(chat: chat, chatsLoaded: chatsLoaded), + // ), + // MenuAnchor( + // animated: true, + // menuChildren: + // <(String, String)>[ + // ( + // "GFM demo", + // "Write a long demo message for all GitHub Flavored Markdown features.", + // ), + // ( + // "Lorem Ipsum", + // "Write a middle long lorem ipsum stand in body text.", + // ), + // ( + // "Best emoji", + // "Think very hard about the objectively best emoji.", + // ), + // ( + // "Math test", + // "Calculate the normalized Shannon entropy for the following values: [0.1, 0.1, 0.2, 0.3, 0.3]", + // ), + // ].map((prompt) { + // return MenuItemButton( + // child: Text( + // prompt.$1, + // maxLines: 1, + // overflow: TextOverflow.ellipsis, + // ), + // onPressed: () async { + // final chat = ChatManager.instance.createChat( + // context: context, + // system: + // "Write using GitHub Flavored Markdown messages. Your messages support all GitHub Flavored Markdown features, including tables, task lists, strikethrough, alert boxes, emojis and autolinks. You must format LaTeX math using the dollar sign syntax (`\$...\$`); bracket style does not work! Speak german to the user, even if he doesn't write you in that language!", + // ); + + // await errorGuard( + // context, + // "M49WC9CW", + // () async { + // final msg = TextMessage( + // prompt.$2, + // sender: MessageSender.user, + // ); + // return chat.send(msg); + // }, + // errorMessage: errorGuardErrorMessageWithFallbackSingle( + // llama.OllamaException, + // "Unable to send message", + // ), + // enableReporting: false, + // ); + + // if (!context.mounted || !chat.alive) return; + // await errorGuard( + // context, + // "W97BM0DJ", + // () async => chat.generateTitle(context: context), + // errorMessage: errorGuardErrorMessageWithFallbackSingle( + // llama.OllamaException, + // "Unable to generate chat title", + // ), + // ); + // }, + // ); + // }).toList(), + // builder: (_, controller, _) => ListTile( + // leading: const Icon(Icons.add_rounded), + // title: const Text("Add chat"), + // onTap: loadingModels || ModelManager.instance.currentModel == null + // ? null + // : controller.open, + // ), + // ), + // ], + // ), + // ); + } +} + +class _ScreenMainNavigationBarWide extends StatelessWidget { + final Color adaptedSurface; + final bool isSettingsRoute; + final void Function()? onDestinationSelected; + const _ScreenMainNavigationBarWide({ + required this.adaptedSurface, + required this.isSettingsRoute, + this.onDestinationSelected, + }); + + @override + Widget build(BuildContext context) { + final breakpoint = Breakpoint.of(context); + final isEmbeddedNavigation = breakpoint.panesRecommended >= 2; + + return NavigationRail( + leadingAtTop: true, + trailingAtBottom: true, + backgroundColor: adaptedSurface, + leading: FloatingActionButton( + onPressed: () async { + ChatManager.instance.currentChat = null; + if (isSettingsRoute) { + await context.router.navigate(const RouteNewChat()); + onDestinationSelected?.call(); + } + }, + elevation: 0, + heroTag: null, + child: const Icon(Icons.add), + ), + destinations: const [ + NavigationRailDestination( + icon: Icon(Icons.near_me), + label: Text("Chats"), + ), + NavigationRailDestination( + icon: Icon(Icons.settings), + label: Text("Settings"), + ), + ], + selectedIndex: isSettingsRoute ? 1 : 0, + onDestinationSelected: (value) async { + switch (value) { + case 0 when isSettingsRoute: + await context.router.navigate( + ChatManager.instance.currentChatId != null + ? RouteChat(chatId: ChatManager.instance.currentChatId) + : const RouteNewChat(), + ); + onDestinationSelected?.call(); + case 1 when !isSettingsRoute: + await context.router.navigate( + isEmbeddedNavigation + ? const RouteSettingsOverview() + : const RouteSettings(), + ); + onDestinationSelected?.call(); + default: + } + }, ); } } -class ScreenMainChatTile extends StatefulWidget { - final Chat chat; - - const ScreenMainChatTile({super.key, required this.chat}); +class DrawerBackButton extends StatefulWidget { + const DrawerBackButton({super.key}); @override - State createState() => _ScreenMainChatTileState(); + State createState() => _DrawerBackButtonState(); } -class _ScreenMainChatTileState extends State { +class _DrawerBackButtonState extends State { + StreamSubscription? _routerStream; + String? path; + @override void initState() { super.initState(); - widget.chat.addListener(onChange); + path = GlobalNavigationObserver.path; + _routerStream = GlobalNavigationObserver.routerStream.listen(onUpdate); } @override void dispose() { - widget.chat.removeListener(onChange); + _routerStream?.cancel(); super.dispose(); } - void onChange() { - if (mounted) setState(() {}); + void onUpdate(_) { + if (GlobalNavigationObserver.path != null) { + path = GlobalNavigationObserver.path; + if (mounted) setState(() {}); + } } @override Widget build(BuildContext context) { - return ListTile( - title: ChatText( - widget.chat.title.emptyOn(AppLocalizations.of(context).newChatTitle), - placeholder: Text(AppLocalizations.of(context).newChatTitle), + final isSettingsRoute = path?.contains("/settings") ?? false; + final state = isSettingsRoute && path != "/settings"; + return TwoStateWidgetManaged( + state: state, + from: const SizedBox.expand(child: DrawerButton()), + to: SizedBox.expand( + child: BackButton( + onPressed: () async { + if (!state) return; + if (context.router.canPop()) { + context.router.popTop(); + } else { + await context.router.navigate(const RouteSettings()); + } + if (mounted) setState(() {}); + }, + ), ), - subtitle: - (widget.chat.messages.isEmpty || - widget.chat.messages.last.runtimeType != TextMessage) - ? null - // : ChatText( - // (widget.chat.messages.last as TextMessage).content, - // placeholder: const Text(""), - // ), - : (widget.chat.messages.last as TextMessage).content.isEmpty - ? const LinearProgressIndicator() - : Text.rich( - Markdown( - (widget.chat.messages.last as TextMessage).content, - ).toTextSpan(context), - ), - onTap: () => ChatManager.instance.deleteChat(widget.chat), ); } } - -extension on String { - String emptyOn(String other) => (this == other) ? "" : this; -} diff --git a/lib/screens/settings.dart b/lib/screens/settings.dart index 6fc1102..bab9089 100644 --- a/lib/screens/settings.dart +++ b/lib/screens/settings.dart @@ -1,312 +1,28 @@ import 'dart:async'; -import 'dart:convert'; -import 'package:bitsdojo_window/bitsdojo_window.dart'; -import 'package:dartx/dartx.dart'; -import 'package:dynamic_color/dynamic_color.dart'; -import 'package:flutter/material.dart'; -import 'package:http/http.dart' as http; -import 'package:transparent_image/transparent_image.dart'; -import 'package:version/version.dart'; +import 'package:auto_route/auto_route.dart'; +import 'package:dynamic_system_colors/dynamic_system_colors.dart'; +import 'package:flutter/foundation.dart'; +import 'package:flutter/material.dart' hide RouteSettings; import '../l10n/gen/app_localizations.dart'; import '../main.dart'; -import '../worker/desktop.dart'; -import '../worker/haptic.dart'; -import '../worker/setter.dart'; -import '../worker/update.dart'; -import 'settings/about.dart'; -import 'settings/behavior.dart'; -import 'settings/export.dart'; -import 'settings/interface.dart'; -import 'settings/voice.dart'; +import '../main.gr.dart'; +import '../services/services.dart'; +import '../widgets/alpha_beta.dart'; +import '../widgets/child_size_notifier.dart'; +import '../widgets/sized_circular_progress_indicator.dart'; +import '../widgets/two_state_widget.dart'; -Widget toggle(BuildContext context, String text, bool value, - Function(bool value) onChanged, - {bool disabled = false, - void Function()? onDisabledTap, - void Function()? onLongTap, - void Function()? onDoubleTap, - Widget? icon, - bool? iconAfterwards}) { - return InkWell( - enableFeedback: false, - splashFactory: NoSplash.splashFactory, - highlightColor: Colors.transparent, - hoverColor: Colors.transparent, - onTap: () { - if (disabled) { - selectionHaptic(); - if (onDisabledTap != null) { - onDisabledTap(); - } - } else { - onChanged(!value); - } - }, - onLongPress: onLongTap, - onDoubleTap: onDoubleTap, - child: Padding( - padding: const EdgeInsets.only(top: 4, bottom: 4), - child: Row(children: [ - Expanded( - child: LayoutBuilder(builder: (context, constraints) { - return Row(mainAxisSize: MainAxisSize.max, children: [ - (icon != null && !(iconAfterwards ?? false)) - ? Padding( - padding: const EdgeInsets.only(right: 8), - child: icon, - ) - : const SizedBox.shrink(), - ConstrainedBox( - constraints: BoxConstraints( - maxWidth: constraints.maxWidth - (icon != null ? 32 : 0)), - child: Text(text, - overflow: TextOverflow.ellipsis, - maxLines: 1, - style: TextStyle(color: disabled ? Colors.grey : null)), - ), - (icon != null && (iconAfterwards ?? false)) - ? Transform.translate( - offset: const Offset(0, 1), - child: Padding( - padding: const EdgeInsets.only(left: 8), - child: icon, - )) - : const SizedBox.shrink(), - Expanded( - child: Padding( - padding: const EdgeInsets.only(left: 16), - child: Divider(color: Theme.of(context).dividerColor)), - ), - ]); - }), - ), - Padding( - padding: const EdgeInsets.only(left: 16), - child: Switch( - value: value, - onChanged: disabled - ? (onDisabledTap != null) - ? (p0) { - selectionHaptic(); - onDisabledTap(); - } - : null - : onChanged, - activeTrackColor: disabled - ? Theme.of(context).colorScheme.primary.withAlpha(50) - : null, - trackOutlineColor: disabled - ? WidgetStatePropertyAll( - Theme.of(context).colorScheme.primary.withAlpha(150)) - : null, - thumbColor: disabled - ? WidgetStatePropertyAll( - Theme.of(context).colorScheme.primary.withAlpha(150)) - : !(prefs?.getBool("useDeviceTheme") ?? false) && value - ? WidgetStatePropertyAll( - Theme.of(context).colorScheme.secondary) - : null)) - ]), - ), - ); -} - -Widget title(String text, {double top = 16, double bottom = 16}) { - return Padding( - padding: EdgeInsets.only(left: 8, right: 8, top: top, bottom: bottom), - child: Row(children: [ - const Expanded(child: Divider()), - Padding( - padding: const EdgeInsets.only(left: 24, right: 24), - child: Text(text)), - const Expanded(child: Divider()) - ])); -} - -Widget titleDivider({double? top, double? bottom, BuildContext? context}) { - top ??= (context != null && desktopLayoutNotRequired(context)) ? 32 : 16; - bottom ??= (context != null && desktopLayoutNotRequired(context)) ? 32 : 16; - return AnimatedContainer( - duration: const Duration(milliseconds: 200), - padding: EdgeInsets.only(left: 8, right: 8, top: top, bottom: bottom), - child: const Row( - mainAxisSize: MainAxisSize.max, - children: [Expanded(child: Divider())])); -} - -Widget verticalTitleDivider( - {double? left, double? right, BuildContext? context}) { - left ??= (context != null && desktopLayoutNotRequired(context)) ? 32 : 16; - right ??= (context != null && desktopLayoutNotRequired(context)) ? 32 : 16; - return AnimatedContainer( - duration: const Duration(milliseconds: 200), - padding: EdgeInsets.only(left: left, right: right, top: 8, bottom: 8), - child: const Row( - mainAxisSize: MainAxisSize.max, - children: [VerticalDivider(width: 1)])); -} - -Widget button(String text, IconData? icon, void Function()? onPressed, - {BuildContext? context, - Color? color, - bool disabled = false, - bool replaceIconIfNull = false, - String? description, - bool onlyDesktopDescription = true, - bool alwaysMobileDescription = false, - String? badge, - String? iconBadge, - bool? iconAfterwards, - void Function()? onDisabledTap, - void Function()? onLongTap, - void Function()? onDoubleTap}) { - if (description != null && - ((context != null && desktopLayoutNotRequired(context)) || - !onlyDesktopDescription) && - !alwaysMobileDescription && - !description.startsWith("\n")) { - description = " • $description"; - } - return AnimatedContainer( - duration: const Duration(milliseconds: 200), - padding: (context != null && desktopLayoutNotRequired(context)) - ? const EdgeInsets.only(top: 8, bottom: 8) - : EdgeInsets.zero, - child: InkWell( - enableFeedback: false, - // disable hint that clickable, other tap functions still functional - splashFactory: (onPressed == null) ? NoSplash.splashFactory : null, - highlightColor: (onPressed == null) ? Colors.transparent : null, - hoverColor: (onPressed == null) ? Colors.transparent : null, - onTap: disabled - ? () { - selectionHaptic(); - if (onDisabledTap != null) { - onDisabledTap(); - } - } - : (onPressed == null && (onLongTap != null || onDoubleTap != null)) - ? selectionHaptic - : onPressed, - onLongPress: (description != null && context != null) - ? (desktopLayoutNotRequired(context) && !alwaysMobileDescription) || - !onlyDesktopDescription - ? null - : () { - selectionHaptic(); - ScaffoldMessenger.of(context).showSnackBar(SnackBar( - content: Text(description!.trim()), - showCloseIcon: true)); - } - : onLongTap, - onDoubleTap: onDoubleTap, - borderRadius: BorderRadius.circular(8), - child: Padding( - padding: const EdgeInsets.all(8), - child: Builder(builder: (context) { - var iconContent = (icon != null || replaceIconIfNull) - ? replaceIconIfNull - ? ImageIcon(MemoryImage(kTransparentImage)) - : Icon(icon, - color: disabled || (iconAfterwards ?? false) - ? Colors.grey - : color) - : const SizedBox.shrink(); - return Row( - crossAxisAlignment: CrossAxisAlignment.center, - children: [ - !(iconAfterwards ?? false) - ? (iconBadge == null) - ? iconContent - : Badge( - label: (iconBadge != "") ? Text(iconBadge) : null, - child: iconContent) - : const SizedBox.shrink(), - (icon != null || replaceIconIfNull) - ? SizedBox( - width: !(iconAfterwards ?? false) ? 16 : null, - height: 42) - : const SizedBox.shrink(), - Expanded(child: Builder(builder: (context) { - Widget textWidget = Text(text, - style: - TextStyle(color: disabled ? Colors.grey : color)); - if (badge != null) { - textWidget = Badge( - label: Text(badge), - offset: const Offset(20, -4), - backgroundColor: - Theme.of(context).colorScheme.primary, - textColor: Theme.of(context).colorScheme.onPrimary, - child: textWidget); - } - if (iconAfterwards ?? false) { - return Row( - mainAxisAlignment: MainAxisAlignment.start, - crossAxisAlignment: CrossAxisAlignment.center, - children: [ - textWidget, - // same info distance as [toggle] - const SizedBox(width: 8), - Transform.translate( - offset: const Offset(0, 1), - child: (iconBadge == null) - ? iconContent - : Badge( - label: (iconBadge != "") - ? Text(iconBadge) - : null, - child: iconContent)), - ]); - } else { - if (description == null || - description!.startsWith("\n")) { - description = description?.removePrefix("\n"); - return Column( - crossAxisAlignment: CrossAxisAlignment.start, - mainAxisSize: MainAxisSize.min, - mainAxisAlignment: MainAxisAlignment.center, - children: [ - textWidget, - (description != null && - !alwaysMobileDescription && - (desktopLayoutNotRequired(context) || - !onlyDesktopDescription)) - ? Text(description!, - style: const TextStyle( - color: Colors.grey, - overflow: TextOverflow.ellipsis)) - : const SizedBox.shrink() - ]); - } else { - return Row( - mainAxisAlignment: MainAxisAlignment.start, - crossAxisAlignment: CrossAxisAlignment.center, - children: [ - textWidget, - (description != null && - !alwaysMobileDescription && - (desktopLayoutNotRequired(context) || - !onlyDesktopDescription)) - ? Expanded( - child: Text(description!, - style: const TextStyle( - color: Colors.grey, - overflow: TextOverflow.ellipsis)), - ) - : const SizedBox.shrink() - ]); - } - } - })) - ]); - }), - )), - ); +@RoutePage() +class ScreenSettingsShell extends StatelessWidget { + const ScreenSettingsShell({super.key}); + + @override + Widget build(_) => const AutoRouter(); } +@RoutePage() class ScreenSettings extends StatefulWidget { const ScreenSettings({super.key}); @@ -315,460 +31,890 @@ class ScreenSettings extends StatefulWidget { } class _ScreenSettingsState extends State { - final hostInputController = TextEditingController( - text: useHost - ? fixedHost - : (prefs?.getString("host") ?? "http://localhost:11434")); - bool hostLoading = false; - bool hostInvalidUrl = false; - bool hostInvalidHost = false; - Future checkHost() async { - setState(() { - hostLoading = true; - hostInvalidUrl = false; - hostInvalidHost = false; - }); - var tmpHost = hostInputController.text.trim().removeSuffix("/").trim(); - - if (tmpHost.isEmpty || !Uri.parse(tmpHost).isAbsolute) { - setState(() { - hostInvalidUrl = true; - hostLoading = false; + @override + Widget build(BuildContext context) { + final breakpoint = Breakpoint.of(context); + if (breakpoint.panesRecommended >= 2) { + context.router.navigate(const RouteSettingsOverview()).then((_) { + if (!context.mounted) return; + context.router.removeWhere((e) => e.name == RouteSettings.page.name); }); - return; } - http.Response? request; - try { - // don't use centralized client because of unexplainable inconsistency - // between the ways of calling a request - var requestBase = http.Request("get", Uri.parse(tmpHost)) - ..headers.addAll( - (jsonDecode(prefs!.getString("hostHeaders") ?? "{}") as Map) - .cast(), - ) - ..followRedirects = false; - request = await http.Response.fromStream(await requestBase.send().timeout( - Duration( - milliseconds: - (5000.0 * (prefs!.getDouble("timeoutMultiplier") ?? 1.0)) - .round()), onTimeout: () { - return http.StreamedResponse(const Stream.empty(), 408); - })); - } catch (e) { - setState(() { - hostInvalidHost = true; - hostLoading = false; - }); - return; - } - if ((request.statusCode == 200 && request.body == "Ollama is running") || - (Uri.parse(tmpHost).toString() == fixedHost)) { - setState(() { - hostLoading = false; - host = tmpHost; - if (hostInputController.text != host! && - (Uri.parse(tmpHost).toString() != fixedHost)) { - hostInputController.text = host!; - } - }); - prefs?.setString("host", host!); - } else { - setState(() { - hostInvalidHost = true; - hostLoading = false; - }); - } - selectionHaptic(); + return const SizedBox.expand(child: SettingsOptions()); } +} - double iconSize = 1; - bool animatedInitialized = false; - bool animatedDesktop = false; +class SettingsOptions extends StatefulWidget { + final bool embeddedNavigation; + const SettingsOptions({super.key, this.embeddedNavigation = false}); + + @override + State createState() => _SettingsOptionsState(); +} + +class _SettingsOptionsState extends State { + StreamSubscription? _routerStream; + String? path; @override void initState() { super.initState(); - WidgetsFlutterBinding.ensureInitialized(); - if (Uri.parse(hostInputController.text.trim().removeSuffix("/").trim()) - .toString() != - fixedHost) { - checkHost(); - } + path = GlobalNavigationObserver.path; + _routerStream = GlobalNavigationObserver.routerStream.listen(onUpdate); } @override void dispose() { + _routerStream?.cancel(); super.dispose(); - hostInputController.dispose(); + } + + void onUpdate(_) { + if (GlobalNavigationObserver.path != null) { + path = GlobalNavigationObserver.path; + if (mounted) setState(() {}); + } } @override Widget build(BuildContext context) { - if (!animatedInitialized) { - animatedInitialized = true; - animatedDesktop = desktopLayoutNotRequired(context); - } - return PopScope( - canPop: !hostLoading, - onPopInvokedWithResult: (didPop, result) { - settingsOpen = false; - FocusManager.instance.primaryFocus?.unfocus(); + final colorScheme = ColorScheme.of(context); + final appLocalizations = AppLocalizations.of(context); + + Widget optionTile({ + required String title, + required String? subtitle, + required Widget leading, + required PageRouteInfo route, + required String path, + bool isBetaFeature = false, + }) => AnimatedContainer( + duration: ExpressiveCurves.expressiveEffects.fastDuration, + curve: ExpressiveCurves.expressiveEffects.fast, + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(12), + border: + (this.path?.contains("/settings/$path") ?? false) && + widget.embeddedNavigation + ? Border.all(color: colorScheme.outline, width: 2) + : Border.all( + color: Theme.of( + context, + ).colorScheme.primary.withValues(alpha: 0), + width: 2, + ), + ), + child: ListTile( + leading: leading, + title: Beta(showBadge: isBetaFeature, child: Text(title)), + subtitle: subtitle != null ? Text(subtitle) : null, + shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)), + onTap: () async { + await context.router.navigate(route); + if (context.mounted) setState(() {}); }, - child: WindowBorder( - color: Theme.of(context).colorScheme.surface, - child: Scaffold( - appBar: AppBar( - title: Row(children: [ - Text(AppLocalizations.of(context).optionSettings), - Expanded( - child: SizedBox(height: 200, child: MoveWindow())) - ]), - actions: desktopControlsActions(context)), - body: Center( - child: Container( - constraints: const BoxConstraints(maxWidth: 1200), - padding: const EdgeInsets.only(left: 16, right: 16), - child: LayoutBuilder(builder: (context, constraints) { - var column1 = - Column(mainAxisSize: MainAxisSize.min, children: [ - AnimatedContainer( - duration: const Duration(milliseconds: 200), - height: animatedDesktop ? 8 : 0, - child: const SizedBox.shrink()), - const SizedBox(height: 8), - TextField( - controller: hostInputController, - keyboardType: TextInputType.url, - autofillHints: const [AutofillHints.url], - readOnly: useHost, - onSubmitted: (value) { - selectionHaptic(); - checkHost(); - }, - decoration: InputDecoration( - labelText: - AppLocalizations.of(context).settingsHost, - hintText: "http://localhost:11434", - prefixIcon: IconButton( - enableFeedback: false, - tooltip: AppLocalizations.of(context) - .tooltipAddHostHeaders, - onPressed: () async { - selectionHaptic(); - var tmp = await prompt(context, - placeholder: - '{"Authorization": "Bearer ..."}', - title: AppLocalizations.of(context) - .settingsHostHeaderTitle, - value: prefs! - .getString("hostHeaders") ?? - "", - enableSuggestions: false, - valueIfCanceled: "{}", - validator: (content) async { - try { - var tmp = jsonDecode(content); - tmp as Map; - return true; - } catch (_) { - return false; - } - }, - validatorError: - AppLocalizations.of(context) - .settingsHostHeaderInvalid, - prefill: !((prefs!.getString( - "hostHeaders") ?? - {}) == - "{}")); - prefs!.setString("hostHeaders", tmp); - }, - icon: const Icon(Icons.add_rounded)), - suffixIcon: useHost - ? const SizedBox.shrink() - : (hostLoading - ? Transform.scale( - scale: 0.5, - child: - const CircularProgressIndicator()) - : IconButton( - enableFeedback: false, - tooltip: - AppLocalizations.of(context) - .tooltipSave, - onPressed: () { - selectionHaptic(); - checkHost(); - }, - icon: const Icon( - Icons.save_rounded), - )), - border: const OutlineInputBorder(), - error: (hostInvalidHost || hostInvalidUrl) - ? InkWell( - onTap: () { - selectionHaptic(); - ScaffoldMessenger.of(context) - .showSnackBar(SnackBar( - content: Text(AppLocalizations - .of(context) - .settingsHostInvalidDetailed( - hostInvalidHost - ? "host" - : "url")), - showCloseIcon: true)); - }, - splashFactory: NoSplash.splashFactory, - highlightColor: Colors.transparent, - hoverColor: Colors.transparent, - child: Row( - children: [ - Icon(Icons.error_rounded, - color: Theme.of(context) - .colorScheme - .error), - const SizedBox(width: 8), - Text( - AppLocalizations.of(context) - .settingsHostInvalid( - hostInvalidHost - ? "host" - : "url"), - style: TextStyle( - color: Theme.of(context) - .colorScheme - .error)) - ], - )) - : null, - helper: InkWell( - onTap: selectionHaptic, - splashFactory: NoSplash.splashFactory, - highlightColor: Colors.transparent, - hoverColor: Colors.transparent, - child: hostLoading - ? Row( - children: [ - const Icon(Icons.search_rounded, - color: Colors.grey), - const SizedBox(width: 8), - Text( - AppLocalizations.of(context) - .settingsHostChecking, - style: const TextStyle( - color: Colors.grey, - fontFamily: - "monospace")) - ], - ) - : Row( - children: [ - Icon(Icons.check_rounded, - color: Colors.green - .harmonizeWith( - Theme.of(context) - .colorScheme - .primary)), - const SizedBox(width: 8), - Text( - AppLocalizations.of(context) - .settingsHostValid, - style: TextStyle( - color: Colors.green - .harmonizeWith( - Theme.of( - context) - .colorScheme - .primary), - fontFamily: - "monospace")) - ], - )))) - ]); - var column2 = - Column(mainAxisSize: MainAxisSize.min, children: [ - button( - AppLocalizations.of(context) - .settingsTitleBehavior, - Icons.psychology_rounded, () { - selectionHaptic(); - Navigator.push( - context, - MaterialPageRoute( - builder: (context) => - const ScreenSettingsBehavior())); - }, - context: context, - description: - "\n${AppLocalizations.of(context).settingsDescriptionBehavior}"), - button( - AppLocalizations.of(context) - .settingsTitleInterface, - Icons.web_asset_rounded, () { - selectionHaptic(); - Navigator.push( - context, - MaterialPageRoute( - builder: (context) => - const ScreenSettingsInterface())); - }, - context: context, - description: - "\n${AppLocalizations.of(context).settingsDescriptionInterface}"), - (!desktopFeature(web: true)) - ? button( - AppLocalizations.of(context) - .settingsTitleVoice, - Icons.headphones_rounded, () { - selectionHaptic(); - Navigator.push( - context, - MaterialPageRoute( - builder: (context) => - const ScreenSettingsVoice())); - }, - context: context, - description: - "\n${AppLocalizations.of(context).settingsDescriptionVoice}", - badge: AppLocalizations.of(context) - .settingsExperimentalBeta) - : const SizedBox.shrink(), - button( - AppLocalizations.of(context).settingsTitleExport, - Icons.share_rounded, () { - selectionHaptic(); - Navigator.push( - context, - MaterialPageRoute( - builder: (context) => - const ScreenSettingsExport())); - }, - context: context, - description: - "\n${AppLocalizations.of(context).settingsDescriptionExport}"), - Builder(builder: (context) { - return button( - AppLocalizations.of(context).settingsTitleAbout, - Icons.help_rounded, () { - selectionHaptic(); - Navigator.push( - context, - MaterialPageRoute( - builder: (context) => - const ScreenSettingsAbout())); - }, - context: context, - description: - "\n${AppLocalizations.of(context).settingsDescriptionAbout}", - iconBadge: (updateStatus == "ok" && - updateDetectedOnStart && - (Version.parse( - latestVersion ?? "1.0.0") > - Version.parse( - currentVersion ?? "2.0.0"))) - ? "" - : null); - }) - ]); - animatedDesktop = desktopLayoutNotRequired(context); - return Column(children: [ - Expanded( - child: desktopLayoutNotRequired(context) - ? Row( - mainAxisSize: MainAxisSize.max, - crossAxisAlignment: - CrossAxisAlignment.start, - children: [ - Expanded( - child: Column( - mainAxisSize: - MainAxisSize.max, - children: [ - column1, - Expanded( - child: Center( - child: InkWell( - splashFactory: - NoSplash.splashFactory, - highlightColor: - Colors.transparent, - enableFeedback: false, - hoverColor: - Colors.transparent, - onTap: () async { - if (iconSize != 1) return; - heavyHaptic(); - setState(() { - iconSize = 0.8; - }); - await Future.delayed( - const Duration( - milliseconds: 200)); - setState(() { - iconSize = 1.2; - }); - await Future.delayed( - const Duration( - milliseconds: 200)); - setState(() { - iconSize = 1; - }); - }, - child: AnimatedScale( - scale: iconSize, - duration: const Duration( - milliseconds: 400), - child: const ImageIcon( - AssetImage( - "assets/logo512.png"), - size: 44), - ), - ))), - Transform.translate( - offset: const Offset(0, 8), - child: button( - AppLocalizations.of( - context) - .settingsSavedAutomatically, - Icons.info_rounded, - null, - color: Colors.grey - .harmonizeWith( - Theme.of(context) - .colorScheme - .primary)), - ) - ])), - verticalTitleDivider( - context: context), - Expanded(child: column2) - ]) - : ListView(children: [ - column1, - AnimatedOpacity( - opacity: animatedDesktop ? 0 : 1, - duration: - const Duration(milliseconds: 200), - child: titleDivider(bottom: 4)), - AnimatedOpacity( - opacity: animatedDesktop ? 0 : 1, - duration: - const Duration(milliseconds: 200), - child: column2) - ])), - const SizedBox(height: 8), - desktopLayoutNotRequired(context) - ? const SizedBox.shrink() - : button( - AppLocalizations.of(context) - .settingsSavedAutomatically, - Icons.info_rounded, - null, - color: Colors.grey.harmonizeWith( - Theme.of(context).colorScheme.primary)) - ]); - })), - )))); + ), + ); + + return ListView( + children: [ + if (!widget.embeddedNavigation) ...[ + const SettingsOverviewHostInput( + embeddedNavigation: false, + padding: EdgeInsetsGeometry.directional(top: 8), + ), + const Padding( + padding: EdgeInsetsDirectional.only(top: 16, start: 128, end: 128), + child: Divider(), + ), + ], + if (widget.embeddedNavigation) + optionTile( + title: appLocalizations.settingsTitleOverview, + subtitle: appLocalizations.settingsDescriptionOverview, + leading: const Icon(Icons.dashboard_rounded), + route: const RouteSettingsOverview(), + path: "overview", + ), + optionTile( + title: appLocalizations.settingsTitleBehavior, + subtitle: appLocalizations.settingsDescriptionBehavior, + leading: const Icon(Icons.psychology), + route: const RouteSettingsBehavior(), + path: "behavior", + ), + optionTile( + title: appLocalizations.settingsTitleInterface, + subtitle: appLocalizations.settingsDescriptionInterface, + leading: const Icon(Icons.palette), + route: const RouteSettingsInterface(), + path: "interface", + ), + optionTile( + title: appLocalizations.settingsTitleVoice, + subtitle: appLocalizations.settingsDescriptionVoice, + leading: const Icon(Icons.record_voice_over), + route: const RouteSettingsVoice(), + path: "voice", + isBetaFeature: true, + ), + optionTile( + title: appLocalizations.settingsTitleExport, + subtitle: appLocalizations.settingsDescriptionExport, + leading: const Icon(Icons.file_upload), + route: const RouteSettingsExport(), + path: "export", + ), + optionTile( + title: appLocalizations.settingsTitleAbout, + subtitle: appLocalizations.settingsDescriptionAbout, + leading: const Icon(Icons.help_outline), + route: const RouteSettingsAbout(), + path: "about", + ), + ], + ); + } +} + +class SettingsOverviewHostInput extends StatefulWidget { + final bool embeddedNavigation; + final EdgeInsetsGeometry? padding; + const SettingsOverviewHostInput({ + super.key, + required this.embeddedNavigation, + this.padding, + }); + + @override + State createState() => + _SettingsOverviewHostInputState(); +} + +class _SettingsOverviewHostInputState extends State { + late final TextEditingController _controller; + String? _oldHostFromHostManager; + + bool _loading = false; + HostSettingError? _error; + + @override + void initState() { + super.initState(); + HostManager.instance.addListener(onUpdate); + + _oldHostFromHostManager = HostManager.instance.host?.toString(); + _controller = TextEditingController(text: _oldHostFromHostManager); + } + + @override + void dispose() { + HostManager.instance.removeListener(onUpdate); + _controller.dispose(); + super.dispose(); + } + + void onUpdate() { + final currentHostFromHostManager = HostManager.instance.host?.toString(); + if (currentHostFromHostManager != _oldHostFromHostManager && + _controller.text == _oldHostFromHostManager) { + _oldHostFromHostManager = currentHostFromHostManager; + _controller.text = currentHostFromHostManager ?? ""; + } + + if (mounted) setState(() {}); + } + + Future _submit() async { + if (_loading) return; + + final value = _controller.text.trim(); + if (value.isEmpty && HostManager.instance.host == null) return; + + _loading = true; + _error = null; + if (mounted) setState(() {}); + + if (value.isEmpty) { + await Future.delayed(Durations.medium1); + HostManager.instance.host = null; + _loading = false; + if (mounted) setState(() {}); + return; + } + + final error = (await Future.wait([ + HostManager.instance.smartSetHost(context, value), + Future.delayed(Durations.long1), + ])).first; + + _loading = false; + _error = error; + if (mounted) setState(() {}); + } + + @override + Widget build(BuildContext context) { + final theme = Theme.of(context); + final colorScheme = ColorScheme.of(context); + final appLocalizations = AppLocalizations.of(context); + + final errorStyle = _error != null; + final errorTitle = _error?.title(context); + final errorDescription = _error?.description(context); + + Widget iconRow( + String text, + Widget Function(Color color) icon, { + String? subtitle, + bool errorStyle = false, + bool missingStyle = false, + bool validStyle = false, + }) { + final color = errorStyle + ? colorScheme.error + : (missingStyle + ? Colors.orange.harmonizeWith(colorScheme.primary) + : (validStyle + ? Colors.green.harmonizeWith(colorScheme.primary) + : colorScheme.onSurfaceVariant.withValues( + alpha: kDisabledOpacity, + ))); + return ListTile( + dense: true, + contentPadding: const EdgeInsetsDirectional.only(top: 4), + minTileHeight: 0, + minVerticalPadding: 0, + isThreeLine: subtitle != null, + leading: icon.call(color), + title: Builder( + builder: (context) => Text( + text, + style: DefaultTextStyle.of( + context, + ).style.copyWith(color: color, fontFamily: "GoogleSansCode"), + ), + ), + subtitle: subtitle != null + ? Builder( + builder: (context) => Text( + subtitle, + style: DefaultTextStyle.of( + context, + ).style.copyWith(color: color), + ), + ) + : null, + ); + } + + return AnimatedSize( + duration: ExpressiveCurves.expressiveSpatial.fastDuration, + curve: ExpressiveCurves.expressiveSpatial.fast, + alignment: Alignment.topCenter, + child: Padding( + padding: widget.padding ?? EdgeInsets.zero, + child: TextField( + controller: _controller, + onSubmitted: (_) => _submit(), + enabled: !_loading && !useHost, + keyboardType: TextInputType.url, + textInputAction: TextInputAction.done, + decoration: InputDecoration( + border: const OutlineInputBorder(), + labelText: appLocalizations.settingsHost, + hintText: "https://localhost:11434", + error: errorStyle + ? iconRow( + errorTitle ?? "", + (color) => Icon(Icons.error_outline, color: color), + subtitle: errorDescription, + errorStyle: true, + ) + : null, + helper: iconRow( + _loading + ? appLocalizations.settingsHostChecking + : (HostManager.instance.host == null + ? appLocalizations.settingsHostMissing + : appLocalizations.settingsHostValid), + (color) => TwoStateWidgetManaged( + state: _loading, + from: HostManager.instance.host == null + ? Icon(Icons.warning_amber, color: color) + : Icon(Icons.check_outlined, color: color), + to: Icon(Icons.search_outlined, color: color), + ), + missingStyle: HostManager.instance.host == null && !_loading, + validStyle: !_loading, + ), + prefixIcon: !widget.embeddedNavigation + ? Padding( + padding: const EdgeInsetsDirectional.only(start: 4), + child: IconButton( + onPressed: () { + showModalBottomSheet( + context: context, + useRootNavigator: true, + clipBehavior: Clip.antiAlias, + isScrollControlled: true, + builder: (_) => + const SettingsOverviewHostHeadersInputMobileSheet(), + ); + }, + tooltip: appLocalizations.tooltipAddHostHeaders, + icon: Icon( + Icons.data_object_outlined, + color: colorScheme.onSurfaceVariant, + ), + ), + ) + : null, + suffixIcon: Padding( + padding: const EdgeInsetsDirectional.only(end: 4), + child: _loading + ? SizedCircularProgressIndicator( + dimension: theme.iconTheme.size ?? 24, + padding: EdgeInsets.zero, + ) + : IconButton( + icon: const Icon(Icons.save), + tooltip: appLocalizations.tooltipSend, + onPressed: _submit, + ), + ), + ), + ), + ), + ); + } +} + +class SettingsOverviewHostHeadersInputMobileSheet extends StatefulWidget { + const SettingsOverviewHostHeadersInputMobileSheet({super.key}); + + @override + State createState() => + _SettingsOverviewHostHeadersInputMobileSheetState(); +} + +class _SettingsOverviewHostHeadersInputMobileSheetState + extends State { + double? _childHeight; + + @override + Widget build(BuildContext context) { + final breakpoint = Breakpoint.of(context); + return LayoutBuilder( + builder: (context, constraints) { + final maxExtend = ((_childHeight ?? 0) / constraints.maxHeight).clamp( + 0.0, + 1.0, + ); + + final initialChildSize = maxExtend.clamp(0.05, 0.85); + final minChildSize = 0.05.clamp(0.05, initialChildSize); + final maxChildSize = 0.85.clamp(initialChildSize, 0.85); + + return DraggableScrollableSheet( + initialChildSize: initialChildSize, + minChildSize: minChildSize, + maxChildSize: maxChildSize, + expand: false, + builder: (context, scrollController) => SingleChildScrollView( + controller: scrollController, + child: MeasureSize( + onChange: (size) { + _childHeight = size.height; + if (mounted) setState(() {}); + }, + child: Padding( + padding: EdgeInsetsDirectional.all(breakpoint.spacing), + child: const SettingsOverviewHostHeadersInput(), + ), + ), + ), + ); + }, + ); + } +} + +class SettingsOverviewHostHeadersInput extends StatefulWidget { + final EdgeInsetsGeometry? padding; + const SettingsOverviewHostHeadersInput({super.key, this.padding}); + + @override + State createState() => + _SettingsOverviewHostHeadersInputState(); +} + +class _SettingsOverviewHostHeadersInputState + extends State { + final GlobalKey _listKey = GlobalKey(); + late final Map _headers; + + @override + void initState() { + super.initState(); + Preferences.instance.addListener(onUpdate); + _headers = Preferences.instance.hostHeaders; + } + + @override + void dispose() { + Preferences.instance.removeListener(onUpdate); + super.dispose(); + } + + void onUpdate() { + final newHeaders = Preferences.instance.hostHeaders; + final diff = {}; + + for (final key in newHeaders.keys) { + if (!_headers.containsKey(key) || _headers[key] != newHeaders[key]) { + diff[key] = newHeaders[key]!; + } + } + for (final key in _headers.keys) { + if (!newHeaders.containsKey(key)) { + diff[key] = null; + } + } + + final listState = _listKey.currentState; + if (listState != null) { + for (final key in diff.keys) { + final value = diff[key]; + if (value == null) { + final index = _headers.keys.toList().indexOf(key); + if (index != -1) { + final oldValue = _headers[key]; + _headers.remove(key); + listState.removeItem( + index, + (context, animation) => SizeTransition( + sizeFactor: animation, + child: SettingsOverviewHostHeadersInputTile( + index: index, + headerKey: key, + headerValue: oldValue ?? "", + onChanged: (_, _) {}, + ), + ), + ); + } + } else { + final index = _headers.keys.toList().indexOf(key); + if (index == -1) { + _headers[key] = value; + listState.insertItem(_headers.length - 1); + } else { + _headers[key] = value; + listState.setState(() {}); + } + } + } + } + + if (mounted) setState(() {}); + } + + @override + Widget build(BuildContext context) { + final tmp = AnimatedList( + key: _listKey, + initialItemCount: _headers.length + 1, + shrinkWrap: true, + physics: const NeverScrollableScrollPhysics(), + itemBuilder: (context, index, animation) { + final header = _headers.entries.toList().elementAtOrNull(index); + return SizeTransition( + sizeFactor: animation, + child: SettingsOverviewHostHeadersInputTile( + index: index, + headerKey: header?.key ?? "", + headerValue: header?.value ?? "", + onChanged: (key, value) { + final headers = _headers.entries.toList(); + final currentElement = headers.elementAtOrNull(index); + if (key == null || value == null) { + if (currentElement != null) { + headers.removeAt(index); + _headers + ..clear() + ..addEntries(headers); + _listKey.currentState?.removeItem( + index, + (context, animation) => SizeTransition( + sizeFactor: animation, + child: SettingsOverviewHostHeadersInputTile( + index: index, + headerKey: currentElement.key, + headerValue: currentElement.value, + onChanged: (_, _) {}, + ), + ), + ); + } + } else { + if (currentElement != null) { + headers[index] = MapEntry(key, value); + _headers + ..clear() + ..addEntries(headers); + } else { + headers.add(MapEntry(key, value)); + _headers + ..clear() + ..addEntries(headers); + _listKey.currentState?.insertItem(headers.length - 1); + } + } + Preferences.instance.hostHeaders = Map.fromEntries(headers); + }, + ), + ); + }, + ); + return Padding(padding: widget.padding ?? EdgeInsets.zero, child: tmp); + } +} + +class SettingsOverviewHostHeadersInputTile extends StatefulWidget { + final int index; + final String headerKey; + final String headerValue; + final void Function(String? key, String? value) onChanged; + + const SettingsOverviewHostHeadersInputTile({ + super.key, + required this.index, + required this.headerKey, + required this.headerValue, + required this.onChanged, + }); + + @override + State createState() => + _SettingsOverviewHostHeadersInputTileState(); +} + +class _SettingsOverviewHostHeadersInputTileState + extends State { + bool _noUpdateFlag = false; + late final TextEditingController _keyController; + late final TextEditingController _valueController; + + late final FocusNode _keyFocusNode; + late final FocusNode _valueFocusNode; + + String? Function() _keyErrorText = () => null; + final Set _defaultAllowedHeaders = { + "authorization", + "content-type", + "user-agent", + "accept", + "x-requested-with", + "openai-beta", + "x-stainless-arch", + "x-stainless-async", + "x-stainless-custom-poll-interval", + "x-stainless-helper-method", + "x-stainless-lang", + "x-stainless-os", + "x-stainless-package-version", + "x-stainless-poll-helper", + "x-stainless-retry-count", + "x-stainless-runtime", + "x-stainless-runtime-version", + "x-stainless-timeout", + }; + + @override + void initState() { + super.initState(); + + _keyController = TextEditingController(text: widget.headerKey) + ..addListener(onUpdate); + _valueController = TextEditingController(text: widget.headerValue) + ..addListener(onUpdate); + + _keyFocusNode = FocusNode()..addListener(onUpdateFocus); + _valueFocusNode = FocusNode()..addListener(onUpdateFocus); + + WidgetsBinding.instance.addPostFrameCallback((_) => onUpdateFocus(true)); + } + + @override + void dispose() { + _keyController.dispose(); + _valueController.dispose(); + _keyFocusNode.dispose(); + _valueFocusNode.dispose(); + super.dispose(); + } + + void onUpdate() { + if (_noUpdateFlag) { + _noUpdateFlag = false; + return; + } + + final newKey = _keyController.text.trim(); + final newValue = _valueController.text.trim(); + + if (newKey.isEmpty || newValue.isEmpty) { + if (newKey.isEmpty && newValue.isEmpty) widget.onChanged(null, null); + return; + } + + if (newKey.isNotEmpty && + Preferences.instance.hostHeaders.entries.toList().asMap().entries.any( + (e) => e.value.key == newKey && e.key != widget.index, + )) { + _keyErrorText = () { + if (newKey != _keyController.text) return null; + final appLocalizations = AppLocalizations.of(context); + return appLocalizations.settingsHostHeaderDuplicate(newKey); + }; + if (mounted) setState(() {}); + return; + } + + if (newKey != widget.headerKey || newValue != widget.headerValue) { + _keyErrorText = () => null; + widget.onChanged( + newKey.isEmpty ? null : newKey, + newValue.isEmpty ? null : newValue, + ); + } + if (mounted) setState(() {}); + } + + void onUpdateFocus([bool force = false]) { + if ((!_keyFocusNode.hasFocus || force) && + _keyController.text.isNotEmpty && + !_defaultAllowedHeaders.contains(_keyController.text.toLowerCase()) && + widget.index != Preferences.instance.hostHeaders.length && + kIsWeb) { + _keyErrorText = () { + if (_keyController.text.isEmpty) return null; + final appLocalizations = AppLocalizations.of(context); + return appLocalizations.settingsHostHeaderUnsupported( + _keyController.text, + ); + }; + } + if (mounted) setState(() {}); + } + + @override + void didUpdateWidget(SettingsOverviewHostHeadersInputTile oldWidget) { + if (_keyController.text != widget.headerKey) { + _noUpdateFlag = true; + _keyController.text = widget.headerKey; + onUpdateFocus(true); + } + if (_valueController.text != widget.headerValue) { + _noUpdateFlag = true; + _valueController.text = widget.headerValue; + } + super.didUpdateWidget(oldWidget); + } + + @override + Widget build(BuildContext context) { + final colorScheme = ColorScheme.of(context); + final textTheme = TextTheme.of(context); + final appLocalizations = AppLocalizations.of(context); + + final isLast = widget.index == Preferences.instance.hostHeaders.length; + final isDummy = widget.headerKey.isEmpty && widget.headerValue.isEmpty; + + return Row( + mainAxisSize: MainAxisSize.max, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Expanded( + flex: 2, + child: AnimatedSize( + duration: ExpressiveCurves.expressiveEffects.fastDuration, + curve: ExpressiveCurves.expressiveEffects.fast, + alignment: Alignment.topCenter, + child: TextField( + controller: _keyController, + focusNode: _keyFocusNode, + autofocus: false, + textInputAction: TextInputAction.next, + textCapitalization: TextCapitalization.words, + decoration: InputDecoration( + label: Text(appLocalizations.settingsHostHeaderHeaderName), + hintText: "Authorization", + helperText: _keyErrorText.call(), + helperStyle: textTheme.bodySmall?.copyWith( + color: Colors.orange.harmonizeWith(colorScheme.primary), + ), + helperMaxLines: 3, + ), + ), + ), + ), + Text(" " * 3), + Expanded( + flex: 3, + child: TextField( + controller: _valueController, + focusNode: _valueFocusNode, + autofocus: false, + textInputAction: isLast + ? TextInputAction.done + : TextInputAction.next, + textCapitalization: TextCapitalization.words, + decoration: InputDecoration( + label: Text(appLocalizations.settingsHostHeaderHeaderValue), + hintText: "Bearer XXX", + ), + ), + ), + isDummy + ? const Padding( + padding: EdgeInsets.all(8.0), + child: Icon(Icons.add), + ) + : IconButton( + onPressed: () => widget.onChanged(null, null), + icon: const Icon(Icons.close), + ), + ], + ); + } +} + +class SettingsPageContainmentBox extends StatefulWidget { + final List Function(BuildContext context, double halfSpacing) + itemBuilder; + + const SettingsPageContainmentBox({super.key, required this.itemBuilder}); + + @override + State createState() => + _SettingsPageContainmentBoxState(); +} + +class _SettingsPageContainmentBoxState + extends State { + final ScrollController _scrollController = ScrollController(); + + @override + void dispose() { + _scrollController.dispose(); + super.dispose(); + } + + @override + Widget build(BuildContext context) { + final breakpoint = Breakpoint.of(context); + final halfSpacing = breakpoint.spacing / 2; + + return ListView( + children: widget.itemBuilder + .call(context, halfSpacing) + .map( + (widget) => Column( + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + ConstrainedBox( + constraints: BoxConstraints(maxWidth: 560 - halfSpacing), + child: SizedBox(width: 560, child: widget), + ), + ], + ), + ) + .toList(), + ); + } +} + +@RoutePage() +class ScreenSettingsOverview extends StatelessWidget { + const ScreenSettingsOverview({super.key}); + + @override + Widget build(BuildContext context) { + final breakpoint = Breakpoint.of(context); + if (breakpoint.panesRecommended < 2) { + context.router.navigate(const RouteSettings()).then((_) { + if (!context.mounted) return; + context.router.removeWhere( + (e) => e.name == RouteSettingsOverview.page.name, + ); + }); + } + + return SettingsPageContainmentBox( + itemBuilder: (context, halfSpacing) => [ + SettingsOverviewHostInput( + embeddedNavigation: true, + padding: EdgeInsetsGeometry.directional(top: 4 + halfSpacing), + ), + const SettingsOverviewHostHeadersInput( + padding: EdgeInsetsDirectional.only(top: 4), + ), + ], + ); + } +} + +// MARK: dummy pages + +@RoutePage() +class ScreenSettingsBehavior extends StatelessWidget { + const ScreenSettingsBehavior({super.key}); + + @override + Widget build(BuildContext context) { + return const Center(child: Text("settings behavior")); + } +} + +@RoutePage() +class ScreenSettingsInterface extends StatelessWidget { + const ScreenSettingsInterface({super.key}); + + @override + Widget build(BuildContext context) { + return const Center(child: Text("settings interface")); + } +} + +@RoutePage() +class ScreenSettingsVoice extends StatelessWidget { + const ScreenSettingsVoice({super.key}); + + @override + Widget build(BuildContext context) { + return const Center(child: Text("settings voice")); + } +} + +@RoutePage() +class ScreenSettingsExport extends StatelessWidget { + const ScreenSettingsExport({super.key}); + + @override + Widget build(BuildContext context) { + return const Center(child: Text("settings export")); + } +} + +@RoutePage() +class ScreenSettingsAbout extends StatelessWidget { + const ScreenSettingsAbout({super.key}); + + @override + Widget build(BuildContext context) { + return const Center(child: Text("settings about")); } } diff --git a/lib/screens/settings/about.dart b/lib/screens/settings/about.dart index 1f5025d..67e5b89 100644 --- a/lib/screens/settings/about.dart +++ b/lib/screens/settings/about.dart @@ -42,141 +42,184 @@ class _ScreenSettingsAboutState extends State { return WindowBorder( color: Theme.of(context).colorScheme.surface, child: Scaffold( - appBar: AppBar( - title: Row(children: [ - Text(AppLocalizations.of(context).settingsTitleAbout), - Expanded(child: SizedBox(height: 200, child: MoveWindow())) - ]), - actions: desktopControlsActions(context)), - body: Center( - child: Container( - constraints: const BoxConstraints(maxWidth: 1000), - padding: const EdgeInsets.only(left: 16, right: 16), - child: Column(children: [ - Expanded( - child: ListView(children: [ + appBar: AppBar( + title: Row( + children: [ + Text(AppLocalizations.of(context).settingsTitleAbout), + Expanded(child: SizedBox(height: 200, child: MoveWindow())), + ], + ), + actions: desktopControlsActions(context), + ), + body: Center( + child: Container( + constraints: const BoxConstraints(maxWidth: 1000), + padding: const EdgeInsets.only(left: 16, right: 16), + child: Column( + children: [ + Expanded( + child: ListView( + children: [ // const SizedBox(height: 8), button( - AppLocalizations.of(context) - .settingsVersion(currentVersion ?? ""), - Icons.verified_rounded, - null), + AppLocalizations.of( + context, + ).settingsVersion(currentVersion ?? ""), + Icons.verified_rounded, + null, + ), (updateStatus == "notAvailable") ? const SizedBox.shrink() : button( (!updateChecked - ? AppLocalizations.of(context) - .settingsUpdateCheck + ? AppLocalizations.of( + context, + ).settingsUpdateCheck : updateLoading - ? AppLocalizations.of(context) - .settingsUpdateChecking - : (updateStatus == "rateLimit") - ? AppLocalizations.of(context) - .settingsUpdateRateLimit - : (updateStatus != "ok") - ? AppLocalizations.of(context) - .settingsUpdateIssue - : (Version.parse(latestVersion ?? - "1.0.0") > - Version.parse( - currentVersion ?? - "2.0.0")) - ? AppLocalizations.of(context) - .settingsUpdateAvailable( - latestVersion!) - : AppLocalizations.of(context) - .settingsUpdateLatest), + ? AppLocalizations.of( + context, + ).settingsUpdateChecking + : (updateStatus == "rateLimit") + ? AppLocalizations.of( + context, + ).settingsUpdateRateLimit + : (updateStatus != "ok") + ? AppLocalizations.of( + context, + ).settingsUpdateIssue + : (Version.parse(latestVersion ?? "1.0.0") > + Version.parse( + currentVersion ?? "2.0.0", + )) + ? AppLocalizations.of( + context, + ).settingsUpdateAvailable(latestVersion!) + : AppLocalizations.of( + context, + ).settingsUpdateLatest), ((updateStatus != "ok") ? Icons.warning_rounded : (Version.parse(latestVersion ?? "1.0.0") > - Version.parse( - currentVersion ?? "2.0.0")) - ? Icons.info_outline_rounded - : Icons.update_rounded), () { - if (updateLoading) return; - selectionHaptic(); - if ((Version.parse(latestVersion ?? "1.0.0") > - Version.parse( - currentVersion ?? "2.0.0")) && - (updateStatus == "ok")) { - updateDialog(context, title); - } else { - checkUpdate(setState); - return; - } - }, - iconBadge: (updateStatus == "ok" && + Version.parse( + currentVersion ?? "2.0.0", + )) + ? Icons.info_outline_rounded + : Icons.update_rounded), + () { + if (updateLoading) return; + selectionHaptic(); + if ((Version.parse(latestVersion ?? "1.0.0") > + Version.parse( + currentVersion ?? "2.0.0", + )) && + (updateStatus == "ok")) { + updateDialog(context, title); + } else { + checkUpdate(setState); + return; + } + }, + iconBadge: + (updateStatus == "ok" && updateDetectedOnStart && (Version.parse(latestVersion ?? "1.0.0") > Version.parse( - currentVersion ?? "2.0.0"))) + currentVersion ?? "2.0.0", + ))) ? "" - : null), + : null, + ), (updateStatus == "notAvailable") ? const SizedBox.shrink() : toggle( context, - AppLocalizations.of(context) - .settingsCheckForUpdates, + AppLocalizations.of( + context, + ).settingsCheckForUpdates, prefs!.getBool("checkUpdateOnSettingsOpen") ?? - true, (value) { - selectionHaptic(); - prefs! - .setBool("checkUpdateOnSettingsOpen", value); - setState(() {}); - }), + true, + (value) { + selectionHaptic(); + prefs!.setBool( + "checkUpdateOnSettingsOpen", + value, + ); + setState(() {}); + }, + ), titleDivider(context: context), - button(AppLocalizations.of(context).settingsGithub, - SimpleIcons.github, () { - selectionHaptic(); - launchUrl( + button( + AppLocalizations.of(context).settingsGithub, + SimpleIcons.github, + () { + selectionHaptic(); + launchUrl( mode: LaunchMode.inAppBrowserView, - Uri.parse(repoUrl)); - }), - button(AppLocalizations.of(context).settingsReportIssue, - Icons.report_rounded, () { - selectionHaptic(); - launchUrl( + Uri.parse(repoUrl), + ); + }, + ), + button( + AppLocalizations.of(context).settingsReportIssue, + Icons.report_rounded, + () { + selectionHaptic(); + launchUrl( mode: LaunchMode.inAppBrowserView, - Uri.parse("$repoUrl/issues")); - }), - button(AppLocalizations.of(context).settingsLicenses, - Icons.gavel_rounded, () { - selectionHaptic(); - var legal = "Copyright 2024 JHubi1"; - Widget icon = const Padding( - padding: EdgeInsets.all(16), - child: ImageIcon(AssetImage("assets/logo512.png"), - size: 48), - ); - if (desktopFeature()) { - showDialog( + Uri.parse("$repoUrl/issues"), + ); + }, + ), + button( + AppLocalizations.of(context).settingsLicenses, + Icons.gavel_rounded, + () { + selectionHaptic(); + const legal = "Copyright 2024 JHubi1"; + const Widget icon = Padding( + padding: EdgeInsets.all(16), + child: ImageIcon( + AssetImage("assets/logo512.png"), + size: 48, + ), + ); + if (desktopFeature()) { + showDialog( context: context, builder: (context) { return Dialog( - child: ClipRRect( - borderRadius: BorderRadius.circular(28), - child: LicensePage( + child: ClipRRect( + borderRadius: BorderRadius.circular(28), + child: LicensePage( applicationName: "Ollama App", applicationVersion: currentVersion, applicationIcon: icon, - applicationLegalese: legal), - )); - }); - } else { - showLicensePage( + applicationLegalese: legal, + ), + ), + ); + }, + ); + } else { + showLicensePage( context: context, applicationName: "Ollama App", applicationVersion: currentVersion, applicationIcon: icon, - applicationLegalese: legal); - } - }), - const SizedBox(height: 16) - ]), - ) - ])), - )), + applicationLegalese: legal, + ); + } + }, + ), + const SizedBox(height: 16), + ], + ), + ), + ], + ), + ), + ), + ), ); } } diff --git a/lib/screens/settings/behavior.dart b/lib/screens/settings/behavior.dart index 3341048..2a685a4 100644 --- a/lib/screens/settings/behavior.dart +++ b/lib/screens/settings/behavior.dart @@ -17,7 +17,8 @@ class ScreenSettingsBehavior extends StatefulWidget { class _ScreenSettingsBehaviorState extends State { final systemInputController = TextEditingController( - text: prefs?.getString("system") ?? "You are a helpful assistant"); + text: prefs?.getString("system") ?? "You are a helpful assistant", + ); @override void dispose() { @@ -30,84 +31,110 @@ class _ScreenSettingsBehaviorState extends State { return WindowBorder( color: Theme.of(context).colorScheme.surface, child: Scaffold( - appBar: AppBar( - title: Row(children: [ - Text(AppLocalizations.of(context).settingsTitleBehavior), - Expanded(child: SizedBox(height: 200, child: MoveWindow())) - ]), - actions: desktopControlsActions(context)), - body: Center( - child: Container( - constraints: const BoxConstraints(maxWidth: 1000), - padding: const EdgeInsets.only(left: 16, right: 16), - child: Column(children: [ - Expanded( - child: ListView(children: [ + appBar: AppBar( + title: Row( + children: [ + Text(AppLocalizations.of(context).settingsTitleBehavior), + Expanded(child: SizedBox(height: 200, child: MoveWindow())), + ], + ), + actions: desktopControlsActions(context), + ), + body: Center( + child: Container( + constraints: const BoxConstraints(maxWidth: 1000), + padding: const EdgeInsets.only(left: 16, right: 16), + child: Column( + children: [ + Expanded( + child: ListView( + children: [ const SizedBox(height: 8), TextField( - controller: systemInputController, - keyboardType: TextInputType.multiline, - maxLines: desktopLayoutNotRequired(context) ? 5 : 2, - decoration: InputDecoration( - labelText: AppLocalizations.of(context) - .settingsSystemMessage, - alignLabelWithHint: true, - hintText: "You are a helpful assistant", - suffixIcon: IconButton( - enableFeedback: false, - tooltip: - AppLocalizations.of(context).tooltipSave, - onPressed: () { - selectionHaptic(); - prefs?.setString( - "system", - (systemInputController.text.isNotEmpty) - ? systemInputController.text - : "You are a helpful assistant"); - }, - icon: const Icon(Icons.save_rounded), - ), - border: const OutlineInputBorder())), + controller: systemInputController, + keyboardType: TextInputType.multiline, + maxLines: desktopLayoutNotRequired(context) ? 5 : 2, + decoration: InputDecoration( + labelText: AppLocalizations.of( + context, + ).settingsSystemMessage, + alignLabelWithHint: true, + hintText: "You are a helpful assistant", + suffixIcon: IconButton( + enableFeedback: false, + tooltip: AppLocalizations.of(context).tooltipSave, + onPressed: () { + selectionHaptic(); + prefs?.setString( + "system", + (systemInputController.text.isNotEmpty) + ? systemInputController.text + : "You are a helpful assistant", + ); + }, + icon: const Icon(Icons.save_rounded), + ), + border: const OutlineInputBorder(), + ), + ), const SizedBox(height: 16), toggle( - context, - AppLocalizations.of(context).settingsUseSystem, - prefs!.getBool("useSystem") ?? true, - (value) { - selectionHaptic(); - prefs!.setBool("useSystem", value); - setState(() {}); - }, - icon: const Icon(Icons.info_rounded, - color: Colors.grey), - iconAfterwards: true, - onLongTap: () { - selectionHaptic(); - ScaffoldMessenger.of(context).showSnackBar(SnackBar( - content: Text(AppLocalizations.of(context) - .settingsUseSystemDescription), - showCloseIcon: true)); - }), + context, + AppLocalizations.of(context).settingsUseSystem, + prefs!.getBool("useSystem") ?? true, + (value) { + selectionHaptic(); + prefs!.setBool("useSystem", value); + setState(() {}); + }, + icon: const Icon( + Icons.info_rounded, + color: Colors.grey, + ), + iconAfterwards: true, + onLongTap: () { + selectionHaptic(); + ScaffoldMessenger.of(context).showSnackBar( + SnackBar( + content: Text( + AppLocalizations.of( + context, + ).settingsUseSystemDescription, + ), + showCloseIcon: true, + ), + ); + }, + ), toggle( - context, - AppLocalizations.of(context).settingsDisableMarkdown, - prefs!.getBool("noMarkdown") ?? false, (value) { - selectionHaptic(); - prefs!.setBool("noMarkdown", value); - setState(() {}); - }) - ]), + context, + AppLocalizations.of(context).settingsDisableMarkdown, + prefs!.getBool("noMarkdown") ?? false, + (value) { + selectionHaptic(); + prefs!.setBool("noMarkdown", value); + setState(() {}); + }, + ), + ], ), - const SizedBox(height: 8), - button( - AppLocalizations.of(context) - .settingsBehaviorNotUpdatedForOlderChats, - Icons.info_rounded, - null, - color: Colors.grey - .harmonizeWith(Theme.of(context).colorScheme.primary)) - ])), - )), + ), + const SizedBox(height: 8), + button( + AppLocalizations.of( + context, + ).settingsBehaviorNotUpdatedForOlderChats, + Icons.info_rounded, + null, + color: Colors.grey.harmonizeWith( + Theme.of(context).colorScheme.primary, + ), + ), + ], + ), + ), + ), + ), ); } } diff --git a/lib/screens/settings/export.dart b/lib/screens/settings/export.dart index 3cb99b8..081136e 100644 --- a/lib/screens/settings/export.dart +++ b/lib/screens/settings/export.dart @@ -29,203 +29,262 @@ class _ScreenSettingsExportState extends State { return WindowBorder( color: Theme.of(context).colorScheme.surface, child: Scaffold( - appBar: AppBar( - title: Row(children: [ - Text(AppLocalizations.of(context).settingsTitleExport), - Expanded(child: SizedBox(height: 200, child: MoveWindow())) - ]), - actions: desktopControlsActions(context)), - body: Center( - child: Container( - constraints: const BoxConstraints(maxWidth: 1000), - padding: const EdgeInsets.only(left: 16, right: 16), - child: Column(children: [ - Expanded( - child: ListView(children: [ + appBar: AppBar( + title: Row( + children: [ + Text(AppLocalizations.of(context).settingsTitleExport), + Expanded(child: SizedBox(height: 200, child: MoveWindow())), + ], + ), + actions: desktopControlsActions(context), + ), + body: Center( + child: Container( + constraints: const BoxConstraints(maxWidth: 1000), + padding: const EdgeInsets.only(left: 16, right: 16), + child: Column( + children: [ + Expanded( + child: ListView( + children: [ // const SizedBox(height: 8), - button(AppLocalizations.of(context).settingsExportChats, - Icons.upload_rounded, () async { - selectionHaptic(); - var name = - "ollama-export-${DateFormat('yyyy-MM-dd-H-m-s').format(DateTime.now())}.json"; - var content = - jsonEncode(prefs!.getStringList("chats") ?? []); - if (kIsWeb) { - // web fallback - var bytes = utf8.encode(content); - var blob = html.Blob([bytes]); - var url = html.Url.createObjectUrlFromBlob(blob); - var anchor = html.document.createElement("a") - as html.AnchorElement - ..href = url - ..style.display = "none" - ..download = name; - html.document.body!.children.add(anchor); + button( + AppLocalizations.of(context).settingsExportChats, + Icons.upload_rounded, + () async { + selectionHaptic(); + final name = + "ollama-export-${DateFormat('yyyy-MM-dd-H-m-s').format(DateTime.now())}.json"; + final content = jsonEncode( + prefs!.getStringList("chats") ?? [], + ); + if (kIsWeb) { + // web fallback + final bytes = utf8.encode(content); + final blob = html.Blob([bytes]); + final url = html.Url.createObjectUrlFromBlob(blob); + final anchor = + html.document.createElement("a") + as html.AnchorElement + ..href = url + ..style.display = "none" + ..download = name; + html.document.body!.children.add(anchor); - anchor.click(); + anchor.click(); - html.document.body!.children.remove(anchor); - html.Url.revokeObjectUrl(url); - } else { - String? path = ""; - try { - path = (await file_selector - .getSaveLocation(acceptedTypeGroups: [ - const file_selector.XTypeGroup( - label: "Ollama App File", - extensions: ["json"]) - ], suggestedName: name)) - ?.path; - } catch (_) { - path = await FilePicker.platform.saveFile( + html.document.body!.children.remove(anchor); + html.Url.revokeObjectUrl(url); + } else { + String? path = ""; + try { + path = (await file_selector.getSaveLocation( + acceptedTypeGroups: [ + const file_selector.XTypeGroup( + label: "Ollama App File", + extensions: ["json"], + ), + ], + suggestedName: name, + ))?.path; + } catch (_) { + path = await FilePicker.platform.saveFile( type: FileType.custom, allowedExtensions: ["json"], fileName: name, - bytes: utf8.encode(jsonEncode( - prefs!.getStringList("chats") ?? []))); + bytes: utf8.encode( + jsonEncode( + prefs!.getStringList("chats") ?? [], + ), + ), + ); + } + selectionHaptic(); + if (path == null) return; + if (desktopFeature()) { + File(path).writeAsString(content); + } } - selectionHaptic(); - if (path == null) return; - if (desktopFeature()) { - File(path).writeAsString(content); - } - } - // ignore: use_build_context_synchronously - ScaffoldMessenger.of(context).showSnackBar(SnackBar( - // ignore: use_build_context_synchronously - content: Text(AppLocalizations.of(context) - .settingsExportChatsSuccess), - showCloseIcon: true)); - }), + // ignore: use_build_context_synchronously + ScaffoldMessenger.of(context).showSnackBar( + SnackBar( + // ignore: use_build_context_synchronously + content: Text( + AppLocalizations.of( + context, + ).settingsExportChatsSuccess, + ), + showCloseIcon: true, + ), + ); + }, + ), allowMultipleChats ? button( AppLocalizations.of(context).settingsImportChats, - Icons.download_rounded, () async { - selectionHaptic(); - await showDialog( + Icons.download_rounded, + () async { + selectionHaptic(); + await showDialog( context: context, builder: (context) { return AlertDialog( - title: Text(AppLocalizations.of(context) - .settingsImportChatsTitle), - content: Text(AppLocalizations.of( - context) - .settingsImportChatsDescription), - actions: [ - TextButton( - onPressed: () { - selectionHaptic(); - Navigator.of(context).pop(); - }, - child: Text(AppLocalizations.of( - context) - .settingsImportChatsCancel)), - TextButton( - onPressed: () async { - selectionHaptic(); - String content; - try { - if (kIsWeb) { - throw Exception( - "web must use file picker"); - } - var result = await file_selector - .openFile( - acceptedTypeGroups: [ - const file_selector - .XTypeGroup( - label: - "Ollama App File", - extensions: [ - "json" - ]) - ]); - if (result == null) { - // ignore: use_build_context_synchronously - Navigator.of(context).pop(); - return; - } - content = await result - .readAsString(); - } catch (_) { - var result = await FilePicker - .platform - .pickFiles( - type: FileType.custom, - allowedExtensions: [ - "json" - ]); - if (result == null) { - // ignore: use_build_context_synchronously - Navigator.of(context).pop(); - return; - } - try { - var file = File(result - .files.single.path!); - content = await file - .readAsString(); - } catch (_) { - // web fallback - content = utf8.decode(result - .files - .single - .bytes! as List); - } - } - List tmpHistory = - jsonDecode(content); - var history = []; - - for (var i = 0; - i < tmpHistory.length; - i++) { - history.add(tmpHistory[i]); - } - - prefs!.setStringList( - "chats", history); - - messages = []; - chatUuid = null; - - setState(() {}); - + title: Text( + AppLocalizations.of( + context, + ).settingsImportChatsTitle, + ), + content: Text( + AppLocalizations.of( + context, + ).settingsImportChatsDescription, + ), + actions: [ + TextButton( + onPressed: () { + selectionHaptic(); + Navigator.of(context).pop(); + }, + child: Text( + AppLocalizations.of( + context, + ).settingsImportChatsCancel, + ), + ), + TextButton( + onPressed: () async { + selectionHaptic(); + String content; + try { + if (kIsWeb) { + throw Exception( + "web must use file picker", + ); + } + final result = await file_selector + .openFile( + acceptedTypeGroups: [ + const file_selector.XTypeGroup( + label: + "Ollama App File", + extensions: ["json"], + ), + ], + ); + if (result == null) { // ignore: use_build_context_synchronously Navigator.of(context).pop(); + return; + } + content = await result + .readAsString(); + } catch (_) { + final result = await FilePicker + .platform + .pickFiles( + type: FileType.custom, + allowedExtensions: ["json"], + ); + if (result == null) { // ignore: use_build_context_synchronously Navigator.of(context).pop(); - // ignore: use_build_context_synchronously - Navigator.of(context).pop(); - // ignore: use_build_context_synchronously - ScaffoldMessenger.of(context) - .showSnackBar(SnackBar( - content: Text(AppLocalizations - // ignore: use_build_context_synchronously - .of(context) - .settingsImportChatsSuccess), - showCloseIcon: true)); - }, - child: Text(AppLocalizations.of( - context) - .settingsImportChatsImport)) - ]); - }); - }) - : const SizedBox.shrink() - ]), + return; + } + try { + final file = File( + result.files.single.path!, + ); + content = await file + .readAsString(); + } catch (_) { + // web fallback + content = utf8.decode( + result.files.single.bytes! + as List, + ); + } + } + final List tmpHistory = + jsonDecode(content); + final history = []; + + for ( + var i = 0; + i < tmpHistory.length; + i++ + ) { + history.add(tmpHistory[i]); + } + + prefs!.setStringList( + "chats", + history, + ); + + messages = []; + chatUuid = null; + + setState(() {}); + + // ignore: use_build_context_synchronously + Navigator.of(context).pop(); + // ignore: use_build_context_synchronously + Navigator.of(context).pop(); + // ignore: use_build_context_synchronously + Navigator.of(context).pop(); + // ignore: use_build_context_synchronously + ScaffoldMessenger.of( + context, + ).showSnackBar( + SnackBar( + content: Text( + AppLocalizations + // ignore: use_build_context_synchronously + .of(context) + .settingsImportChatsSuccess, + ), + showCloseIcon: true, + ), + ); + }, + child: Text( + AppLocalizations.of( + context, + ).settingsImportChatsImport, + ), + ), + ], + ); + }, + ); + }, + ) + : const SizedBox.shrink(), + ], ), - const SizedBox(height: 8), - button(AppLocalizations.of(context).settingsExportInfo, - Icons.info_rounded, null, - color: Colors.grey.harmonizeWith( - Theme.of(context).colorScheme.primary)), - button(AppLocalizations.of(context).settingsExportWarning, - Icons.warning_rounded, null, - color: Colors.orange - .harmonizeWith(Theme.of(context).colorScheme.primary)) - ])), - )), + ), + const SizedBox(height: 8), + button( + AppLocalizations.of(context).settingsExportInfo, + Icons.info_rounded, + null, + color: Colors.grey.harmonizeWith( + Theme.of(context).colorScheme.primary, + ), + ), + button( + AppLocalizations.of(context).settingsExportWarning, + Icons.warning_rounded, + null, + color: Colors.orange.harmonizeWith( + Theme.of(context).colorScheme.primary, + ), + ), + ], + ), + ), + ), + ), ); } } diff --git a/lib/screens/settings/interface.dart b/lib/screens/settings/interface.dart index 4019ebc..9e9ca6e 100644 --- a/lib/screens/settings/interface.dart +++ b/lib/screens/settings/interface.dart @@ -45,403 +45,499 @@ class _ScreenSettingsInterfaceState extends State { return WindowBorder( color: Theme.of(context).colorScheme.surface, child: Scaffold( - appBar: AppBar( - title: Row(children: [ - Text(AppLocalizations.of(context).settingsTitleInterface), - Expanded(child: SizedBox(height: 200, child: MoveWindow())) - ]), - actions: desktopControlsActions(context)), - body: Center( - child: Container( - constraints: const BoxConstraints(maxWidth: 1000), - padding: const EdgeInsets.only(left: 16, right: 16), - child: Column(children: [ - Expanded( - child: ListView(children: [ + appBar: AppBar( + title: Row( + children: [ + Text(AppLocalizations.of(context).settingsTitleInterface), + Expanded(child: SizedBox(height: 200, child: MoveWindow())), + ], + ), + actions: desktopControlsActions(context), + ), + body: Center( + child: Container( + constraints: const BoxConstraints(maxWidth: 1000), + padding: const EdgeInsets.only(left: 16, right: 16), + child: Column( + children: [ + Expanded( + child: ListView( + children: [ // const SizedBox(height: 8), toggle( - context, - AppLocalizations.of(context).settingsShowModelTags, - prefs!.getBool("modelTags") ?? false, (value) { - selectionHaptic(); - prefs!.setBool("modelTags", value); - setState(() {}); - }), + context, + AppLocalizations.of(context).settingsShowModelTags, + prefs!.getBool("modelTags") ?? false, + (value) { + selectionHaptic(); + prefs!.setBool("modelTags", value); + setState(() {}); + }, + ), toggle( - context, - AppLocalizations.of(context).settingsPreloadModels, - prefs!.getBool("preloadModel") ?? true, (value) { - selectionHaptic(); - prefs!.setBool("preloadModel", value); - setState(() {}); - }), + context, + AppLocalizations.of(context).settingsPreloadModels, + prefs!.getBool("preloadModel") ?? true, + (value) { + selectionHaptic(); + prefs!.setBool("preloadModel", value); + setState(() {}); + }, + ), toggle( - context, - AppLocalizations.of(context) - .settingsResetOnModelChange, - prefs!.getBool("resetOnModelSelect") ?? true, - (value) { - selectionHaptic(); - prefs!.setBool("resetOnModelSelect", value); - setState(() {}); - }), + context, + AppLocalizations.of(context).settingsResetOnModelChange, + prefs!.getBool("resetOnModelSelect") ?? true, + (value) { + selectionHaptic(); + prefs!.setBool("resetOnModelSelect", value); + setState(() {}); + }, + ), titleDivider( - bottom: desktopLayoutNotRequired(context) ? 38 : 20, - context: context), + bottom: desktopLayoutNotRequired(context) ? 38 : 20, + context: context, + ), SegmentedButton( - segments: [ - ButtonSegment( - value: "stream", - label: Text(AppLocalizations.of(context) - .settingsRequestTypeStream), - icon: const Icon(Icons.stream_rounded)), - ButtonSegment( - value: "request", - label: Text(AppLocalizations.of(context) - .settingsRequestTypeRequest), - icon: const Icon(Icons.send_rounded)) - ], - selected: { - prefs!.getString("requestType") ?? "stream" - }, - onSelectionChanged: (p0) { - selectionHaptic(); - setState(() { - prefs!.setString("requestType", p0.elementAt(0)); - }); - }), + segments: [ + ButtonSegment( + value: "stream", + label: Text( + AppLocalizations.of( + context, + ).settingsRequestTypeStream, + ), + icon: const Icon(Icons.stream_rounded), + ), + ButtonSegment( + value: "request", + label: Text( + AppLocalizations.of( + context, + ).settingsRequestTypeRequest, + ), + icon: const Icon(Icons.send_rounded), + ), + ], + selected: {prefs!.getString("requestType") ?? "stream"}, + onSelectionChanged: (p0) { + selectionHaptic(); + setState(() { + prefs!.setString("requestType", p0.elementAt(0)); + }); + }, + ), const SizedBox(height: 16), toggle( - context, - AppLocalizations.of(context).settingsGenerateTitles, - prefs!.getBool("generateTitles") ?? true, (value) { - selectionHaptic(); - prefs!.setBool("generateTitles", value); - setState(() {}); - }), + context, + AppLocalizations.of(context).settingsGenerateTitles, + prefs!.getBool("generateTitles") ?? true, + (value) { + selectionHaptic(); + prefs!.setBool("generateTitles", value); + setState(() {}); + }, + ), toggle( - context, - AppLocalizations.of(context).settingsEnableEditing, - prefs!.getBool("enableEditing") ?? true, (value) { - selectionHaptic(); - prefs!.setBool("enableEditing", value); - setState(() {}); - }), + context, + AppLocalizations.of(context).settingsEnableEditing, + prefs!.getBool("enableEditing") ?? true, + (value) { + selectionHaptic(); + prefs!.setBool("enableEditing", value); + setState(() {}); + }, + ), toggle( - context, - AppLocalizations.of(context).settingsAskBeforeDelete, - prefs!.getBool("askBeforeDeletion") ?? false, - (value) { - selectionHaptic(); - prefs!.setBool("askBeforeDeletion", value); - setState(() {}); - }), + context, + AppLocalizations.of(context).settingsAskBeforeDelete, + prefs!.getBool("askBeforeDeletion") ?? false, + (value) { + selectionHaptic(); + prefs!.setBool("askBeforeDeletion", value); + setState(() {}); + }, + ), toggle( - context, - AppLocalizations.of(context).settingsShowTips, - prefs!.getBool("tips") ?? true, (value) { - selectionHaptic(); - prefs!.setBool("tips", value); - setState(() {}); - }), + context, + AppLocalizations.of(context).settingsShowTips, + prefs!.getBool("tips") ?? true, + (value) { + selectionHaptic(); + prefs!.setBool("tips", value); + setState(() {}); + }, + ), titleDivider(context: context), toggle( + context, + AppLocalizations.of( context, - AppLocalizations.of(context) - .settingsKeepModelLoadedAlways, - int.parse(prefs!.getString("keepAlive") ?? "300") == - -1, (value) { - selectionHaptic(); - setState(() { - if (value) { - prefs!.setString("keepAlive", "-1"); - } else { - prefs!.setString("keepAlive", "300"); - } - }); - }), + ).settingsKeepModelLoadedAlways, + int.parse(prefs!.getString("keepAlive") ?? "300") == -1, + (value) { + selectionHaptic(); + setState(() { + if (value) { + prefs!.setString("keepAlive", "-1"); + } else { + prefs!.setString("keepAlive", "300"); + } + }); + }, + ), toggle( + context, + AppLocalizations.of( context, - AppLocalizations.of(context) - .settingsKeepModelLoadedNever, - int.parse(prefs!.getString("keepAlive") ?? "300") == - 0, (value) { - selectionHaptic(); - setState(() { - if (value) { - prefs!.setString("keepAlive", "0"); - } else { - prefs!.setString("keepAlive", "300"); - } - }); - }), + ).settingsKeepModelLoadedNever, + int.parse(prefs!.getString("keepAlive") ?? "300") == 0, + (value) { + selectionHaptic(); + setState(() { + if (value) { + prefs!.setString("keepAlive", "0"); + } else { + prefs!.setString("keepAlive", "300"); + } + }); + }, + ), button( - (int.parse(prefs!.getString("keepAlive") ?? "300") > - 0) - ? AppLocalizations.of(context) - .settingsKeepModelLoadedSet((int.parse( - prefs!.getString("keepAlive") ?? - "300") ~/ - 60) - .toString()) - : AppLocalizations.of(context) - .settingsKeepModelLoadedFor, - Icons.snooze_rounded, () async { - selectionHaptic(); - var loaded = false; - await showDialog( + (int.parse(prefs!.getString("keepAlive") ?? "300") > 0) + ? AppLocalizations.of( + context, + ).settingsKeepModelLoadedSet( + (int.parse( + prefs!.getString("keepAlive") ?? + "300", + ) ~/ + 60) + .toString(), + ) + : AppLocalizations.of( + context, + ).settingsKeepModelLoadedFor, + Icons.snooze_rounded, + () async { + selectionHaptic(); + var loaded = false; + await showDialog( context: context, builder: (context) { return Dialog( - alignment: desktopLayout(context) - ? null - : Alignment.bottomRight, - child: StatefulBuilder( - builder: (context, setLocalState) { + alignment: desktopLayout(context) + ? null + : Alignment.bottomRight, + child: StatefulBuilder( + builder: (context, setLocalState) { if (int.parse( - prefs!.getString("keepAlive") ?? - "0") <= + prefs!.getString("keepAlive") ?? + "0", + ) <= 0 && loaded == false) { prefs!.setString("keepAlive", "0"); WidgetsBinding.instance .addPostFrameCallback((timeStamp) { - setLocalState(() {}); - Future load() async { - try { - while (int.parse(prefs! - .getString("keepAlive")!) < - 300) { - await Future.delayed( - const Duration( - milliseconds: 5)); - prefs!.setString( + setLocalState(() {}); + Future load() async { + try { + while (int.parse( + prefs!.getString( + "keepAlive", + )!, + ) < + 300) { + await Future.delayed( + const Duration( + milliseconds: 5, + ), + ); + prefs!.setString( + "keepAlive", + (int.parse( + prefs!.getString( + "keepAlive", + )!, + ) + + 30) + .toString(), + ); + setLocalState(() {}); + setState(() {}); + } + prefs!.setString( "keepAlive", - (int.parse(prefs!.getString( - "keepAlive")!) + - 30) - .toString()); - setLocalState(() {}); - setState(() {}); + "300", + ); + loaded = true; + } catch (_) { + prefs!.setString( + "keepAlive", + "300", + ); + loaded = true; + } } - prefs! - .setString("keepAlive", "300"); - loaded = true; - } catch (_) { - prefs! - .setString("keepAlive", "300"); - loaded = true; - } - } - load(); - }); + load(); + }); } else { loaded = true; } return Padding( padding: const EdgeInsets.all(16), child: Theme( - data: (prefs?.getBool( - "useDeviceTheme") ?? + data: + (prefs?.getBool("useDeviceTheme") ?? false) ? Theme.of(context) : ThemeData.from( colorScheme: ColorScheme.fromSeed( - seedColor: Colors.black, - brightness: - Theme.of(context) - .colorScheme - .brightness)), + seedColor: Colors.black, + brightness: Theme.of( + context, + ).colorScheme.brightness, + ), + ), child: DurationPicker( - duration: Duration( - seconds: int.parse(prefs! - .getString( - "keepAlive") ?? - "300")), - baseUnit: BaseUnit.minute, - lowerBound: - const Duration(minutes: 1), - upperBound: - const Duration(minutes: 60), - onChange: (value) { - if (!loaded) return; - if (value.inSeconds == 0) return; - prefs!.setString("keepAlive", - value.inSeconds.toString()); - setLocalState(() {}); - setState(() {}); - }), + duration: Duration( + seconds: int.parse( + prefs!.getString("keepAlive") ?? + "300", + ), + ), + baseUnit: BaseUnit.minute, + lowerBound: const Duration( + minutes: 1, + ), + upperBound: const Duration( + minutes: 60, + ), + onChange: (value) { + if (!loaded) return; + if (value.inSeconds == 0) return; + prefs!.setString( + "keepAlive", + value.inSeconds.toString(), + ); + setLocalState(() {}); + setState(() {}); + }, + ), ), ); - })); - }); - }), + }, + ), + ); + }, + ); + }, + ), titleDivider(context: context), button( - AppLocalizations.of(context) - .settingsTimeoutMultiplier, - Icons.info_rounded, - null, - iconAfterwards: true, - context: context, - alwaysMobileDescription: true, - description: - "\n${AppLocalizations.of(context).settingsTimeoutMultiplierDescription}"), + AppLocalizations.of(context).settingsTimeoutMultiplier, + Icons.info_rounded, + null, + iconAfterwards: true, + context: context, + alwaysMobileDescription: true, + description: + "\n${AppLocalizations.of(context).settingsTimeoutMultiplierDescription}", + ), Slider( - value: prefs!.getDouble("timeoutMultiplier") ?? 1, - min: 0.5, - divisions: 19, - max: 10, - label: (prefs!.getDouble("timeoutMultiplier") ?? 1) - .toString() - .removeSuffix(".0"), - onChanged: (value) { - selectionHaptic(); - prefs!.setDouble("timeoutMultiplier", value); - setState(() {}); - }), + value: prefs!.getDouble("timeoutMultiplier") ?? 1, + min: 0.5, + divisions: 19, + max: 10, + label: (prefs!.getDouble("timeoutMultiplier") ?? 1) + .toString() + .removeSuffix(".0"), + onChanged: (value) { + selectionHaptic(); + prefs!.setDouble("timeoutMultiplier", value); + setState(() {}); + }, + ), button( - AppLocalizations.of(context) - .settingsTimeoutMultiplierExample, - Icons.calculate_rounded, - null, - onlyDesktopDescription: false, - // making it complicated because web is weird and doesn't like to round numbers - description: "\n${() { - var value = - prefs!.getDouble("timeoutMultiplier") ?? 1; - if (value == 10) { - return "${value.round()}."; - } else { - if (!value.toString().contains(".")) { - return "${value.toString()}.0"; + AppLocalizations.of( + context, + ).settingsTimeoutMultiplierExample, + Icons.calculate_rounded, + null, + onlyDesktopDescription: false, + // making it complicated because web is weird and doesn't like to round numbers + description: + "\n${() { + final value = prefs!.getDouble("timeoutMultiplier") ?? 1; + if (value == 10) { + return "${value.round()}."; + } else { + if (!value.toString().contains(".")) { + return "${value.toString()}.0"; + } + return value; } - return value; - } - }.call()} x 30s = ${((prefs!.getDouble("timeoutMultiplier") ?? 1) * 30).round()}s ${secondsBeautify((prefs!.getDouble("timeoutMultiplier") ?? 1) * 30)}"), + }.call()} x 30s = ${((prefs!.getDouble("timeoutMultiplier") ?? 1) * 30).round()}s ${secondsBeautify((prefs!.getDouble("timeoutMultiplier") ?? 1) * 30)}", + ), titleDivider(context: context), toggle( + context, + AppLocalizations.of( context, - AppLocalizations.of(context) - .settingsEnableHapticFeedback, - prefs!.getBool("enableHaptic") ?? true, (value) { - prefs!.setBool("enableHaptic", value); - selectionHaptic(); - setState(() {}); - }), + ).settingsEnableHapticFeedback, + prefs!.getBool("enableHaptic") ?? true, + (value) { + prefs!.setBool("enableHaptic", value); + selectionHaptic(); + setState(() {}); + }, + ), desktopFeature() ? toggle( context, - AppLocalizations.of(context) - .settingsMaximizeOnStart, + AppLocalizations.of( + context, + ).settingsMaximizeOnStart, prefs!.getBool("maximizeOnStart") ?? false, (value) { - selectionHaptic(); - prefs!.setBool("maximizeOnStart", value); - setState(() {}); - }) + selectionHaptic(); + prefs!.setBool("maximizeOnStart", value); + setState(() {}); + }, + ) : const SizedBox.shrink(), const SizedBox(height: 8), SegmentedButton( - segments: [ - ButtonSegment( - value: "dark", - label: Text(AppLocalizations.of(context) - .settingsBrightnessDark), - icon: const Icon(Icons.brightness_4_rounded)), - ButtonSegment( - value: "system", - label: Text(AppLocalizations.of(context) - .settingsBrightnessSystem), - icon: - const Icon(Icons.brightness_auto_rounded)), - ButtonSegment( - value: "light", - label: Text(AppLocalizations.of(context) - .settingsBrightnessLight), - icon: const Icon(Icons.brightness_high_rounded)) - ], - selected: { - prefs!.getString("brightness") ?? "system" - }, - onSelectionChanged: (p0) async { - selectionHaptic(); - await prefs! - .setString("brightness", p0.elementAt(0)); - setMainAppState!(() {}); - setState(() {}); - }), + segments: [ + ButtonSegment( + value: "dark", + label: Text( + AppLocalizations.of( + context, + ).settingsBrightnessDark, + ), + icon: const Icon(Icons.brightness_4_rounded), + ), + ButtonSegment( + value: "system", + label: Text( + AppLocalizations.of( + context, + ).settingsBrightnessSystem, + ), + icon: const Icon(Icons.brightness_auto_rounded), + ), + ButtonSegment( + value: "light", + label: Text( + AppLocalizations.of( + context, + ).settingsBrightnessLight, + ), + icon: const Icon(Icons.brightness_high_rounded), + ), + ], + selected: {prefs!.getString("brightness") ?? "system"}, + onSelectionChanged: (p0) async { + selectionHaptic(); + await prefs!.setString("brightness", p0.elementAt(0)); + setMainAppState!(() {}); + setState(() {}); + }, + ), AnimatedContainer( - duration: const Duration(milliseconds: 200), - height: desktopLayoutNotRequired(context) ? 16 : 8), + duration: const Duration(milliseconds: 200), + height: desktopLayoutNotRequired(context) ? 16 : 8, + ), (colorSchemeLight != null && colorSchemeDark != null) ? SegmentedButton( segments: [ - ButtonSegment( - value: "device", - label: Text(AppLocalizations.of(context) - .settingsThemeDevice), - icon: const Icon(Icons.devices_rounded)), - ButtonSegment( - value: "ollama", - label: Text(AppLocalizations.of(context) - .settingsThemeOllama), - icon: const ImageIcon( - AssetImage("assets/logo512.png"))) - ], + ButtonSegment( + value: "device", + label: Text( + AppLocalizations.of( + context, + ).settingsThemeDevice, + ), + icon: const Icon(Icons.devices_rounded), + ), + ButtonSegment( + value: "ollama", + label: Text( + AppLocalizations.of( + context, + ).settingsThemeOllama, + ), + icon: const ImageIcon( + AssetImage("assets/logo512.png"), + ), + ), + ], selected: { - (prefs?.getBool("useDeviceTheme") ?? false) - ? "device" - : "ollama" - }, + (prefs?.getBool("useDeviceTheme") ?? false) + ? "device" + : "ollama", + }, onSelectionChanged: (p0) async { selectionHaptic(); - await prefs!.setBool("useDeviceTheme", - p0.elementAt(0) == "device"); + await prefs!.setBool( + "useDeviceTheme", + p0.elementAt(0) == "device", + ); setMainAppState!(() {}); setState(() {}); - }) + }, + ) : const SizedBox.shrink(), titleDivider(), button( - AppLocalizations.of(context).settingsTemporaryFixes, - Icons.fast_forward_rounded, () { - selectionHaptic(); - showModalBottomSheet( + AppLocalizations.of(context).settingsTemporaryFixes, + Icons.fast_forward_rounded, + () { + selectionHaptic(); + showModalBottomSheet( context: context, builder: (context) { return StatefulBuilder( - builder: (context, setState) { - return Container( - width: double.infinity, - padding: EdgeInsets.only( + builder: (context, setState) { + return Container( + width: double.infinity, + padding: EdgeInsets.only( left: 16, right: 16, top: 16, - bottom: desktopLayout(context) ? 16 : 0), - child: Column( + bottom: desktopLayout(context) ? 16 : 0, + ), + child: Column( mainAxisSize: MainAxisSize.min, children: [ button( - AppLocalizations.of(context) - .settingsTemporaryFixesDescription, - Icons.info_rounded, - null, - color: Colors.grey.harmonizeWith( - Theme.of(context) - .colorScheme - .primary)), + AppLocalizations.of( + context, + ).settingsTemporaryFixesDescription, + Icons.info_rounded, + null, + color: Colors.grey.harmonizeWith( + Theme.of( + context, + ).colorScheme.primary, + ), + ), button( - AppLocalizations.of(context) - .settingsTemporaryFixesInstructions, - Icons.warning_rounded, - null, - color: Colors.orange.harmonizeWith( - Theme.of(context) - .colorScheme - .primary)), + AppLocalizations.of( + context, + ).settingsTemporaryFixesInstructions, + Icons.warning_rounded, + null, + color: Colors.orange.harmonizeWith( + Theme.of( + context, + ).colorScheme.primary, + ), + ), titleDivider(), // Text( // AppLocalizations.of(context)! @@ -449,37 +545,57 @@ class _ScreenSettingsInterfaceState extends State { // style: const TextStyle( // color: Colors.grey)), toggle( - context, - "Fixing code block not scrollable", - prefs!.getBool( - "fixCodeblockScroll") ?? - false, (value) { - selectionHaptic(); - prefs!.setBool( - "fixCodeblockScroll", value); - if ((prefs!.getBool( - "fixCodeblockScroll") ?? - false) == - false) { - prefs!.remove("fixCodeblockScroll"); - } - setState(() {}); - }, onLongTap: () { - selectionHaptic(); - launchUrl(Uri.parse( - "https://github.com/JHubi1/ollama-app/issues/26")); - }), - const SizedBox(height: 16) - ]), - ); - }); - }); - }), - const SizedBox(height: 16) - ]), - ) - ])), - )), + context, + "Fixing code block not scrollable", + prefs!.getBool( + "fixCodeblockScroll", + ) ?? + false, + (value) { + selectionHaptic(); + prefs!.setBool( + "fixCodeblockScroll", + value, + ); + if ((prefs!.getBool( + "fixCodeblockScroll", + ) ?? + false) == + false) { + prefs!.remove( + "fixCodeblockScroll", + ); + } + setState(() {}); + }, + onLongTap: () { + selectionHaptic(); + launchUrl( + Uri.parse( + "https://github.com/JHubi1/ollama-app/issues/26", + ), + ); + }, + ), + const SizedBox(height: 16), + ], + ), + ); + }, + ); + }, + ); + }, + ), + const SizedBox(height: 16), + ], + ), + ), + ], + ), + ), + ), + ), ); } } diff --git a/lib/screens/settings/voice.dart b/lib/screens/settings/voice.dart index 21e26f7..1de99ff 100644 --- a/lib/screens/settings/voice.dart +++ b/lib/screens/settings/voice.dart @@ -32,7 +32,7 @@ class _ScreenSettingsVoiceState extends State { bool dialogMustLoad = true; Future load() async { - var tmp = await speech.locales(); + final tmp = await speech.locales(); languageOptionIds = tmp.map((e) => e.localeId); languageOptions = tmp.map((e) => e.name); @@ -43,8 +43,9 @@ class _ScreenSettingsVoiceState extends State { voiceLanguageOptions = await voice.getLanguages as List; for (var i = 0; i < languageOptionIds.length; i++) { - if (voiceLanguageOptions - .contains(languageOptionIds.elementAt(i).replaceAll("_", "-"))) { + if (voiceLanguageOptions.contains( + languageOptionIds.elementAt(i).replaceAll("_", "-"), + )) { voiceLanguageOptionsAvailable.add(languageOptionIds.elementAt(i)); } } @@ -63,298 +64,381 @@ class _ScreenSettingsVoiceState extends State { return WindowBorder( color: Theme.of(context).colorScheme.surface, child: Scaffold( - appBar: AppBar( - title: Badge( - label: Text( - AppLocalizations.of(context).settingsExperimentalBeta), - offset: const Offset(20, -4), - backgroundColor: Theme.of(context).colorScheme.primary, - textColor: Theme.of(context).colorScheme.onPrimary, - child: - Text(AppLocalizations.of(context).settingsTitleVoice))), - body: Padding( - padding: const EdgeInsets.only(left: 16, right: 16), - child: Column(children: [ - Expanded( - child: ListView(children: [ + appBar: AppBar( + title: Badge( + label: Text(AppLocalizations.of(context).settingsExperimentalBeta), + offset: const Offset(20, -4), + backgroundColor: Theme.of(context).colorScheme.primary, + textColor: Theme.of(context).colorScheme.onPrimary, + child: Text(AppLocalizations.of(context).settingsTitleVoice), + ), + ), + body: Padding( + padding: const EdgeInsets.only(left: 16, right: 16), + child: Column( + children: [ + Expanded( + child: ListView( + children: [ (permissionLoading || (permissionBluetooth && permissionRecord && voiceSupported && voiceLanguageOptionsAvailable.contains( - prefs!.getString("voiceLanguage") ?? - "en_US"))) + prefs!.getString("voiceLanguage") ?? "en_US", + ))) ? const SizedBox.shrink() : button( permissionLoading - ? AppLocalizations.of(context) - .settingsVoicePermissionLoading + ? AppLocalizations.of( + context, + ).settingsVoicePermissionLoading : (!voiceLanguageOptionsAvailable.contains( - prefs!.getString("voiceLanguage") ?? - "en_US") && - (prefs!.getBool("voiceModeEnabled") ?? - false)) - ? AppLocalizations.of(context) - .settingsVoiceTtsNotSupported - : !(permissionBluetooth && permissionRecord) - ? AppLocalizations.of(context) - .settingsVoicePermissionNot - : AppLocalizations.of(context) - .settingsVoiceNotSupported, - Icons.info_rounded, () { - selectionHaptic(); - if (permissionLoading) return; - if (!(permissionBluetooth && permissionRecord)) { - Future load() async { - try { - if (await Permission - .bluetooth.isPermanentlyDenied || - await Permission - .microphone.isPermanentlyDenied) { - await openAppSettings(); - } - permissionRecord = await Permission.microphone - .request() - .isGranted; - permissionBluetooth = await Permission - .bluetoothConnect - .request() - .isGranted; - permissionLoading = false; - - if (permissionBluetooth && permissionRecord) { - voiceSupported = await speech.initialize(); - } - - setState(() {}); - } catch (_) { - permissionLoading = false; + prefs!.getString("voiceLanguage") ?? + "en_US", + ) && + (prefs!.getBool("voiceModeEnabled") ?? + false)) + ? AppLocalizations.of( + context, + ).settingsVoiceTtsNotSupported + : !(permissionBluetooth && permissionRecord) + ? AppLocalizations.of( + context, + ).settingsVoicePermissionNot + : AppLocalizations.of( + context, + ).settingsVoiceNotSupported, + Icons.info_rounded, + () { + selectionHaptic(); + if (permissionLoading) return; + if (!(permissionBluetooth && permissionRecord)) { + Future load() async { try { + if (await Permission + .bluetooth + .isPermanentlyDenied || + await Permission + .microphone + .isPermanentlyDenied) { + await openAppSettings(); + } + permissionRecord = await Permission + .microphone + .request() + .isGranted; + permissionBluetooth = await Permission + .bluetoothConnect + .request() + .isGranted; + permissionLoading = false; + + if (permissionBluetooth && + permissionRecord) { + voiceSupported = await speech + .initialize(); + } + setState(() {}); - } catch (_) {} + } catch (_) { + permissionLoading = false; + try { + setState(() {}); + } catch (_) {} + } + } + + load(); + } else if (!voiceLanguageOptions.contains( + prefs!.getString("voiceLanguage") ?? "en_US", + )) { + ScaffoldMessenger.of(context).showSnackBar( + SnackBar( + content: Text( + AppLocalizations.of( + context, + ).settingsVoiceTtsNotSupportedDescription, + ), + showCloseIcon: true, + ), + ); + } + }, + ), + toggle( + context, + AppLocalizations.of(context).settingsVoiceEnable, + prefs!.getBool("voiceModeEnabled") ?? false, + (value) { + selectionHaptic(); + prefs!.setBool("voiceModeEnabled", value); + setState(() {}); + }, + disabled: !voiceSupported, + ), + button( + ((prefs!.getString("voiceLanguage") ?? "") == "" || + languageOptions.isEmpty) + ? AppLocalizations.of(context).settingsVoiceNoLanguage + : () { + for ( + var i = 0; + i < languageOptionIds.length; + i++ + ) { + if (languageOptionIds.elementAt(i) == + prefs!.getString("voiceLanguage")) { + return languageOptions.elementAt(i); + } + } + return ""; + }(), + Icons.language_rounded, + () { + var usedIndex = -1; + Function? setModalState; + + selectionHaptic(); + + showModalBottomSheet( + context: context, + builder: (context) => StatefulBuilder( + builder: (context, setLocalState) { + setModalState = setLocalState; + + Future loadSelected() async { + await load(); + if ((prefs!.getString("voiceLanguage") ?? "") != + "") { + for ( + var i = 0; + i < languageOptionIds.length; + i++ + ) { + if (languageOptionIds.elementAt(i) == + (prefs!.getString("voiceLanguage") ?? + "")) { + setModalState!(() { + usedIndex = i; + }); + break; + } + } } } - load(); - } else if (!voiceLanguageOptions.contains( - prefs!.getString("voiceLanguage") ?? "en_US")) { - ScaffoldMessenger.of(context).showSnackBar(SnackBar( - content: Text(AppLocalizations.of(context) - .settingsVoiceTtsNotSupportedDescription), - showCloseIcon: true)); - } - }), - toggle( - context, - AppLocalizations.of(context).settingsVoiceEnable, - prefs!.getBool("voiceModeEnabled") ?? false, (value) { - selectionHaptic(); - prefs!.setBool("voiceModeEnabled", value); - setState(() {}); - }, disabled: !voiceSupported), - button( - ((prefs!.getString("voiceLanguage") ?? "") == "" || - languageOptions.isEmpty) - ? AppLocalizations.of(context) - .settingsVoiceNoLanguage - : () { - for (var i = 0; - i < languageOptionIds.length; - i++) { - if (languageOptionIds.elementAt(i) == - prefs!.getString("voiceLanguage")) { - return languageOptions.elementAt(i); - } - } - return ""; - }(), - Icons.language_rounded, () { - var usedIndex = -1; - Function? setModalState; + if (dialogMustLoad) { + loadSelected(); + dialogMustLoad = false; + } - selectionHaptic(); - - showModalBottomSheet( - context: context, - builder: - (context) => StatefulBuilder( - builder: (context, setLocalState) { - setModalState = setLocalState; - - Future loadSelected() async { - await load(); - if ((prefs!.getString("voiceLanguage") ?? - "") != - "") { - for (var i = 0; - i < languageOptionIds.length; - i++) { - if (languageOptionIds.elementAt(i) == - (prefs!.getString( - "voiceLanguage") ?? - "")) { - setModalState!(() { - usedIndex = i; - }); - break; - } - } - } - } - - if (dialogMustLoad) { - loadSelected(); - dialogMustLoad = false; - } - - return PopScope( - onPopInvokedWithResult: - (didPop, result) { - if (usedIndex == -1) return; - prefs!.setString( - "voiceLanguage", - languageOptionIds - .elementAt(usedIndex)); - setState(() { - dialogMustLoad = true; - }); - }, - child: Container( - width: double.infinity, - padding: const EdgeInsets.only( - left: 16, - right: 16, - top: 16, - bottom: 0), - child: Column( - mainAxisSize: MainAxisSize.min, - children: [ - Container( - width: ((Platform - .isWindows || - Platform - .isLinux || - Platform - .isMacOS) && - MediaQuery.of( - context) - .size - .width >= - 1000) - ? 300 - : double.infinity, - constraints: BoxConstraints( - maxHeight: - MediaQuery.of( - context) - .size - .height * - 0.4), - child: - SingleChildScrollView( - scrollDirection: - Axis.vertical, - child: Wrap( - spacing: 5.0, - alignment: - WrapAlignment - .center, - children: List< - Widget>.generate( - languageOptionIds - .length, - (int index) { - return ChoiceChip( - label: Text( - languageOptions - .elementAt(index)), - selected: - usedIndex == - index, - avatar: (usedIndex == - index) - ? null - : (voiceLanguageOptionsAvailable.contains(languageOptionIds.elementAt(index))) - ? const Icon(Icons.spatial_tracking_rounded) - : null, - checkmarkColor: (usedIndex == index && - !(prefs?.getBool("useDeviceTheme") ?? - false)) - ? ((MediaQuery.of(context).platformBrightness == Brightness.light) - ? themeLight().colorScheme.secondary - : themeDark().colorScheme.secondary) - : null, - labelStyle: (usedIndex == index && - !(prefs?.getBool("useDeviceTheme") ?? - false)) - ? TextStyle( - color: (MediaQuery.of(context).platformBrightness == Brightness.light) ? themeLight().colorScheme.secondary : themeDark().colorScheme.secondary) - : null, - selectedColor: (prefs?.getBool("useDeviceTheme") ?? - false) - ? null - : (MediaQuery.of(context).platformBrightness == Brightness.light) - ? themeLight().colorScheme.primary - : themeDark().colorScheme.primary, - onSelected: - (bool - selected) { - selectionHaptic(); - setLocalState( - () { - usedIndex = selected - ? index - : -1; - }); - }, - ); - }, - ).toList(), - ))) - ]))); - })); - }, - disabled: !voiceSupported || - !(prefs!.getBool("voiceModeEnabled") ?? false)), + return PopScope( + onPopInvokedWithResult: (didPop, result) { + if (usedIndex == -1) return; + prefs!.setString( + "voiceLanguage", + languageOptionIds.elementAt(usedIndex), + ); + setState(() { + dialogMustLoad = true; + }); + }, + child: Container( + width: double.infinity, + padding: const EdgeInsets.only( + left: 16, + right: 16, + top: 16, + bottom: 0, + ), + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + Container( + width: + ((Platform.isWindows || + Platform.isLinux || + Platform.isMacOS) && + MediaQuery.of( + context, + ).size.width >= + 1000) + ? 300 + : double.infinity, + constraints: BoxConstraints( + maxHeight: + MediaQuery.of( + context, + ).size.height * + 0.4, + ), + child: SingleChildScrollView( + scrollDirection: Axis.vertical, + child: Wrap( + spacing: 5.0, + alignment: WrapAlignment.center, + children: List.generate( + languageOptionIds.length, + (int index) { + return ChoiceChip( + label: Text( + languageOptions.elementAt( + index, + ), + ), + selected: usedIndex == index, + avatar: (usedIndex == index) + ? null + : (voiceLanguageOptionsAvailable + .contains( + languageOptionIds + .elementAt( + index, + ), + )) + ? const Icon( + Icons + .spatial_tracking_rounded, + ) + : null, + checkmarkColor: + (usedIndex == index && + !(prefs?.getBool( + "useDeviceTheme", + ) ?? + false)) + ? ((MediaQuery.of( + context, + ).platformBrightness == + Brightness + .light) + ? themeLight() + .colorScheme + .secondary + : themeDark() + .colorScheme + .secondary) + : null, + labelStyle: + (usedIndex == index && + !(prefs?.getBool( + "useDeviceTheme", + ) ?? + false)) + ? TextStyle( + color: + (MediaQuery.of( + context, + ).platformBrightness == + Brightness + .light) + ? themeLight() + .colorScheme + .secondary + : themeDark() + .colorScheme + .secondary, + ) + : null, + selectedColor: + (prefs?.getBool( + "useDeviceTheme", + ) ?? + false) + ? null + : (MediaQuery.of( + context, + ).platformBrightness == + Brightness.light) + ? themeLight() + .colorScheme + .primary + : themeDark() + .colorScheme + .primary, + onSelected: (bool selected) { + selectionHaptic(); + setLocalState(() { + usedIndex = selected + ? index + : -1; + }); + }, + ); + }, + ).toList(), + ), + ), + ), + ], + ), + ), + ); + }, + ), + ); + }, + disabled: + !voiceSupported || + !(prefs!.getBool("voiceModeEnabled") ?? false), + ), titleDivider(), toggle( - context, - AppLocalizations.of(context).settingsVoiceLimitLanguage, - prefs!.getBool("voiceLimitLanguage") ?? true, (value) { - selectionHaptic(); - prefs!.setBool("voiceLimitLanguage", value); - setState(() {}); - }, - disabled: !voiceSupported || - !(prefs!.getBool("voiceModeEnabled") ?? false)), + context, + AppLocalizations.of(context).settingsVoiceLimitLanguage, + prefs!.getBool("voiceLimitLanguage") ?? true, + (value) { + selectionHaptic(); + prefs!.setBool("voiceLimitLanguage", value); + setState(() {}); + }, + disabled: + !voiceSupported || + !(prefs!.getBool("voiceModeEnabled") ?? false), + ), toggle( - context, - AppLocalizations.of(context).settingsVoicePunctuation, - prefs!.getBool("aiPunctuation") ?? true, (value) { - selectionHaptic(); - prefs!.setBool("aiPunctuation", value); - setState(() {}); - }, - disabled: !voiceSupported || - !(prefs!.getBool("voiceModeEnabled") ?? false)) - ]), + context, + AppLocalizations.of(context).settingsVoicePunctuation, + prefs!.getBool("aiPunctuation") ?? true, + (value) { + selectionHaptic(); + prefs!.setBool("aiPunctuation", value); + setState(() {}); + }, + disabled: + !voiceSupported || + !(prefs!.getBool("voiceModeEnabled") ?? false), + ), + ], ), - const SizedBox(height: 8), - button( - AppLocalizations.of(context) - .settingsExperimentalBetaFeature, - Icons.warning_rounded, - null, - color: Colors.orange - .harmonizeWith(Theme.of(context).colorScheme.primary), - onLongTap: () { + ), + const SizedBox(height: 8), + button( + AppLocalizations.of(context).settingsExperimentalBetaFeature, + Icons.warning_rounded, + null, + color: Colors.orange.harmonizeWith( + Theme.of(context).colorScheme.primary, + ), + onLongTap: () { selectionHaptic(); - ScaffoldMessenger.of(context).showSnackBar(SnackBar( - content: Text(AppLocalizations.of(context) - .settingsExperimentalBetaDescription), - showCloseIcon: true)); - }) - ]))), + ScaffoldMessenger.of(context).showSnackBar( + SnackBar( + content: Text( + AppLocalizations.of( + context, + ).settingsExperimentalBetaDescription, + ), + showCloseIcon: true, + ), + ); + }, + ), + ], + ), + ), + ), ); } } diff --git a/lib/screens/voice.dart b/lib/screens/voice.dart index 913e8fa..39e22de 100644 --- a/lib/screens/voice.dart +++ b/lib/screens/voice.dart @@ -37,36 +37,39 @@ class _ScreenVoiceState extends State { speaking = true; sttDone = false; }); - var textOldOld = text; + final textOldOld = text; var textOld = ""; text = ""; speech.listen( - localeId: prefs!.getString("voiceLanguage") ?? "en-US", - listenOptions: - stt.SpeechListenOptions(listenMode: stt.ListenMode.dictation), - onResult: (result) { - lightHaptic(); - if (!speaking) return; - setState(() { - sttDone = result.finalResult; - text = result.recognizedWords; - }); - }, - pauseFor: const Duration(seconds: 3)); + localeId: prefs!.getString("voiceLanguage") ?? "en-US", + listenOptions: stt.SpeechListenOptions( + listenMode: stt.ListenMode.dictation, + ), + onResult: (result) { + lightHaptic(); + if (!speaking) return; + setState(() { + sttDone = result.finalResult; + text = result.recognizedWords; + }); + }, + pauseFor: const Duration(seconds: 3), + ); var start = DateTime.now(); var timeout = false; - await Future.doWhile(() => - Future.delayed(const Duration(milliseconds: 1)).then((_) { - if (textOld != text) { - start = DateTime.now(); - } - timeout = - DateTime.now().difference(start) >= const Duration(seconds: 3); - textOld = text; - return !sttDone && speaking && !timeout; - })); + await Future.doWhile( + () => Future.delayed(const Duration(milliseconds: 1)).then((_) { + if (textOld != text) { + start = DateTime.now(); + } + timeout = + DateTime.now().difference(start) >= const Duration(seconds: 3); + textOld = text; + return !sttDone && speaking && !timeout; + }), + ); if (!sttDone || timeout) { sttDone = true; speech.stop(); @@ -103,17 +106,21 @@ class _ScreenVoiceState extends State { aiThinking = true; try { if (prefs!.getBool("aiPunctuation") ?? true) { - var generated = await ollamaClient + final generated = await ollamaClient .generateCompletion( request: llama.GenerateCompletionRequest( - model: model!, - prompt: - "Add punctuation and syntax to the following sentence. You must not change order of words or a word in itself! You must not add any word or phrase or remove one! Do not change between formal and personal form, keep the original one!\n\n$text", - keepAlive: int.parse(prefs!.getString("keepAlive") ?? "300")), + model: model!, + prompt: + "Add punctuation and syntax to the following sentence. You must not change order of words or a word in itself! You must not add any word or phrase or remove one! Do not change between formal and personal form, keep the original one!\n\n$text", + keepAlive: int.parse(prefs!.getString("keepAlive") ?? "300"), + ), ) - .timeout(Duration( + .timeout( + Duration( seconds: (10.0 * (prefs!.getDouble("timeoutMultiplier") ?? 1.0)) - .round())); + .round(), + ), + ); setState(() { text = generated.response!; }); @@ -121,50 +128,59 @@ class _ScreenVoiceState extends State { } catch (_) {} // ignore: use_build_context_synchronously - send(text, context, setState, onStream: (currentText, done) async { - setState(() { - aiText = currentText; - lightHaptic(); - updateScrollState(); - }); - if (done) { - aiThinking = false; - heavyHaptic(); + send( + text, + context, + setState, + onStream: (currentText, done) async { + setState(() { + aiText = currentText; + lightHaptic(); + updateScrollState(); + }); + if (done) { + aiThinking = false; + heavyHaptic(); - if (currentText.isEmpty) { - text = ""; - speaking = false; - try { - setState(() {}); - } catch (_) {} - return; + if (currentText.isEmpty) { + text = ""; + speaking = false; + try { + setState(() {}); + } catch (_) {} + return; + } + + if ((await voice.getLanguages as List).contains( + (prefs!.getString("voiceLanguage") ?? "en_US").replaceAll("_", "-"), + )) { + voice + ..setLanguage( + (prefs!.getString("voiceLanguage") ?? "en_US").replaceAll( + "_", + "-", + ), + ) + ..setSpeechRate(0.6) + ..setCompletionHandler(() async { + speaking = false; + try { + setState(() {}); + } catch (_) {} + process(); + }); + final tmp = aiText + ..replaceAll("-", ".") + ..replaceAll("*", "."); + + voice.speak(tmp); + } } - - if ((await voice.getLanguages as List).contains( - (prefs!.getString("voiceLanguage") ?? "en_US") - .replaceAll("_", "-"))) { - voice - ..setLanguage((prefs!.getString("voiceLanguage") ?? "en_US") - .replaceAll("_", "-")) - ..setSpeechRate(0.6) - ..setCompletionHandler(() async { - speaking = false; - try { - setState(() {}); - } catch (_) {} - process(); - }); - var tmp = aiText - ..replaceAll("-", ".") - ..replaceAll("*", "."); - - voice.speak(tmp); - } - } - }, - addToSystem: (prefs!.getBool("voiceLimitLanguage") ?? true) - ? "You must write in the following language: ${prefs!.getString("voiceLanguage") ?? "en_US"}!" - : null); + }, + addToSystem: (prefs!.getBool("voiceLimitLanguage") ?? true) + ? "You must write in the following language: ${prefs!.getString("voiceLanguage") ?? "en_US"}!" + : null, + ); } final ScrollController scrollController = ScrollController(); @@ -183,7 +199,7 @@ class _ScreenVoiceState extends State { scrollController.addListener(updateScrollState); Future load() async { - var tmp = await speech.locales(); + final tmp = await speech.locales(); languageOptionIds = tmp.map((e) => e.localeId); languageOptions = tmp.map((e) => e.name); setState(() {}); @@ -202,198 +218,235 @@ class _ScreenVoiceState extends State { @override Widget build(BuildContext context) { return Theme( - data: themeDark(), - child: PopScope( - canPop: !aiThinking, - onPopInvokedWithResult: (didPop, result) { - if (!didPop) return; - speaking = false; - voice.stop(); - if (chatUuid != null) { - loadChat(chatUuid!, setGlobalState!); - } - settingsOpen = false; - logoVisible = true; - }, - child: Scaffold( - appBar: AppBar( - scrolledUnderElevation: 0.0, - leading: IconButton( - enableFeedback: false, - onPressed: () { - Navigator.of(context).pop(); - }, - icon: const Icon(Icons.close_rounded, - color: Colors.grey)), - title: Row( - mainAxisSize: MainAxisSize.max, - children: [ - Expanded( - child: Text( - (model ?? - AppLocalizations.of(context) - .noSelectedModel) - .split(":")[0], - textAlign: TextAlign.center, - overflow: TextOverflow.fade, - style: const TextStyle( - fontFamily: "monospace", fontSize: 16))), - ], + data: themeDark(), + child: PopScope( + canPop: !aiThinking, + onPopInvokedWithResult: (didPop, result) { + if (!didPop) return; + speaking = false; + voice.stop(); + if (chatUuid != null) { + loadChat(chatUuid!, setGlobalState!); + } + settingsOpen = false; + logoVisible = true; + }, + child: Scaffold( + appBar: AppBar( + scrolledUnderElevation: 0.0, + leading: IconButton( + enableFeedback: false, + onPressed: () { + Navigator.of(context).pop(); + }, + icon: const Icon(Icons.close_rounded, color: Colors.grey), + ), + title: Row( + mainAxisSize: MainAxisSize.max, + children: [ + Expanded( + child: Text( + (model ?? AppLocalizations.of(context).noSelectedModel) + .split(":")[0], + textAlign: TextAlign.center, + overflow: TextOverflow.fade, + style: const TextStyle( + fontFamily: "monospace", + fontSize: 16, ), - actions: [ - IconButton( - enableFeedback: false, - onPressed: () { - speaking = false; - settingsOpen = false; - logoVisible = true; - Navigator.of(context).pushReplacement( - MaterialPageRoute( - builder: (context) => - const ScreenSettingsVoice())); - }, - icon: const Icon( - Icons.settings_rounded, - color: Colors.grey, - )) - ]), - body: SafeArea( - child: Column(mainAxisSize: MainAxisSize.max, children: [ - Expanded( - child: Column(mainAxisSize: MainAxisSize.max, children: [ - Expanded( - child: Padding( + ), + ), + ], + ), + actions: [ + IconButton( + enableFeedback: false, + onPressed: () { + speaking = false; + settingsOpen = false; + logoVisible = true; + Navigator.of(context).pushReplacement( + MaterialPageRoute( + builder: (context) => const ScreenSettingsVoice(), + ), + ); + }, + icon: const Icon(Icons.settings_rounded, color: Colors.grey), + ), + ], + ), + body: SafeArea( + child: Column( + mainAxisSize: MainAxisSize.max, + children: [ + Expanded( + child: Column( + mainAxisSize: MainAxisSize.max, + children: [ + Expanded( + child: Padding( padding: const EdgeInsets.only(left: 16, right: 16), child: Center( - child: Text(text, - textAlign: TextAlign.center, - overflow: TextOverflow.fade, - style: const TextStyle( - color: Colors.grey, - fontFamily: "monospace"))), - )) - ]), - ), - Expanded( - child: Center( - child: DateTimeLoopBuilder( - timeUnit: TimeUnit.seconds, - builder: (context, dateTime, child) { - return SizedBox( - height: 96, - width: 96, - child: AnimatedScale( - scale: speaking - ? aiThinking - ? dateTime.second.isEven - ? 2.4 - : 2 - : 2 - : dateTime.second - .toString() - .endsWith("1") - ? 1.6 - : 1.4, - duration: aiThinking - ? const Duration(seconds: 1) - : const Duration(milliseconds: 200), - curve: Curves.easeInOut, - child: InkWell( - borderRadius: - BorderRadius.circular(48), - onTap: () { - if (speaking && !aiThinking) { - intendedStop = true; - speaking = false; - voice.stop(); - return; - } - process(); - }, - child: CircleAvatar( - backgroundColor: themeDark() + child: Text( + text, + textAlign: TextAlign.center, + overflow: TextOverflow.fade, + style: const TextStyle( + color: Colors.grey, + fontFamily: "monospace", + ), + ), + ), + ), + ), + ], + ), + ), + Expanded( + child: Center( + child: DateTimeLoopBuilder( + timeUnit: TimeUnit.seconds, + builder: (context, dateTime, child) { + return SizedBox( + height: 96, + width: 96, + child: AnimatedScale( + scale: speaking + ? aiThinking + ? dateTime.second.isEven + ? 2.4 + : 2 + : 2 + : dateTime.second.toString().endsWith("1") + ? 1.6 + : 1.4, + duration: aiThinking + ? const Duration(seconds: 1) + : const Duration(milliseconds: 200), + curve: Curves.easeInOut, + child: InkWell( + borderRadius: BorderRadius.circular(48), + onTap: () { + if (speaking && !aiThinking) { + intendedStop = true; + speaking = false; + voice.stop(); + return; + } + process(); + }, + child: CircleAvatar( + backgroundColor: themeDark().colorScheme.primary + .withAlpha(!speaking ? 200 : 255), + child: AnimatedSwitcher( + duration: const Duration(milliseconds: 200), + child: speaking + ? aiThinking + ? Icon( + Icons.auto_awesome_rounded, + color: themeDark() .colorScheme - .primary - .withAlpha( - !speaking ? 200 : 255), - child: AnimatedSwitcher( - duration: const Duration( - milliseconds: 200), - child: speaking - ? aiThinking - ? Icon(Icons.auto_awesome_rounded, - color: themeDark() - .colorScheme - .secondary, - key: const ValueKey( - "aiThinking")) - : sttDone - ? Icon(Icons.volume_up_rounded, - color: themeDark() - .colorScheme - .secondary, - key: const ValueKey( - "tts")) - : Icon(Icons.mic_rounded, - color: themeDark() - .colorScheme - .secondary, - key: const ValueKey("stt")) - : null)))), - ); - }))), - Expanded( - child: Column(mainAxisSize: MainAxisSize.max, children: [ - Expanded( - child: Stack( + .secondary, + key: const ValueKey( + "aiThinking", + ), + ) + : sttDone + ? Icon( + Icons.volume_up_rounded, + color: themeDark() + .colorScheme + .secondary, + key: const ValueKey("tts"), + ) + : Icon( + Icons.mic_rounded, + color: themeDark() + .colorScheme + .secondary, + key: const ValueKey("stt"), + ) + : null, + ), + ), + ), + ), + ); + }, + ), + ), + ), + Expanded( + child: Column( + mainAxisSize: MainAxisSize.max, + children: [ + Expanded( + child: Stack( children: [ ShaderMask( - shaderCallback: (Rect bounds) { - return LinearGradient( - begin: Alignment.bottomCenter, - end: Alignment.topCenter, - colors: const [ - Colors.transparent, - Colors.black - ], - stops: [0.0, atScrollEnd ? 0.0 : 0.1], - ).createShader(bounds); - }, - blendMode: BlendMode.dstIn, - child: SingleChildScrollView( - controller: scrollController, - child: Padding( - padding: const EdgeInsets.only( - left: 16, right: 16), - child: Center( - child: Text(aiText, - textAlign: TextAlign.center, - overflow: TextOverflow.fade, - style: const TextStyle( - fontFamily: - "monospace")))))), + shaderCallback: (Rect bounds) { + return LinearGradient( + begin: Alignment.bottomCenter, + end: Alignment.topCenter, + colors: const [ + Colors.transparent, + Colors.black, + ], + stops: [0.0, atScrollEnd ? 0.0 : 0.1], + ).createShader(bounds); + }, + blendMode: BlendMode.dstIn, + child: SingleChildScrollView( + controller: scrollController, + child: Padding( + padding: const EdgeInsets.only( + left: 16, + right: 16, + ), + child: Center( + child: Text( + aiText, + textAlign: TextAlign.center, + overflow: TextOverflow.fade, + style: const TextStyle( + fontFamily: "monospace", + ), + ), + ), + ), + ), + ), if (!atScrollEnd) Positioned( right: 0, bottom: 0, child: IconButton( - icon: const Icon( - Icons.arrow_downward_rounded, - color: Colors.grey), - onPressed: () { - scrollController.animateTo( - scrollController - .position.maxScrollExtent, - duration: - const Duration(milliseconds: 500), - curve: Curves.easeInOut); - }), - ) + icon: const Icon( + Icons.arrow_downward_rounded, + color: Colors.grey, + ), + onPressed: () { + scrollController.animateTo( + scrollController.position.maxScrollExtent, + duration: const Duration( + milliseconds: 500, + ), + curve: Curves.easeInOut, + ); + }, + ), + ), ], - )) - ]), - ) - ]), - )))); + ), + ), + ], + ), + ), + ], + ), + ), + ), + ), + ); } } diff --git a/lib/screens/welcome.dart b/lib/screens/welcome.dart index f2ef95c..e79c61d 100644 --- a/lib/screens/welcome.dart +++ b/lib/screens/welcome.dart @@ -27,84 +27,105 @@ class _ScreenWelcomeState extends State { precacheImage(const AssetImage("assets/welcome/3dark.png"), context); return Scaffold( - bottomNavigationBar: BottomSheet( - enableDrag: false, - backgroundColor: (Theme.of(context).brightness == Brightness.light) - ? Colors.grey[100] - : Colors.grey[900], - onClosing: () {}, - builder: (context) { - return Container( - width: double.infinity, - padding: const EdgeInsets.all(16), - child: Column(mainAxisSize: MainAxisSize.min, children: [ - SmoothPageIndicator( - controller: _pageController, - count: 3, - effect: ExpandingDotsEffect( - activeDotColor: (Theme.of(context).brightness == - Brightness.light) - ? themeLight().colorScheme.primary - : themeDark().colorScheme.primary)), - ])); - }), - floatingActionButtonLocation: FloatingActionButtonLocation.endDocked, - floatingActionButton: FloatingActionButton( - onPressed: () { - if (page < 2) { - _pageController.nextPage( - duration: const Duration(milliseconds: 250), - curve: Curves.easeInOut); - } else { - prefs!.setBool("welcomeFinished", true); - Navigator.pushReplacement( - context, - MaterialPageRoute( - builder: (context) => const ScreenMain())); - } - }, - child: (page < 2) - ? const Icon(Icons.arrow_forward) - : const Icon(Icons.check_rounded)), - body: SafeArea( - child: Column(children: [ - Expanded( - child: PageView( + bottomNavigationBar: BottomSheet( + enableDrag: false, + backgroundColor: (Theme.of(context).brightness == Brightness.light) + ? Colors.grey[100] + : Colors.grey[900], + onClosing: () {}, + builder: (context) { + return Container( + width: double.infinity, + padding: const EdgeInsets.all(16), + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + SmoothPageIndicator( controller: _pageController, - onPageChanged: (value) { - setState(() { - page = value; - }); - }, - children: [ - Center( + count: 3, + effect: ExpandingDotsEffect( + activeDotColor: + (Theme.of(context).brightness == Brightness.light) + ? themeLight().colorScheme.primary + : themeDark().colorScheme.primary, + ), + ), + ], + ), + ); + }, + ), + floatingActionButtonLocation: FloatingActionButtonLocation.endDocked, + floatingActionButton: FloatingActionButton( + onPressed: () { + if (page < 2) { + _pageController.nextPage( + duration: const Duration(milliseconds: 250), + curve: Curves.easeInOut, + ); + } else { + prefs!.setBool("welcomeFinished", true); + Navigator.pushReplacement( + context, + MaterialPageRoute(builder: (context) => const ScreenMain()), + ); + } + }, + child: (page < 2) + ? const Icon(Icons.arrow_forward) + : const Icon(Icons.check_rounded), + ), + body: SafeArea( + child: Column( + children: [ + Expanded( + child: PageView( + controller: _pageController, + onPageChanged: (value) { + setState(() { + page = value; + }); + }, + children: [ + Center( child: (Theme.of(context).brightness == Brightness.light) ? FadeInImage( placeholder: MemoryImage(kTransparentImage), - image: const AssetImage("assets/welcome/1.png")) + image: const AssetImage("assets/welcome/1.png"), + ) : FadeInImage( placeholder: MemoryImage(kTransparentImage), - image: - const AssetImage("assets/welcome/1dark.png"))), - Center( + image: const AssetImage("assets/welcome/1dark.png"), + ), + ), + Center( child: (Theme.of(context).brightness == Brightness.light) ? FadeInImage( placeholder: MemoryImage(kTransparentImage), - image: const AssetImage("assets/welcome/2.png")) + image: const AssetImage("assets/welcome/2.png"), + ) : FadeInImage( placeholder: MemoryImage(kTransparentImage), - image: - const AssetImage("assets/welcome/2dark.png"))), - Center( + image: const AssetImage("assets/welcome/2dark.png"), + ), + ), + Center( child: (Theme.of(context).brightness == Brightness.light) ? FadeInImage( placeholder: MemoryImage(kTransparentImage), - image: const AssetImage("assets/welcome/3.png")) + image: const AssetImage("assets/welcome/3.png"), + ) : FadeInImage( placeholder: MemoryImage(kTransparentImage), - image: - const AssetImage("assets/welcome/3dark.png"))) - ])), - ]))); + image: const AssetImage("assets/welcome/3dark.png"), + ), + ), + ], + ), + ), + ], + ), + ), + ); } } diff --git a/lib/services/chat.dart b/lib/services/chat.dart index a0ca1f5..0089133 100644 --- a/lib/services/chat.dart +++ b/lib/services/chat.dart @@ -3,11 +3,15 @@ import 'dart:convert'; import 'package:dartx/dartx.dart'; import 'package:flutter/material.dart'; -import 'package:ollama_dart/ollama_dart.dart' as ollama; +import 'package:image/image.dart' as img; +import 'package:mime/mime.dart'; +import 'package:ollama_dart/ollama_dart.dart' as llama; +import 'package:shared_preferences/shared_preferences.dart'; import 'package:uuid/uuid.dart'; import '../l10n/gen/app_localizations.dart'; import '../main.dart'; +import 'chat_database/chat_database.dart'; import 'clients.dart' as clients; import 'haptic.dart'; import 'model.dart'; @@ -36,6 +40,14 @@ abstract class Message extends ChangeNotifier { operator ==(Object other) => other is Message && other.id == id; @override int get hashCode => id.hashCode; + + static Message Function(Map json)? constructorForType( + String type, + ) => switch (type) { + "text" => TextMessage.fromJson, + "image" => ImageMessage.fromJson, + _ => null, + }; } class TextMessage extends Message { @@ -44,6 +56,12 @@ class TextMessage extends Message { String _content; String get content => _content; + String? _thinking; + String? get thinking => _thinking; + + ChatUsageStats? _stats; + ChatUsageStats? get stats => _stats; + bool _includesError = false; bool get includesError => _includesError; @@ -62,24 +80,49 @@ class TextMessage extends Message { "type": "text", "role": sender.name, "content": content, + "thinking": thinking, + "stats": stats?.toJson(), "includesError": includesError, "createdAt": createdAt.toUtc().millisecondsSinceEpoch, }; + TextMessage.fromJson(Map json) + : _content = json["content"], + _thinking = json["thinking"], + _stats = json["stats"] != null + ? ChatUsageStats.fromJson(json["stats"]) + : null, + _includesError = json["includesError"] ?? false, + super( + sender: MessageSender.values.byName(json["role"]), + createdAt: DateTime.fromMillisecondsSinceEpoch( + json["createdAt"], + isUtc: true, + ), + ); + factory TextMessage.fromStream( - Stream stream, { + Stream stream, { required MessageSender sender, Completer? completer, void Function(String content)? onContent, + Future Function()? saveChat, }) => TextMessage("", sender: sender, createdAt: DateTime.now()) - .._contentFromStream(stream, completer: completer, onContent: onContent); + .._contentFromStream( + stream, + completer: completer, + onContent: onContent, + saveChat: saveChat, + ); Future _contentFromStream( - Stream stream, { + Stream stream, { Completer? completer, void Function(String content)? onContent, + Future Function()? saveChat, }) async { assert(!_locked, "Cannot modify a locked message."); + if (_locked) return; _locked = true; try { @@ -89,11 +132,20 @@ class TextMessage extends Message { sub?.cancel(); return; } - _content += event.message.content; + + if (event.message?.thinking != null) { + _thinking = "${_thinking ?? ""}${event.message?.thinking}"; + } + if (event.done ?? false) { + _stats = ChatUsageStats.fromChatResponse(event); + } + + _content += event.message!.content!; chatHaptic(); notifyListeners(); onContent?.call(_content); + saveChat?.call(); }); await sub.asFuture(); } catch (e, s) { @@ -110,12 +162,22 @@ class TextMessage extends Message { } class ImageMessage extends Message { - final Uri image; + Uri _image; + Uri get image => _image; + final String? name; + double? _aspectRatio; + double? get aspectRatio => _aspectRatio; + + int? _width; + int? get width => _width; + ImageMessage({ - required this.image, + required this._image, this.name, + this._aspectRatio, + this._width, required super.sender, super.createdAt, }); @@ -126,8 +188,128 @@ class ImageMessage extends Message { "role": sender.name, "image": image.toString(), "name": name, + "aspectRatio": aspectRatio, + "width": width, "createdAt": createdAt.toUtc().millisecondsSinceEpoch, }; + + ImageMessage.fromJson(Map json) + : _image = json["image"] != null + ? Uri.parse(json["image"]) + : Uri.parse( + "data:${lookupMimeType(json["name"])};base64,${json["content"]}", + ), + name = json["name"], + _aspectRatio = (json["aspectRatio"] as num?)?.toDouble(), + _width = json["width"], + super( + sender: MessageSender.values.byName(json["role"]), + createdAt: DateTime.fromMillisecondsSinceEpoch( + json["createdAt"], + isUtc: true, + ), + ); + + Future computeImageMeta() async { + try { + final data = image.scheme == "ref" + ? await (chatDb.select(chatDb.assets) + ..where((a) => a.id.equals(image.authority))) + .getSingleOrNull() + .then((r) => r?.data) + : base64Decode(image.toString().split(",").last); + if (data == null) return; + + final decodedImage = img.decodeImage(data); + if (decodedImage == null) return; + + _aspectRatio = decodedImage.width / decodedImage.height; + _width = decodedImage.width; + } catch (_) {} + } + + Future get imageProvider async { + assert( + image.scheme == "ref", + "Only images with a 'ref' scheme should be used; please await for migration before using images.", + ); + + if (image.scheme == "ref") { + final img = await (chatDb.select( + chatDb.assets, + )..where((img) => img.id.equals(image.authority))).getSingleOrNull(); + if (img != null) return MemoryImage(img.data); + } else if (image.scheme == "data") { + return MemoryImage(base64Decode(image.toString().split(",").last)); + } + return NetworkImage(image.toString()); + } +} + +class ChatUsageStats { + final Duration totalDuration; + final Duration? loadDuration; + final int promptEvalCount; + final Duration? promptEvalDuration; + final int evalCount; + final Duration? evalDuration; + + ChatUsageStats._({ + required this.totalDuration, + required this.loadDuration, + required this.promptEvalCount, + required this.promptEvalDuration, + required this.evalCount, + required this.evalDuration, + }); + + static ChatUsageStats? fromChatResponse(llama.ChatStreamEvent response) { + if (response.totalDuration == null || + response.promptEvalCount == null || + response.evalCount == null) { + return null; + } + + return ChatUsageStats._( + totalDuration: Duration(microseconds: response.totalDuration! ~/ 1000), + loadDuration: response.loadDuration != null + ? Duration(microseconds: response.loadDuration! ~/ 1000) + : null, + promptEvalCount: response.promptEvalCount!, + promptEvalDuration: response.promptEvalDuration != null + ? Duration(microseconds: response.promptEvalDuration! ~/ 1000) + : null, + evalCount: response.evalCount!, + evalDuration: response.evalDuration != null + ? Duration(microseconds: response.evalDuration! ~/ 1000) + : null, + ); + } + + Map toJson() => { + "totalDuration": totalDuration.inMicroseconds, + "loadDuration": loadDuration?.inMicroseconds, + "promptEvalCount": promptEvalCount, + "promptEvalDuration": promptEvalDuration?.inMicroseconds, + "evalCount": evalCount, + "evalDuration": evalDuration?.inMicroseconds, + }; + + factory ChatUsageStats.fromJson(Map json) => + ChatUsageStats._( + totalDuration: Duration(microseconds: json["totalDuration"]), + loadDuration: json["loadDuration"] != null + ? Duration(microseconds: json["loadDuration"]) + : null, + promptEvalCount: json["promptEvalCount"], + promptEvalDuration: json["loadDuration"] != null + ? Duration(microseconds: json["promptEvalDuration"]) + : null, + evalCount: json["evalCount"], + evalDuration: json["loadDuration"] != null + ? Duration(microseconds: json["evalDuration"]) + : null, + ); } class Chat extends ChangeNotifier { @@ -143,12 +325,14 @@ class Chat extends ChangeNotifier { String? get modelName => _modelName; set modelName(String? name) { assert(alive, "Chat must be alive to be modified."); + if (!alive) return; _modelName = name; if (ChatManager.instance.currentChatId == id) { ModelManager.instance.currentModelName = name; } notifyListeners(); + saveChat(); } Model? get model => ModelManager.instance.models.firstOrNullWhere( @@ -160,26 +344,28 @@ class Chat extends ChangeNotifier { String get title => _title; set title(String title) { assert(alive, "Chat must be alive to be modified."); + if (!alive) return; _title = title; notifyListeners(); + saveChat(); } - final Set _messages; - Set get messages => Set.unmodifiable(_messages); + final List _messages; + List get messages => List.unmodifiable(_messages); final String? system; Chat._({ + String? id, required String? modelName, - required String title, + required this._title, required this.createdAt, - Set? messages, + List? messages, String? system, - }) : id = const Uuid().v4(), - _modelName = modelName, - _title = title, - _messages = messages ?? {}, + }) : id = id ?? const Uuid().v4(), + _modelName = modelName ?? ModelManager.instance.currentModelName, + _messages = messages ?? [], system = system ?? Preferences.instance.system { addListener(ChatManager.instance.notifyListeners); for (var m in _messages) { @@ -199,31 +385,35 @@ class Chat extends ChangeNotifier { super.dispose(); } - List toApi() { - var messages = []; - var images = []; + List toApi() { + final messages = []; + final images = []; - var systemMessage = system; + final systemMessage = system; if (systemMessage != null) { messages.add( - ollama.Message(role: ollama.MessageRole.system, content: systemMessage), + llama.ChatMessage( + role: llama.MessageRole.system, + content: systemMessage, + ), ); } for (var message in _messages) { switch (message) { - case TextMessage message: + case final TextMessage message: messages.add( - ollama.Message( + llama.ChatMessage( role: message.sender == MessageSender.user - ? ollama.MessageRole.user - : ollama.MessageRole.assistant, + ? llama.MessageRole.user + : llama.MessageRole.assistant, content: message.content, images: images.map((e) => e.image.toString()).toList(), ), ); images.clear(); - case ImageMessage message: + case final ImageMessage message + when message.sender == MessageSender.user: images.add(message); } } @@ -246,19 +436,20 @@ class Chat extends ChangeNotifier { }) async { assert(alive, "Chat must be alive to be modified."); assert(model != null, "Chat model must be set to generate a title."); + if (!alive || model == null) return; if (_messages.isEmpty || model == null || !Preferences.instance.generateTitles) { - _title = AppLocalizations.of(mainContext!).newChatTitle; + _title = AppLocalizations.of(context).newChatTitle; return; } - var effectiveThink = + final effectiveThink = (think ?? false) && model!.capabilities.contains(ModelCapability.thinking); - var content = jsonEncode( + final content = jsonEncode( (toJson()["messages"] as List>) .map( (e) => e @@ -268,32 +459,32 @@ class Chat extends ChangeNotifier { ) .toList(), ); - var request = ollama.GenerateChatCompletionRequest( + final request = llama.ChatRequest( model: modelName!, messages: [ - const ollama.Message( - role: ollama.MessageRole.system, + const llama.ChatMessage( + role: llama.MessageRole.system, content: "Generate a two to five word title for the conversation provided by the user. " - "If an object or person is very important in the conversation, put it in the title as well; keep the focus on the main subject. Also make an assumption about things happening in the conversation following the messages provided. " - "You must not put the assistant in the focus and you must not put the word 'assistant' in the title! " - "Use a factual, formal tone; don't make the title dramatic using dramatic words. Preferably use nouns and adjectives, not verbs. Also avoid using words like 'simple' or 'easy' to not belittle the user or their problem. " - "Do preferably use title case. You must not use markdown or any other formatting language! You must not use emojis or any other symbols! You must not use general clauses like 'assistance', 'help' or 'session' in your title!\n\n" - "Example bad titles compared to good titles:\n\n~~User Introduces Themselves~~ -> User Introduction\n~~User Asks for Help with a Problem~~ -> Problem Help\n~~User has a _**big**_ Problem~~ -> Big Problem", + "Focus on the main subject or topic. If a specific object, person, or concept is central to the discussion, include it in the title. " + "Do not focus on the assistant; never use the word 'assistant' or imply it is the subject. " + "Use a factual, neutral tone. Prefer nouns and adjectives over verbs. Do not use dramatic, vague, or belittling words such as 'simple', 'easy', 'big', or 'interesting'. " + "Use title case. Do not use markdown, emojis, symbols, or generic terms like 'assistance', 'help', 'session', or 'conversation'.\n\n" + "---\n\nExamples (bad -> good):\n\n~~User Introduces Themselves~~ -> User Introduction\n~~User Asks for Help with a Problem~~ -> Problem Troubleshooting\n~~User has a _**big**_ Problem~~ -> Issue Diagnosis\n~~Simple Python Question~~ -> Python Syntax Help", ), - ollama.Message( - role: ollama.MessageRole.user, + llama.ChatMessage( + role: llama.MessageRole.user, content: "```json\n$content\n```", ), ], - keepAlive: Preferences.instance.keepAlive, - think: effectiveThink, + keepAlive: llama.KeepAlive.number(Preferences.instance.keepAlive), + think: llama.ThinkValue.enabled(effectiveThink), ); - ollama.GenerateChatCompletionResponse generated; + llama.ChatResponse generated; try { - generated = await clients.ollamaClient - .generateChatCompletion(request: request) + generated = await clients.ollamaClient.chat + .create(request: request) .timeout(TimeoutMultiplier.long); } catch (e, s) { if (alive) Error.throwWithStackTrace(e, s); @@ -301,7 +492,7 @@ class Chat extends ChangeNotifier { } if (!alive) return; - var newTitle = generated.message.content; + var newTitle = generated.message!.content!; newTitle = newTitle.replaceAll("\n", " "); for (var term in [ @@ -336,9 +527,11 @@ class Chat extends ChangeNotifier { void append(Message message) { assert(alive, "Chat must be alive to be modified."); + if (!alive) return; _messages.add(message..addListener(notifyListeners)); notifyListeners(); + saveChat(); } Future send( @@ -348,6 +541,7 @@ class Chat extends ChangeNotifier { void Function(String content)? onContent, }) async { assert(alive, "Chat must be alive to be modified."); + if (!alive) return; assert(model != null, "Chat model must be set to send messages."); if (model == null) return; @@ -356,32 +550,36 @@ class Chat extends ChangeNotifier { completer.isCompleted, "Cannot send a message while another message is being sent.", ); + if (!completer.isCompleted) return; _messages.add(message..addListener(notifyListeners)); - var finalThink = + final finalThink = (think ?? Preferences.instance.thinking) && model!.capabilities.contains(ModelCapability.thinking); if (message is TextMessage && message.sender == MessageSender.user) { completer = Completer(); - var message = TextMessage.fromStream( - clients.ollamaClient.generateChatCompletionStream( - request: ollama.GenerateChatCompletionRequest( + final message = TextMessage.fromStream( + clients.ollamaClient.chat.createStream( + request: llama.ChatRequest( model: model!.name, messages: toApi(), stream: true, - keepAlive: Preferences.instance.keepAlive, - think: finalThink, + keepAlive: llama.KeepAlive.number(Preferences.instance.keepAlive), + think: llama.ThinkValue.enabled(finalThink), ), ), sender: MessageSender.assistant, completer: completer, onContent: onContent, + saveChat: () async { + if (alive) await saveChat(); + }, )..addListener(notifyListeners); _messages.add(message); - var future = completer.future.catchError((e, s) { + final future = completer.future.catchError((e, s) { message .._locked = false .._includesError = true @@ -389,18 +587,74 @@ class Chat extends ChangeNotifier { Error.throwWithStackTrace(e, s); }); if (awaitCompletion) await future; + } else if (message is ImageMessage) { + await message.computeImageMeta(); } notifyListeners(); - await ChatManager.instance.saveChats(); + if (alive) await saveChat(); } - void deleteMessage(Message message) { + void delete(Message message) { assert(alive, "Chat must be alive to be modified."); + if (!alive) return; _messages.remove(message); message.removeListener(notifyListeners); notifyListeners(); + saveChat(); + } + + Future saveChat() async { + assert(alive, "Chat must be alive to be modified."); + if (!alive) return; + + await chatDb + .into(chatDb.chats) + .insertOnConflictUpdate( + ChatsCompanion(id: Value(id), json: Value(jsonEncode(toJson()))), + ); + } + + Future _migrateImageStorageFormat() async { + var didMigrate = false; + for (var message in _messages.whereType()) { + if (message.image.scheme != "ref") { + final id = const Uuid().v4(); + final data = base64Decode(message.image.toString().split(",").last); + await chatDb + .into(chatDb.assets) + .insert( + AssetsCompanion( + id: Value(id), + chatId: Value(this.id), + data: Value(data), + ), + ); + + double? aspectRatio; + int? width; + if (message.aspectRatio == null || message.width == null) { + try { + final decodedImage = img.decodeImage(data); + if (decodedImage != null) { + aspectRatio = decodedImage.width / decodedImage.height; + width = decodedImage.width; + } + } catch (_) {} + } else { + aspectRatio = message.aspectRatio; + width = message.width; + } + + message + .._image = Uri.parse("ref://$id") + .._aspectRatio = aspectRatio + .._width = width; + didMigrate = true; + } + } + return didMigrate; } @override @@ -416,12 +670,16 @@ class ChatManager extends ChangeNotifier { String? _currentChatId; String? get currentChatId => _currentChatId; set currentChatId(String? id) { + final chat = _chats.where((e) => e.id == id).firstOrNull; + if (chat == null && id != null) { + throw ArgumentError.value(id, "id", "Chat with this ID does not exist."); + } + _currentChatId = id; if (id != null) { - ModelManager.instance.currentModelName = _chats - .singleWhere((e) => e.id == id) - .model! - .name; + if (chat!.model != null) { + ModelManager.instance.currentModelName = chat.model!.name; + } } notifyListeners(); } @@ -436,90 +694,94 @@ class ChatManager extends ChangeNotifier { ChatManager._(); - void clearChats() { - for (var chat in _chats) { - chat.dispose(); - } - _chats.clear(); - _currentChatId = null; - notifyListeners(); - } - Future loadChats() async { DateTime getDateTimeFromMilliseconds(int? milliseconds) { if (milliseconds == null) return DateTime.now(); return DateTime.fromMillisecondsSinceEpoch(milliseconds, isUtc: true); } - var stored = prefs!.getStringList("chats") ?? []; + var stored = []; + + final oldPrefs = await SharedPreferences.getInstance(); + final oldStored = oldPrefs.getStringList("chats") ?? []; + if (oldStored.isNotEmpty) { + stored = oldStored; + await oldPrefs.remove("chats"); + await prefs!.remove("chats"); + } + + if (stored.isEmpty) { + stored = await chatDb + .select(chatDb.chats) + .get() + .then((rows) => rows.map((r) => r.json).toList()); + } + _chats.clear(); for (var chatJson in stored) { try { - var chatData = jsonDecode(chatJson); + final chatData = jsonDecode(chatJson); - var messages = {}; + final messages = []; String? system; - for (var message in chatData["messages"]) { + + final data = chatData["messages"] is String + ? (jsonDecode(chatData["messages"]) as List) + : chatData["messages"]; + + for (var message in data) { if (message["role"] == "system") { system = message["content"]; + continue; } - switch (message["type"]) { - case "text": - messages.add( - TextMessage( - message["content"], - sender: MessageSender.values.byName(message["role"]), - createdAt: getDateTimeFromMilliseconds(message["createdAt"]), - ), - ); - case "image": - messages.add( - ImageMessage( - image: Uri.parse(message["image"]), - name: message["name"], - sender: MessageSender.values.byName(message["role"]), - createdAt: getDateTimeFromMilliseconds(message["createdAt"]), - ), - ); - } + final constructor = Message.constructorForType( + message["type"] ?? "text", + ); + if (constructor == null) continue; + messages.add(constructor.call(message)); } _chats.add( Chat._( + id: chatData["uuid"] ?? chatData["id"], modelName: chatData["model"], createdAt: getDateTimeFromMilliseconds(chatData["createdAt"]), title: chatData["title"], messages: messages, system: system, - ), + )..addListener(notifyListeners), ); } catch (_) { rethrow; } } + + if (oldStored.isNotEmpty) await saveChats(); notifyListeners(); } Future saveChats() async { - prefs!.setStringList( - "chats", - _chats.map((c) => jsonEncode(c.toJson())).toList(), - ); + for (var chat in _chats) { + await chatDb + .into(chatDb.chats) + .insertOnConflictUpdate( + ChatsCompanion( + id: Value(chat.id), + json: Value(jsonEncode(chat.toJson())), + ), + ); + if (await chat._migrateImageStorageFormat()) await chat.saveChat(); + } } Chat createChat({ required BuildContext? context, - required Model? model, + Model? model, String? title, String? system, }) { - assert( - allowMultipleChats || chats.isEmpty, - "Cannot create a new chat when multiple chats are not allowed and there is already a chat.", - ); - - var chat = Chat._( + final chat = Chat._( modelName: model?.name, createdAt: DateTime.now(), title: @@ -533,7 +795,7 @@ class ChatManager extends ChangeNotifier { _currentChatId = chat.id; notifyListeners(); - saveChats(); + chat.saveChat(); return chat; } @@ -549,7 +811,7 @@ class ChatManager extends ChangeNotifier { if (_currentChatId == chat.id) _currentChatId = null; notifyListeners(); - saveChats(); + (chatDb.delete(chatDb.chats)..where((c) => c.id.equals(chat.id))).go(); } } @@ -561,7 +823,7 @@ Future showDeleteChatDialog( FutureOr Function()? onDelete, }) { chat ??= ChatManager.instance.currentChat; - var completer = Completer(); + final completer = Completer(); if (Preferences.instance.askBeforeDeletion) { showDialog( context: context, @@ -575,6 +837,8 @@ Future showDeleteChatDialog( ); } else { ChatManager.instance.deleteChat(chat!); + onDelete?.call(); + completer.complete(true); } return completer.future; } @@ -622,15 +886,32 @@ class DeleteChatDialog extends StatelessWidget { // MARK: Chat Text Widget +class _AnimatedWord { + final String text; + final WidgetSpan? widgetSpan; + TextStyle? style; + AnimationController? controller; + + bool get isWidget => widgetSpan != null; + + _AnimatedWord(this.text, this.style, [this.controller]) : widgetSpan = null; + _AnimatedWord.forWidget(this.widgetSpan) + : text = '', + style = null, + controller = null; +} + class ChatText extends StatefulWidget { - final String content; + final InlineSpan content; final Duration? flyInDuration; + final Duration? wordDelay; final Widget? placeholder; const ChatText( this.content, { super.key, this.flyInDuration, + this.wordDelay, this.placeholder, }); @@ -639,84 +920,221 @@ class ChatText extends StatefulWidget { } class _ChatTextState extends State with TickerProviderStateMixin { - late List _words; - late final List _controllers; + final List<_AnimatedWord> _words = []; + final List<_AnimatedWord> _queue = []; + Timer? _delayTimer; + DateTime? _lastWordShownAt; + + List _splitWords(String text) { + if (text.isEmpty) return const []; + return text.split(RegExp(r'(?=\s)')).where((s) => s.isNotEmpty).toList(); + } + + List<_AnimatedWord> _flattenAll(InlineSpan span, [TextStyle? parent]) { + final result = <_AnimatedWord>[]; + if (span is TextSpan) { + final eff = (parent != null && span.style != null) + ? parent.merge(span.style!) + : (span.style ?? parent); + if (span.text?.isNotEmpty ?? false) { + result.add(_AnimatedWord(span.text!, eff)); + } + for (var child in span.children ?? const []) { + result.addAll(_flattenAll(child, eff)); + } + } else if (span is WidgetSpan) { + result.add(_AnimatedWord.forWidget(span)); + } + return result; + } @override void initState() { super.initState(); - _words = _splitWords(widget.content); - _controllers = List.generate(_words.length, (_) => null); + for (var entry in _flattenAll(widget.content)) { + if (entry.isWidget) { + _words.add(entry); + } else { + for (var w in _splitWords(entry.text)) { + _words.add(_AnimatedWord(w, entry.style)); + } + } + } + } + + @override + void didUpdateWidget(ChatText old) { + super.didUpdateWidget(old); + if (widget.content != old.content) _processUpdate(); + } + + void _enqueue(_AnimatedWord word) { + if (widget.wordDelay == null) { + word.controller?.forward(); + return; + } + _queue.add(word); + _drainQueue(); + } + + void _drainQueue() { + if ((_delayTimer?.isActive ?? false) || _queue.isEmpty) return; + final remaining = + widget.wordDelay! - + (_lastWordShownAt != null + ? DateTime.now().difference(_lastWordShownAt!) + : widget.wordDelay!); + if (remaining <= Duration.zero) { + _lastWordShownAt = DateTime.now(); + _queue.removeAt(0).controller?.forward(); + if (_queue.isNotEmpty) _drainQueue(); + } else { + _delayTimer = Timer(remaining, _drainQueue); + } + } + + void _cancelQueue() { + _delayTimer?.cancel(); + _delayTimer = null; + _lastWordShownAt = null; + _queue.clear(); } @override void dispose() { - for (var c in _controllers) { - c?.dispose(); + _cancelQueue(); + for (var w in _words) { + w.controller?.dispose(); } super.dispose(); } - List _splitWords(String s) { - if (s.trim().isEmpty) return []; - return s.split(RegExp(r"(?=\s+)")); + void _processUpdate() { + final newEntries = _flattenAll(widget.content); + final newFullText = newEntries + .where((e) => !e.isWidget) + .map((e) => e.text) + .join(); + final oldFullText = _words + .where((w) => !w.isWidget) + .map((w) => w.text) + .join(); + + if (newFullText == oldFullText) { + _syncStyles(newEntries); + setState(() {}); + return; + } + + if (newFullText.startsWith(oldFullText)) { + _animateAppended(newEntries, oldFullText.length); + } else { + _reset(newEntries); + } + } + + void _syncStyles(List<_AnimatedWord> entries) { + final flat = <_AnimatedWord>[]; + for (var entry in entries) { + if (entry.isWidget) continue; + for (var w in _splitWords(entry.text)) { + flat.add(_AnimatedWord(w, entry.style)); + } + } + final textWords = _words.where((w) => !w.isWidget).toList(); + for (var i = 0; i < textWords.length && i < flat.length; i++) { + textWords[i].style = flat[i].style; + } + } + + void _animateAppended(List<_AnimatedWord> newEntries, int oldLength) { + var seen = 0; + for (var leaf in newEntries) { + if (leaf.isWidget) { + if (seen >= oldLength) _words.add(leaf); + continue; + } + final leafEnd = seen + leaf.text.length; + if (leafEnd <= oldLength) { + seen = leafEnd; + continue; + } + final start = (oldLength - seen).clamp(0, leaf.text.length); + final newText = leaf.text.substring(start); + for (var word in _splitWords(newText)) { + final ctrl = AnimationController( + value: 0.0, + vsync: this, + duration: widget.flyInDuration ?? Durations.medium1, + ); + final entry = _AnimatedWord(word, leaf.style, ctrl); + ctrl + ..addListener(() { + if (mounted) setState(() {}); + }) + ..addStatusListener((status) { + if (status == AnimationStatus.completed) { + entry.controller?.dispose(); + entry.controller = null; + } + }); + _words.add(entry); + _enqueue(entry); + } + seen = leafEnd; + } + } + + void _reset(List<_AnimatedWord> entries) { + _cancelQueue(); + for (var w in _words) { + w.controller?.dispose(); + } + _words.clear(); + for (var entry in entries) { + if (entry.isWidget) { + _words.add(entry); + } else { + for (var w in _splitWords(entry.text)) { + _words.add(_AnimatedWord(w, entry.style)); + } + } + } + setState(() {}); } @override Widget build(BuildContext context) { - var newWords = _splitWords( - widget.content.replaceFirst( - RegExp("^${RegExp.escape(_words.join())}"), - "", + final isEmpty = _words.isEmpty; + if (isEmpty && widget.placeholder != null) return widget.placeholder!; + + final defaultColor = + Theme.of(context).textTheme.bodyMedium?.color ?? Colors.black; + + return AnimatedSize( + alignment: Alignment.topLeft, + duration: Durations.short2, + child: Text.rich( + TextSpan( + children: _words + .where((w) => w.controller?.status != AnimationStatus.dismissed) + .map((word) { + final alpha = word.controller?.value; + if (alpha == null) { + return TextSpan(text: word.text, style: word.style); + } + + final baseColor = word.style?.color ?? defaultColor; + return TextSpan( + text: word.text, + style: (word.style ?? const TextStyle()).copyWith( + color: baseColor.withValues(alpha: alpha), + ), + ); + }) + .toList(), + ), ), ); - - for (var word in newWords.asMap().entries) { - _words.add(word.value); - _controllers.add( - AnimationController( - value: 0, - vsync: this, - duration: widget.flyInDuration ?? Durations.medium1, - ) - ..addListener(() { - var index = word.key + _words.length - newWords.length; - if (_controllers[index]?.value == 1) { - _controllers[index]!.dispose(); - _controllers[index] = null; - } - - setState(() {}); - }) - ..forward(), - ); - } - - var finalColor = - Theme.of(context).textTheme.bodyMedium?.color ?? Colors.black; - return (_words.isEmpty && widget.placeholder != null) - ? widget.placeholder! - : AnimatedSize( - alignment: Alignment.topLeft, - duration: Durations.short2, - child: Text.rich( - TextSpan( - children: _words - .asMap() - .entries - .map( - (e) => TextSpan( - text: e.value, - style: TextStyle( - color: finalColor.withValues( - alpha: _controllers[e.key]?.value, - ), - ), - ), - ) - .toList(), - ), - ), - ); } } diff --git a/lib/services/chat_database/chat_database.dart b/lib/services/chat_database/chat_database.dart new file mode 100644 index 0000000..e5771d3 --- /dev/null +++ b/lib/services/chat_database/chat_database.dart @@ -0,0 +1,58 @@ +import 'package:drift/drift.dart'; +import 'package:drift_flutter/drift_flutter.dart'; + +export 'package:drift/drift.dart'; + +part 'chat_database.g.dart'; + +late final ChatDatabase chatDb; + +class Chats extends Table { + TextColumn get id => text().unique()(); + TextColumn get json => text()(); + + @override + Set>? get primaryKey => {id}; +} + +class Assets extends Table { + TextColumn get id => text().unique()(); + TextColumn get chatId => + text().references(Chats, #id, onDelete: KeyAction.cascade)(); + BlobColumn get data => blob()(); + + @override + Set>? get primaryKey => {id}; +} + +@DriftDatabase(tables: [Chats, Assets]) +class ChatDatabase extends _$ChatDatabase { + ChatDatabase([QueryExecutor? executor]) + : super(executor ?? _openConnection()); + + @override + int get schemaVersion => 1; + + static QueryExecutor _openConnection() { + return driftDatabase( + name: "chatDatabase", + native: const DriftNativeOptions(), + web: DriftWebOptions( + sqlite3Wasm: Uri.parse("sqlite3.wasm"), + driftWorker: Uri.parse("drift_worker.js"), + ), + ); + } + + @override + MigrationStrategy get migration { + return MigrationStrategy( + beforeOpen: (details) async { + await customStatement("PRAGMA foreign_keys = ON"); + }, + onUpgrade: (Migrator m, int from, int to) async { + // if (from < 2) {} + }, + ); + } +} diff --git a/lib/services/chat_database/chat_database.g.dart b/lib/services/chat_database/chat_database.g.dart new file mode 100644 index 0000000..a5398b8 --- /dev/null +++ b/lib/services/chat_database/chat_database.g.dart @@ -0,0 +1,998 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'chat_database.dart'; + +// ignore_for_file: type=lint +class $ChatsTable extends Chats with TableInfo<$ChatsTable, Chat> { + @override + final GeneratedDatabase attachedDatabase; + final String? _alias; + $ChatsTable(this.attachedDatabase, [this._alias]); + static const VerificationMeta _idMeta = const VerificationMeta('id'); + @override + late final GeneratedColumn id = GeneratedColumn( + 'id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + defaultConstraints: GeneratedColumn.constraintIsAlways('UNIQUE'), + ); + static const VerificationMeta _jsonMeta = const VerificationMeta('json'); + @override + late final GeneratedColumn json = GeneratedColumn( + 'json', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + ); + @override + List get $columns => [id, json]; + @override + String get aliasedName => _alias ?? actualTableName; + @override + String get actualTableName => $name; + static const String $name = 'chats'; + @override + VerificationContext validateIntegrity( + Insertable instance, { + bool isInserting = false, + }) { + final context = VerificationContext(); + final data = instance.toColumns(true); + if (data.containsKey('id')) { + context.handle(_idMeta, id.isAcceptableOrUnknown(data['id']!, _idMeta)); + } else if (isInserting) { + context.missing(_idMeta); + } + if (data.containsKey('json')) { + context.handle( + _jsonMeta, + json.isAcceptableOrUnknown(data['json']!, _jsonMeta), + ); + } else if (isInserting) { + context.missing(_jsonMeta); + } + return context; + } + + @override + Set get $primaryKey => {id}; + @override + Chat map(Map data, {String? tablePrefix}) { + final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; + return Chat( + id: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}id'], + )!, + json: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}json'], + )!, + ); + } + + @override + $ChatsTable createAlias(String alias) { + return $ChatsTable(attachedDatabase, alias); + } +} + +class Chat extends DataClass implements Insertable { + final String id; + final String json; + const Chat({required this.id, required this.json}); + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + map['id'] = Variable(id); + map['json'] = Variable(json); + return map; + } + + ChatsCompanion toCompanion(bool nullToAbsent) { + return ChatsCompanion(id: Value(id), json: Value(json)); + } + + factory Chat.fromJson( + Map json, { + ValueSerializer? serializer, + }) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return Chat( + id: serializer.fromJson(json['id']), + json: serializer.fromJson(json['json']), + ); + } + @override + Map toJson({ValueSerializer? serializer}) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return { + 'id': serializer.toJson(id), + 'json': serializer.toJson(json), + }; + } + + Chat copyWith({String? id, String? json}) => + Chat(id: id ?? this.id, json: json ?? this.json); + Chat copyWithCompanion(ChatsCompanion data) { + return Chat( + id: data.id.present ? data.id.value : this.id, + json: data.json.present ? data.json.value : this.json, + ); + } + + @override + String toString() { + return (StringBuffer('Chat(') + ..write('id: $id, ') + ..write('json: $json') + ..write(')')) + .toString(); + } + + @override + int get hashCode => Object.hash(id, json); + @override + bool operator ==(Object other) => + identical(this, other) || + (other is Chat && other.id == this.id && other.json == this.json); +} + +class ChatsCompanion extends UpdateCompanion { + final Value id; + final Value json; + final Value rowid; + const ChatsCompanion({ + this.id = const Value.absent(), + this.json = const Value.absent(), + this.rowid = const Value.absent(), + }); + ChatsCompanion.insert({ + required String id, + required String json, + this.rowid = const Value.absent(), + }) : id = Value(id), + json = Value(json); + static Insertable custom({ + Expression? id, + Expression? json, + Expression? rowid, + }) { + return RawValuesInsertable({ + if (id != null) 'id': id, + if (json != null) 'json': json, + if (rowid != null) 'rowid': rowid, + }); + } + + ChatsCompanion copyWith({ + Value? id, + Value? json, + Value? rowid, + }) { + return ChatsCompanion( + id: id ?? this.id, + json: json ?? this.json, + rowid: rowid ?? this.rowid, + ); + } + + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + if (id.present) { + map['id'] = Variable(id.value); + } + if (json.present) { + map['json'] = Variable(json.value); + } + if (rowid.present) { + map['rowid'] = Variable(rowid.value); + } + return map; + } + + @override + String toString() { + return (StringBuffer('ChatsCompanion(') + ..write('id: $id, ') + ..write('json: $json, ') + ..write('rowid: $rowid') + ..write(')')) + .toString(); + } +} + +class $AssetsTable extends Assets with TableInfo<$AssetsTable, Asset> { + @override + final GeneratedDatabase attachedDatabase; + final String? _alias; + $AssetsTable(this.attachedDatabase, [this._alias]); + static const VerificationMeta _idMeta = const VerificationMeta('id'); + @override + late final GeneratedColumn id = GeneratedColumn( + 'id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + defaultConstraints: GeneratedColumn.constraintIsAlways('UNIQUE'), + ); + static const VerificationMeta _chatIdMeta = const VerificationMeta('chatId'); + @override + late final GeneratedColumn chatId = GeneratedColumn( + 'chat_id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'REFERENCES chats (id) ON DELETE CASCADE', + ), + ); + static const VerificationMeta _dataMeta = const VerificationMeta('data'); + @override + late final GeneratedColumn data = GeneratedColumn( + 'data', + aliasedName, + false, + type: DriftSqlType.blob, + requiredDuringInsert: true, + ); + @override + List get $columns => [id, chatId, data]; + @override + String get aliasedName => _alias ?? actualTableName; + @override + String get actualTableName => $name; + static const String $name = 'assets'; + @override + VerificationContext validateIntegrity( + Insertable instance, { + bool isInserting = false, + }) { + final context = VerificationContext(); + final data = instance.toColumns(true); + if (data.containsKey('id')) { + context.handle(_idMeta, id.isAcceptableOrUnknown(data['id']!, _idMeta)); + } else if (isInserting) { + context.missing(_idMeta); + } + if (data.containsKey('chat_id')) { + context.handle( + _chatIdMeta, + chatId.isAcceptableOrUnknown(data['chat_id']!, _chatIdMeta), + ); + } else if (isInserting) { + context.missing(_chatIdMeta); + } + if (data.containsKey('data')) { + context.handle( + _dataMeta, + this.data.isAcceptableOrUnknown(data['data']!, _dataMeta), + ); + } else if (isInserting) { + context.missing(_dataMeta); + } + return context; + } + + @override + Set get $primaryKey => {id}; + @override + Asset map(Map data, {String? tablePrefix}) { + final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; + return Asset( + id: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}id'], + )!, + chatId: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}chat_id'], + )!, + data: attachedDatabase.typeMapping.read( + DriftSqlType.blob, + data['${effectivePrefix}data'], + )!, + ); + } + + @override + $AssetsTable createAlias(String alias) { + return $AssetsTable(attachedDatabase, alias); + } +} + +class Asset extends DataClass implements Insertable { + final String id; + final String chatId; + final Uint8List data; + const Asset({required this.id, required this.chatId, required this.data}); + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + map['id'] = Variable(id); + map['chat_id'] = Variable(chatId); + map['data'] = Variable(data); + return map; + } + + AssetsCompanion toCompanion(bool nullToAbsent) { + return AssetsCompanion( + id: Value(id), + chatId: Value(chatId), + data: Value(data), + ); + } + + factory Asset.fromJson( + Map json, { + ValueSerializer? serializer, + }) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return Asset( + id: serializer.fromJson(json['id']), + chatId: serializer.fromJson(json['chatId']), + data: serializer.fromJson(json['data']), + ); + } + @override + Map toJson({ValueSerializer? serializer}) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return { + 'id': serializer.toJson(id), + 'chatId': serializer.toJson(chatId), + 'data': serializer.toJson(data), + }; + } + + Asset copyWith({String? id, String? chatId, Uint8List? data}) => Asset( + id: id ?? this.id, + chatId: chatId ?? this.chatId, + data: data ?? this.data, + ); + Asset copyWithCompanion(AssetsCompanion data) { + return Asset( + id: data.id.present ? data.id.value : this.id, + chatId: data.chatId.present ? data.chatId.value : this.chatId, + data: data.data.present ? data.data.value : this.data, + ); + } + + @override + String toString() { + return (StringBuffer('Asset(') + ..write('id: $id, ') + ..write('chatId: $chatId, ') + ..write('data: $data') + ..write(')')) + .toString(); + } + + @override + int get hashCode => Object.hash(id, chatId, $driftBlobEquality.hash(data)); + @override + bool operator ==(Object other) => + identical(this, other) || + (other is Asset && + other.id == this.id && + other.chatId == this.chatId && + $driftBlobEquality.equals(other.data, this.data)); +} + +class AssetsCompanion extends UpdateCompanion { + final Value id; + final Value chatId; + final Value data; + final Value rowid; + const AssetsCompanion({ + this.id = const Value.absent(), + this.chatId = const Value.absent(), + this.data = const Value.absent(), + this.rowid = const Value.absent(), + }); + AssetsCompanion.insert({ + required String id, + required String chatId, + required Uint8List data, + this.rowid = const Value.absent(), + }) : id = Value(id), + chatId = Value(chatId), + data = Value(data); + static Insertable custom({ + Expression? id, + Expression? chatId, + Expression? data, + Expression? rowid, + }) { + return RawValuesInsertable({ + if (id != null) 'id': id, + if (chatId != null) 'chat_id': chatId, + if (data != null) 'data': data, + if (rowid != null) 'rowid': rowid, + }); + } + + AssetsCompanion copyWith({ + Value? id, + Value? chatId, + Value? data, + Value? rowid, + }) { + return AssetsCompanion( + id: id ?? this.id, + chatId: chatId ?? this.chatId, + data: data ?? this.data, + rowid: rowid ?? this.rowid, + ); + } + + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + if (id.present) { + map['id'] = Variable(id.value); + } + if (chatId.present) { + map['chat_id'] = Variable(chatId.value); + } + if (data.present) { + map['data'] = Variable(data.value); + } + if (rowid.present) { + map['rowid'] = Variable(rowid.value); + } + return map; + } + + @override + String toString() { + return (StringBuffer('AssetsCompanion(') + ..write('id: $id, ') + ..write('chatId: $chatId, ') + ..write('data: $data, ') + ..write('rowid: $rowid') + ..write(')')) + .toString(); + } +} + +abstract class _$ChatDatabase extends GeneratedDatabase { + _$ChatDatabase(QueryExecutor e) : super(e); + $ChatDatabaseManager get managers => $ChatDatabaseManager(this); + late final $ChatsTable chats = $ChatsTable(this); + late final $AssetsTable assets = $AssetsTable(this); + @override + Iterable> get allTables => + allSchemaEntities.whereType>(); + @override + List get allSchemaEntities => [chats, assets]; + @override + StreamQueryUpdateRules get streamUpdateRules => const StreamQueryUpdateRules([ + WritePropagation( + on: TableUpdateQuery.onTableName( + 'chats', + limitUpdateKind: UpdateKind.delete, + ), + result: [TableUpdate('assets', kind: UpdateKind.delete)], + ), + ]); +} + +typedef $$ChatsTableCreateCompanionBuilder = + ChatsCompanion Function({ + required String id, + required String json, + Value rowid, + }); +typedef $$ChatsTableUpdateCompanionBuilder = + ChatsCompanion Function({ + Value id, + Value json, + Value rowid, + }); + +final class $$ChatsTableReferences + extends BaseReferences<_$ChatDatabase, $ChatsTable, Chat> { + $$ChatsTableReferences(super.$_db, super.$_table, super.$_typedResult); + + static MultiTypedResultKey<$AssetsTable, List> _assetsRefsTable( + _$ChatDatabase db, + ) => MultiTypedResultKey.fromTable( + db.assets, + aliasName: 'chats__id__assets__chat_id', + ); + + $$AssetsTableProcessedTableManager get assetsRefs { + final manager = $$AssetsTableTableManager( + $_db, + $_db.assets, + ).filter((f) => f.chatId.id.sqlEquals($_itemColumn('id')!)); + + final cache = $_typedResult.readTableOrNull(_assetsRefsTable($_db)); + return ProcessedTableManager( + manager.$state.copyWith(prefetchedData: cache), + ); + } +} + +class $$ChatsTableFilterComposer extends Composer<_$ChatDatabase, $ChatsTable> { + $$ChatsTableFilterComposer({ + required super.$db, + required super.$table, + super.joinBuilder, + super.$addJoinBuilderToRootComposer, + super.$removeJoinBuilderFromRootComposer, + }); + ColumnFilters get id => $composableBuilder( + column: $table.id, + builder: (column) => ColumnFilters(column), + ); + + ColumnFilters get json => $composableBuilder( + column: $table.json, + builder: (column) => ColumnFilters(column), + ); + + Expression assetsRefs( + Expression Function($$AssetsTableFilterComposer f) f, + ) { + final $$AssetsTableFilterComposer composer = $composerBuilder( + composer: this, + getCurrentColumn: (t) => t.id, + referencedTable: $db.assets, + getReferencedColumn: (t) => t.chatId, + builder: + ( + joinBuilder, { + $addJoinBuilderToRootComposer, + $removeJoinBuilderFromRootComposer, + }) => $$AssetsTableFilterComposer( + $db: $db, + $table: $db.assets, + $addJoinBuilderToRootComposer: $addJoinBuilderToRootComposer, + joinBuilder: joinBuilder, + $removeJoinBuilderFromRootComposer: + $removeJoinBuilderFromRootComposer, + ), + ); + return f(composer); + } +} + +class $$ChatsTableOrderingComposer + extends Composer<_$ChatDatabase, $ChatsTable> { + $$ChatsTableOrderingComposer({ + required super.$db, + required super.$table, + super.joinBuilder, + super.$addJoinBuilderToRootComposer, + super.$removeJoinBuilderFromRootComposer, + }); + ColumnOrderings get id => $composableBuilder( + column: $table.id, + builder: (column) => ColumnOrderings(column), + ); + + ColumnOrderings get json => $composableBuilder( + column: $table.json, + builder: (column) => ColumnOrderings(column), + ); +} + +class $$ChatsTableAnnotationComposer + extends Composer<_$ChatDatabase, $ChatsTable> { + $$ChatsTableAnnotationComposer({ + required super.$db, + required super.$table, + super.joinBuilder, + super.$addJoinBuilderToRootComposer, + super.$removeJoinBuilderFromRootComposer, + }); + GeneratedColumn get id => + $composableBuilder(column: $table.id, builder: (column) => column); + + GeneratedColumn get json => + $composableBuilder(column: $table.json, builder: (column) => column); + + Expression assetsRefs( + Expression Function($$AssetsTableAnnotationComposer a) f, + ) { + final $$AssetsTableAnnotationComposer composer = $composerBuilder( + composer: this, + getCurrentColumn: (t) => t.id, + referencedTable: $db.assets, + getReferencedColumn: (t) => t.chatId, + builder: + ( + joinBuilder, { + $addJoinBuilderToRootComposer, + $removeJoinBuilderFromRootComposer, + }) => $$AssetsTableAnnotationComposer( + $db: $db, + $table: $db.assets, + $addJoinBuilderToRootComposer: $addJoinBuilderToRootComposer, + joinBuilder: joinBuilder, + $removeJoinBuilderFromRootComposer: + $removeJoinBuilderFromRootComposer, + ), + ); + return f(composer); + } +} + +class $$ChatsTableTableManager + extends + RootTableManager< + _$ChatDatabase, + $ChatsTable, + Chat, + $$ChatsTableFilterComposer, + $$ChatsTableOrderingComposer, + $$ChatsTableAnnotationComposer, + $$ChatsTableCreateCompanionBuilder, + $$ChatsTableUpdateCompanionBuilder, + (Chat, $$ChatsTableReferences), + Chat, + PrefetchHooks Function({bool assetsRefs}) + > { + $$ChatsTableTableManager(_$ChatDatabase db, $ChatsTable table) + : super( + TableManagerState( + db: db, + table: table, + createFilteringComposer: () => + $$ChatsTableFilterComposer($db: db, $table: table), + createOrderingComposer: () => + $$ChatsTableOrderingComposer($db: db, $table: table), + createComputedFieldComposer: () => + $$ChatsTableAnnotationComposer($db: db, $table: table), + updateCompanionCallback: + ({ + Value id = const Value.absent(), + Value json = const Value.absent(), + Value rowid = const Value.absent(), + }) => ChatsCompanion(id: id, json: json, rowid: rowid), + createCompanionCallback: + ({ + required String id, + required String json, + Value rowid = const Value.absent(), + }) => ChatsCompanion.insert(id: id, json: json, rowid: rowid), + withReferenceMapper: (p0) => p0 + .map( + (e) => + (e.readTable(table), $$ChatsTableReferences(db, table, e)), + ) + .toList(), + prefetchHooksCallback: ({assetsRefs = false}) { + return PrefetchHooks( + db: db, + explicitlyWatchedTables: [if (assetsRefs) db.assets], + addJoins: null, + getPrefetchedDataCallback: (items) async { + return [ + if (assetsRefs) + await $_getPrefetchedData( + currentTable: table, + referencedTable: $$ChatsTableReferences._assetsRefsTable( + db, + ), + managerFromTypedResult: (p0) => + $$ChatsTableReferences(db, table, p0).assetsRefs, + referencedItemsForCurrentItem: (item, referencedItems) => + referencedItems.where((e) => e.chatId == item.id), + typedResults: items, + ), + ]; + }, + ); + }, + ), + ); +} + +typedef $$ChatsTableProcessedTableManager = + ProcessedTableManager< + _$ChatDatabase, + $ChatsTable, + Chat, + $$ChatsTableFilterComposer, + $$ChatsTableOrderingComposer, + $$ChatsTableAnnotationComposer, + $$ChatsTableCreateCompanionBuilder, + $$ChatsTableUpdateCompanionBuilder, + (Chat, $$ChatsTableReferences), + Chat, + PrefetchHooks Function({bool assetsRefs}) + >; +typedef $$AssetsTableCreateCompanionBuilder = + AssetsCompanion Function({ + required String id, + required String chatId, + required Uint8List data, + Value rowid, + }); +typedef $$AssetsTableUpdateCompanionBuilder = + AssetsCompanion Function({ + Value id, + Value chatId, + Value data, + Value rowid, + }); + +final class $$AssetsTableReferences + extends BaseReferences<_$ChatDatabase, $AssetsTable, Asset> { + $$AssetsTableReferences(super.$_db, super.$_table, super.$_typedResult); + + static $ChatsTable _chatIdTable(_$ChatDatabase db) => + db.chats.createAlias('assets__chat_id__chats__id'); + + $$ChatsTableProcessedTableManager get chatId { + final $_column = $_itemColumn('chat_id')!; + + final manager = $$ChatsTableTableManager( + $_db, + $_db.chats, + ).filter((f) => f.id.sqlEquals($_column)); + final item = $_typedResult.readTableOrNull(_chatIdTable($_db)); + if (item == null) return manager; + return ProcessedTableManager( + manager.$state.copyWith(prefetchedData: [item]), + ); + } +} + +class $$AssetsTableFilterComposer + extends Composer<_$ChatDatabase, $AssetsTable> { + $$AssetsTableFilterComposer({ + required super.$db, + required super.$table, + super.joinBuilder, + super.$addJoinBuilderToRootComposer, + super.$removeJoinBuilderFromRootComposer, + }); + ColumnFilters get id => $composableBuilder( + column: $table.id, + builder: (column) => ColumnFilters(column), + ); + + ColumnFilters get data => $composableBuilder( + column: $table.data, + builder: (column) => ColumnFilters(column), + ); + + $$ChatsTableFilterComposer get chatId { + final $$ChatsTableFilterComposer composer = $composerBuilder( + composer: this, + getCurrentColumn: (t) => t.chatId, + referencedTable: $db.chats, + getReferencedColumn: (t) => t.id, + builder: + ( + joinBuilder, { + $addJoinBuilderToRootComposer, + $removeJoinBuilderFromRootComposer, + }) => $$ChatsTableFilterComposer( + $db: $db, + $table: $db.chats, + $addJoinBuilderToRootComposer: $addJoinBuilderToRootComposer, + joinBuilder: joinBuilder, + $removeJoinBuilderFromRootComposer: + $removeJoinBuilderFromRootComposer, + ), + ); + return composer; + } +} + +class $$AssetsTableOrderingComposer + extends Composer<_$ChatDatabase, $AssetsTable> { + $$AssetsTableOrderingComposer({ + required super.$db, + required super.$table, + super.joinBuilder, + super.$addJoinBuilderToRootComposer, + super.$removeJoinBuilderFromRootComposer, + }); + ColumnOrderings get id => $composableBuilder( + column: $table.id, + builder: (column) => ColumnOrderings(column), + ); + + ColumnOrderings get data => $composableBuilder( + column: $table.data, + builder: (column) => ColumnOrderings(column), + ); + + $$ChatsTableOrderingComposer get chatId { + final $$ChatsTableOrderingComposer composer = $composerBuilder( + composer: this, + getCurrentColumn: (t) => t.chatId, + referencedTable: $db.chats, + getReferencedColumn: (t) => t.id, + builder: + ( + joinBuilder, { + $addJoinBuilderToRootComposer, + $removeJoinBuilderFromRootComposer, + }) => $$ChatsTableOrderingComposer( + $db: $db, + $table: $db.chats, + $addJoinBuilderToRootComposer: $addJoinBuilderToRootComposer, + joinBuilder: joinBuilder, + $removeJoinBuilderFromRootComposer: + $removeJoinBuilderFromRootComposer, + ), + ); + return composer; + } +} + +class $$AssetsTableAnnotationComposer + extends Composer<_$ChatDatabase, $AssetsTable> { + $$AssetsTableAnnotationComposer({ + required super.$db, + required super.$table, + super.joinBuilder, + super.$addJoinBuilderToRootComposer, + super.$removeJoinBuilderFromRootComposer, + }); + GeneratedColumn get id => + $composableBuilder(column: $table.id, builder: (column) => column); + + GeneratedColumn get data => + $composableBuilder(column: $table.data, builder: (column) => column); + + $$ChatsTableAnnotationComposer get chatId { + final $$ChatsTableAnnotationComposer composer = $composerBuilder( + composer: this, + getCurrentColumn: (t) => t.chatId, + referencedTable: $db.chats, + getReferencedColumn: (t) => t.id, + builder: + ( + joinBuilder, { + $addJoinBuilderToRootComposer, + $removeJoinBuilderFromRootComposer, + }) => $$ChatsTableAnnotationComposer( + $db: $db, + $table: $db.chats, + $addJoinBuilderToRootComposer: $addJoinBuilderToRootComposer, + joinBuilder: joinBuilder, + $removeJoinBuilderFromRootComposer: + $removeJoinBuilderFromRootComposer, + ), + ); + return composer; + } +} + +class $$AssetsTableTableManager + extends + RootTableManager< + _$ChatDatabase, + $AssetsTable, + Asset, + $$AssetsTableFilterComposer, + $$AssetsTableOrderingComposer, + $$AssetsTableAnnotationComposer, + $$AssetsTableCreateCompanionBuilder, + $$AssetsTableUpdateCompanionBuilder, + (Asset, $$AssetsTableReferences), + Asset, + PrefetchHooks Function({bool chatId}) + > { + $$AssetsTableTableManager(_$ChatDatabase db, $AssetsTable table) + : super( + TableManagerState( + db: db, + table: table, + createFilteringComposer: () => + $$AssetsTableFilterComposer($db: db, $table: table), + createOrderingComposer: () => + $$AssetsTableOrderingComposer($db: db, $table: table), + createComputedFieldComposer: () => + $$AssetsTableAnnotationComposer($db: db, $table: table), + updateCompanionCallback: + ({ + Value id = const Value.absent(), + Value chatId = const Value.absent(), + Value data = const Value.absent(), + Value rowid = const Value.absent(), + }) => AssetsCompanion( + id: id, + chatId: chatId, + data: data, + rowid: rowid, + ), + createCompanionCallback: + ({ + required String id, + required String chatId, + required Uint8List data, + Value rowid = const Value.absent(), + }) => AssetsCompanion.insert( + id: id, + chatId: chatId, + data: data, + rowid: rowid, + ), + withReferenceMapper: (p0) => p0 + .map( + (e) => + (e.readTable(table), $$AssetsTableReferences(db, table, e)), + ) + .toList(), + prefetchHooksCallback: ({chatId = false}) { + return PrefetchHooks( + db: db, + explicitlyWatchedTables: [], + addJoins: + < + T extends TableManagerState< + dynamic, + dynamic, + dynamic, + dynamic, + dynamic, + dynamic, + dynamic, + dynamic, + dynamic, + dynamic, + dynamic + > + >(state) { + if (chatId) { + state = + state.withJoin( + currentTable: table, + currentColumn: table.chatId, + referencedTable: $$AssetsTableReferences + ._chatIdTable(db), + referencedColumn: $$AssetsTableReferences + ._chatIdTable(db) + .id, + ) + as T; + } + + return state; + }, + getPrefetchedDataCallback: (items) async { + return []; + }, + ); + }, + ), + ); +} + +typedef $$AssetsTableProcessedTableManager = + ProcessedTableManager< + _$ChatDatabase, + $AssetsTable, + Asset, + $$AssetsTableFilterComposer, + $$AssetsTableOrderingComposer, + $$AssetsTableAnnotationComposer, + $$AssetsTableCreateCompanionBuilder, + $$AssetsTableUpdateCompanionBuilder, + (Asset, $$AssetsTableReferences), + Asset, + PrefetchHooks Function({bool chatId}) + >; + +class $ChatDatabaseManager { + final _$ChatDatabase _db; + $ChatDatabaseManager(this._db); + $$ChatsTableTableManager get chats => + $$ChatsTableTableManager(_db, _db.chats); + $$AssetsTableTableManager get assets => + $$AssetsTableTableManager(_db, _db.assets); +} diff --git a/lib/services/clients.dart b/lib/services/clients.dart index ad3ee06..4942379 100644 --- a/lib/services/clients.dart +++ b/lib/services/clients.dart @@ -5,6 +5,7 @@ import 'package:http/http.dart' as http; import 'package:ollama_dart/ollama_dart.dart' as llama; import '../main.dart'; +import 'host.dart'; const String userAgent = "Ollama App"; @@ -13,14 +14,16 @@ class OllamaHttpOverrides extends HttpOverrides { HttpClient createHttpClient(SecurityContext? context) { return super.createHttpClient(context) ..userAgent = userAgent - ..badCertificateCallback = (_, __, ___) => true; + ..badCertificateCallback = (_, _, _) => true; } } final httpClient = http.Client(); llama.OllamaClient get ollamaClient => llama.OllamaClient( - headers: (jsonDecode(prefs!.getString("hostHeaders") ?? "{}") as Map) - .cast(), - baseUrl: "$host/api", - client: httpClient, + config: llama.OllamaConfig( + baseUrl: HostManager.instance.host!.toString(), + defaultHeaders: (jsonDecode(prefs!.getString("hostHeaders") ?? "{}") as Map) + .cast(), + ), + httpClient: httpClient, ); diff --git a/lib/services/curves.dart b/lib/services/curves.dart new file mode 100644 index 0000000..308f89f --- /dev/null +++ b/lib/services/curves.dart @@ -0,0 +1,50 @@ +import 'package:flutter/animation.dart'; + +typedef CurveGroup = ({ + Curve fast, + Duration fastDuration, + Curve normal, + Duration normalDuration, + Curve slow, + Duration slowDuration, +}); + +final class ExpressiveCurves { + ExpressiveCurves._(); + + static const CurveGroup expressiveSpatial = ( + fast: Cubic(0.42, 1.67, 0.21, 0.90), + fastDuration: Duration(milliseconds: 350), + normal: Cubic(0.38, 1.21, 0.22, 1.00), + normalDuration: Duration(milliseconds: 500), + slow: Cubic(0.39, 1.29, 0.35, 0.98), + slowDuration: Duration(milliseconds: 650), + ); + + static const CurveGroup expressiveEffects = ( + fast: Cubic(0.31, 0.94, 0.34, 1.00), + fastDuration: Duration(milliseconds: 150), + normal: Cubic(0.34, 0.80, 0.34, 1.00), + normalDuration: Duration(milliseconds: 200), + slow: Cubic(0.34, 0.88, 0.34, 1.00), + slowDuration: Duration(milliseconds: 300), + ); + + static const CurveGroup standardSpatial = ( + fast: Cubic(0.27, 1.06, 0.18, 1.00), + fastDuration: Duration(milliseconds: 350), + normal: Cubic(0.27, 1.06, 0.18, 1.00), + normalDuration: Duration(milliseconds: 500), + slow: Cubic(0.27, 1.06, 0.18, 1.00), + slowDuration: Duration(milliseconds: 750), + ); + + static const CurveGroup standardEffects = ( + fast: Cubic(0.31, 0.94, 0.34, 1.00), + fastDuration: Duration(milliseconds: 150), + normal: Cubic(0.34, 0.80, 0.34, 1.00), + normalDuration: Duration(milliseconds: 200), + slow: Cubic(0.34, 0.88, 0.34, 1.00), + slowDuration: Duration(milliseconds: 300), + ); +} diff --git a/lib/services/error.dart b/lib/services/error.dart index 72162e9..a934080 100644 --- a/lib/services/error.dart +++ b/lib/services/error.dart @@ -6,11 +6,12 @@ import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:http/http.dart'; -import 'package:ollama_dart/ollama_dart.dart'; +import 'package:ollama_dart/ollama_dart.dart' as llama; import 'package:url_launcher/url_launcher.dart'; import '../l10n/gen/app_localizations.dart'; import 'markdown.dart'; +import 'services.dart'; final _logIdRegex = RegExp(r"^[A-Z0-9]{2,16}$"); @@ -23,7 +24,7 @@ typedef ErrorGuardDetailsMessageGenerator = typedef ErrorGuardIgnoreIfGenerator = bool Function(Object exception); String _defaultErrorMessage(Object exception) => switch (exception) { - OllamaClientException _ => "Unknown client error", + llama.OllamaException _ => "Unknown client error", AssertionError _ => exception.toString().split(": ").elementAtOrNull(4) ?? "An assertion failed", @@ -39,15 +40,15 @@ String? _defaultDetailsMessage( Object exception, StackTrace stackTrace, ) => switch (exception) { - OllamaClientException e => + final llama.OllamaException e => [ - "SocketException", - "HttpException", - "ClientException", - ].contains(e.body.toString().split(":").first) + SocketException, + HttpException, + ClientException, + ].contains(e.cause.runtimeType) ? "A network error occurred while trying to connect to the server." "\n\nYou may check your network connection and server reachability and try again." - : "The Ollama API client received a faulty response with code `${e.code}`." + : "The Ollama API client received a faulty response with code `${e.message.split(":").last.trim()}`." "\n\nPlease check your Ollama server or proxy configuration and try again.", AssertionError _ => "An assertion failed, meaning that the app is misconfigured or a bug occurred." @@ -123,7 +124,7 @@ ErrorGuardDetailsMessageGenerator errorGuardDetailsMessageWithFallbackSingle( /// alphanumeric characters, starting with a letter. Other values will be /// ignored. The recommended length is 8 characters. /// -/// To generate a random log ID, run: `dart run tools/logid.dart` +/// To generate a random log ID, run: `dart run tool/logid.dart` /// /// The [errorMessage] function is used to generate the error message. This /// should be a short message that describes the error in a user-friendly way. @@ -156,6 +157,11 @@ ErrorGuardDetailsMessageGenerator errorGuardDetailsMessageWithFallbackSingle( /// useful if it's certain that the error is not caused by a bug in the app, /// but rather by a user error or a misconfiguration. [forceReporting] can be /// used to force the reporting of the error. +/// +/// The [onError] callback can be used to perform additional actions when an +/// error occurs, such as logging the error to a remote server or performing +/// additional cleanup. This callback is called before the error dialog or +/// snackbar is shown. Future errorGuard( BuildContext context, String? logId, @@ -167,6 +173,7 @@ Future errorGuard( bool enableDetails = true, bool enableReporting = true, bool forceReporting = false, + void Function(Object exception, StackTrace stackTrace)? onError, }) async { assert( logId == null || _logIdRegex.hasMatch(logId), @@ -193,9 +200,10 @@ Future errorGuard( action.call(), ).catchError(Error.throwWithStackTrace); } catch (exception, stackTrace) { + onError?.call(exception, stackTrace); if (context.mounted && !ignoreIf.call(exception)) { - var dateTime = DateTime.now(); - var colorScheme = Theme.of(context).colorScheme; + final dateTime = DateTime.now(); + final colorScheme = Theme.of(context).colorScheme; logId = logId?.toUpperCase(); if (logId != null && !_logIdRegex.hasMatch(logId)) { @@ -261,24 +269,26 @@ Future errorGuard( if (instantDialog) { showErrorDialog(); } else { - ScaffoldMessenger.of(context).showSnackBar( - SnackBar( - content: Text( - _removeNewlines( - Markdown(errorMessageText).toTextSpan(context).toPlainText(), + ScaffoldMessenger.of(context) + ..clearSnackBars() + ..showSnackBar( + SnackBar( + content: Text( + _removeNewlines( + Markdown(errorMessageText).toTextSpan(context).toPlainText(), + ), + style: TextStyle(color: colorScheme.onErrorContainer), ), - style: TextStyle(color: colorScheme.onErrorContainer), + backgroundColor: colorScheme.errorContainer, + action: !enableDetails + ? null + : SnackBarAction( + label: AppLocalizations.of(context).errorGuardDetails, + textColor: colorScheme.onErrorContainer, + onPressed: showErrorDialog, + ), ), - backgroundColor: colorScheme.errorContainer, - action: !enableDetails - ? null - : SnackBarAction( - label: AppLocalizations.of(context).errorGuardDetails, - textColor: colorScheme.onErrorContainer, - onPressed: showErrorDialog, - ), - ), - ); + ); } } return null; @@ -317,6 +327,7 @@ class _ErrorGuardDetailsDialogState extends State<_ErrorGuardDetailsDialog> { return PopScope( canPop: !widget.forceReporting, child: AlertDialog( + constraints: const BoxConstraints(minWidth: 280, maxWidth: 560), title: Stack( children: [ Align( @@ -334,7 +345,14 @@ class _ErrorGuardDetailsDialogState extends State<_ErrorGuardDetailsDialog> { ), ), ), - Text(AppLocalizations.of(context).errorGuardTitle), + Builder( + builder: (context) => Text( + AppLocalizations.of(context).errorGuardTitle, + style: DefaultTextStyle.of( + context, + ).style.copyWith(fontFamily: "GoogleSansCode"), + ), + ), ], ), content: Column( @@ -344,7 +362,14 @@ class _ErrorGuardDetailsDialogState extends State<_ErrorGuardDetailsDialog> { if (widget.logId != null) Transform.translate( offset: const Offset(0, -20), - child: Text("@${widget.logId}"), + child: Builder( + builder: (context) => Text( + "@${widget.logId}", + style: DefaultTextStyle.of( + context, + ).style.copyWith(fontFamily: "GoogleSansCode"), + ), + ), ), Text.rich(Markdown(widget.errorMessage).toTextSpan(context)), @@ -414,7 +439,7 @@ ${(widget.exception != null) ? "```\n${widget.exception}\n```" : "> Not availabl Stack Trace ``` -${widget.stackTrace.toString().trim()} +${widget.stackTrace.toString().trim().split("\n").take(20).join("\n")} ``` @@ -424,7 +449,7 @@ ${widget.stackTrace.toString().trim()} The app suggested the following cause of the issue: - ***Error Message:*** ${_removeNewlines(widget.errorMessage)} -- ***Details Message:*** ${_removeNewlines((widget.detailsMessage ?? "None provided").trim())}""" +- ***Details Message:*** ${_removeNewlines((widget.detailsMessage ?? "*None provided*").trim())}""" .trim(); void _report() { @@ -434,7 +459,7 @@ The app suggested the following cause of the issue: url += "&description=${Uri.encodeComponent('Received error: "${widget.errorMessage.replaceFirst(RegExp(r".$"), "")}"${(widget.logId != null) ? " (@${widget.logId})" : ""}')}"; - var contextText = _reportText(); + final contextText = _reportText(); url += "&context=${Uri.encodeComponent(contextText)}"; Clipboard.setData(ClipboardData(text: url)); @@ -475,17 +500,24 @@ class _ErrorGuardDetailsPanelState extends State<_ErrorGuardDetailsPanel> super.initState(); _animationController = AnimationController( value: widget.isExpanded ? 1.0 : 0.0, - duration: kThemeAnimationDuration, + duration: ExpressiveCurves.expressiveEffects.fastDuration, vsync: this, ); _expanded = widget.isExpanded; _animation = CurvedAnimation( parent: _animationController, - curve: Curves.fastEaseInToSlowEaseOut, + curve: ExpressiveCurves.expressiveEffects.fast, + reverseCurve: ExpressiveCurves.expressiveEffects.fast.flipped, ); } + @override + void dispose() { + _animationController.dispose(); + super.dispose(); + } + void _toggleAnimation() { setState(() { _expanded = !_expanded; @@ -514,7 +546,7 @@ class _ErrorGuardDetailsPanelState extends State<_ErrorGuardDetailsPanel> @override Widget build(BuildContext context) { - var theme = Theme.of(context); + final theme = Theme.of(context); return DecoratedBox( decoration: BoxDecoration( @@ -541,7 +573,7 @@ class _ErrorGuardDetailsPanelState extends State<_ErrorGuardDetailsPanel> ), SizeTransition( sizeFactor: _animation, - axisAlignment: -1, + alignment: Alignment.topCenter, child: _monospacedContent( child: Padding( padding: const EdgeInsets.only(left: 16, right: 16, bottom: 12), @@ -549,7 +581,7 @@ class _ErrorGuardDetailsPanelState extends State<_ErrorGuardDetailsPanel> ? Text( widget.content.trim(), style: const TextStyle( - fontFamily: "monospace", + fontFamily: "GoogleSansCode", height: kTextHeightNone, ), ) diff --git a/lib/services/haptic.dart b/lib/services/haptic.dart index 675752a..112ec59 100644 --- a/lib/services/haptic.dart +++ b/lib/services/haptic.dart @@ -30,9 +30,22 @@ DateTime _lastHapticChat = DateTime.fromMillisecondsSinceEpoch(0); void chatHaptic() { if (!Preferences.instance.enableHaptic) return; - var now = DateTime.now(); + final now = DateTime.now(); if (now.difference(_lastHapticChat) < _hapticChatDelay) return; _lastHapticChat = now; HapticFeedback.selectionClick(); } + +const Duration _hapticThinkingDelay = Duration(milliseconds: 150); +DateTime _lastHapticThinking = DateTime.fromMillisecondsSinceEpoch(0); + +void chatHapticThinking() { + if (!Preferences.instance.enableHaptic) return; + + final now = DateTime.now(); + if (now.difference(_lastHapticThinking) < _hapticThinkingDelay) return; + _lastHapticThinking = now; + + HapticFeedback.successNotification(); +} diff --git a/lib/services/host.dart b/lib/services/host.dart new file mode 100644 index 0000000..3cedaf0 --- /dev/null +++ b/lib/services/host.dart @@ -0,0 +1,142 @@ +import 'dart:convert'; + +import 'package:flutter/material.dart'; +import 'package:ollama_dart/ollama_dart.dart' as ollama; + +import '../l10n/gen/app_localizations.dart'; +import '../main.dart'; +import 'clients.dart' as clients; +import 'services.dart'; + +enum HostSettingError { + invalidUrl, + unreachable, + undetectable, + timeout, + outdated, + other; + + String title(BuildContext context) { + final appLocalizations = AppLocalizations.of(context); + return appLocalizations.settingsHostInvalid(name); + } + + String description(BuildContext context) { + final appLocalizations = AppLocalizations.of(context); + return appLocalizations.settingsHostInvalidDetailed(name); + } +} + +typedef HostSettingErrorCallbackStore = ({ + Object exception, + StackTrace stackTrace, +}); + +class HostManager extends ChangeNotifier { + static final HostManager _instance = HostManager._(); + static HostManager get instance => _instance; + + bool _silentFlag = false; + + Uri? _host; + Uri? get host => _host; + set host(Uri? value) { + assert(!useHost, "Host is fixed to $fixedHost and cannot be changed."); + if (useHost) return; + + var uri = value; + if (uri != null && uri.path.endsWith("/")) { + uri = uri.replace(path: uri.path.substring(0, uri.path.length - 1)); + } + + if (uri == _host) return; + _host = uri; + Preferences.instance.host = uri; + if (!_silentFlag) notifyListeners(); + } + + HostManager._() + : _host = useHost ? Uri.parse(fixedHost) : Preferences.instance.host; + + Future smartSetHost( + BuildContext context, + String value, + ) async { + assert(!useHost, "Host is fixed to $fixedHost and cannot be changed."); + + final uri = Uri.tryParse(value)?.normalizePath(); + if (uri == null || (uri.scheme != "http" && uri.scheme != "https")) { + return HostSettingError.invalidUrl; + } + + final oldHost = this.host; + final host = uri.resolve("api/version"); + + try { + HostSettingErrorCallbackStore? error; + final response = await errorGuard( + context, + "S4XS48Z9", + () async => clients.httpClient + .get(host, headers: Preferences.instance.hostHeaders) + .timeout( + TimeoutMultiplier.medium, + onTimeout: () => throw HostSettingError.timeout, + ), + errorMessage: errorGuardErrorMessageWithFallback( + (_) => "Failed to connect to host.", + ), + enableReporting: false, + onError: (e, s) => error = (exception: e, stackTrace: s), + ); + if (response == null) { + Error.throwWithStackTrace(error!.exception, error!.stackTrace); + } + + if (response.statusCode == 200) { + final version = jsonDecode(response.body)["version"] as String?; + if (!RegExp(r"(?:.*?\.){2}.*").hasMatch(version ?? "")) { + return HostSettingError.undetectable; + } + + _silentFlag = true; + this.host = host.resolve(".."); + ollama.ListResponse? data; + + try { + if (context.mounted) { + data = await errorGuard( + context, + "U7K91OW8", + () async => clients.ollamaClient.models.list().timeout( + TimeoutMultiplier.medium, + onTimeout: () => throw HostSettingError.timeout, + ), + enableReporting: false, + onError: (e, s) => error = (exception: e, stackTrace: s), + ); + } + if (data == null) { + Error.throwWithStackTrace(error!.exception, error!.stackTrace); + } + } on HostSettingError catch (_) { + rethrow; + } catch (_) { + this.host = oldHost; + return HostSettingError.undetectable; + } + + // TODO: maybe implement version checking + + ModelManager.instance.loadModelsRaw(data: data); + _silentFlag = false; + notifyListeners(); + return null; + } + } on HostSettingError catch (e) { + return e; + } catch (_) {} + + return HostSettingError.unreachable; + } +} diff --git a/lib/services/markdown.dart b/lib/services/markdown.dart index 2a22a68..b29ae31 100644 --- a/lib/services/markdown.dart +++ b/lib/services/markdown.dart @@ -365,6 +365,7 @@ final class Markdown { final document = md.Document( blockSyntaxes: [ LatexBlockSyntax(), + LatexBracketBlockSyntax(), DetailsBlockSyntax(), const md.AlertBlockSyntax(), ], @@ -373,6 +374,7 @@ final class Markdown { SubscriptSyntax(), MarkSyntax(), LatexInlineSyntax(), + LatexBracketInlineSyntax(), md.EmojiSyntax(), md.ColorSwatchSyntax(), ], @@ -381,7 +383,7 @@ final class Markdown { ); final String content; - Tree get tree => document.parse( + late final Tree tree = document.parse( content.trim().replaceAll(RegExp(r"", dotAll: true), ""), ); @@ -389,16 +391,9 @@ final class Markdown { final TextStyle? _textStyle; final TapLongPressGestureRecognizer? _recognizer; - Markdown( - this.content, { - Uri? rootUrl, - TextStyle? textStyle, - TapLongPressGestureRecognizer? recognizer, - }) : _rootUrl = rootUrl, - _textStyle = textStyle, - _recognizer = recognizer; + Markdown(this.content, {this._rootUrl, this._textStyle, this._recognizer}); - double get _paragraphHeight => (TextPainter( + late final double _paragraphHeight = (TextPainter( text: TextSpan(text: "A", style: _textStyle), textDirection: TextDirection.ltr, )..layout()).preferredLineHeight; @@ -438,33 +433,33 @@ final class Markdown { _MarkdownPayload payload, { bool block = false, }) { - var context = payload.context; - var theme = Theme.of(context); - var colorScheme = theme.colorScheme; + final context = payload.context; + final theme = Theme.of(context); + final colorScheme = theme.colorScheme; - var codeBox = BoxDecoration( + final codeBox = BoxDecoration( color: colorScheme.surfaceContainerHigh, border: Border.all(color: colorScheme.outline), borderRadius: BorderRadius.circular(6), ); - var spans = []; + final spans = []; for (var nodePair in payload.tree.asMap().entries) { var skipParagraphBreak = false; - var node = nodePair.value; + final node = nodePair.value; if (node is md.Text) { var text = node.text.replaceAll("\n", " "); while (text.contains(" ")) { text = text.replaceAll(" ", " "); } - var nodeStyle = const TextStyle( + const nodeStyle = TextStyle( // height: kTextHeightNone, fontFeatures: [FontFeature.tabularFigures()], ); spans.addAll( text - .split(r"(?=\s|\/|\\)") + .split(RegExp(r"(?=\s|\/|\\)")) .map( (t) => TextSpan( text: t, @@ -476,7 +471,7 @@ final class Markdown { } else if (node is md.Element) { switch (node.tag) { case "p": // MARK: Paragraph - var block = + final block = node.children!.length == 1 && node.children![0] is md.Element && (node.children![0] as md.Element).tag == "img"; @@ -486,13 +481,16 @@ final class Markdown { spans.addAll( _toInlineSpans(payload.copy(tree: node.children), block: block), ); + case "br": // MARK: Line Break + skipParagraphBreak = true; + spans.add(const TextSpan(text: "\n")); case "hr": // MARK: Horizontal Rule spans.add(const WidgetSpan(child: Divider())); case "h1" || "h2" || "h3" || "h4" || "h5" || "h6": // MARK: Headings - var multiplier = + final multiplier = (payload.textStyle?.fontSize ?? kDefaultFontSize) / kDefaultFontSize; - var size = + final size = switch (node.tag) { // stolen from HTML "h1" => 32, @@ -504,7 +502,7 @@ final class Markdown { _ => kDefaultFontSize, } - 2; - var nodeStyle = TextStyle( + final nodeStyle = TextStyle( fontSize: multiplier * size, fontWeight: FontWeight.bold, ); @@ -517,7 +515,7 @@ final class Markdown { ), ); case "em": // MARK: Italic - var nodeStyle = const TextStyle(fontStyle: FontStyle.italic); + const nodeStyle = TextStyle(fontStyle: FontStyle.italic); spans.addAll( _toInlineSpans( payload.copy( @@ -527,7 +525,7 @@ final class Markdown { ), ); case "strong": // MARK: Bold - var nodeStyle = const TextStyle(fontWeight: FontWeight.bold); + const nodeStyle = TextStyle(fontWeight: FontWeight.bold); spans.addAll( _toInlineSpans( payload.copy( @@ -537,9 +535,7 @@ final class Markdown { ), ); case "del": // MARK: Strikethrough - var nodeStyle = const TextStyle( - decoration: TextDecoration.lineThrough, - ); + const nodeStyle = TextStyle(decoration: TextDecoration.lineThrough); spans.addAll( _toInlineSpans( payload.copy( @@ -549,10 +545,10 @@ final class Markdown { ), ); case "mark": // MARK: Highlight - var color = Colors.amberAccent + final color = Colors.amberAccent .harmonizeWith(colorScheme.primary) .withValues(alpha: 0.6); - var nodeStyle = TextStyle( + final nodeStyle = TextStyle( color: color.computeLuminance() > 0.5 ? Colors.black : Colors.white, @@ -567,13 +563,13 @@ final class Markdown { ), ); case "a": // MARK: Link - var href = node.attributes["href"]; - var parsed = href == null ? null : Uri.tryParse(href); - var valid = + final href = node.attributes["href"]; + final parsed = href == null ? null : Uri.tryParse(href); + final valid = href != null && parsed != null && ["http", "https"].contains(parsed.scheme); - var recognizer = valid + final recognizer = valid ? TapLongPressGestureRecognizer( onTap: () => launchUrl(payload.resolveUri(href)), onLongPress: () { @@ -583,8 +579,10 @@ final class Markdown { ) : null; - var linkColor = Colors.lightBlue.harmonizeWith(colorScheme.primary); - var nodeStyle = TextStyle( + final linkColor = Colors.lightBlue.harmonizeWith( + colorScheme.primary, + ); + final nodeStyle = TextStyle( color: valid ? linkColor : theme.disabledColor, decoration: TextDecoration.underline, decorationThickness: 1.2, @@ -603,9 +601,9 @@ final class Markdown { ); case "sub": // MARK: Subscript/Superscript case "sup": - var isSup = node.tag == "sup"; + final isSup = node.tag == "sup"; - var referenceTo = + final referenceTo = isSup && node.attributes["class"] == "footnote-ref" ? (node.children!.first as md.Element).attributes["href"] ?.substring(1) @@ -633,7 +631,9 @@ final class Markdown { } catch (_) {} if (referenced == null) break; - var referencedTree = List.from(referenced.children!); + final referencedTree = List.from( + referenced.children!, + ); if (referencedTree.isEmpty) break; referencedTree.last = md.Element( @@ -649,7 +649,7 @@ final class Markdown { alignment: PlaceholderAlignment.middle, child: Builder( builder: (context) { - var nodeStyle = TextStyle( + final nodeStyle = TextStyle( color: colorScheme.onSurface, fontSize: math.max( 8.0, @@ -705,7 +705,7 @@ final class Markdown { alignment: PlaceholderAlignment.middle, child: Builder( builder: (context) { - var nodeStyle = TextStyle( + final nodeStyle = TextStyle( fontSize: math.max( 8.0, (DefaultTextStyle.of(context).style.fontSize ?? @@ -728,9 +728,9 @@ final class Markdown { ), ); case "code": // MARK: Inline code - var text = (node.children!.first as md.Text).text; + final text = (node.children!.first as md.Text).text; - var backgroundColor = + final backgroundColor = node.attributes.containsKey("class") && node.attributes["class"] == "gfm-color_chip" ? (node.children!.firstWhere( @@ -739,7 +739,7 @@ final class Markdown { as md.Element) .attributes["style"] : null; - var colorCode = + final colorCode = backgroundColor != null && backgroundColor.startsWith("background-color:") ? backgroundColor @@ -747,32 +747,32 @@ final class Markdown { .trim() .replaceAll(RegExp(r"^#|;$"), "") : null; - var color = colorCode != null + final color = colorCode != null ? switch (colorCode.length) { 3 || 4 => () { - var r = int.tryParse(colorCode[0] * 2, radix: 16); - var g = int.tryParse(colorCode[1] * 2, radix: 16); - var b = int.tryParse(colorCode[2] * 2, radix: 16); - var a = int.tryParse( + final r = int.tryParse(colorCode[0] * 2, radix: 16); + final g = int.tryParse(colorCode[1] * 2, radix: 16); + final b = int.tryParse(colorCode[2] * 2, radix: 16); + final a = int.tryParse( (colorCode.length == 4 ? colorCode[3] : "F") * 2, radix: 16, ); return Color.fromARGB(a ?? 255, r ?? 0, g ?? 0, b ?? 0); }(), 6 || 8 => () { - var r = int.tryParse( + final r = int.tryParse( colorCode.substring(0, 2), radix: 16, ); - var g = int.tryParse( + final g = int.tryParse( colorCode.substring(2, 4), radix: 16, ); - var b = int.tryParse( + final b = int.tryParse( colorCode.substring(4, 6), radix: 16, ); - var a = int.tryParse( + final a = int.tryParse( (colorCode.length == 8 ? colorCode.substring(6, 8) : "FF"), @@ -784,10 +784,10 @@ final class Markdown { } : null; - var textStyle = _mergeOrOther( + final textStyle = _mergeOrOther( payload.textStyle, TextStyle( - fontFamily: "monospace", + fontFamily: "GoogleSansCode", fontSize: 14, backgroundColor: Colors.transparent, color: @@ -795,7 +795,7 @@ final class Markdown { theme.colorScheme.onSurface, ), ); - var parts = [ + final parts = [ ...text .split(RegExp(r"(?<=\s|\/|\\)")) .map((t) => Text(t, style: textStyle)), @@ -818,10 +818,10 @@ final class Markdown { } for (var w in parts.asMap().entries) { - var isStart = w.key == 0; - var isEnd = w.key == parts.length - 1; + final isStart = w.key == 0; + final isEnd = w.key == parts.length - 1; - var decoration = BoxDecoration( + final decoration = BoxDecoration( color: codeBox.color, // border: Border( // top: codeBox.border!.top, @@ -856,7 +856,7 @@ final class Markdown { ), ), ); - var padding = EdgeInsets.only( + final padding = EdgeInsets.only( left: isStart ? 2 : 0, right: isEnd ? 2 : 0, ); @@ -869,6 +869,7 @@ final class Markdown { height: kTextHeightNone, backgroundColor: decoration.color, ), + spellOut: color != null, ), WidgetSpan( alignment: PlaceholderAlignment.middle, @@ -881,9 +882,9 @@ final class Markdown { ]); } case "pre": // MARK: Fenced code block - var codeElement = node.children!.first as md.Element; - var text = (codeElement.children!.first as md.Text).text.trim(); - var lang = + final codeElement = node.children!.first as md.Element; + final text = (codeElement.children!.first as md.Text).text.trim(); + final lang = codeElement.attributes.containsKey("class") && codeElement.attributes["class"]!.startsWith("language-") ? codeElement.attributes["class"]!.substring(9) @@ -953,8 +954,8 @@ final class Markdown { ), ); case "img": // MARK: Image - var alt = (node.attributes["alt"] ?? "").trim(); - var title = node.attributes["title"]?.trim(); + final alt = (node.attributes["alt"] ?? "").trim(); + final title = node.attributes["title"]?.trim(); var src = node.attributes["src"]; void imageScreen(String src) => Navigator.of(context).push( @@ -1073,7 +1074,7 @@ final class Markdown { ].contains(alertType)) { alertType = null; } - var alertTitle = alertType != null + final alertTitle = alertType != null ? node.children!.removeAt( node.children!.indexWhere( (e) => @@ -1083,16 +1084,17 @@ final class Markdown { ) as md.Element : null; - var alertIcon = alertType != null + final alertIcon = alertType != null ? switch (alertType) { "note" => Icons.info_outline, "tip" => Icons.lightbulb_outline, - "important" || "warning" => Icons.feedback_outlined, + "important" => Icons.feedback_outlined, + "warning" => Icons.warning_amber_rounded, "caution" => Icons.report_outlined, _ => null, } : null; - var alertColor = alertType != null + final alertColor = alertType != null ? (switch (alertType) { "note" => Colors.blue[600], "tip" => Colors.green[600], @@ -1103,12 +1105,12 @@ final class Markdown { })?.harmonizeWith(colorScheme.primary) : null; - var contentSpans = _toInlineSpans( + final contentSpans = _toInlineSpans( payload.copy(tree: node.children), block: true, ); if (alertType != null && alertTitle != null) { - var titleSpans = _toInlineSpans( + final titleSpans = _toInlineSpans( payload.copy(tree: alertTitle.children), ); contentSpans.insertAll(0, [ @@ -1154,12 +1156,12 @@ final class Markdown { ), ); case "table": // MARK: Table - var head = + final head = node.children!.firstWhere( (e) => e is md.Element && e.tag == "thead", ) as md.Element; - var body = + final body = node.children!.firstWhere( (e) => e is md.Element && e.tag == "tbody", orElse: () => md.Element("tbody", []), @@ -1180,35 +1182,37 @@ final class Markdown { headingRowColor: WidgetStatePropertyAll( colorScheme.surfaceContainerLow, ), - columns: (head.children!.first as md.Element).children!.map(( - e, - ) { - e as md.Element; - var align = switch (e.attributes["align"]) { - "left" => MainAxisAlignment.start, - "right" => MainAxisAlignment.end, - _ => MainAxisAlignment.center, - }; - return DataColumn( - label: Padding( - padding: const EdgeInsets.only(top: 4, bottom: 4), - child: Text( - // TODO: Reimplement table to better fit rendered content. Then use [Text.rich] here. - TextSpan( - children: _toInlineSpans( - payload.copy(tree: e.children), + columns: (head.children!.first as md.Element).children!.map( + (e) { + e as md.Element; + final align = switch (e.attributes["align"]) { + "left" => MainAxisAlignment.start, + "right" => MainAxisAlignment.end, + _ => MainAxisAlignment.center, + }; + return DataColumn( + label: Expanded( + child: Padding( + padding: const EdgeInsets.only(top: 4, bottom: 4), + child: Text.rich( + TextSpan( + children: _toInlineSpans( + payload.copy(tree: e.children), + ), + ), + overflow: TextOverflow.visible, ), - ).toPlainText(), + ), ), - ), - headingRowAlignment: align, - ); - }).toList(), + headingRowAlignment: align, + ); + }, + ).toList(), rows: body.children!.map((e) { return DataRow( cells: (e as md.Element).children!.map((c) { c as md.Element; - var align = switch (c.attributes["align"]) { + final align = switch (c.attributes["align"]) { "right" => Alignment.centerRight, "center" => Alignment.center, _ => Alignment.centerLeft, @@ -1216,13 +1220,13 @@ final class Markdown { return DataCell( Align( alignment: align, - child: Text( - // TODO: Reimplement table to better fit rendered content. Then use [Text.rich] here. + child: Text.rich( TextSpan( children: _toInlineSpans( payload.copy(tree: c.children), ), - ).toPlainText(), + ), + overflow: TextOverflow.visible, ), ), ); @@ -1236,20 +1240,20 @@ final class Markdown { ); case "ol": // MARK: Ordered/Unordered List case "ul": - var ordered = node.tag == "ol"; + final ordered = node.tag == "ol"; - var checkboxes = {}; - var elements = []; + final checkboxes = {}; + final elements = []; for (var child in (node.children ?? []).asMap().entries) { if (child.value is md.Element && (child.value as md.Element).tag == "li") { - var childElement = child.value as md.Element; - var children = childElement.children ?? []; + final childElement = child.value as md.Element; + final children = childElement.children ?? []; if (!ordered && childElement.attributes.containsKey("class") && childElement.attributes["class"] == "task-list-item") { - var checkbox = + final checkbox = childElement.children!.firstWhere( (e) => e is md.Element && @@ -1257,7 +1261,7 @@ final class Markdown { e.attributes["type"] == "checkbox", ) as md.Element; - var isChecked = checkbox.attributes.containsKey("checked"); + final isChecked = checkbox.attributes.containsKey("checked"); checkboxes[child.key] = isChecked; children.removeAt(0); } @@ -1302,13 +1306,16 @@ final class Markdown { ), ), ) - : ExcludeSemantics( - child: Text( - ordered ? "${e.key + 1}." : "•", - style: TextStyle( - height: - payload.textStyle?.height ?? - kTextHeightNone, + : Transform.translate( + offset: const Offset(0, 2), + child: ExcludeSemantics( + child: Text( + ordered ? "${e.key + 1}." : "•", + style: TextStyle( + height: + payload.textStyle?.height ?? + kTextHeightNone, + ), ), ), ), @@ -1323,12 +1330,12 @@ final class Markdown { ), ); case "details": // MARK: Details - var opened = + final opened = node.attributes.containsKey("class") && node.attributes["class"]! .split(" ") .contains("markdown-details-open"); - var summary = + final summary = (node.children!.removeAt( node.children!.indexWhere( (e) => @@ -1360,8 +1367,8 @@ final class Markdown { ), ); case "latex": // MARK: LaTeX Block - var text = (node.children!.first as md.Text).text; - var errorStyle = TextStyle( + final text = (node.children!.first as md.Text).text; + final errorStyle = TextStyle( color: colorScheme.error, fontStyle: FontStyle.italic, ); @@ -1394,8 +1401,8 @@ final class Markdown { ), ); case "latexInline": // MARK: LaTeX Inline - var text = (node.children!.first as md.Text).text; - var errorStyle = TextStyle( + final text = (node.children!.first as md.Text).text; + final errorStyle = TextStyle( color: colorScheme.error, fontStyle: FontStyle.italic, ); @@ -1433,7 +1440,7 @@ final class Markdown { } class SuperscriptSyntax extends md.InlineSyntax { - SuperscriptSyntax() : super(r"\^.+\^"); + SuperscriptSyntax() : super(r"\^.+?\^"); @override bool onMatch(md.InlineParser parser, Match match) { @@ -1465,11 +1472,11 @@ class SubscriptSyntax extends md.InlineSyntax { } class MarkSyntax extends md.InlineSyntax { - MarkSyntax() : super(r"<\s*mark\s*>(.*)<\/\s*mark\s*>"); + MarkSyntax() : super(r"<\s*mark\s*>(.*?)<\/\s*mark\s*>"); @override bool onMatch(md.InlineParser parser, Match match) { - var text = match[1]!.trim(); + final text = match[1]!.trim(); if (text.isEmpty) { parser.addNode(md.Text(match[0]!)); } else { @@ -1482,14 +1489,14 @@ class MarkSyntax extends md.InlineSyntax { class LatexBlockSyntax extends md.BlockSyntax { @override md.Node? parse(md.BlockParser parser) { - var opening = parser.current.content; - var m = pattern.firstMatch(opening)!; - var after = opening.substring(m.end); + final opening = parser.current.content; + final m = pattern.firstMatch(opening)!; + final after = opening.substring(m.end); parser.advance(); - var buf = StringBuffer(); + final buf = StringBuffer(); - var closeSame = after.indexOf(_pattern); + final closeSame = after.indexOf(_pattern); if (closeSame != -1) { buf.write(after.substring(0, closeSame)); return _toElement(buf); @@ -1497,8 +1504,8 @@ class LatexBlockSyntax extends md.BlockSyntax { if (after.isNotEmpty) buf.write(after); while (!parser.isDone) { - var line = parser.current.content; - var ci = line.indexOf(_pattern); + final line = parser.current.content; + final ci = line.indexOf(_pattern); if (ci != -1) { buf.write(line.substring(0, ci)); parser.advance(); @@ -1520,7 +1527,7 @@ class LatexBlockSyntax extends md.BlockSyntax { } class LatexInlineSyntax extends md.InlineSyntax { - LatexInlineSyntax() : super(r"\$.+\$"); + LatexInlineSyntax() : super(r"\$.+?\$"); @override bool onMatch(md.InlineParser parser, Match match) { @@ -1535,17 +1542,73 @@ class LatexInlineSyntax extends md.InlineSyntax { } } +class LatexBracketBlockSyntax extends md.BlockSyntax { + @override + md.Node? parse(md.BlockParser parser) { + final opening = parser.current.content; + final m = pattern.firstMatch(opening)!; + final after = opening.substring(m.end); + parser.advance(); + + final buf = StringBuffer(); + + final closeSame = after.indexOf(_closePattern); + if (closeSame != -1) { + buf.write(after.substring(0, closeSame)); + return _toElement(buf); + } + + if (after.isNotEmpty) buf.write(after); + while (!parser.isDone) { + final line = parser.current.content; + final ci = line.indexOf(_closePattern); + if (ci != -1) { + buf.write(line.substring(0, ci)); + parser.advance(); + return _toElement(buf); + } + buf.writeln(line); + parser.advance(); + } + + return _toElement(buf); + } + + md.Element _toElement(StringBuffer buf) => + md.Element("latex", [md.Text(buf.toString())]); + + @override + RegExp get pattern => RegExp(r"^\\\["); + final _closePattern = r"\]"; +} + +class LatexBracketInlineSyntax extends md.InlineSyntax { + LatexBracketInlineSyntax() : super(r"\\\(.+?\\\)"); + + @override + bool onMatch(md.InlineParser parser, Match match) { + var text = match[0]!.substring(2); + text = text.substring(0, text.length - 2).trim(); + if (text.isEmpty) { + parser.addNode(md.Text(match[0]!)); + } else { + parser.addNode(md.Element("latexInline", [md.Text(text)])); + } + return true; + } +} + class DetailsBlockSyntax extends md.BlockSyntax { @override md.Node? parse(md.BlockParser parser) { - var opening = parser.current.content; - var m = pattern.firstMatch(opening)!; - var after = opening.substring(m.end); + final opening = parser.current.content; + final m = pattern.firstMatch(opening)!; + final after = opening.substring(m.end); parser.advance(); - var buf = StringBuffer(); + final buf = StringBuffer(); - var closeSame = after.indexOf(_closePattern); + final closeSame = after.indexOf(_closePattern); if (closeSame != -1) { buf.write(after.substring(0, closeSame)); return _toElement(parser.document, buf); @@ -1553,8 +1616,8 @@ class DetailsBlockSyntax extends md.BlockSyntax { if (after.isNotEmpty) buf.write(after); while (!parser.isDone) { - var line = parser.current.content; - var ci = line.indexOf(_closePattern); + final line = parser.current.content; + final ci = line.indexOf(_closePattern); if (ci != -1) { buf.write(line.substring(0, ci)); parser.advance(); @@ -1568,12 +1631,12 @@ class DetailsBlockSyntax extends md.BlockSyntax { } md.Element? _toElement(md.Document doc, StringBuffer buf) { - var m = _realPattern.firstMatch(""); + final m = _realPattern.firstMatch(""); if (m == null) return null; var className = "markdown-details"; if (m.namedGroup("argument") != null) { - var argument = m.namedGroup("argument")!.trim(); + final argument = m.namedGroup("argument")!.trim(); if (argument.isNotEmpty && ["open"].contains(argument)) { className += " markdown-details-$argument"; } @@ -1587,9 +1650,9 @@ class DetailsBlockSyntax extends md.BlockSyntax { summary = summary.replaceAll(" " * 2, " " * 1); } - var content = m.namedGroup("content")!.trimRight().split("\n\n"); - var contentFirstLine = content.removeAt(0); - var contentNodes = [ + final content = m.namedGroup("content")!.trimRight().split("\n\n"); + final contentFirstLine = content.removeAt(0); + final contentNodes = [ md.Element("p", [md.Text(summary)]) ..attributes["class"] = "markdown-details-summary", if (contentFirstLine.trim().isNotEmpty) @@ -1730,9 +1793,10 @@ class _MarkdownFootnoteModal extends StatefulWidget { class _MarkdownFootnoteModalState extends State<_MarkdownFootnoteModal> { var urlMode = false; + bool _loadedData = false; + bool urlModeError = false; late final Uri? urlModeUrl; OgpData? urlModeData; - bool _loadedData = false; Uri? urlModeFavicon; @override @@ -1744,32 +1808,35 @@ class _MarkdownFootnoteModalState extends State<_MarkdownFootnoteModal> { urlMode = urlModeUrl != null && ["http", "https"].contains(urlModeUrl!.scheme); if (urlMode) { - OgpDataExtract.execute(urlModeUrl!.toString(), userAgent: userAgent).then( - (v) { - _loadedData = true; - if (mounted) setState(() {}); - urlModeData = v; - if (mounted) setState(() {}); - }, - ); - OgpDataExtract.fetchFavicon( - urlModeUrl!.toString(), - userAgent: userAgent, - ).then((v) async { - for (var i in v) { - if (i == null) continue; - var req = await httpClient.head(Uri.parse(i)); - if (req.statusCode == 200 && - [ - "image/jpeg", - "image/png", - ].contains(req.headers["content-type"])) { - urlModeFavicon = Uri.parse(i); - if (mounted && _loadedData) setState(() {}); - break; - } - } - }); + OgpDataExtract.execute(urlModeUrl!.toString(), userAgent: userAgent) + .then((v) { + _loadedData = true; + if (mounted) setState(() {}); + urlModeData = v; + if (mounted) setState(() {}); + }) + .catchError((_) { + _loadedData = true; + urlModeError = true; + if (mounted) setState(() {}); + }); + OgpDataExtract.fetchFavicon(urlModeUrl!.toString(), userAgent: userAgent) + .then((v) async { + for (var i in v) { + if (i == null) continue; + final req = await httpClient.head(Uri.parse(i)); + if (req.statusCode == 200 && + [ + "image/jpeg", + "image/png", + ].contains(req.headers["content-type"])) { + urlModeFavicon = Uri.parse(i); + if (mounted && _loadedData) setState(() {}); + break; + } + } + }) + .catchError((_) {}); } } @@ -1777,21 +1844,21 @@ class _MarkdownFootnoteModalState extends State<_MarkdownFootnoteModal> { Widget build(BuildContext context) { Widget child; if (urlMode) { - var image = urlModeData?.image; - var title = urlModeData?.title ?? urlModeUrl.toString(); - var description = urlModeData?.description; + final image = urlModeData?.image; + final title = urlModeData?.title ?? urlModeUrl.toString(); + final description = urlModeData?.description; - var duration = kThemeAnimationDuration; - var curve = Curves.fastEaseInToSlowEaseOut; + const duration = kThemeAnimationDuration; + const curve = Curves.fastEaseInToSlowEaseOut; - child = InkWell( - onTap: () async { - await launchUrl(urlModeUrl!, mode: LaunchMode.inAppBrowserView); - if (context.mounted) Navigator.of(context).pop(); - }, - borderRadius: BorderRadius.circular(12), - child: Card.filled( - margin: EdgeInsets.zero, + child = Card.filled( + margin: EdgeInsets.zero, + clipBehavior: Clip.antiAlias, + child: InkWell( + onTap: () async { + await launchUrl(urlModeUrl!, mode: LaunchMode.inAppBrowserView); + if (context.mounted) Navigator.of(context).pop(); + }, child: Column( mainAxisSize: MainAxisSize.min, children: [ @@ -1799,40 +1866,34 @@ class _MarkdownFootnoteModalState extends State<_MarkdownFootnoteModal> { Column( mainAxisSize: MainAxisSize.min, children: [ - ClipRRect( - borderRadius: const BorderRadius.only( - topLeft: Radius.circular(12), - topRight: Radius.circular(12), - ), - child: AnimatedSize( - duration: duration, - curve: curve, - alignment: Alignment.topCenter, - child: image != null - ? Image.network( - image, - loadingBuilder: (_, child, loadingProgress) { - if (loadingProgress == null) return child; - return SizedBox( - width: double.infinity, - child: LinearProgressIndicator( - value: - loadingProgress.expectedTotalBytes != - null - ? loadingProgress - .cumulativeBytesLoaded / - loadingProgress - .expectedTotalBytes! - : null, - ), - ); - }, - ) - : const SizedBox( - width: double.infinity, - child: LinearProgressIndicator(), - ), - ), + AnimatedSize( + duration: duration, + curve: curve, + alignment: Alignment.topCenter, + child: image != null + ? Image.network( + image, + loadingBuilder: (_, child, loadingProgress) { + if (loadingProgress == null) return child; + return SizedBox( + width: double.infinity, + child: LinearProgressIndicator( + value: + loadingProgress.expectedTotalBytes != + null + ? loadingProgress + .cumulativeBytesLoaded / + loadingProgress + .expectedTotalBytes! + : null, + ), + ); + }, + ) + : const SizedBox( + width: double.infinity, + child: LinearProgressIndicator(), + ), ), if (_loadedData) const Divider(height: 1), ], @@ -1841,6 +1902,8 @@ class _MarkdownFootnoteModalState extends State<_MarkdownFootnoteModal> { duration: duration, curve: curve, child: ListTile( + isThreeLine: + !(!_loadedData || (_loadedData && description == null)), leading: SizedBox( height: 24, width: 24, @@ -1957,6 +2020,12 @@ class _MarkdownDetailsState extends State<_MarkdownDetails> ); } + @override + void dispose() { + _animationController.dispose(); + super.dispose(); + } + void _toggleAnimation() { if (!kIsWeb) selectionHaptic(); setState(() { @@ -2001,7 +2070,7 @@ class _MarkdownDetailsState extends State<_MarkdownDetails> ), SizeTransition( sizeFactor: _animation, - axisAlignment: -1, + alignment: Alignment.topCenter, child: Text.rich( TextSpan(children: widget.contentSpans), style: widget.textStyle, @@ -2083,7 +2152,7 @@ class _MarkdownImageScreenState extends State<_MarkdownImageScreen> @override void dispose() { - _controller.removeListener(onUpdate); + _controller.dispose(); super.dispose(); } @@ -2093,8 +2162,8 @@ class _MarkdownImageScreenState extends State<_MarkdownImageScreen> @override Widget build(BuildContext context) { - var finalTitle = widget.title ?? "Untitled Image"; - var titleWidget = Text( + final finalTitle = widget.title ?? "Untitled Image"; + final titleWidget = Text( finalTitle, maxLines: 1, overflow: TextOverflow.ellipsis, @@ -2103,8 +2172,8 @@ class _MarkdownImageScreenState extends State<_MarkdownImageScreen> data: ThemeBuilderData.current!.themeDark(), child: LayoutBuilder( builder: (context, constraints) { - var ratio = _ratioFromDouble(_imageSize?.aspectRatio ?? 1); - var realScale = _imageSize != null + final ratio = _ratioFromDouble(_imageSize?.aspectRatio ?? 1); + final realScale = _imageSize != null ? (_toRealScale( _controller.value.getMaxScaleOnAxis(), _imageSize!, @@ -2224,14 +2293,14 @@ class _MarkdownImageScreenState extends State<_MarkdownImageScreen> Size imageSize, BoxConstraints constraints, ) { - var iw = imageSize.width; - var ih = imageSize.height; - var cw = constraints.maxWidth; - var ch = constraints.maxHeight; + final iw = imageSize.width; + final ih = imageSize.height; + final cw = constraints.maxWidth; + final ch = constraints.maxHeight; if (iw <= 0 || ih <= 0 || cw <= 0 || ch <= 0) return scale; - var fitScale = (iw / ih > cw / ch) ? cw / iw : ch / ih; + final fitScale = (iw / ih > cw / ch) ? cw / iw : ch / ih; return scale * fitScale; } @@ -2240,17 +2309,17 @@ class _MarkdownImageScreenState extends State<_MarkdownImageScreen> Size imageSize, BoxConstraints constraints, ) { - var iw = imageSize.width * (constraints.maxWidth / imageSize.width); - var ih = imageSize.height * (constraints.maxHeight / imageSize.height); - var cw = constraints.maxWidth; - var ch = constraints.maxHeight; + final iw = imageSize.width * (constraints.maxWidth / imageSize.width); + final ih = imageSize.height * (constraints.maxHeight / imageSize.height); + final cw = constraints.maxWidth; + final ch = constraints.maxHeight; if (iw <= 0 || ih <= 0) return realScale; if (!cw.isFinite || !ch.isFinite) return realScale; if (cw <= 0 || ch <= 0) return realScale; // if (iw <= cw && ih <= ch) return realScale; - var fitScale = (iw / ih > cw / ch) ? cw / iw : ch / ih; + final fitScale = (iw / ih > cw / ch) ? cw / iw : ch / ih; return (realScale / fitScale) * (imageSize.width / constraints.maxWidth); } @@ -2262,10 +2331,10 @@ class _MarkdownImageScreenState extends State<_MarkdownImageScreen> if (!v.isFinite || v <= 0) { throw ArgumentError.value(v, 'v', 'must be > 0 and finite'); } - var inverted = preferWidth && v < 1.0; + final inverted = preferWidth && v < 1.0; if (inverted) v = 1.0 / v; - var a0 = v.floor(); + final a0 = v.floor(); if ((v - a0).abs() < 1e-12) { return inverted ? (denominator: 1, numerator: a0) @@ -2280,12 +2349,12 @@ class _MarkdownImageScreenState extends State<_MarkdownImageScreen> while (true) { if (frac.abs() < 1e-12) break; - var x = 1.0 / frac; - var a = x.floor(); + final x = 1.0 / frac; + final a = x.floor(); var p2 = a * p1 + p0; var q2 = a * q1 + q0; if (q2 > maxDen) { - var k = (maxDen - q0) ~/ q1; + final k = (maxDen - q0) ~/ q1; if (k <= 0) { p2 = p1; q2 = q1; @@ -2308,7 +2377,7 @@ class _MarkdownImageScreenState extends State<_MarkdownImageScreen> a = a.abs(); b = b.abs(); while (b != 0) { - var t = a % b; + final t = a % b; a = b; b = t; } @@ -2318,11 +2387,11 @@ class _MarkdownImageScreenState extends State<_MarkdownImageScreen> var num = p1; var den = q1; if (inverted) { - var t = num; + final t = num; num = den; den = t; } - var gg = g(num, den); + final gg = g(num, den); return (numerator: num ~/ gg, denominator: den ~/ gg); } } @@ -2371,9 +2440,9 @@ class TapLongPressGestureRecognizer extends GestureRecognizer { extension on md.Node { String toInfoString() { switch (this) { - case md.Text e: + case final md.Text e: return 'Text("${e.text}")'; - case md.Element e: + case final md.Element e: return 'Element(tag: ${e.tag}, attributes: ${e.attributes}, children: [${e.children?.map((e) => e.toInfoString()).join(", ")}])'; default: return 'Node()'; diff --git a/lib/services/model.dart b/lib/services/model.dart index ff4ea0f..8429968 100644 --- a/lib/services/model.dart +++ b/lib/services/model.dart @@ -2,54 +2,136 @@ import 'dart:convert'; import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; +import 'package:intl/intl.dart'; import 'package:ollama_dart/ollama_dart.dart' as ollama; +import '../main.dart'; import 'clients.dart' as clients; import 'preferences.dart'; -typedef ModelCapability = ollama.Capability; +/// Container class for model capabilities. +/// +/// See more: +/// - https://github.com/ollama/ollama/blob/main/types/model/capability.go +class ModelCapability { + final String name; + + ModelCapability(this.name); + + static ModelCapability completion = ModelCapability("completion"); + static ModelCapability tools = ModelCapability("tools"); + static ModelCapability insertion = ModelCapability("insert"); + static ModelCapability vision = ModelCapability("vision"); + static ModelCapability embedding = ModelCapability("embedding"); + static ModelCapability thinking = ModelCapability("thinking"); + static ModelCapability image = ModelCapability("image"); + static ModelCapability audio = ModelCapability("audio"); + + @override + operator ==(Object other) => other is ModelCapability && other.name == name; + @override + int get hashCode => name.hashCode; + + IconData? get icon => switch (name) { + "completion" => Icons.text_fields, + "tools" => Icons.build, + "insert" => Icons.read_more, + "vision" => Icons.visibility_outlined, + "embedding" => Icons.link, + "thinking" => Icons.emoji_objects_outlined, + "image" => Icons.image_outlined, + "audio" => Icons.audiotrack_outlined, + _ => Icons.inventory_2_outlined, + }; +} class Model extends ChangeNotifier { final String name; + List nameColored(BuildContext context) => + nameColoredStatic(name: name, context: context)!; + static List? nameColoredStatic({ + required String? name, + required BuildContext context, + }) { + if (name == null) return null; + final colorScheme = ColorScheme.of(context); + + final parts = name.split(":"); + assert( + parts.isNotEmpty && parts.length <= 2, + "Model name should be in format family:version, but got $name", + ); + + return [ + TextSpan(text: parts.first), + if (parts.length == 2) + TextSpan( + text: ":${parts.last}", + style: TextStyle(color: colorScheme.outline), + ), + ]; + } + + DateTime _modifiedAt; + DateTime get modifiedAt => _modifiedAt; + + int _size; + int get size => _size; + String _family; String get family => _family; Set _families; Set get families => Set.unmodifiable(_families); + String _parameterSize; + String get parameterSize => _parameterSize; + Set _capabilities; Set get capabilities => Set.unmodifiable(_capabilities); Model._( this.name, { - required String family, - Set families = const {}, - Set capabilities = const {}, - }) : _family = family, - _families = families, - _capabilities = capabilities; + required this._modifiedAt, + required this._size, + required this._family, + this._families = const {}, + required this._parameterSize, + }) : _capabilities = const {}; - factory Model.fromApi({required ollama.Model model, ollama.ModelInfo? info}) { + factory Model.fromApi({required ollama.ModelSummary model}) { return Model._( model.model!, + modifiedAt: + DateTime.tryParse(model.modifiedAt!)?.toLocal() ?? + DateTime.fromMillisecondsSinceEpoch(0), + size: model.size ?? 0, family: model.details!.family!, families: model.details!.families?.toSet() ?? {}, - capabilities: info?.capabilities?.toSet() ?? {}, + parameterSize: model.details!.parameterSize!.replaceAll(".0", ""), ); } + bool _didCallUpdateData = false; + bool get didCallUpdateData => _didCallUpdateData; + Future updateData() async { - var data = await clients.ollamaClient.showModelInfo( - request: ollama.ModelInfoRequest(model: name), + final data = await clients.ollamaClient.models.show( + request: ollama.ShowRequest(model: name), ); // just in case, but should always be present if (data.details != null) { - _family = data.details!.family!; - _families = data.details!.families?.toSet() ?? {}; + _family = data.details?["family"] ?? _family; + _families = data.details?["families"] != null + ? (data.details!["families"] as List).map((e) => e.toString()).toSet() + : _families; + _parameterSize = data.details?["parameterSize"] ?? _parameterSize; } - _capabilities = data.capabilities!.toSet(); + _capabilities = data.capabilities!.map(ModelCapability.new).toSet(); + + _didCallUpdateData = true; notifyListeners(); } @@ -58,15 +140,15 @@ class Model extends ChangeNotifier { // sending a [GenerateChatCompletionRequest] without [messages], which is // required for loading, otherwise a message will be generated - var headers = { + final headers = { "Content-Type": "application/json", ...Preferences.instance.hostHeaders, }; - var body = {"model": name, "keep_alive": Preferences.instance.keepAlive}; + final body = {"model": name, "keep_alive": Preferences.instance.keepAlive}; await clients.httpClient .post( - Uri.parse("${clients.ollamaClient.baseUrl}/api/generate"), + Uri.parse("${clients.ollamaClient.config.baseUrl}/api/generate"), headers: headers, body: jsonEncode(body), ) @@ -89,12 +171,16 @@ class ModelManager extends ChangeNotifier { String? _currentModelName; String? get currentModelName => _currentModelName; set currentModelName(String? name) { + assert(!useModel, "Model if fixed to $fixedModel and cannot be changed."); + if (useModel) return; + + if (name == _currentModelName) return; _currentModelName = name; Preferences.instance.model = name; notifyListeners(); } - Model? get currentModel => _currentModelName == null + Model? get currentModel => _currentModelName == null || _models.isEmpty ? null : _models.singleWhere((e) => e.name == _currentModelName); set currentModel(Model? model) => currentModelName = model?.name; @@ -102,11 +188,23 @@ class ModelManager extends ChangeNotifier { final Set _models = {}; Set get models => Set.unmodifiable(_models); - ModelManager._() : _currentModelName = Preferences.instance.model; + ModelManager._() + : _currentModelName = useModel ? fixedModel : Preferences.instance.model; Future loadModels({bool fetchCapabilitiesInBackground = true}) async { - var data = await clients.ollamaClient.listModels(); + final data = await clients.ollamaClient.models.list().timeout( + TimeoutMultiplier.medium, + ); + loadModelsRaw( + data: data, + fetchCapabilitiesInBackground: fetchCapabilitiesInBackground, + ); + } + void loadModelsRaw({ + required ollama.ListResponse data, + bool fetchCapabilitiesInBackground = true, + }) { for (var model in _models) { model.dispose(); } @@ -115,6 +213,10 @@ class ModelManager extends ChangeNotifier { for (var model in data.models!) { _models.add(Model.fromApi(model: model)..addListener(notifyListeners)); } + if (_currentModelName != null && + !_models.any((e) => e.name == _currentModelName)) { + _currentModelName = null; + } _initialized = true; notifyListeners(); @@ -123,60 +225,49 @@ class ModelManager extends ChangeNotifier { compute((_) async { for (var model in _instance.models) { await model.updateData().catchError((_) {}); + notifyListeners(); } }, null); } } } -// MARK: Model Set Dialog +String bytesToHumanReadable(int bytes, {String? locale}) { + const units = ["B", "KB", "MB", "GB", "TB"]; + var value = bytes.toDouble(); + var unitIndex = 0; -// class ModelSetDialog extends StatefulWidget { -// final bool contentOnly; + while (value >= 1024 && unitIndex < units.length - 1) { + value /= 1024; + unitIndex++; + } + final nf = NumberFormat.decimalPattern(locale) + ..maximumFractionDigits = 2 + ..minimumFractionDigits = 0; -// const ModelSetDialog({super.key, this.contentOnly = false}); + final separator = + RegExp(r"(\s)") + .firstMatch( + NumberFormat.simpleCurrency(locale: locale, name: "USD").format(0), + ) + ?.group(1) ?? + ""; -// @override -// State createState() => _ModelSetDialogState(); -// } + return "${nf.format(value)}$separator${units[unitIndex]}"; +} -// class _ModelSetDialogState extends State { -// @override -// void initState() { -// super.initState(); -// ModelManager.instance.addListener(onChange); -// ModelManager.instance.loadModels(); -// } - -// @override -// void dispose() { -// ModelManager.instance.removeListener(onChange); -// super.dispose(); -// } - -// void onChange() { -// setState(() {}); -// } - -// @override -// Widget build(BuildContext context) { -// Widget main = Placeholder(); - -// if (widget.contentOnly) { -// return main; -// } else { -// return AlertDialog(); -// } -// } -// } - -// void showModelSetDialog(BuildContext context) { -// if (Display.of(context).isDesktop) { -// showModalBottomSheet( -// context: context, -// builder: (_) => const ModelSetDialog(contentOnly: true), -// ); -// } else { -// showDialog(context: context, builder: (_) => const ModelSetDialog()); -// } -// } +extension FilterSupportedCapabilities on Iterable { + Iterable filterSupportedCapabilities() => where( + (capability) => switch (capability.name) { + "completion" => false, + "tools" => false, + "insert" => false, + "vision" => true, + "embedding" => false, + "thinking" => true, + "image" => false, + "audio" => false, + _ => false, + }, + ); +} diff --git a/lib/services/preferences.dart b/lib/services/preferences.dart index 1039f55..8e01b86 100644 --- a/lib/services/preferences.dart +++ b/lib/services/preferences.dart @@ -14,12 +14,14 @@ class Preferences extends ChangeNotifier { } } - String? get host => prefs?.getString("host") ?? (useHost ? fixedHost : null); - set host(String? value) { - if (value == null || value.isEmpty) { + Uri? get host => Uri.tryParse( + prefs?.getString("host") ?? (useHost ? fixedHost : null) ?? "::", + ); + set host(Uri? value) { + if (value == null) { prefs?.remove("host"); } else { - prefs?.setString("host", value); + prefs?.setString("host", value.toString()); } notifyListeners(); } @@ -41,6 +43,16 @@ class Preferences extends ChangeNotifier { notifyListeners(); } + String? get user => prefs?.getString("user"); + set user(String? value) { + if (value == null || value.isEmpty) { + prefs?.remove("user"); + } else { + prefs?.setString("user", value); + } + notifyListeners(); + } + bool get useSystem => prefs?.getBool("useSystem") ?? true; set useSystem(bool value) { prefs?.setBool("useSystem", value); @@ -48,7 +60,9 @@ class Preferences extends ChangeNotifier { } String? get system => useSystem - ? prefs?.getString("system") ?? "You are a helpful assistant." + ? prefs?.getString("system") ?? + "You are a helpful assistant. Answer the user's questions to the best of your ability. If you do not know the answer, say that you do not know and do not fabricate information.\n\n" + "You may use Markdown to format your response. For math, you may use LaTeX with standard Markdown LaTeX syntax." : null; set system(String? value) { if (value == null || value.isEmpty) { diff --git a/lib/services/responsive.dart b/lib/services/responsive.dart index fa155fd..c87b2d1 100644 --- a/lib/services/responsive.dart +++ b/lib/services/responsive.dart @@ -3,20 +3,164 @@ import 'dart:io' as io; import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; -class Display { - late final Size size; +enum BreakpointNavigationType { + navigationBar, + navigationRail, + navigationRailExtended, + navigationRailModal, +} - Display.from(BuildContext context) { - size = MediaQuery.sizeOf(context); +enum Breakpoint { + compact( + null, + 599, + panes: [1], + panesRecommended: 1, + navigation: [.navigationBar, .navigationRailModal], + fullScreenDialogRecommended: true, + spacing: 16, + ), + medium( + 600, + 839, + panes: [1, 2], + panesRecommended: 1, + navigation: [.navigationBar, .navigationRail], + spacing: 24, + ), + expanded( + 840, + 1199, + panes: [1, 2], + panesRecommended: 2, + panesFixedWidth: 360, + navigation: [.navigationRail, .navigationRailExtended], + spacing: 24, + ), + large( + 1200, + 1599, + panes: [1, 2], + panesRecommended: 2, + panesFixedWidth: 412, + navigation: [.navigationRail, .navigationRailExtended], + spacing: 24, + ), + extraLarge( + 1600, + null, + panes: [1, 2, 3], + panesRecommended: 2, + panesFixedWidth: 412, + panesThirdFixedWidth: 400, + navigation: [.navigationRail, .navigationRailExtended], + spacing: 24, + ); + + final int? from; + final int? to; + + /// List of possible pane numbers according to specification. + /// + /// [panesRecommended] is always included in this list. It is the recommended + /// number of panes for this breakpoint. If not using the recommended number + /// of panes, good reasoning should be documented. + /// + /// The first two panes can use either of the following layout types: + /// - Split-pane layout: + /// - The two panes are displayed side by side, with the same width. If the + /// navigation rail is used, it is counted into the 50 percent split of the + /// pane next to it, typically the first one. + /// - Fixed-and-flexible layout: + /// - One pane, typically the first one, has the fixed width of + /// [panesFixedWidth]. The other pane takes up the remaining space. + /// - This layout option is only supported if [panesFixedWidth] is not null. + /// + /// All panes are separated by [spacing] and have that much space to the + /// screen edge as well. + /// + /// The third pane of the [extraLarge] breakpoint is largely independent of + /// the rules for the first two panes. It must always have the width of + /// [panesThirdFixedWidth]. It is separated from the other panes by [spacing]. + /// Stylistically, according to the specification, it should be a *standard + /// side sheet*. + final List panes; + + /// The number of panes that are recommended for this breakpoint. + final int panesRecommended; + + /// The fixed width for the fixed pane in a fixed-and-flexible layout. + /// + /// See the documentation for [panes] for more details on usage of this + /// property. + /// + /// If this is null, a fixed-and-flexible layout is not supported for this + /// breakpoint. + final double? panesFixedWidth; + + /// The fixed width for the third pane in the [extraLarge] breakpoint. + /// + /// This is only used for the third pane in the [extraLarge] breakpoint. If a + /// third pane is used, the width of ot must always be set to this value. + /// + /// The third pane is separated from the other panes by [spacing]. + /// Stylistically, according to the specification, it should be a *standard + /// side sheet*. + final double? panesThirdFixedWidth; + + /// The navigation types that are recommended for this breakpoint. + final List navigation; + + /// Whether a full screen dialog is recommended for this breakpoint. + /// + /// If this is false, using a normal dialog is recommended instead. Normal + /// dialogs can still be used for this breakpoint if this is true. + final bool fullScreenDialogRecommended; + + /// The spacing between panes, and between panes and the screen edge. + /// + /// This is the spacing that is recommended for this breakpoint. + /// + /// Please note that the Material specification also includes a complicated + /// set of guidelines for spacing using rulers. Usually these can be sidelined + /// in favor of a single spacing value that is used everywhere. Read more + /// here: https://m3.material.io/foundations/layout/grids-spacing/grids + final double spacing; + + const Breakpoint( + this.from, + this.to, { + required this.panes, + required this.panesRecommended, + this.panesFixedWidth, + this.panesThirdFixedWidth, + required this.navigation, + this.fullScreenDialogRecommended = false, + required this.spacing, + }); + + /// Returns the [Breakpoint] for the given [BuildContext]. + static Breakpoint of(BuildContext context) { + final width = MediaQuery.sizeOf(context).width.ceil(); + return Breakpoint.values.firstWhere( + (breakpoint) => + width >= (breakpoint.from ?? double.negativeInfinity) && + width <= (breakpoint.to ?? double.infinity), + ); } - bool get isMobile => size.width < 600; + bool operator >(Breakpoint b) => + (to ?? double.infinity) > (b.from ?? double.negativeInfinity); + bool operator <(Breakpoint b) => + (from ?? double.negativeInfinity) < (b.to ?? double.infinity); + bool operator >=(Breakpoint b) => this > b || this == b; + bool operator <=(Breakpoint b) => this < b || this == b; - bool get isTabletOrLess => size.width < 1200; - bool get isTablet => size.width >= 600 && size.width < 1200; - bool get isTabletOrMore => size.width >= 600; - - bool get isDesktop => size.width >= 1200; + @override + String toString() => + "Breakpoint.$name(from: $from, to: $to, panes: $panes, panesRecommended: $panesRecommended, navigation: $navigation, fullScreenDialogRecommended: $fullScreenDialogRecommended, spacing: $spacing)"; + String toPrettyString() => + "Breakpoint.$name(\n\tfrom: $from,\n\tto: $to,\n\tpanes: $panes,\n\tpanesRecommended: $panesRecommended,\n\tnavigation: [${navigation.map((n) => n.name).join(", ")}],\n\tfullScreenDialogRecommended: $fullScreenDialogRecommended,\n\tspacing: $spacing\n)"; } class LayoutFeature { diff --git a/lib/services/services.dart b/lib/services/services.dart new file mode 100644 index 0000000..2cce01e --- /dev/null +++ b/lib/services/services.dart @@ -0,0 +1,12 @@ +export 'chat.dart'; +export 'chat_database/chat_database.dart' show ChatDatabase, chatDb; +export 'clients.dart'; +export 'curves.dart'; +export 'error.dart'; +export 'haptic.dart'; +export 'host.dart'; +export 'markdown.dart'; +export 'model.dart'; +export 'preferences.dart'; +export 'responsive.dart'; +export 'theme.dart'; diff --git a/lib/services/theme.dart b/lib/services/theme.dart index a458667..94db221 100644 --- a/lib/services/theme.dart +++ b/lib/services/theme.dart @@ -1,3 +1,4 @@ +import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; import '../l10n/gen/app_localizations.dart'; @@ -23,24 +24,32 @@ class ThemeBuilderData { } ThemeData themeModifier(BuildContext context, ThemeData theme) { - var isMobile = Display.from(context).isMobile; + final floatingSnackbar = Breakpoint.of(context) <= Breakpoint.medium; return theme.copyWith( pageTransitionsTheme: const PageTransitionsTheme( - builders: { - TargetPlatform.android: PredictiveBackPageTransitionsBuilder(), + builders: { + TargetPlatform.android: FadeForwardsPageTransitionsBuilder(), + TargetPlatform.iOS: CupertinoPageTransitionsBuilder(), + TargetPlatform.macOS: CupertinoPageTransitionsBuilder(), + TargetPlatform.windows: FadeForwardsPageTransitionsBuilder(), + TargetPlatform.linux: FadeForwardsPageTransitionsBuilder(), }, ), sliderTheme: theme.sliderTheme.copyWith(year2023: false), progressIndicatorTheme: theme.progressIndicatorTheme.copyWith( year2023: false, ), + dividerTheme: theme.dividerTheme.copyWith( + color: theme.colorScheme.outline, + ), snackBarTheme: theme.snackBarTheme.copyWith( - behavior: isMobile ? null : SnackBarBehavior.floating, - width: isMobile ? null : 288, + behavior: SnackBarBehavior.floating, + width: floatingSnackbar ? null : 288, ), listTileTheme: theme.listTileTheme.copyWith( contentPadding: const EdgeInsets.symmetric(horizontal: 16), ), + materialTapTargetSize: MaterialTapTargetSize.shrinkWrap, ); } @@ -48,10 +57,16 @@ class ThemeBuilderData { if (Preferences.instance.themeSystem && dynamicLight != null) { return ThemeData.from(colorScheme: dynamicLight!); } else { + final colorScheme = ColorScheme.fromSeed( + seedColor: Colors.black, + dynamicSchemeVariant: DynamicSchemeVariant.content, + ); return ThemeData.from( - colorScheme: ColorScheme.fromSeed( - seedColor: Colors.black, - dynamicSchemeVariant: DynamicSchemeVariant.content, + colorScheme: colorScheme.copyWith( + surface: Colors.white, + onPrimaryContainer: colorScheme.onPrimary, + onSecondaryContainer: colorScheme.onSurface, + onTertiaryContainer: colorScheme.onTertiary, ), ); } @@ -61,12 +76,18 @@ class ThemeBuilderData { if (Preferences.instance.themeSystem && dynamicDark != null) { return ThemeData.from(colorScheme: dynamicDark!); } else { + final colorScheme = ColorScheme.fromSeed( + seedColor: Colors.white, + dynamicSchemeVariant: DynamicSchemeVariant.content, + brightness: Brightness.dark, + ); return ThemeData.from( - colorScheme: ColorScheme.fromSeed( - seedColor: Colors.white, - dynamicSchemeVariant: DynamicSchemeVariant.content, - brightness: Brightness.dark, - ).copyWith(surface: Colors.black), + colorScheme: colorScheme.copyWith( + surface: Colors.black, + onPrimaryContainer: colorScheme.onPrimary, + onSecondaryContainer: colorScheme.onSurface, + onTertiaryContainer: colorScheme.onTertiary, + ), ).copyWith(dividerColor: Colors.white30); } } @@ -86,7 +107,9 @@ class _ThemeBuilderState extends State { @override void initState() { super.initState(); - Preferences.instance.addListener(onChange); + WidgetsBinding.instance.addPostFrameCallback( + (_) => Preferences.instance.addListener(onChange), + ); } @override diff --git a/lib/util/extensions.dart b/lib/util/extensions.dart new file mode 100644 index 0000000..be4d43a --- /dev/null +++ b/lib/util/extensions.dart @@ -0,0 +1,26 @@ +extension StringExtension on String { + String toTitleCase() { + if (isEmpty) return this; + return split(" ") + .map( + (word) => word.isEmpty + ? word + : "${word[0].toUpperCase()}${word.substring(1)}", + ) + .join(" "); + } + + String emptyOn(String other) => (this == other) ? "" : this; +} + +extension IterableExtension on Iterable { + Iterable joinElements(T separator) { + if (length <= 1) return this; + final result = []; + for (var i = 0; i < length; i++) { + result.add(elementAt(i)); + if (i < length - 1) result.add(separator); + } + return result; + } +} diff --git a/lib/widgets/alert_dialog_child.dart b/lib/widgets/alert_dialog_child.dart new file mode 100644 index 0000000..61ca40d --- /dev/null +++ b/lib/widgets/alert_dialog_child.dart @@ -0,0 +1,344 @@ +import 'dart:ui'; + +import 'package:flutter/foundation.dart'; +import 'package:flutter/material.dart'; + +/// A widget that implements the Material Design alert dialog layout structure. +/// +/// The handling is identical to [AlertDialog], but instead of returning a +/// [Dialog], it returns a [Widget] that can be used as a child of a [Dialog]: +/// +/// ```dart +/// Dialog( +/// semanticsRole: SemanticsRole.alertDialog, +/// child: AlertDialogChild(...), +/// ); +/// ``` +/// +/// The code above would be equivalent to using [AlertDialog] directly. +/// Dialog-level properties ([backgroundColor], [elevation], [shadowColor], +/// [surfaceTintColor], [insetPadding], [clipBehavior], [shape], [alignment], +/// [constraints]) are not accepted by this widget and must be passed directly +/// to the [Dialog] wrapper instead. +class AlertDialogChild extends StatelessWidget { + const AlertDialogChild({ + super.key, + this.icon, + this.iconPadding, + this.iconColor, + this.title, + this.titlePadding, + this.titleTextStyle, + this.content, + this.contentPadding, + this.contentTextStyle, + this.actions, + this.actionsPadding, + this.actionsAlignment, + this.actionsOverflowAlignment, + this.actionsOverflowDirection, + this.actionsOverflowButtonSpacing, + this.buttonPadding, + this.semanticLabel, + this.scrollable = false, + }); + + final Widget? icon; + final Color? iconColor; + final EdgeInsetsGeometry? iconPadding; + final Widget? title; + final EdgeInsetsGeometry? titlePadding; + final TextStyle? titleTextStyle; + final Widget? content; + final EdgeInsetsGeometry? contentPadding; + final TextStyle? contentTextStyle; + final List? actions; + final EdgeInsetsGeometry? actionsPadding; + final MainAxisAlignment? actionsAlignment; + final OverflowBarAlignment? actionsOverflowAlignment; + final VerticalDirection? actionsOverflowDirection; + final double? actionsOverflowButtonSpacing; + final EdgeInsetsGeometry? buttonPadding; + final String? semanticLabel; + final bool scrollable; + + @override + Widget build(BuildContext context) { + assert( + debugCheckHasMaterialLocalizations(context), + "No MaterialLocalizations found.", + ); + final theme = Theme.of(context); + + final dialogTheme = DialogTheme.of(context); + final defaults = theme.useMaterial3 + ? _DialogDefaultsM3(context) + : _DialogDefaultsM2(context); + + final label = switch (defaultTargetPlatform) { + TargetPlatform.iOS || TargetPlatform.macOS => semanticLabel, + TargetPlatform.android || + TargetPlatform.fuchsia || + TargetPlatform.linux || + TargetPlatform.windows => + semanticLabel ?? MaterialLocalizations.of(context).alertDialogLabel, + }; + + // The paddingScaleFactor is used to adjust the padding of Dialog's + // children. + const fontSizeToScale = 14.0; + final effectiveTextScale = + MediaQuery.textScalerOf(context).scale(fontSizeToScale) / + fontSizeToScale; + final paddingScaleFactor = _scalePadding(effectiveTextScale); + final textDirection = Directionality.maybeOf(context); + + Widget? iconWidget; + Widget? titleWidget; + Widget? contentWidget; + Widget? actionsWidget; + + if (icon != null) { + final belowIsTitle = title != null; + final belowIsContent = !belowIsTitle && content != null; + final defaultIconPadding = EdgeInsets.only( + left: 24.0, + top: 24.0, + right: 24.0, + bottom: belowIsTitle + ? 16.0 + : belowIsContent + ? 0.0 + : 24.0, + ); + final effectiveIconPadding = + iconPadding?.resolve(textDirection) ?? defaultIconPadding; + iconWidget = Padding( + padding: EdgeInsets.only( + left: effectiveIconPadding.left * paddingScaleFactor, + right: effectiveIconPadding.right * paddingScaleFactor, + top: effectiveIconPadding.top * paddingScaleFactor, + bottom: effectiveIconPadding.bottom, + ), + child: IconTheme( + data: IconThemeData( + color: iconColor ?? dialogTheme.iconColor ?? defaults.iconColor, + ), + child: icon!, + ), + ); + } + + if (title != null) { + final defaultTitlePadding = EdgeInsets.only( + left: 24.0, + top: icon == null ? 24.0 : 0.0, + right: 24.0, + bottom: content == null ? 20.0 : 0.0, + ); + final effectiveTitlePadding = + titlePadding?.resolve(textDirection) ?? defaultTitlePadding; + titleWidget = Padding( + padding: EdgeInsets.only( + left: effectiveTitlePadding.left * paddingScaleFactor, + right: effectiveTitlePadding.right * paddingScaleFactor, + top: icon == null + ? effectiveTitlePadding.top * paddingScaleFactor + : effectiveTitlePadding.top, + bottom: effectiveTitlePadding.bottom, + ), + child: DefaultTextStyle( + style: + titleTextStyle ?? + dialogTheme.titleTextStyle ?? + defaults.titleTextStyle!, + textAlign: icon == null ? TextAlign.start : TextAlign.center, + child: Semantics( + // For iOS platform, the focus always lands on the title. + // Set nameRoute to false to avoid title being announce twice. + namesRoute: + label == null && defaultTargetPlatform != TargetPlatform.iOS, + container: true, + child: title, + ), + ), + ); + } + + if (content != null) { + final defaultContentPadding = EdgeInsets.only( + left: 24.0, + top: theme.useMaterial3 ? 16.0 : 20.0, + right: 24.0, + bottom: 24.0, + ); + final effectiveContentPadding = + contentPadding?.resolve(textDirection) ?? defaultContentPadding; + contentWidget = Padding( + padding: EdgeInsets.only( + left: effectiveContentPadding.left * paddingScaleFactor, + right: effectiveContentPadding.right * paddingScaleFactor, + top: title == null && icon == null + ? effectiveContentPadding.top * paddingScaleFactor + : effectiveContentPadding.top, + bottom: effectiveContentPadding.bottom, + ), + child: DefaultTextStyle( + style: + contentTextStyle ?? + dialogTheme.contentTextStyle ?? + defaults.contentTextStyle!, + child: Semantics( + container: true, + explicitChildNodes: true, + child: content, + ), + ), + ); + } + + if (actions != null) { + final spacing = (buttonPadding?.horizontal ?? 16) / 2; + actionsWidget = Padding( + padding: + actionsPadding ?? + dialogTheme.actionsPadding ?? + (theme.useMaterial3 + ? defaults.actionsPadding! + : defaults.actionsPadding!.add(EdgeInsets.all(spacing))), + child: OverflowBar( + alignment: actionsAlignment ?? MainAxisAlignment.end, + spacing: spacing, + overflowAlignment: + actionsOverflowAlignment ?? OverflowBarAlignment.end, + overflowDirection: actionsOverflowDirection ?? VerticalDirection.down, + overflowSpacing: actionsOverflowButtonSpacing ?? 0, + children: actions!, + ), + ); + } + + List columnChildren; + if (scrollable) { + columnChildren = [ + if (title != null || content != null) + Flexible( + child: SingleChildScrollView( + child: Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.stretch, + children: [?iconWidget, ?titleWidget, ?contentWidget], + ), + ), + ), + ?actionsWidget, + ]; + } else { + columnChildren = [ + ?iconWidget, + ?titleWidget, + if (contentWidget != null) Flexible(child: contentWidget), + ?actionsWidget, + ]; + } + + Widget dialogChild = IntrinsicWidth( + child: Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.stretch, + children: columnChildren, + ), + ); + + if (label != null) { + dialogChild = Semantics( + scopesRoute: true, + explicitChildNodes: true, + namesRoute: true, + label: label, + child: dialogChild, + ); + } + + return dialogChild; + } +} + +class _DialogDefaultsM2 extends DialogThemeData { + _DialogDefaultsM2(this.context) + : super( + alignment: Alignment.center, + elevation: 24.0, + shape: const RoundedRectangleBorder( + borderRadius: BorderRadius.all(Radius.circular(4.0)), + ), + clipBehavior: Clip.none, + ); + + final BuildContext context; + late final ThemeData theme = Theme.of(context); + late final TextTheme textTheme = theme.textTheme; + late final IconThemeData iconTheme = theme.iconTheme; + + @override + Color? get iconColor => iconTheme.color; + + @override + Color? get backgroundColor => + theme.brightness == Brightness.dark ? Colors.grey[800]! : Colors.white; + + @override + Color? get shadowColor => theme.shadowColor; + + @override + TextStyle? get titleTextStyle => textTheme.titleLarge; + + @override + TextStyle? get contentTextStyle => textTheme.titleMedium; + + @override + EdgeInsetsGeometry? get actionsPadding => EdgeInsets.zero; +} + +class _DialogDefaultsM3 extends DialogThemeData { + _DialogDefaultsM3(this.context) + : super( + alignment: Alignment.center, + elevation: 6.0, + shape: const RoundedRectangleBorder( + borderRadius: BorderRadius.all(Radius.circular(28.0)), + ), + clipBehavior: Clip.none, + ); + + final BuildContext context; + late final ColorScheme _colors = Theme.of(context).colorScheme; + late final TextTheme _textTheme = Theme.of(context).textTheme; + + @override + Color? get iconColor => _colors.secondary; + + @override + Color? get backgroundColor => _colors.surfaceContainerHigh; + + @override + Color? get shadowColor => Colors.transparent; + + @override + Color? get surfaceTintColor => Colors.transparent; + + @override + TextStyle? get titleTextStyle => _textTheme.headlineSmall; + + @override + TextStyle? get contentTextStyle => _textTheme.bodyMedium; + + @override + EdgeInsetsGeometry? get actionsPadding => + const EdgeInsets.only(left: 24.0, right: 24.0, bottom: 24.0); +} + +double _scalePadding(double textScaleFactor) { + final clampedTextScaleFactor = clampDouble(textScaleFactor, 1.0, 2.0); + return lerpDouble(1.0, 1.0 / 3.0, clampedTextScaleFactor - 1.0)!; +} diff --git a/lib/widgets/alpha_beta.dart b/lib/widgets/alpha_beta.dart new file mode 100644 index 0000000..884415a --- /dev/null +++ b/lib/widgets/alpha_beta.dart @@ -0,0 +1,107 @@ +import 'package:flutter/material.dart'; + +import '../l10n/gen/app_localizations.dart'; + +class AlphaBadge extends StatelessWidget { + const AlphaBadge({super.key}); + + @override + Widget build(BuildContext context) { + final colorScheme = ColorScheme.of(context); + final textTheme = TextTheme.of(context); + final appLocalizations = AppLocalizations.of(context); + + return Container( + clipBehavior: Clip.antiAlias, + decoration: ShapeDecoration( + color: colorScheme.errorContainer, + shape: const StadiumBorder(), + ), + padding: const EdgeInsets.symmetric(horizontal: 4), + alignment: Alignment.center, + child: Text( + appLocalizations.settingsExperimentalAlpha, + style: textTheme.labelSmall!.copyWith( + color: colorScheme.onErrorContainer, + ), + textAlign: TextAlign.center, + softWrap: false, + overflow: TextOverflow.ellipsis, + maxLines: 1, + ), + ); + } +} + +class Alpha extends StatelessWidget { + final Widget child; + final bool showBadge; + const Alpha({super.key, required this.child, this.showBadge = true}); + + @override + Widget build(BuildContext context) => Row( + mainAxisSize: MainAxisSize.min, + children: [ + Flexible(child: child), + if (showBadge) ...[ + const SizedBox(width: 2), + Transform.translate( + offset: const Offset(0, -4), + child: const AlphaBadge(), + ), + ], + ], + ); +} + +class BetaBadge extends StatelessWidget { + const BetaBadge({super.key}); + + @override + Widget build(BuildContext context) { + final colorScheme = ColorScheme.of(context); + final textTheme = TextTheme.of(context); + final appLocalizations = AppLocalizations.of(context); + + return Container( + clipBehavior: Clip.antiAlias, + decoration: ShapeDecoration( + color: colorScheme.primaryContainer, + shape: const StadiumBorder(), + ), + padding: const EdgeInsets.symmetric(horizontal: 4), + alignment: Alignment.center, + child: Text( + appLocalizations.settingsExperimentalBeta, + style: textTheme.labelSmall!.copyWith( + color: colorScheme.onPrimaryContainer, + ), + textAlign: TextAlign.center, + softWrap: false, + overflow: TextOverflow.ellipsis, + maxLines: 1, + ), + ); + } +} + +class Beta extends StatelessWidget { + final Widget child; + final bool showBadge; + const Beta({super.key, required this.child, this.showBadge = true}); + + @override + Widget build(BuildContext context) => Row( + mainAxisSize: MainAxisSize.min, + children: [ + Flexible(child: child), + if (showBadge) ...[ + const SizedBox(width: 2), + Transform.translate( + offset: const Offset(0, -4), + child: const BetaBadge(), + ), + ], + ], + ); +} diff --git a/lib/widgets/child_size_notifier.dart b/lib/widgets/child_size_notifier.dart new file mode 100644 index 0000000..56f904d --- /dev/null +++ b/lib/widgets/child_size_notifier.dart @@ -0,0 +1,35 @@ +import 'package:flutter/material.dart'; +import 'package:flutter/rendering.dart'; + +class MeasureSize extends SingleChildRenderObjectWidget { + final void Function(Size size) onChange; + const MeasureSize({super.key, required this.onChange, required super.child}); + + @override + RenderObject createRenderObject(BuildContext context) => + MeasureSizeRenderObject(onChange); + + @override + void updateRenderObject( + BuildContext context, + MeasureSizeRenderObject renderObject, + ) => renderObject.onChange = onChange; +} + +class MeasureSizeRenderObject extends RenderProxyBox { + ValueChanged onChange; + Size? _oldSize; + + MeasureSizeRenderObject(this.onChange); + + @override + void performLayout() { + super.performLayout(); + final newSize = child?.size ?? Size.zero; + + if (_oldSize != newSize) { + _oldSize = newSize; + WidgetsBinding.instance.addPostFrameCallback((_) => onChange(newSize)); + } + } +} diff --git a/lib/widgets/hero_text_flight_shuttle_builder.dart b/lib/widgets/hero_text_flight_shuttle_builder.dart index 2277a7f..5d1c376 100644 --- a/lib/widgets/hero_text_flight_shuttle_builder.dart +++ b/lib/widgets/hero_text_flight_shuttle_builder.dart @@ -22,13 +22,13 @@ Widget heroTextFlightShuttleBuilder( (begin, end) = (end, begin); } - var styleTween = TextStyleTween(begin: begin, end: end); - var textString = fromText.data ?? toText.data ?? ""; + final styleTween = TextStyleTween(begin: begin, end: end); + final textString = fromText.data ?? toText.data ?? ""; return AnimatedBuilder( animation: animation, builder: (context, child) { - var currentStyle = styleTween.lerp(animation.value); + final currentStyle = styleTween.lerp(animation.value); return Material( type: MaterialType.transparency, child: Center( diff --git a/lib/widgets/list_tile_slide.dart b/lib/widgets/list_tile_slide.dart index 54e9bbf..f254db3 100644 --- a/lib/widgets/list_tile_slide.dart +++ b/lib/widgets/list_tile_slide.dart @@ -62,7 +62,7 @@ class ListTileSlide extends StatelessWidget { @override Widget build(BuildContext context) { - var content = Column( + final content = Column( mainAxisSize: MainAxisSize.min, crossAxisAlignment: CrossAxisAlignment.start, children: [ @@ -87,9 +87,9 @@ class ListTileSlide extends StatelessWidget { ], ); - var theme = Theme.of(context); - var switchTheme = SliderTheme.of(context); - var effectiveActiveColor = + final theme = Theme.of(context); + final switchTheme = SliderTheme.of(context); + final effectiveActiveColor = thumbColor ?? switchTheme.thumbColor ?? theme.colorScheme.secondary; return MergeSemantics( diff --git a/lib/widgets/list_tile_switch.dart b/lib/widgets/list_tile_switch.dart index 457e8cb..c0b0e8c 100644 --- a/lib/widgets/list_tile_switch.dart +++ b/lib/widgets/list_tile_switch.dart @@ -85,13 +85,13 @@ class ListTileSwitchInteractive extends StatelessWidget { @override Widget build(BuildContext context) { - var listTileTheme = ListTileTheme.of(context); - var effectiveControlAffinity = + final listTileTheme = ListTileTheme.of(context); + final effectiveControlAffinity = controlAffinity ?? listTileTheme.controlAffinity ?? ListTileControlAffinity.platform; - var controlChildren = [ + final controlChildren = [ const SizedBox(height: 32, child: VerticalDivider()), const SizedBox(width: 8), Switch( @@ -126,7 +126,7 @@ class ListTileSwitchInteractive extends StatelessWidget { overlayColor: overlayColor, ), ]; - var control = ExcludeFocus( + final control = ExcludeFocus( child: Row( mainAxisSize: MainAxisSize.min, crossAxisAlignment: CrossAxisAlignment.center, @@ -144,15 +144,15 @@ class ListTileSwitchInteractive extends StatelessWidget { ListTileControlAffinity.platform => (secondary, control), }; - var theme = Theme.of(context); - var switchTheme = SwitchTheme.of(context); - var states = {if (selected) WidgetState.selected}; - var effectiveActiveColor = + final theme = Theme.of(context); + final switchTheme = SwitchTheme.of(context); + final states = {if (selected) WidgetState.selected}; + final effectiveActiveColor = activeThumbColor ?? switchTheme.thumbColor?.resolve(states) ?? theme.colorScheme.secondary; - var effectiveContentPadding = + final effectiveContentPadding = contentPadding ?? EdgeInsets.only( left: effectiveControlAffinity == ListTileControlAffinity.leading @@ -273,13 +273,13 @@ class ListTileSwitch extends StatelessWidget { @override Widget build(BuildContext context) { - var listTileTheme = ListTileTheme.of(context); - var effectiveControlAffinity = + final listTileTheme = ListTileTheme.of(context); + final effectiveControlAffinity = controlAffinity ?? listTileTheme.controlAffinity ?? ListTileControlAffinity.platform; - var control = ExcludeFocus( + final control = ExcludeFocus( child: IgnorePointer( child: Switch( value: value, @@ -316,15 +316,15 @@ class ListTileSwitch extends StatelessWidget { ListTileControlAffinity.platform => (secondary, control), }; - var theme = Theme.of(context); - var switchTheme = SwitchTheme.of(context); - var states = {if (selected) WidgetState.selected}; - var effectiveActiveColor = + final theme = Theme.of(context); + final switchTheme = SwitchTheme.of(context); + final states = {if (selected) WidgetState.selected}; + final effectiveActiveColor = activeThumbColor ?? switchTheme.thumbColor?.resolve(states) ?? theme.colorScheme.secondary; - var effectiveContentPadding = + final effectiveContentPadding = contentPadding ?? EdgeInsets.only( left: effectiveControlAffinity == ListTileControlAffinity.leading diff --git a/lib/widgets/model_adder.dart b/lib/widgets/model_adder.dart new file mode 100644 index 0000000..5f3fbdf --- /dev/null +++ b/lib/widgets/model_adder.dart @@ -0,0 +1,551 @@ +import 'dart:async'; + +import 'package:flutter/material.dart'; +import 'package:intl/intl.dart'; +import 'package:ollama_dart/ollama_dart.dart' as ollama; +import 'package:url_launcher/url_launcher.dart'; + +import '../l10n/gen/app_localizations.dart'; +import '../services/services.dart'; +import 'alert_dialog_child.dart'; + +final modelTagRegex = RegExp( + r"^((?:(?:(?:[a-zA-Z0-9]|[a-zA-Z0-9][a-zA-Z0-9-]*[a-zA-Z0-9])(?:\.(?:[a-zA-Z0-9]|[a-zA-Z0-9][a-zA-Z0-9-]*[a-zA-Z0-9]))*|\[(?:[a-fA-F0-9:]+)\])(?::[0-9]+)?/)?[a-z0-9]+(?:(?:[._]|__|[-]+)[a-z0-9]+)*(?:/[a-z0-9]+(?:(?:[._]|__|[-]+)[a-z0-9]+)*)*)(?::([\w][\w.-]{0,127}))?$", +); + +Map> _downloadingModels = {}; +Map _downloadingModelsState = {}; + +class _DownloadingModelsState with ChangeNotifier { + double? progress; + int? total; + bool completedType; + + bool? pageState; + + bool cancelFlag; + bool cancelledType; + + bool errorNotFoundType; + bool errorNetworkType; + bool errorCorruptionType; + + void _notify() => notifyListeners(); + + _DownloadingModelsState() + : progress = null, + total = null, + completedType = false, + pageState = null, + cancelFlag = false, + cancelledType = false, + errorNotFoundType = false, + errorNetworkType = false, + errorCorruptionType = false; +} + +class ModelAdder extends StatefulWidget { + const ModelAdder({super.key}); + + @override + State createState() => _ModelAdderState(); +} + +class _ModelAdderState extends State { + bool? get _pageState => + modelName != null ? _downloadingModelsState[modelName]?.pageState : false; + set _pageState(bool? value) => modelName != null + ? _downloadingModelsState[modelName]!.pageState = value + : null; + bool _wantPop = false; + + final _controller = TextEditingController(); + final _focusNode = FocusNode(); + + String? Function() _errorText = () => null; + bool _errorIsCorruption = false; + + String? modelName; + + @override + void dispose() { + _controller.dispose(); + _focusNode.dispose(); + _downloadingModelsState.forEach( + (_, state) => state.removeListener(onUpdate), + ); + super.dispose(); + } + + Future _downloadModel([String? model]) async { + var modelName = model ?? _controller.text.trim(); + if (modelName.isEmpty) { + _focusNode.requestFocus(); + return; + } + if (!modelName.contains(":")) { + modelName = "$modelName:latest"; + } + + _errorText = () => null; + _errorIsCorruption = false; + if (mounted) setState(() {}); + + if (ModelManager.instance.models.any((model) => model.name == modelName)) { + _errorText = () => + AppLocalizations.of(context).modelDialogAddPromptAlreadyExists; + if (mounted) setState(() {}); + _focusNode.requestFocus(); + return; + } + + if (!modelTagRegex.hasMatch(modelName)) { + _errorText = () => + AppLocalizations.of(context).modelDialogAddPromptInvalid; + if (mounted) setState(() {}); + _focusNode.requestFocus(); + return; + } + + _pageState = null; + if (mounted) setState(() {}); + + try { + _downloadingModelsState + .putIfAbsent(modelName, _DownloadingModelsState.new) + .addListener(onUpdate); + this.modelName = modelName; + + var sub = _downloadingModels[modelName]; + sub ??= ollamaClient.models + .pullStream( + request: ollama.PullRequest(model: modelName, stream: true), + ) + .listen( + (event) { + final state = _downloadingModelsState[modelName]!; + if (state.cancelFlag) { + state + ..cancelledType = true + .._notify(); + this.modelName = null; + _downloadingModels.remove(modelName); + _downloadingModelsState.remove(modelName); + sub?.cancel(); + return; + } else if (event.status == "success") { + state + ..completedType = true + .._notify(); + this.modelName = null; + _downloadingModels.remove(modelName); + _downloadingModelsState.remove(modelName); + return; + } + if ((event.total ?? 0) == 0 && (event.progress ?? 0) == 0) return; + + _pageState = true; + if ((event.progress ?? -1) > (state.progress ?? -1)) { + state.progress = event.progress; + } + if ((event.total ?? -1) > (state.total ?? -1)) { + state.total = event.total; + } + + if (!_downloadingModels.containsKey(modelName)) { + _downloadingModels[modelName] = sub!; + } + state._notify(); + }, + onError: (Object error) { + final state = _downloadingModelsState[modelName]; + if (error is ollama.StreamException) { + if (error.message.contains("EOF")) { + state?.errorCorruptionType = true; + } else { + state?.errorNotFoundType = true; + } + } else { + state?.errorNetworkType = true; + } + state?._notify(); + this.modelName = null; + _downloadingModels.remove(modelName); + _downloadingModelsState.remove(modelName); + sub?.cancel(); + }, + ); + } catch (_) { + if (!mounted) return; + _downloadingModelsState[modelName] + ?..errorNetworkType = true + .._notify(); + _downloadingModels.remove(modelName); + _downloadingModelsState.remove(modelName); + if (mounted) { + setState(() {}); + _focusNode.requestFocus(); + } + } + } + + void onUpdate() { + final state = modelName != null ? _downloadingModelsState[modelName] : null; + if (state == null || !mounted) return; + if (state.completedType || state.cancelledType) { + final modelName = state.completedType ? this.modelName : null; + ModelManager.instance.loadModels().then((_) { + if (mounted) Navigator.of(context).pop(modelName); + }); + return; + } else if (state.errorNotFoundType || + state.errorNetworkType || + state.errorCorruptionType) { + _errorText = () { + if (state.errorNotFoundType) { + return AppLocalizations.of(context).modelDialogAddPromptNotFound; + } else if (state.errorNetworkType) { + return AppLocalizations.of(context).modelDialogAddPromptNetworkError; + } else if (state.errorCorruptionType) { + _errorIsCorruption = true; + return AppLocalizations.of( + context, + ).modelDialogAddPromptCorruptionError; + } + return null; + }; + if (mounted) setState(() {}); + _focusNode.requestFocus(); + } + setState(() {}); + } + + @override + Widget build(BuildContext context) { + final state = modelName != null ? _downloadingModelsState[modelName] : null; + + final colorScheme = ColorScheme.of(context); + final textTheme = TextTheme.of(context); + final appLocalizations = AppLocalizations.of(context); + + final breakpoint = Breakpoint.of(context); + final embeddedNavigation = breakpoint.panesRecommended >= 2; + + final alignment = !embeddedNavigation + ? AlignmentDirectional.bottomCenter + : AlignmentDirectional.topStart; + const constraints = BoxConstraints(minWidth: 280, maxWidth: 360); + + final childInput = AlertDialogChild( + key: const ValueKey("input"), + title: Text(appLocalizations.modelDialogAddPromptTitle), + content: SizedBox( + width: 560, + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + Text(appLocalizations.modelDialogAddPromptDescription), + const SizedBox(height: 12), + TextField( + controller: _controller, + focusNode: _focusNode, + autofocus: true, + autocorrect: false, + enableSuggestions: false, + keyboardType: TextInputType.text, + textInputAction: TextInputAction.done, + maxLines: 1, + onSubmitted: (_) => _downloadModel(), + decoration: InputDecoration( + hintText: "gemma4:latest", + errorText: _errorText.call(), + errorMaxLines: 4, + ), + ), + + if (_errorIsCorruption) ...[ + ListTile( + contentPadding: EdgeInsets.zero, + minTileHeight: 0, + title: Text.rich( + TextSpan( + style: TextStyle(color: colorScheme.error), + children: [ + TextSpan(text: appLocalizations.learnMore), + const TextSpan(text: " "), + WidgetSpan( + child: Builder( + builder: (context) => Transform.translate( + offset: const Offset(0, -1), + child: Icon( + Icons.open_in_new, + size: DefaultTextStyle.of(context).style.fontSize, + color: colorScheme.error, + ), + ), + ), + ), + ], + ), + ), + onTap: () => launchUrl( + webOnlyWindowName: "_blank", + Uri.parse( + "https://github.com/ollama/ollama/issues/8088#issuecomment-2541889671", + ), + ), + ), + Text.rich( + TextSpan( + children: [ + TextSpan( + text: appLocalizations + .modelDialogAddPromptCorruptionErrorSolution + .split("\n") + .first, + ), + const TextSpan(text: "\n"), + TextSpan( + text: r"$ rm -f $OLLAMA_MODELS/blobs/sha256-*partial*", + style: textTheme.labelMedium?.copyWith( + fontFamily: "GoogleSansCode", + color: colorScheme.error, + ), + ), + const TextSpan(text: "\n"), + TextSpan( + text: appLocalizations + .modelDialogAddPromptCorruptionErrorSolution + .split("\n") + .last, + ), + ], + style: textTheme.labelMedium?.copyWith( + color: colorScheme.error, + ), + ), + ), + ], + + if (_downloadingModels.isNotEmpty) ...[ + const SizedBox(height: 24), + SizedBox( + width: double.infinity, + child: Card.outlined( + margin: EdgeInsets.zero, + child: Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Padding( + padding: const EdgeInsetsDirectional.only( + top: 6, + start: 8, + ), + child: Text(appLocalizations.modelDialogAddCurrently), + ), + Padding( + padding: const EdgeInsetsDirectional.only( + top: 4, + bottom: 8, + start: 8, + end: 8, + ), + child: Wrap( + spacing: 4, + runSpacing: 4, + children: _downloadingModels.keys + .map( + (modelName) => ActionChip( + label: Text(modelName), + onPressed: () => _downloadModel(modelName), + ), + ) + .toList(), + ), + ), + ], + ), + ), + ), + ], + ], + ), + ), + actions: [ + TextButton( + onPressed: () => Navigator.of(context).pop(), + child: Text(appLocalizations.modelDialogAddCancelConfirm), + ), + TextButton( + onPressed: _downloadModel, + child: Text(appLocalizations.modelDialogAdd), + ), + ], + ); + const childLoading = SizedBox( + key: ValueKey("loading"), + width: 560, + child: Padding( + padding: EdgeInsets.all(24), + child: LinearProgressIndicator(), + ), + ); + final childDownload = Builder( + builder: (context) { + final closeInput = [ + if (embeddedNavigation) const Divider(height: 1), + Padding( + padding: const EdgeInsetsDirectional.only( + top: 24, + bottom: 4, + start: 24, + end: 24, + ), + child: Text(appLocalizations.modelDialogAddCancelTitle), + ), + Padding( + padding: const EdgeInsetsDirectional.only( + bottom: 24, + start: 24, + end: 24, + ), + child: Row( + mainAxisSize: MainAxisSize.max, + mainAxisAlignment: MainAxisAlignment.center, + children: [ + FilledButton( + onPressed: () async => state!.cancelFlag = true, + style: FilledButton.styleFrom( + backgroundColor: colorScheme.error, + ), + child: Text(appLocalizations.modelDialogAddCancelConfirm), + ), + const SizedBox(width: 4), + OutlinedButton( + onPressed: () => Navigator.of(context).pop(), + child: Text(appLocalizations.modelDialogAddCancelHide), + ), + ], + ), + ), + if (!embeddedNavigation) const Divider(height: 1), + ]; + return SizedBox( + key: const ValueKey("download"), + width: 560, + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + if (_wantPop && !embeddedNavigation) ...closeInput, + Padding( + padding: const EdgeInsets.all(24), + child: ListTile( + minTileHeight: 0, + minVerticalPadding: 0, + contentPadding: EdgeInsets.zero, + title: Builder( + builder: (context) => Text( + modelName ?? appLocalizations.noSelectedModel, + style: DefaultTextStyle.of( + context, + ).style.copyWith(fontFamily: "GoogleSansCode"), + ), + ), + subtitle: Builder( + builder: (context) => Wrap( + children: [ + Text( + NumberFormat.decimalPercentPattern( + locale: appLocalizations.localeName, + decimalDigits: 2, + ).format(state?.progress ?? 0), + style: DefaultTextStyle.of(context).style.copyWith( + fontFeatures: const [FontFeature.tabularFigures()], + ), + ), + Text(" " * 3), + const Text("/"), + Text(" " * 3), + Text.rich( + TextSpan( + children: [ + WidgetSpan( + child: Transform.translate( + offset: const Offset(0, -1), + child: Icon( + Icons.sd_storage_outlined, + size: DefaultTextStyle.of( + context, + ).style.fontSize, + ), + ), + ), + const TextSpan(text: " "), + TextSpan( + text: bytesToHumanReadable( + state?.total ?? 0, + locale: appLocalizations.localeName, + ), + ), + ], + ), + ), + ], + ), + ), + trailing: CircularProgressIndicator( + value: state?.progress ?? 0, + ), + ), + ), + if (_wantPop && embeddedNavigation) ...closeInput, + ], + ), + ); + }, + ); + + return PopScope( + canPop: _pageState == false, + onPopInvokedWithResult: (didPop, result) { + if (_pageState != true || _wantPop) return; + _wantPop = true; + if (mounted) setState(() {}); + }, + child: Dialog( + alignment: alignment, + constraints: constraints, + insetPadding: const EdgeInsets.all(12), // 24 + child: AnimatedSize( + duration: ExpressiveCurves.expressiveSpatial.normalDuration, + curve: ExpressiveCurves.expressiveSpatial.normal, + alignment: embeddedNavigation + ? AlignmentGeometry.topCenter + : AlignmentGeometry.bottomCenter, + child: AnimatedSwitcher( + duration: ExpressiveCurves.expressiveSpatial.normalDuration, + switchInCurve: ExpressiveCurves.expressiveSpatial.normal, + switchOutCurve: ExpressiveCurves.expressiveEffects.slow.flipped, + transitionBuilder: (child, animation) => SizeTransition( + sizeFactor: animation, + alignment: embeddedNavigation + ? AlignmentDirectional.bottomCenter + : AlignmentDirectional.topCenter, + child: FadeTransition(opacity: animation, child: child), + ), + child: _pageState == null + ? childLoading + : (_pageState! ? childDownload : childInput), + ), + ), + ), + ); + } +} + +Future showModelAdderDialog(BuildContext context) async => + showDialog( + context: context, + builder: (context) => const ModelAdder(), + ); diff --git a/lib/widgets/model_selector.dart b/lib/widgets/model_selector.dart new file mode 100644 index 0000000..bca490e --- /dev/null +++ b/lib/widgets/model_selector.dart @@ -0,0 +1,454 @@ +import 'dart:math'; + +import 'package:flutter/material.dart'; +import 'package:get_time_ago/get_time_ago.dart'; +import 'package:intl/intl.dart' as intl; + +import '../l10n/gen/app_localizations.dart'; +import '../services/services.dart'; +import '../util/extensions.dart'; +import 'model_adder.dart'; +import 'sized_circular_progress_indicator.dart'; +import 'two_state_widget.dart'; + +// MARK: Model Selector Dialog + +class _ModelSelectorDialog extends StatelessWidget { + final GlobalKey? anchorKey; + + const _ModelSelectorDialog({this.anchorKey}); + + @override + Widget build(BuildContext context) { + final breakpoint = Breakpoint.of(context); + final embeddedNavigation = breakpoint.panesRecommended >= 2; + if (!embeddedNavigation) { + return const Dialog.fullscreen( + child: ModelSelector(embeddedNavigation: false), + ); + } + + var anchorRect = Rect.zero; + final renderBox = anchorKey?.currentContext?.findRenderObject(); + if (renderBox is RenderBox) { + anchorRect = renderBox.localToGlobal(Offset.zero) & renderBox.size; + } + + final screenSize = MediaQuery.sizeOf(context); + final isRtl = Directionality.of(context) == TextDirection.rtl; + const minWidth = 280.0; + const maxWidth = 560.0; + const edgeGap = 24.0; + + final anchorEdge = isRtl + ? screenSize.width - anchorRect.right + : anchorRect.left; + final position = anchorEdge + .clamp(0.0, max(0.0, screenSize.width - maxWidth)) + .toDouble(); + final effectiveMaxWidth = (screenSize.width - position - edgeGap).clamp( + minWidth, + maxWidth, + ); + final top = anchorRect.bottom - 24; + + final popup = ConstrainedBox( + constraints: BoxConstraints( + minWidth: minWidth, + maxWidth: effectiveMaxWidth, + maxHeight: screenSize.height - top - edgeGap, + ), + child: Material( + type: MaterialType.card, + elevation: 6, + shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)), + clipBehavior: Clip.antiAlias, + child: const ModelSelector(embeddedNavigation: true), + ), + ); + + return Stack( + children: [ + if (isRtl) + Positioned(right: position, top: top, child: popup) + else + Positioned(left: position, top: top, child: popup), + ], + ); + } +} + +Future showModelSelector({ + required BuildContext context, + required GlobalKey? anchorKey, +}) => showDialog( + context: context, + barrierDismissible: true, + barrierColor: Colors.transparent, + builder: (context) => _ModelSelectorDialog(anchorKey: anchorKey), +); + +// MARK: Model Selector + +class ModelSelector extends StatefulWidget { + final bool embeddedNavigation; + const ModelSelector({super.key, required this.embeddedNavigation}); + + @override + State createState() => _ModelSelectorState(); +} + +class _ModelSelectorState extends State { + @override + void initState() { + super.initState(); + ModelManager.instance.addListener(onUpdate); + } + + @override + void dispose() { + ModelManager.instance.removeListener(onUpdate); + super.dispose(); + } + + void onUpdate() { + if (mounted) setState(() {}); + } + + @override + Widget build(BuildContext context) { + final breakpoint = Breakpoint.of(context); + return SingleChildScrollView( + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + ModelSelectorHeader(embeddedNavigation: widget.embeddedNavigation), + ...ModelManager.instance.models.map((model) { + final tmp = ModelSelectorTile( + model: model, + onTap: () { + ModelManager.instance.currentModel = model; + Navigator.of(context).pop(); + }, + ); + if (widget.embeddedNavigation) { + return tmp; + } else { + return Padding( + padding: EdgeInsets.symmetric( + horizontal: breakpoint.spacing / 2, + ), + child: tmp, + ); + } + }), + ], + ), + ); + } +} + +class ModelSelectorHeader extends StatefulWidget { + final bool embeddedNavigation; + const ModelSelectorHeader({super.key, required this.embeddedNavigation}); + + @override + State createState() => _ModelSelectorHeaderState(); +} + +class _ModelSelectorHeaderState extends State { + bool _isLoading = false; + bool _loadingFromAdd = false; + + Future refreshModels() async { + if (_isLoading) return; + if (HostManager.instance.host == null) { + Navigator.of(context).pop(); + } + + _isLoading = true; + if (mounted) setState(() {}); + + if (mounted) { + await errorGuard(context, "N3AI17J1", () async { + try { + await Future.wait([ + ModelManager.instance.loadModels(), + Future.delayed(Durations.long1), + ]).catchError(Error.throwWithStackTrace); + } finally { + _isLoading = false; + _loadingFromAdd = false; + if (mounted) setState(() {}); + } + }); + } + } + + @override + Widget build(BuildContext context) { + final iconTheme = IconTheme.of(context); + final textTheme = TextTheme.of(context); + final appLocalizations = AppLocalizations.of(context); + + final title = Text( + appLocalizations.modelDialogTitle, + style: widget.embeddedNavigation ? textTheme.titleMedium : null, + ); + final actions = [ + TwoStateWidgetManaged( + state: _isLoading && _loadingFromAdd, + dimension: (iconTheme.size ?? 24) + (8 * 2), + from: IconButton( + icon: const Icon(Icons.add), + tooltip: appLocalizations.modelDialogAdd, + onPressed: !_isLoading + ? () async { + final addedModel = await showModelAdderDialog(context); + if (addedModel != null) { + ModelManager.instance.currentModelName = addedModel; + if (!context.mounted) return; + Navigator.of(context).pop(); + + ScaffoldMessenger.of(context).clearSnackBars(); + ScaffoldMessenger.of(context).showSnackBar( + SnackBar( + content: Text( + appLocalizations.modelDialogAddDownloadSuccess, + ), + ), + ); + } + } + : null, + ), + to: const AspectRatio( + aspectRatio: 1 / 1, + child: SizedBox.expand( + child: Center( + child: SizedCircularProgressIndicator( + dimension: 24, + padding: EdgeInsets.zero, + ), + ), + ), + ), + ), + Padding( + padding: EdgeInsetsDirectional.only( + end: widget.embeddedNavigation ? 0 : 8, + ), + child: TwoStateWidgetManaged( + state: _isLoading && !_loadingFromAdd, + dimension: (iconTheme.size ?? 24) + (8 * 2), + from: IconButton( + icon: const Icon(Icons.refresh), + tooltip: appLocalizations.modelDialogRefresh, + onPressed: !_isLoading ? refreshModels : null, + ), + to: const AspectRatio( + aspectRatio: 1 / 1, + child: SizedBox.expand( + child: Center( + child: SizedCircularProgressIndicator( + dimension: 24, + padding: EdgeInsets.zero, + ), + ), + ), + ), + ), + ), + ]; + + return widget.embeddedNavigation + ? Row(mainAxisAlignment: MainAxisAlignment.end, children: actions) + : AppBar( + leading: const AspectRatio( + aspectRatio: 1 / 1, + child: SizedBox.expand(child: BackButton()), + ), + title: title, + actions: actions, + ); + } +} + +class ModelSelectorTile extends StatelessWidget { + final Model model; + final VoidCallback? onTap; + + const ModelSelectorTile({ + super.key, + required this.model, + required this.onTap, + }); + + @override + Widget build(BuildContext context) { + final colorScheme = Theme.of(context).colorScheme; + final iconTheme = IconTheme.of(context); + final appLocalizations = AppLocalizations.of(context); + + final Widget subtitle = Builder( + builder: (context) => Wrap( + children: [ + Text.rich( + TextSpan( + children: [ + WidgetSpan( + child: Transform.translate( + offset: const Offset(0, -1), + child: Icon( + Icons.functions, + size: DefaultTextStyle.of(context).style.fontSize, + ), + ), + ), + const TextSpan(text: " "), + TextSpan( + text: model.parameterSize.replaceAll( + ".", + intl.NumberFormat.decimalPattern( + appLocalizations.localeName, + ).symbols.DECIMAL_SEP, + ), + ), + ], + ), + ), + Text(" " * 3), + if ((model.families.isEmpty ? [model.family] : model.families) + .length != + 1 || + (model.families.isEmpty ? [model.family] : model.families) + .first != + model.name.split(":").first) ...[ + Text.rich( + TextSpan( + children: [ + WidgetSpan( + child: Transform.translate( + offset: const Offset(0, -1), + child: Icon( + Icons.group, + size: DefaultTextStyle.of(context).style.fontSize, + ), + ), + ), + const TextSpan(text: " "), + TextSpan( + children: + (model.families.isEmpty + ? [model.family] + : model.families) + .map((family) => TextSpan(text: family)) + .joinElements(const TextSpan(text: ", ")) + .toList(), + ), + ], + ), + ), + Text(" " * 3), + ], + Text.rich( + TextSpan( + children: [ + WidgetSpan( + child: Transform.translate( + offset: const Offset(0, -1), + child: Icon( + Icons.sd_storage_outlined, + size: DefaultTextStyle.of(context).style.fontSize, + ), + ), + ), + const TextSpan(text: " "), + TextSpan( + text: bytesToHumanReadable( + model.size, + locale: appLocalizations.localeName, + ), + ), + ], + ), + ), + Text(" " * 3), + Text.rich( + TextSpan( + children: [ + WidgetSpan( + child: Transform.translate( + offset: const Offset(0, -1), + child: Icon( + Icons.calendar_today, + size: DefaultTextStyle.of(context).style.fontSize, + ), + ), + ), + const TextSpan(text: " "), + TextSpan( + text: GetTimeAgo.parse( + model.modifiedAt, + locale: appLocalizations.localeName, + pattern: intl.DateFormat.yMd( + appLocalizations.localeName, + ).pattern, + ), + ), + ], + ), + ), + ], + ), + ); + + final tmp = ListTile( + title: Builder( + builder: (context) => Text.rich( + TextSpan(children: model.nameColored(context)), + style: DefaultTextStyle.of( + context, + ).style.copyWith(fontFamily: "GoogleSansCode"), + ), + ), + subtitle: subtitle, + trailing: Row( + mainAxisSize: MainAxisSize.min, + children: model.capabilities + .map( + (capability) => Padding( + padding: const EdgeInsetsDirectional.only(start: 2), + child: Tooltip( + message: capability.name.toTitleCase(), + child: Icon( + capability.icon, + size: (iconTheme.size ?? 24) - ((iconTheme.size ?? 24) / 3), + ), + ), + ), + ) + .toList(), + ), + shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)), + onTap: onTap, + ); + + return AnimatedContainer( + duration: ExpressiveCurves.expressiveEffects.fastDuration, + curve: ExpressiveCurves.expressiveEffects.fast, + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(12), + border: ModelManager.instance.currentModel == model + ? Border.all(color: colorScheme.outline, width: 2) + : Border.all( + color: Theme.of( + context, + ).colorScheme.primary.withValues(alpha: 0), + width: 2, + ), + ), + child: tmp, + ); + } +} diff --git a/lib/widgets/sized_circular_progress_indicator.dart b/lib/widgets/sized_circular_progress_indicator.dart new file mode 100644 index 0000000..fb7fd2f --- /dev/null +++ b/lib/widgets/sized_circular_progress_indicator.dart @@ -0,0 +1,76 @@ +import 'package:flutter/material.dart'; + +class SizedCircularProgressIndicator extends StatelessWidget { + final double? dimension; + final double? value; + final Color? backgroundColor; + final Color? color; + final Animation? valueColor; + final String? semanticsLabel; + final String? semanticsValue; + final double? strokeWidth; + final double? strokeAlign; + final StrokeCap? strokeCap; + final BoxConstraints? constraints; + final double? trackGap; + + @Deprecated( + 'Set this flag to false to opt into the 2024 progress indicator appearance. Defaults to true. ' + 'In the future, this flag will default to false. Use ProgressIndicatorThemeData to customize individual properties. ' + 'This feature was deprecated after v3.27.0-0.2.pre.', + ) + final bool? year2023; + final EdgeInsetsGeometry? padding; + final AnimationController? controller; + + const SizedCircularProgressIndicator({ + super.key, + this.value, + this.dimension, + this.backgroundColor, + this.color, + this.valueColor, + this.strokeWidth, + this.strokeAlign, + this.semanticsLabel, + this.semanticsValue, + this.strokeCap, + this.constraints, + this.trackGap, + @Deprecated( + 'Set this flag to false to opt into the 2024 progress indicator appearance. Defaults to true. ' + 'In the future, this flag will default to false. Use ProgressIndicatorThemeData to customize individual properties. ' + 'This feature was deprecated after v3.27.0-0.1.pre.', + ) + this.year2023, + this.padding, + this.controller, + }); + + @override + Widget build(BuildContext context) { + final scale = (dimension ?? 40) / 40; + + final indicator = CircularProgressIndicator( + value: value, + backgroundColor: backgroundColor, + color: color, + valueColor: valueColor, + strokeWidth: (strokeWidth ?? 4) / scale, + strokeAlign: strokeAlign, + semanticsLabel: semanticsLabel, + semanticsValue: semanticsValue, + strokeCap: strokeCap, + constraints: constraints, + trackGap: trackGap, + // pipe through + // ignore: deprecated_member_use + year2023: year2023, + padding: padding, + controller: controller, + ); + if (scale == 1) return indicator; + + return Transform.scale(scale: scale, child: indicator); + } +} diff --git a/lib/widgets/two_state_widget.dart b/lib/widgets/two_state_widget.dart new file mode 100644 index 0000000..1479eca --- /dev/null +++ b/lib/widgets/two_state_widget.dart @@ -0,0 +1,128 @@ +import 'package:flutter/material.dart'; +import '../services/services.dart'; + +/// Widget used to blend between two widget states, like icons. +class TwoStateWidget extends StatelessWidget { + final Animation animation; + + final double? dimension; + + final Widget? from; + final Widget? to; + + const TwoStateWidget({ + super.key, + required this.animation, + this.dimension, + this.from, + this.to, + }); + + @override + Widget build(BuildContext context) { + final animationFrom = ReverseAnimation(animation); + final animationTo = animation; + + final children = [ + if (from != null) ScaleTransition(scale: animationFrom, child: from), + if (to != null) ScaleTransition(scale: animationTo, child: to), + ]; + final child = Stack( + children: children.map((child) { + if (dimension != null) return Positioned.fill(child: child); + return child; + }).toList(), + ); + + if (dimension != null) { + return ConstrainedBox( + constraints: BoxConstraints.tight(Size.square(dimension!)), + child: child, + ); + } + return child; + } +} + +class TwoStateWidgetManaged extends StatefulWidget { + final bool state; + + final double? dimension; + + final Widget? from; + final Widget? to; + + final Duration duration; + final Curve curve; + + TwoStateWidgetManaged({ + super.key, + required this.state, + this.dimension, + this.from, + this.to, + Duration? duration, + Curve? curve, + }) : duration = duration ?? ExpressiveCurves.expressiveSpatial.fastDuration, + curve = curve ?? ExpressiveCurves.expressiveSpatial.fast; + + @override + State createState() => _TwoStateWidgetManagedState(); +} + +class _TwoStateWidgetManagedState extends State + with SingleTickerProviderStateMixin { + late final AnimationController _animationController; + Animation? _animation; + + @override + void initState() { + super.initState(); + _animationController = AnimationController( + vsync: this, + value: widget.state ? 1.0 : 0.0, + duration: widget.duration, + ); + _animation = CurvedAnimation( + parent: _animationController, + curve: widget.curve, + ); + } + + @override + void didUpdateWidget(covariant TwoStateWidgetManaged oldWidget) { + super.didUpdateWidget(oldWidget); + + if (widget.duration != oldWidget.duration) { + _animationController.duration = widget.duration; + } + if (widget.curve != oldWidget.curve) { + _animation = CurvedAnimation( + parent: _animationController, + curve: widget.curve, + ); + } + + if (widget.state != oldWidget.state) { + if (widget.state) { + _animationController.forward(); + } else { + _animationController.reverse(); + } + } + } + + @override + void dispose() { + _animationController.dispose(); + super.dispose(); + } + + @override + Widget build(BuildContext context) => TwoStateWidget( + animation: _animation!, + dimension: widget.dimension, + from: widget.from, + to: widget.to, + ); +} diff --git a/lib/worker/desktop.dart b/lib/worker/desktop.dart index 1148f7a..925a967 100644 --- a/lib/worker/desktop.dart +++ b/lib/worker/desktop.dart @@ -15,56 +15,81 @@ bool desktopFeature({bool web = false}) { } } -bool desktopLayout(BuildContext context, - {bool web = true, double? value, double valueCap = 1000}) { +bool desktopLayout( + BuildContext context, { + bool web = true, + double? value, + double valueCap = 1000, +}) { value ??= MediaQuery.of(context).size.width; return desktopFeature(web: web) || value >= valueCap; } -bool desktopLayoutRequired(BuildContext context, - {bool web = true, double? value, double valueCap = 1000}) { +bool desktopLayoutRequired( + BuildContext context, { + bool web = true, + double? value, + double valueCap = 1000, +}) { value ??= MediaQuery.of(context).size.width; return desktopFeature(web: web) && value >= valueCap; } -bool desktopLayoutNotRequired(BuildContext context, - {bool web = true, double? value, double valueCap = 1000}) { +bool desktopLayoutNotRequired( + BuildContext context, { + bool web = true, + double? value, + double valueCap = 1000, +}) { value ??= MediaQuery.of(context).size.width; return value >= valueCap; } Widget desktopControls(BuildContext context) { return SizedBox( - height: 200, - child: WindowTitleBarBox( - child: Row( + height: 200, + child: WindowTitleBarBox( + child: Row( children: [ SizedBox( - width: 46, - height: 200, - child: MinimizeWindowButton( - animate: true, - colors: WindowButtonColors( - iconNormal: Theme.of(context).colorScheme.primary))), + width: 46, + height: 200, + child: MinimizeWindowButton( + animate: true, + colors: WindowButtonColors( + iconNormal: Theme.of(context).colorScheme.primary, + ), + ), + ), SizedBox( - width: 46, - height: 72, - child: MaximizeWindowButton( - animate: true, - colors: WindowButtonColors( - iconNormal: Theme.of(context).colorScheme.primary))), + width: 46, + height: 72, + child: MaximizeWindowButton( + animate: true, + colors: WindowButtonColors( + iconNormal: Theme.of(context).colorScheme.primary, + ), + ), + ), SizedBox( - width: 46, - height: 72, - child: CloseWindowButton( - animate: true, - colors: WindowButtonColors( - iconNormal: Theme.of(context).colorScheme.primary))), + width: 46, + height: 72, + child: CloseWindowButton( + animate: true, + colors: WindowButtonColors( + iconNormal: Theme.of(context).colorScheme.primary, + ), + ), + ), ], - ))); + ), + ), + ); } -List? desktopControlsActions(BuildContext context, - [List? ifNotAvailable]) { +List? desktopControlsActions( + BuildContext context, [ + List? ifNotAvailable, +]) { return desktopFeature() ? [desktopControls(context)] : ifNotAvailable; } diff --git a/lib/worker/sender.dart b/lib/worker/sender.dart index c8cded5..e4c8df5 100644 --- a/lib/worker/sender.dart +++ b/lib/worker/sender.dart @@ -27,23 +27,26 @@ Future> getHistory([String? addToSystem]) async { system += "\n$addToSystem"; } - var history = (prefs!.getBool("useSystem") ?? true) + final history = (prefs!.getBool("useSystem") ?? true) ? [llama.Message(role: llama.MessageRole.system, content: system)] : []; - var history2 = []; + final history2 = []; images = []; for (var i = 0; i < messages.length; i++) { if (jsonDecode(jsonEncode(messages[i]))["text"] != null) { - history2.add(llama.Message( + history2.add( + llama.Message( role: (messages[i].author.id == user.id) ? llama.MessageRole.user : llama.MessageRole.system, content: jsonDecode(jsonEncode(messages[i]))["text"], - images: (images.isNotEmpty) ? images : null)); + images: (images.isNotEmpty) ? images : null, + ), + ); images = []; } else { var uri = jsonDecode(jsonEncode(messages[i]))["uri"] as String; - var content = (uri.startsWith("data:image/png;base64,")) + final content = (uri.startsWith("data:image/png;base64,")) ? uri.removePrefix("data:image/png;base64,") : base64.encode(await File(uri).readAsBytes()); uri = uri.removePrefix("data:image/png;base64,"); @@ -61,7 +64,8 @@ List getHistoryString([String? uuid]) { for (var i = 0; i < (prefs!.getStringList("chats") ?? []).length; i++) { if (jsonDecode((prefs!.getStringList("chats") ?? [])[i])["uuid"] == uuid) { messages = jsonDecode( - jsonDecode((prefs!.getStringList("chats") ?? [])[i])["messages"]); + jsonDecode((prefs!.getStringList("chats") ?? [])[i])["messages"], + ); break; } } @@ -73,7 +77,7 @@ List getHistoryString([String? uuid]) { if (messages[i]["type"] == "image") { messages[i] = { "role": messages[i]["role"]!, - "content": "<${messages[i]["role"]} inserted an image>" + "content": "<${messages[i]["role"]} inserted an image>", }; } } @@ -82,28 +86,34 @@ List getHistoryString([String? uuid]) { } Future getTitleAi(List history) async { - var generated = await ollamaClient + final generated = await ollamaClient .generateChatCompletion( request: llama.GenerateChatCompletionRequest( - model: model!, - messages: [ - const llama.Message( - role: llama.MessageRole.system, - content: - "Generate a three to six word title for the conversation provided by the user. If an object or person is very important in the conversation, put it in the title as well; keep the focus on the main subject. You must not put the assistant in the focus and you must not put the word 'assistant' in the title! Do preferably use title case. Use a formal tone, don't use dramatic words, like 'mystery' Use spaces between words, do not use camel case! You must not use markdown or any other formatting language! You must not use emojis or any other symbols! You must not use general clauses like 'assistance', 'help' or 'session' in your title! \n\n~~User Introduces Themselves~~ -> User Introduction\n~~User Asks for Help with a Problem~~ -> Problem Help\n~~User has a _**big**_ Problem~~ -> Big Problem"), - llama.Message( - role: llama.MessageRole.user, - content: "```\n${jsonEncode(history)}\n```") - ], - keepAlive: int.parse(prefs!.getString("keepAlive") ?? "300")), + model: model!, + messages: [ + const llama.Message( + role: llama.MessageRole.system, + content: + "Generate a three to six word title for the conversation provided by the user. If an object or person is very important in the conversation, put it in the title as well; keep the focus on the main subject. You must not put the assistant in the focus and you must not put the word 'assistant' in the title! Do preferably use title case. Use a formal tone, don't use dramatic words, like 'mystery' Use spaces between words, do not use camel case! You must not use markdown or any other formatting language! You must not use emojis or any other symbols! You must not use general clauses like 'assistance', 'help' or 'session' in your title! \n\n~~User Introduces Themselves~~ -> User Introduction\n~~User Asks for Help with a Problem~~ -> Problem Help\n~~User has a _**big**_ Problem~~ -> Big Problem", + ), + llama.Message( + role: llama.MessageRole.user, + content: "```\n${jsonEncode(history)}\n```", + ), + ], + keepAlive: int.parse(prefs!.getString("keepAlive") ?? "300"), + ), ) - .timeout(Duration( - seconds: - (10.0 * (prefs!.getDouble("timeoutMultiplier") ?? 1.0)).round())); + .timeout( + Duration( + seconds: (10.0 * (prefs!.getDouble("timeoutMultiplier") ?? 1.0)) + .round(), + ), + ); var title = generated.message.content; title = title.replaceAll("\n", " "); - var terms = [ + final terms = [ '"', "'", "*", @@ -119,7 +129,7 @@ Future getTitleAi(List history) async { "[", "]", "{", - "}" + "}", ]; for (var i = 0; i < terms.length; i++) { title = title.replaceAll(terms[i], ""); @@ -138,12 +148,12 @@ Future getTitleAi(List history) async { Future setTitleAi(List history) async { try { - var title = await getTitleAi(history); - var tmp = prefs!.getStringList("chats") ?? []; + final title = await getTitleAi(history); + final tmp = prefs!.getStringList("chats") ?? []; for (var i = 0; i < tmp.length; i++) { if (jsonDecode((prefs!.getStringList("chats") ?? [])[i])["uuid"] == chatUuid) { - var tmp2 = jsonDecode(tmp[i]); + final tmp2 = jsonDecode(tmp[i]); tmp2["title"] = title; tmp[i] = jsonEncode(tmp2); break; @@ -153,18 +163,25 @@ Future setTitleAi(List history) async { } catch (_) {} } -Future send(String value, BuildContext context, Function setState, - {void Function(String currentText, bool done)? onStream, - String? addToSystem}) async { +Future send( + String value, + BuildContext context, + Function setState, { + void Function(String currentText, bool done)? onStream, + String? addToSystem, +}) async { selectionHaptic(); setState(() { sendable = false; }); if (host == null) { - ScaffoldMessenger.of(context).showSnackBar(SnackBar( + ScaffoldMessenger.of(context).showSnackBar( + SnackBar( content: Text(AppLocalizations.of(context).noHostSelected), - showCloseIcon: true)); + showCloseIcon: true, + ), + ); if (onStream != null) { onStream("", true); } @@ -173,9 +190,12 @@ Future send(String value, BuildContext context, Function setState, if (!chatAllowed || model == null) { if (model == null) { - ScaffoldMessenger.of(context).showSnackBar(SnackBar( + ScaffoldMessenger.of(context).showSnackBar( + SnackBar( content: Text(AppLocalizations.of(context).noModelSelected), - showCloseIcon: true)); + showCloseIcon: true, + ), + ); } if (onStream != null) { onStream("", true); @@ -188,26 +208,30 @@ Future send(String value, BuildContext context, Function setState, newChat = true; chatUuid = const Uuid().v4(); prefs!.setStringList( - "chats", - (prefs!.getStringList("chats") ?? []).append([ - jsonEncode({ - "title": AppLocalizations.of(context).newChatTitle, - "uuid": chatUuid, - "messages": [] - }) - ]).toList()); + "chats", + (prefs!.getStringList("chats") ?? []).append([ + jsonEncode({ + "title": AppLocalizations.of(context).newChatTitle, + "uuid": chatUuid, + "messages": [], + }), + ]).toList(), + ); } - var history = await getHistory(addToSystem); + final history = await getHistory(addToSystem); - history.add(llama.Message( + history.add( + llama.Message( role: llama.MessageRole.user, content: value.trim(), - images: (images.isNotEmpty) ? images : null)); + images: (images.isNotEmpty) ? images : null, + ), + ); messages.insert( - 0, - types.TextMessage( - author: user, id: const Uuid().v4(), text: value.trim())); + 0, + types.TextMessage(author: user, id: const Uuid().v4(), text: value.trim()), + ); saveChat(chatUuid!, setState); @@ -215,20 +239,24 @@ Future send(String value, BuildContext context, Function setState, chatAllowed = false; var text = ""; - var newId = const Uuid().v4(); + final newId = const Uuid().v4(); try { if ((prefs!.getString("requestType") ?? "stream") == "stream") { - var stream = ollamaClient + final stream = ollamaClient .generateChatCompletionStream( request: llama.GenerateChatCompletionRequest( - model: model!, - messages: history, - keepAlive: int.parse(prefs!.getString("keepAlive") ?? "300")), + model: model!, + messages: history, + keepAlive: int.parse(prefs!.getString("keepAlive") ?? "300"), + ), ) - .timeout(Duration( + .timeout( + Duration( seconds: (30.0 * (prefs!.getDouble("timeoutMultiplier") ?? 1.0)) - .round())); + .round(), + ), + ); await for (var res in stream) { text += res.message.content; @@ -240,7 +268,9 @@ Future send(String value, BuildContext context, Function setState, } if (chatAllowed) return ""; messages.insert( - 0, types.TextMessage(author: assistant, id: newId, text: text)); + 0, + types.TextMessage(author: assistant, id: newId, text: text), + ); //TODO: add functionality // // chatKey!.currentState!.scrollToMessage(messages[1].id, @@ -256,18 +286,26 @@ Future send(String value, BuildContext context, Function setState, request = await ollamaClient .generateChatCompletion( request: llama.GenerateChatCompletionRequest( - model: model!, - messages: history, - keepAlive: int.parse(prefs!.getString("keepAlive") ?? "300")), + model: model!, + messages: history, + keepAlive: int.parse(prefs!.getString("keepAlive") ?? "300"), + ), ) - .timeout(Duration( + .timeout( + Duration( seconds: (30.0 * (prefs!.getDouble("timeoutMultiplier") ?? 1.0)) - .round())); + .round(), + ), + ); if (chatAllowed) return ""; messages.insert( - 0, - types.TextMessage( - author: assistant, id: newId, text: request.message.content)); + 0, + types.TextMessage( + author: assistant, + id: newId, + text: request.message.content, + ), + ); text = request.message.content; setState(() {}); heavyHaptic(); @@ -283,7 +321,7 @@ Future send(String value, BuildContext context, Function setState, chatAllowed = true; messages.removeAt(0); if (messages.isEmpty) { - var tmp = prefs!.getStringList("chats") ?? []; + final tmp = prefs!.getStringList("chats") ?? []; for (var i = 0; i < tmp.length; i++) { if (jsonDecode((prefs!.getStringList("chats") ?? [])[i])["uuid"] == chatUuid) { @@ -296,11 +334,14 @@ Future send(String value, BuildContext context, Function setState, } }); // ignore: use_build_context_synchronously - ScaffoldMessenger.of(context).showSnackBar(SnackBar( + ScaffoldMessenger.of(context).showSnackBar( + SnackBar( content: // ignore: use_build_context_synchronously Text(AppLocalizations.of(context).settingsHostInvalid("timeout")), - showCloseIcon: true)); + showCloseIcon: true, + ), + ); return ""; } //TODO: add functionality diff --git a/lib/worker/setter.dart b/lib/worker/setter.dart index b0b1fa4..2cf0472 100644 --- a/lib/worker/setter.dart +++ b/lib/worker/setter.dart @@ -18,9 +18,9 @@ import 'sender.dart'; import 'theme.dart'; void setModel(BuildContext context, Function setState) { - var models = []; - var modelsReal = []; - var modal = []; + final models = []; + final modelsReal = []; + final modal = []; var usedIndex = -1; var oldIndex = -1; var addIndex = -1; @@ -30,16 +30,22 @@ void setModel(BuildContext context, Function setState) { setState(() {}); Future load() async { try { - var list = await ollamaClient.listModels().timeout(Duration( - seconds: - (10.0 * (prefs!.getDouble("timeoutMultiplier") ?? 1.0)).round())); + final list = await ollamaClient.listModels().timeout( + Duration( + seconds: (10.0 * (prefs!.getDouble("timeoutMultiplier") ?? 1.0)) + .round(), + ), + ); for (var i = 0; i < list.models!.length; i++) { - var details = await ollamaClient.showModelInfo( - request: llama.ModelInfoRequest(model: list.models![i].model!)); + final details = await ollamaClient.showModelInfo( + request: llama.ModelInfoRequest(model: list.models![i].model!), + ); models.add(list.models![i].model!.split(":")[0]); modelsReal.add(list.models![i].model!); - modal.add((list.models![i].details!.families ?? []).contains("clip") || - (details.capabilities ?? []).contains(llama.Capability.vision)); + modal.add( + (list.models![i].details!.families ?? []).contains("clip") || + (details.capabilities ?? []).contains(llama.Capability.vision), + ); } addIndex = models.length; @@ -54,7 +60,7 @@ void setModel(BuildContext context, Function setState) { } } if (prefs!.getBool("modelTags") == null) { - var duplicateFinder = []; + final duplicateFinder = []; for (var model in models) { if (duplicateFinder.contains(model)) { prefs!.setBool("modelTags", true); @@ -73,11 +79,15 @@ void setModel(BuildContext context, Function setState) { // ignore: use_build_context_synchronously Navigator.of(context).pop(); // ignore: use_build_context_synchronously - ScaffoldMessenger.of(context).showSnackBar(SnackBar( + ScaffoldMessenger.of(context).showSnackBar( + SnackBar( content: Text( - // ignore: use_build_context_synchronously - AppLocalizations.of(context).settingsHostInvalid("timeout")), - showCloseIcon: true)); + // ignore: use_build_context_synchronously + AppLocalizations.of(context).settingsHostInvalid("timeout"), + ), + showCloseIcon: true, + ), + ); } } @@ -86,9 +96,10 @@ void setModel(BuildContext context, Function setState) { load(); - var content = StatefulBuilder(builder: (context, setLocalState) { - setModalState = setLocalState; - return PopScope( + final content = StatefulBuilder( + builder: (context, setLocalState) { + setModalState = setLocalState; + return PopScope( canPop: false, onPopInvokedWithResult: (didPop, result) async { if (!loaded) return; @@ -125,25 +136,35 @@ void setModel(BuildContext context, Function setState) { headers: { "Content-Type": "application/json", ...(jsonDecode(prefs!.getString("hostHeaders") ?? "{}") - as Map) + as Map), }.cast(), body: jsonEncode({ "model": model!, - "keep_alive": - int.parse(prefs!.getString("keepAlive") ?? "300") + "keep_alive": int.parse( + prefs!.getString("keepAlive") ?? "300", + ), }), ) - .timeout(Duration( - seconds: (10.0 * - (prefs!.getDouble("timeoutMultiplier") ?? 1.0)) - .round())); + .timeout( + Duration( + seconds: + (10.0 * + (prefs!.getDouble("timeoutMultiplier") ?? + 1.0)) + .round(), + ), + ); } catch (_) { // ignore: use_build_context_synchronously - ScaffoldMessenger.of(context).showSnackBar(SnackBar( + ScaffoldMessenger.of(context).showSnackBar( + SnackBar( // ignore: use_build_context_synchronously - content: Text(AppLocalizations.of(context) - .settingsHostInvalid("timeout")), - showCloseIcon: true)); + content: Text( + AppLocalizations.of(context).settingsHostInvalid("timeout"), + ), + showCloseIcon: true, + ), + ); setState(() { model = null; chatAllowed = false; @@ -164,138 +185,149 @@ void setModel(BuildContext context, Function setState) { } }, child: Container( - width: desktopLayout(context) ? null : double.infinity, - padding: EdgeInsets.only( - left: 16, - right: 16, - top: 16, - bottom: desktopLayout(context) ? 16 : 0), - child: (!loaded) - ? SizedBox( - width: desktopLayout(context) ? 300 : double.infinity, - child: const LinearProgressIndicator()) - : Column(mainAxisSize: MainAxisSize.min, children: [ + width: desktopLayout(context) ? null : double.infinity, + padding: EdgeInsets.only( + left: 16, + right: 16, + top: 16, + bottom: desktopLayout(context) ? 16 : 0, + ), + child: (!loaded) + ? SizedBox( + width: desktopLayout(context) ? 300 : double.infinity, + child: const LinearProgressIndicator(), + ) + : Column( + mainAxisSize: MainAxisSize.min, + children: [ Container( - width: desktopLayout(context) ? 300 : double.infinity, - constraints: BoxConstraints( - maxHeight: - MediaQuery.of(context).size.height * 0.4), - child: SingleChildScrollView( - scrollDirection: Axis.vertical, - child: Wrap( - spacing: desktopLayout(context) ? 10.0 : 5.0, - runSpacing: - desktopFeature(web: true) ? 10.0 : 0.0, - alignment: WrapAlignment.center, - children: List.generate( - models.length, - (int index) { - return ChoiceChip( - label: Row( - mainAxisSize: MainAxisSize.min, - children: [ - Text(models[index]), - ((prefs!.getBool("modelTags") ?? - false) && - modelsReal[index] - .split(":") - .length > - 1) - ? Text( - ":${modelsReal[index].split(":")[1]}", - style: const TextStyle( - color: Colors.grey)) - : const SizedBox.shrink() - ]), - selected: usedIndex == index, - avatar: (usedIndex == index) - ? null - : (addIndex == index) - ? const Icon(Icons.add_rounded) - : ((recommendedModels - .contains(models[index])) - ? const Icon(Icons.star_rounded) - : ((modal[index]) - ? const Icon(Icons - .collections_rounded) - : null)), - checkmarkColor: (usedIndex == index && - !(prefs?.getBool( - "useDeviceTheme") ?? - false)) - ? ((MediaQuery.of(context) - .platformBrightness == - Brightness.light) - ? themeLight().colorScheme.secondary - : themeDark().colorScheme.secondary) - : null, - labelStyle: (usedIndex == index && - !(prefs?.getBool( - "useDeviceTheme") ?? - false)) - ? TextStyle( - color: (MediaQuery.of(context) - .platformBrightness == - Brightness.light) - ? themeLight() - .colorScheme - .secondary - : themeDark() - .colorScheme - .secondary) - : null, - selectedColor: (prefs - ?.getBool("useDeviceTheme") ?? - false) - ? null - : (MediaQuery.of(context) - .platformBrightness == - Brightness.light) - ? themeLight().colorScheme.primary - : themeDark().colorScheme.primary, - onSelected: (bool selected) { - selectionHaptic(); - if (addIndex == index) { - usedIndex = oldIndex; - Navigator.of(context).pop(); - addModel(context, setState); - } - if (!chatAllowed && model != null) { - return; - } - setLocalState(() { - usedIndex = selected ? index : -1; - }); - }, - ); - }, - ).toList(), - ))) - ]))); - }); + width: desktopLayout(context) ? 300 : double.infinity, + constraints: BoxConstraints( + maxHeight: MediaQuery.of(context).size.height * 0.4, + ), + child: SingleChildScrollView( + scrollDirection: Axis.vertical, + child: Wrap( + spacing: desktopLayout(context) ? 10.0 : 5.0, + runSpacing: desktopFeature(web: true) ? 10.0 : 0.0, + alignment: WrapAlignment.center, + children: List.generate(models.length, ( + int index, + ) { + return ChoiceChip( + label: Row( + mainAxisSize: MainAxisSize.min, + children: [ + Text(models[index]), + ((prefs!.getBool("modelTags") ?? false) && + modelsReal[index].split(":").length > + 1) + ? Text( + ":${modelsReal[index].split(":")[1]}", + style: const TextStyle( + color: Colors.grey, + ), + ) + : const SizedBox.shrink(), + ], + ), + selected: usedIndex == index, + avatar: (usedIndex == index) + ? null + : (addIndex == index) + ? const Icon(Icons.add_rounded) + : ((recommendedModels.contains(models[index])) + ? const Icon(Icons.star_rounded) + : ((modal[index]) + ? const Icon( + Icons.collections_rounded, + ) + : null)), + checkmarkColor: + (usedIndex == index && + !(prefs?.getBool("useDeviceTheme") ?? + false)) + ? ((MediaQuery.of( + context, + ).platformBrightness == + Brightness.light) + ? themeLight().colorScheme.secondary + : themeDark().colorScheme.secondary) + : null, + labelStyle: + (usedIndex == index && + !(prefs?.getBool("useDeviceTheme") ?? + false)) + ? TextStyle( + color: + (MediaQuery.of( + context, + ).platformBrightness == + Brightness.light) + ? themeLight().colorScheme.secondary + : themeDark().colorScheme.secondary, + ) + : null, + selectedColor: + (prefs?.getBool("useDeviceTheme") ?? false) + ? null + : (MediaQuery.of( + context, + ).platformBrightness == + Brightness.light) + ? themeLight().colorScheme.primary + : themeDark().colorScheme.primary, + onSelected: (bool selected) { + selectionHaptic(); + if (addIndex == index) { + usedIndex = oldIndex; + Navigator.of(context).pop(); + addModel(context, setState); + } + if (!chatAllowed && model != null) { + return; + } + setLocalState(() { + usedIndex = selected ? index : -1; + }); + }, + ); + }).toList(), + ), + ), + ), + ], + ), + ), + ); + }, + ); if (desktopLayoutNotRequired(context)) { showDialog( - context: context, - builder: (context) { - return Transform.translate( - offset: desktopLayoutRequired(context) - ? const Offset(289, 0) - : Offset.zero, - child: Dialog( - surfaceTintColor: - (Theme.of(context).brightness == Brightness.dark) - ? Colors.grey[800] - : null, - alignment: desktopLayoutRequired(context) - ? Alignment.topLeft - : Alignment.topCenter, - child: content), - ); - }); + context: context, + builder: (context) { + return Transform.translate( + offset: desktopLayoutRequired(context) + ? const Offset(289, 0) + : Offset.zero, + child: Dialog( + surfaceTintColor: (Theme.of(context).brightness == Brightness.dark) + ? Colors.grey[800] + : null, + alignment: desktopLayoutRequired(context) + ? Alignment.topLeft + : Alignment.topCenter, + child: content, + ), + ); + }, + ); } else { showModalBottomSheet( - context: context, builder: (context) => Container(child: content)); + context: context, + builder: (context) => Container(child: content), + ); } } @@ -304,19 +336,25 @@ Future addModel(BuildContext context, Function setState) async { var networkError = false; var ratelimitError = false; var alreadyExists = false; - var invalidText = AppLocalizations.of(context).modelDialogAddPromptInvalid; - var networkErrorText = - AppLocalizations.of(context).settingsHostInvalid("other"); - var timeoutErrorText = - AppLocalizations.of(context).settingsHostInvalid("timeout"); - var ratelimitErrorText = - AppLocalizations.of(context).settingsHostInvalid("ratelimit"); - var alreadyExistsText = - AppLocalizations.of(context).modelDialogAddPromptAlreadyExists; - var downloadSuccessText = - AppLocalizations.of(context).modelDialogAddDownloadSuccess; - var downloadFailedText = - AppLocalizations.of(context).modelDialogAddDownloadFailed; + final invalidText = AppLocalizations.of(context).modelDialogAddPromptInvalid; + final networkErrorText = AppLocalizations.of( + context, + ).settingsHostInvalid("other"); + final timeoutErrorText = AppLocalizations.of( + context, + ).settingsHostInvalid("timeout"); + final ratelimitErrorText = AppLocalizations.of( + context, + ).settingsHostInvalid("ratelimit"); + final alreadyExistsText = AppLocalizations.of( + context, + ).modelDialogAddPromptAlreadyExists; + final downloadSuccessText = AppLocalizations.of( + context, + ).modelDialogAddDownloadSuccess; + final downloadFailedText = AppLocalizations.of( + context, + ).modelDialogAddDownloadFailed; var requestedModel = await prompt( context, title: AppLocalizations.of(context).modelDialogAddPromptTitle, @@ -332,11 +370,14 @@ Future addModel(BuildContext context, Function setState) async { ratelimitError = false; alreadyExists = false; try { - var request = await ollamaClient.listModels().timeout(Duration( + final request = await ollamaClient.listModels().timeout( + Duration( seconds: (10.0 * (prefs!.getDouble("timeoutMultiplier") ?? 1.0)) - .round())); + .round(), + ), + ); for (var element in request.models!) { - var localModel = element.model!.removeSuffix(":latest"); + final localModel = element.model!.removeSuffix(":latest"); if (localModel == model) { alreadyExists = true; } @@ -351,34 +392,44 @@ Future addModel(BuildContext context, Function setState) async { if (!(prefs!.getBool("allowWebProxy") ?? false)) { var returnValue = false; await showDialog( - context: mainContext!, - barrierDismissible: false, - builder: (context) { - return AlertDialog( - title: Text(AppLocalizations.of(context) - .modelDialogAddAllowanceTitle), - content: SizedBox( - width: 640, - child: Text(AppLocalizations.of(context) - .modelDialogAddAllowanceDescription), + context: mainContext!, + barrierDismissible: false, + builder: (context) { + return AlertDialog( + title: Text( + AppLocalizations.of(context).modelDialogAddAllowanceTitle, + ), + content: SizedBox( + width: 640, + child: Text( + AppLocalizations.of( + context, + ).modelDialogAddAllowanceDescription, + ), + ), + actions: [ + TextButton( + onPressed: () { + canceled = true; + Navigator.of(context).pop(); + }, + child: Text( + AppLocalizations.of(context).modelDialogAddAllowanceDeny, ), - actions: [ - TextButton( - onPressed: () { - canceled = true; - Navigator.of(context).pop(); - }, - child: Text(AppLocalizations.of(context) - .modelDialogAddAllowanceDeny)), - TextButton( - onPressed: () { - returnValue = true; - Navigator.of(context).pop(); - }, - child: Text(AppLocalizations.of(context) - .modelDialogAddAllowanceAllow)) - ]); - }); + ), + TextButton( + onPressed: () { + returnValue = true; + Navigator.of(context).pop(); + }, + child: Text( + AppLocalizations.of(context).modelDialogAddAllowanceAllow, + ), + ), + ], + ); + }, + ); if (!returnValue) return false; prefs!.setBool("allowWebProxy", true); } @@ -388,9 +439,12 @@ Future addModel(BuildContext context, Function setState) async { try { response = await httpClient .get(Uri.parse("$endpoint${Uri.encodeComponent(model)}")) - .timeout(Duration( + .timeout( + Duration( seconds: (10.0 * (prefs!.getDouble("timeoutMultiplier") ?? 1.0)) - .round())); + .round(), + ), + ); } catch (_) { networkError = true; return false; @@ -398,34 +452,46 @@ Future addModel(BuildContext context, Function setState) async { if (response.statusCode == 200) { var returnValue = false; await showDialog( - context: mainContext!, - barrierDismissible: false, - builder: (context) { - return AlertDialog( - title: Text(AppLocalizations.of(context) - .modelDialogAddAssuranceTitle(model)), - content: SizedBox( - width: 640, - child: Text(AppLocalizations.of(context) - .modelDialogAddAssuranceDescription(model)), + context: mainContext!, + barrierDismissible: false, + builder: (context) { + return AlertDialog( + title: Text( + AppLocalizations.of( + context, + ).modelDialogAddAssuranceTitle(model), + ), + content: SizedBox( + width: 640, + child: Text( + AppLocalizations.of( + context, + ).modelDialogAddAssuranceDescription(model), + ), + ), + actions: [ + TextButton( + onPressed: () { + canceled = true; + Navigator.of(context).pop(); + }, + child: Text( + AppLocalizations.of(context).modelDialogAddAssuranceCancel, ), - actions: [ - TextButton( - onPressed: () { - canceled = true; - Navigator.of(context).pop(); - }, - child: Text(AppLocalizations.of(context) - .modelDialogAddAssuranceCancel)), - TextButton( - onPressed: () { - returnValue = true; - Navigator.of(context).pop(); - }, - child: Text(AppLocalizations.of(context) - .modelDialogAddAssuranceAdd)) - ]); - }); + ), + TextButton( + onPressed: () { + returnValue = true; + Navigator.of(context).pop(); + }, + child: Text( + AppLocalizations.of(context).modelDialogAddAssuranceAdd, + ), + ), + ], + ); + }, + ); return returnValue; } if (response.statusCode == 429) { @@ -446,45 +512,57 @@ Future addModel(BuildContext context, Function setState) async { double? percent; Function? setDialogState; showModalBottomSheet( - context: mainContext!, - builder: (context) { - return StatefulBuilder(builder: (context, setLocalState) { + context: mainContext!, + builder: (context) { + return StatefulBuilder( + builder: (context, setLocalState) { setDialogState = setLocalState; return PopScope( - canPop: false, - child: Container( - width: double.infinity, - padding: EdgeInsets.only( - left: 16, - right: 16, - top: 16, - bottom: desktopLayout(context) ? 16 : 0), - child: Column( - mainAxisSize: MainAxisSize.min, - children: [ - Text( - percent == null - ? AppLocalizations.of(context) - .modelDialogAddDownloadPercentLoading - : AppLocalizations.of(context) - .modelDialogAddDownloadPercent( - (percent * 100).round().toString()), - ), - const Padding(padding: EdgeInsets.only(top: 8)), - LinearProgressIndicator(value: percent), - ], - ))); - }); - }); + canPop: false, + child: Container( + width: double.infinity, + padding: EdgeInsets.only( + left: 16, + right: 16, + top: 16, + bottom: desktopLayout(context) ? 16 : 0, + ), + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + Text( + percent == null + ? AppLocalizations.of( + context, + ).modelDialogAddDownloadPercentLoading + : AppLocalizations.of( + context, + ).modelDialogAddDownloadPercent( + (percent * 100).round().toString(), + ), + ), + const Padding(padding: EdgeInsets.only(top: 8)), + LinearProgressIndicator(value: percent), + ], + ), + ), + ); + }, + ); + }, + ); try { - var stream = ollamaClient + final stream = ollamaClient .pullModelStream(request: llama.PullModelRequest(model: requestedModel)) - .timeout(Duration( + .timeout( + Duration( seconds: (10.0 * (prefs!.getDouble("timeoutMultiplier") ?? 1.0)) - .round())); + .round(), + ), + ); var alreadyProgressed = false; await for (var res in stream) { - var tmpPercent = + final tmpPercent = (res.completed ?? 0).toInt() / (res.total ?? 100).toInt(); if ((tmpPercent * 100).round() == 0) { if (!alreadyProgressed) { @@ -507,9 +585,12 @@ Future addModel(BuildContext context, Function setState) async { } var exists = false; try { - var request = await ollamaClient.listModels().timeout(Duration( - seconds: - (10.0 * (prefs!.getDouble("timeoutMultiplier") ?? 1.0)).round())); + final request = await ollamaClient.listModels().timeout( + Duration( + seconds: (10.0 * (prefs!.getDouble("timeoutMultiplier") ?? 1.0)) + .round(), + ), + ); for (var element in request.models!) { if (element.model == model) { exists = true; @@ -530,10 +611,12 @@ Future addModel(BuildContext context, Function setState) async { Navigator.of(mainContext!).pop(); if (!exists) { ScaffoldMessenger.of(mainContext!).showSnackBar( - SnackBar(content: Text(downloadFailedText), showCloseIcon: true)); + SnackBar(content: Text(downloadFailedText), showCloseIcon: true), + ); } else { ScaffoldMessenger.of(mainContext!).showSnackBar( - SnackBar(content: Text(timeoutErrorText), showCloseIcon: true)); + SnackBar(content: Text(timeoutErrorText), showCloseIcon: true), + ); } return; } @@ -544,11 +627,13 @@ Future addModel(BuildContext context, Function setState) async { }); Navigator.of(mainContext!).pop(); ScaffoldMessenger.of(mainContext!).showSnackBar( - SnackBar(content: Text(downloadSuccessText), showCloseIcon: true)); + SnackBar(content: Text(downloadSuccessText), showCloseIcon: true), + ); } catch (_) { Navigator.of(mainContext!).pop(); ScaffoldMessenger.of(mainContext!).showSnackBar( - SnackBar(content: Text(downloadFailedText), showCloseIcon: true)); + SnackBar(content: Text(downloadFailedText), showCloseIcon: true), + ); } } @@ -565,11 +650,11 @@ Future saveChat(String uuid, Function setState) async { if ((jsonDecode(jsonEncode(messages[i])) as Map).containsKey("text")) { history.add({ "role": (messages[i].author == user) ? "user" : "assistant", - "content": jsonDecode(jsonEncode(messages[i]))["text"] + "content": jsonDecode(jsonEncode(messages[i]))["text"], }); } else { - var uri = jsonDecode(jsonEncode(messages[i]))["uri"] as String; - var content = (uri.startsWith("data:image/png;base64,")) + final uri = jsonDecode(jsonEncode(messages[i]))["uri"] as String; + final content = (uri.startsWith("data:image/png;base64,")) ? uri.removePrefix("data:image/png;base64,") : base64.encode(await File(uri).readAsBytes()); history.add({ @@ -577,7 +662,7 @@ Future saveChat(String uuid, Function setState) async { "type": "image", "name": (messages[i] as types.ImageMessage).name, "size": (messages[i] as types.ImageMessage).size.toString(), - "content": content + "content": content, }); } } @@ -585,24 +670,26 @@ Future saveChat(String uuid, Function setState) async { for (var i = 0; i < (prefs!.getStringList("chats") ?? []).length; i++) { if (jsonDecode((prefs!.getStringList("chats") ?? [])[i])["uuid"] == chatUuid) { - var tmp = prefs!.getStringList("chats")!..removeAt(i); + final tmp = prefs!.getStringList("chats")!..removeAt(i); prefs!.setStringList("chats", tmp); chatUuid = null; return; } } } - if (jsonDecode((prefs!.getStringList("chats") ?? [])[index])["messages"] - .length >= + if (jsonDecode( + (prefs!.getStringList("chats") ?? [])[index], + )["messages"].length >= 1) { - if (jsonDecode(jsonDecode((prefs!.getStringList("chats") ?? [])[index])[ - "messages"])[0]["role"] == + if (jsonDecode( + jsonDecode((prefs!.getStringList("chats") ?? [])[index])["messages"], + )[0]["role"] == "system") { history.add({ "role": "system", - "content": jsonDecode(jsonDecode( - (prefs!.getStringList("chats") ?? [])[index])["messages"])[0] - ["content"] + "content": jsonDecode( + jsonDecode((prefs!.getStringList("chats") ?? [])[index])["messages"], + )[0]["content"], }); } } else { @@ -614,17 +701,19 @@ Future saveChat(String uuid, Function setState) async { history.add({"role": "system", "content": system}); } history = history.reversed.toList(); - var tmp = prefs!.getStringList("chats") ?? [] + final tmp = prefs!.getStringList("chats") ?? [] ..removeAt(index) ..insert( - 0, - jsonEncode({ - "title": - jsonDecode((prefs!.getStringList("chats") ?? [])[index])["title"], - "uuid": uuid, - "model": model, - "messages": jsonEncode(history) - })); + 0, + jsonEncode({ + "title": jsonDecode( + (prefs!.getStringList("chats") ?? [])[index], + )["title"], + "uuid": uuid, + "model": model, + "messages": jsonEncode(history), + }), + ); prefs!.setStringList("chats", tmp); setState(() {}); } @@ -640,27 +729,32 @@ void loadChat(String uuid, Function setState) { messages = []; model = null; setState(() {}); - var history = jsonDecode( - jsonDecode((prefs!.getStringList("chats") ?? [])[index])["messages"]); + final history = jsonDecode( + jsonDecode((prefs!.getStringList("chats") ?? [])[index])["messages"], + ); for (var i = 0; i < history.length; i++) { if (history[i]["role"] != "system") { if ((history[i] as Map).containsKey("type") && history[i]["type"] == "image") { messages.insert( - 0, - types.ImageMessage( - author: (history[i]["role"] == "user") ? user : assistant, - id: const Uuid().v4(), - name: history[i]["name"], - size: int.parse(history[i]["size"]), - uri: "data:image/png;base64,${history[i]["content"]}")); + 0, + types.ImageMessage( + author: (history[i]["role"] == "user") ? user : assistant, + id: const Uuid().v4(), + name: history[i]["name"], + size: int.parse(history[i]["size"]), + uri: "data:image/png;base64,${history[i]["content"]}", + ), + ); } else { messages.insert( - 0, - types.TextMessage( - author: (history[i]["role"] == "user") ? user : assistant, - id: const Uuid().v4(), - text: history[i]["content"])); + 0, + types.TextMessage( + author: (history[i]["role"] == "user") ? user : assistant, + id: const Uuid().v4(), + text: history[i]["content"], + ), + ); } } } @@ -668,32 +762,35 @@ void loadChat(String uuid, Function setState) { setState(() {}); } -Future prompt(BuildContext context, - {String description = "", - String value = "", - String title = "", - String? valueIfCanceled, - TextInputType keyboard = TextInputType.text, - bool autocorrect = true, - Iterable autofillHints = const [], - bool enableSuggestions = true, - Icon? prefixIcon, - int maxLines = 1, - String? uuid, - Future Function(String content)? validator, - String? validatorError, - String? Function(String content)? validatorErrorCallback, - String? placeholder, - bool prefill = true}) async { +Future prompt( + BuildContext context, { + String description = "", + String value = "", + String title = "", + String? valueIfCanceled, + TextInputType keyboard = TextInputType.text, + bool autocorrect = true, + Iterable autofillHints = const [], + bool enableSuggestions = true, + Icon? prefixIcon, + int maxLines = 1, + String? uuid, + Future Function(String content)? validator, + String? validatorError, + String? Function(String content)? validatorErrorCallback, + String? placeholder, + bool prefill = true, +}) async { var returnText = (valueIfCanceled != null) ? valueIfCanceled : value; - var controller = TextEditingController(text: prefill ? value : ""); + final controller = TextEditingController(text: prefill ? value : ""); var loading = false; String? error; await showModalBottomSheet( - context: context, - isScrollControlled: true, - builder: (context) { - return StatefulBuilder(builder: (context, setLocalState) { + context: context, + isScrollControlled: true, + builder: (context) { + return StatefulBuilder( + builder: (context, setLocalState) { Future submit() async { selectionHaptic(); if (validator != null) { @@ -701,7 +798,7 @@ Future prompt(BuildContext context, error = null; loading = true; }); - var valid = await validator(controller.text); + final valid = await validator(controller.text); setLocalState(() { loading = false; }); @@ -724,102 +821,116 @@ Future prompt(BuildContext context, } return PopScope( - child: Container( - padding: EdgeInsets.only( - left: 16, - right: 16, - top: 16, - bottom: desktopFeature(web: true) - ? 12 - : MediaQuery.of(context).viewInsets.bottom), - width: double.infinity, - child: Column( - mainAxisSize: MainAxisSize.min, - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - (title != "") - ? Text(title, - style: const TextStyle( - fontWeight: FontWeight.bold)) - : const SizedBox.shrink(), - (title != "") - ? const Divider() - : const SizedBox.shrink(), - (description != "") - ? Text(description) - : const SizedBox.shrink(), - const SizedBox(height: 8), - TextField( - controller: controller, - autofocus: true, - keyboardType: keyboard, - autocorrect: autocorrect, - autofillHints: autofillHints, - enableSuggestions: enableSuggestions, - maxLines: maxLines, - onSubmitted: (_) => submit(), - decoration: InputDecoration( - border: const OutlineInputBorder(), - hintText: placeholder, - errorText: error, - suffixIcon: IconButton( - enableFeedback: false, - tooltip: AppLocalizations.of(context) - .tooltipSave, - onPressed: submit, - icon: const Icon(Icons.save_rounded)), - prefixIcon: (title == - AppLocalizations.of(context) - .dialogEnterNewTitle && - uuid != null) - ? IconButton( - enableFeedback: false, - tooltip: AppLocalizations.of(context) - .tooltipLetAIThink, - onPressed: () async { - selectionHaptic(); - setLocalState(() { - loading = true; - }); + child: Container( + padding: EdgeInsets.only( + left: 16, + right: 16, + top: 16, + bottom: desktopFeature(web: true) + ? 12 + : MediaQuery.of(context).viewInsets.bottom, + ), + width: double.infinity, + child: Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + (title != "") + ? Text( + title, + style: const TextStyle(fontWeight: FontWeight.bold), + ) + : const SizedBox.shrink(), + (title != "") ? const Divider() : const SizedBox.shrink(), + (description != "") + ? Text(description) + : const SizedBox.shrink(), + const SizedBox(height: 8), + TextField( + controller: controller, + autofocus: true, + keyboardType: keyboard, + autocorrect: autocorrect, + autofillHints: autofillHints, + enableSuggestions: enableSuggestions, + maxLines: maxLines, + onSubmitted: (_) => submit(), + decoration: InputDecoration( + border: const OutlineInputBorder(), + hintText: placeholder, + errorText: error, + suffixIcon: IconButton( + enableFeedback: false, + tooltip: AppLocalizations.of(context).tooltipSave, + onPressed: submit, + icon: const Icon(Icons.save_rounded), + ), + prefixIcon: + (title == + AppLocalizations.of( + context, + ).dialogEnterNewTitle && + uuid != null) + ? IconButton( + enableFeedback: false, + tooltip: AppLocalizations.of( + context, + ).tooltipLetAIThink, + onPressed: () async { + selectionHaptic(); + setLocalState(() { + loading = true; + }); - try { - var title = await getTitleAi( - getHistoryString(uuid)); - controller.text = title; - setLocalState(() { - loading = false; - }); - } catch (_) { - try { - setLocalState(() { - loading = false; - }); - // ignore: use_build_context_synchronously - ScaffoldMessenger.of(context) - .showSnackBar(SnackBar( - content: Text( - AppLocalizations.of( - // ignore: use_build_context_synchronously - context) - .settingsHostInvalid( - "timeout")), - showCloseIcon: true)); - } catch (_) {} - } - }, - icon: const Icon( - Icons.auto_awesome_rounded)) - : prefixIcon)), - SizedBox( - height: 3, - child: loading - ? const LinearProgressIndicator() - : const SizedBox.shrink()), - (MediaQuery.of(context).viewInsets.bottom != 0) - ? const SizedBox(height: 16) - : const SizedBox.shrink() - ]))); - }); - }); + try { + final title = await getTitleAi( + getHistoryString(uuid), + ); + controller.text = title; + setLocalState(() { + loading = false; + }); + } catch (_) { + try { + setLocalState(() { + loading = false; + }); + // ignore: use_build_context_synchronously + ScaffoldMessenger.of(context).showSnackBar( + SnackBar( + content: Text( + AppLocalizations.of( + // ignore: use_build_context_synchronously + context, + ).settingsHostInvalid("timeout"), + ), + showCloseIcon: true, + ), + ); + } catch (_) {} + } + }, + icon: const Icon(Icons.auto_awesome_rounded), + ) + : prefixIcon, + ), + ), + SizedBox( + height: 3, + child: loading + ? const LinearProgressIndicator() + : const SizedBox.shrink(), + ), + (MediaQuery.of(context).viewInsets.bottom != 0) + ? const SizedBox(height: 16) + : const SizedBox.shrink(), + ], + ), + ), + ); + }, + ); + }, + ); return returnText; } diff --git a/lib/worker/update.dart b/lib/worker/update.dart index 21b7540..242fa57 100644 --- a/lib/worker/update.dart +++ b/lib/worker/update.dart @@ -23,23 +23,26 @@ String? updateUrl; String? latestVersion; String? currentVersion; String? updateChangeLog; -Future updatesSupported(Function setState, - [bool takeAction = false]) async { +Future updatesSupported( + Function setState, [ + bool takeAction = false, +]) async { var returnValue = true; - var installerApps = [ + final installerApps = [ "org.fdroid.fdroid", "org.gdroid.gdroid", "eu.bubu1.fdroidclassic", "in.sunilpaulmathew.izzyondroid", "com.looker.droidify", "com.machiav3lli.fdroid", - "nya.kitsunyan.foxydroid" + "nya.kitsunyan.foxydroid", ]; if (!desktopFeature(web: true)) { if ((await InstallReferrer.referrer != InstallationAppReferrer.androidManually) || - (installerApps - .contains((await InstallReferrer.app).packageName ?? ""))) { + (installerApps.contains( + (await InstallReferrer.app).packageName ?? "", + ))) { returnValue = false; if (await InstallReferrer.referrer == InstallationAppReferrer.androidDebug) { @@ -75,20 +78,26 @@ Future checkUpdate(Function setState) async { return false; } - var repo = repoUrl.split("/"); + final repo = repoUrl.split("/"); currentVersion = (await PackageInfo.fromPlatform()).version; // currentVersion = "1.0.0"; String? version; try { - var request = await httpClient - .get(Uri.parse( - "https://api.github.com/repos/${repo[3]}/${repo[4]}/releases")) - .timeout(Duration( + final request = await httpClient + .get( + Uri.parse( + "https://api.github.com/repos/${repo[3]}/${repo[4]}/releases", + ), + ) + .timeout( + Duration( milliseconds: (5000.0 * (prefs!.getDouble("timeoutMultiplier") ?? 1.0)) - .round())); + .round(), + ), + ); if (request.statusCode == 403) { setState(() { updateStatus = "rateLimit"; @@ -126,41 +135,53 @@ Future checkUpdate(Function setState) async { Future updateDialog(BuildContext context, Function title) async { await showDialog( - context: context, - builder: (context) { - return AlertDialog( - title: Text(AppLocalizations.of(context).settingsUpdateDialogTitle), - content: Column(mainAxisSize: MainAxisSize.min, children: [ - Text( - AppLocalizations.of(context).settingsUpdateDialogDescription), - title(AppLocalizations.of(context).settingsUpdateChangeLog), - Flexible( - child: SingleChildScrollView( - child: Container( - constraints: const BoxConstraints(maxWidth: 1000), - child: MarkdownBody( + context: context, + builder: (context) { + return AlertDialog( + title: Text(AppLocalizations.of(context).settingsUpdateDialogTitle), + content: Column( + mainAxisSize: MainAxisSize.min, + children: [ + Text(AppLocalizations.of(context).settingsUpdateDialogDescription), + title(AppLocalizations.of(context).settingsUpdateChangeLog), + Flexible( + child: SingleChildScrollView( + child: Container( + constraints: const BoxConstraints(maxWidth: 1000), + child: MarkdownBody( data: updateChangeLog ?? "No changelog given.", - shrinkWrap: true), - ))) - ]), - actions: [ - TextButton( - onPressed: () { - selectionHaptic(); - Navigator.of(context).pop(); - }, - child: Text( - AppLocalizations.of(context).settingsUpdateDialogCancel)), - TextButton( - onPressed: () { - selectionHaptic(); - Navigator.of(context).pop(); - launchUrl( - mode: LaunchMode.inAppBrowserView, - Uri.parse(updateUrl!)); - }, - child: Text( - AppLocalizations.of(context).settingsUpdateDialogUpdate)) - ]); - }); + shrinkWrap: true, + ), + ), + ), + ), + ], + ), + actions: [ + TextButton( + onPressed: () { + selectionHaptic(); + Navigator.of(context).pop(); + }, + child: Text( + AppLocalizations.of(context).settingsUpdateDialogCancel, + ), + ), + TextButton( + onPressed: () { + selectionHaptic(); + Navigator.of(context).pop(); + launchUrl( + mode: LaunchMode.inAppBrowserView, + Uri.parse(updateUrl!), + ); + }, + child: Text( + AppLocalizations.of(context).settingsUpdateDialogUpdate, + ), + ), + ], + ); + }, + ); } diff --git a/pubspec.lock b/pubspec.lock index 0fc5911..cc8fe7c 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -1,6 +1,38 @@ # Generated by pub # See https://dart.dev/tools/pub/glossary#lockfile packages: + _fe_analyzer_shared: + dependency: transitive + description: + name: _fe_analyzer_shared + sha256: cd6add6f846f35fb79f3c315296703c1a24f3cfd7f4739d91a74961c1c7e9f1b + url: "https://pub.dev" + source: hosted + version: "100.0.0" + analyzer: + dependency: transitive + description: + name: analyzer + sha256: "6ba98576948803398b69e3a444df24eacdbe12ed699c7014e120ea38552debbf" + url: "https://pub.dev" + source: hosted + version: "13.0.0" + ansicolor: + dependency: transitive + description: + name: ansicolor + sha256: "50e982d500bc863e1d703448afdbf9e5a72eb48840a4f766fa361ffd6877055f" + url: "https://pub.dev" + source: hosted + version: "2.0.3" + archive: + dependency: transitive + description: + name: archive + sha256: a96e8b390886ee8abb49b7bd3ac8df6f451c621619f52a26e815fdcf568959ff + url: "https://pub.dev" + source: hosted + version: "4.0.9" args: dependency: transitive description: @@ -17,6 +49,22 @@ packages: url: "https://pub.dev" source: hosted version: "2.13.0" + auto_route: + dependency: "direct main" + description: + name: auto_route + sha256: e9acfeb3df33d188fce4ad0239ef4238f333b7aa4d95ec52af3c2b9360dcd969 + url: "https://pub.dev" + source: hosted + version: "11.1.0" + auto_route_generator: + dependency: "direct dev" + description: + name: auto_route_generator + sha256: f58fdb20c51b36afe7bf8a162aea3b22dff580a8a0b5c35e8e3a91367121c8f1 + url: "https://pub.dev" + source: hosted + version: "10.6.0" bitsdojo_window: dependency: "direct main" description: @@ -65,14 +113,62 @@ packages: url: "https://pub.dev" source: hosted version: "2.1.2" + build: + dependency: transitive + description: + name: build + sha256: a156715e7cd728130c592f30552575908aae5b100005fbc1f0fb16b3c03a3d10 + url: "https://pub.dev" + source: hosted + version: "4.0.6" + build_config: + dependency: transitive + description: + name: build_config + sha256: "4070d2a59f8eec34c97c86ceb44403834899075f66e8a9d59706f8e7834f6f71" + url: "https://pub.dev" + source: hosted + version: "1.3.0" + build_daemon: + dependency: transitive + description: + name: build_daemon + sha256: bf05f6e12cfea92d3c09308d7bcdab1906cd8a179b023269eed00c071004b957 + url: "https://pub.dev" + source: hosted + version: "4.1.1" + build_runner: + dependency: "direct dev" + description: + name: build_runner + sha256: "1523ce62448ebac2c15a8ba5fbad8acac169788658a7dd2a1c2d9c2a9318b9a6" + url: "https://pub.dev" + source: hosted + version: "2.15.0" + built_collection: + dependency: transitive + description: + name: built_collection + sha256: "376e3dd27b51ea877c28d525560790aee2e6fbb5f20e2f85d5081027d94e2100" + url: "https://pub.dev" + source: hosted + version: "5.1.1" + built_value: + dependency: transitive + description: + name: built_value + sha256: "34e4067d30ce212937df995f03b69992eea683539ceeac7f679a1f1eba055b56" + url: "https://pub.dev" + source: hosted + version: "8.12.6" characters: dependency: transitive description: name: characters - sha256: f71061c654a3380576a52b451dd5532377954cf9dbd272a78fc8479606670803 + sha256: faf38497bda5ead2a8c7615f4f7939df04333478bf32e4173fcb06d428b5716b url: "https://pub.dev" source: hosted - version: "1.4.0" + version: "1.4.1" charcode: dependency: transitive description: @@ -81,6 +177,22 @@ packages: url: "https://pub.dev" source: hosted version: "1.4.0" + checked_yaml: + dependency: transitive + description: + name: checked_yaml + sha256: "959525d3162f249993882720d52b7e0c833978df229be20702b33d48d91de70f" + url: "https://pub.dev" + source: hosted + version: "2.0.4" + cli_util: + dependency: transitive + description: + name: cli_util + sha256: ff6785f7e9e3c38ac98b2fb035701789de90154024a75b6cb926445e83197d1c + url: "https://pub.dev" + source: hosted + version: "0.4.2" clock: dependency: transitive description: @@ -89,6 +201,22 @@ packages: url: "https://pub.dev" source: hosted version: "1.1.2" + code_assets: + dependency: transitive + description: + name: code_assets + sha256: bf394f466ba9205f1812a0433b392d6af280f155f56651eda7c18cc32ed493b8 + url: "https://pub.dev" + source: hosted + version: "1.2.1" + code_builder: + dependency: transitive + description: + name: code_builder + sha256: "6a6cab2ba4680d6423f34a9b972a4c9a94ebe1b62ecec4e1a1f2cba91fd1319d" + url: "https://pub.dev" + source: hosted + version: "4.11.1" collection: dependency: transitive description: @@ -97,6 +225,14 @@ packages: url: "https://pub.dev" source: hosted version: "1.19.1" + convert: + dependency: transitive + description: + name: convert + sha256: b30acd5944035672bc15c6b7a8b47d773e41e2f17de064350988c5d02adb1c68 + url: "https://pub.dev" + source: hosted + version: "3.1.2" cross_file: dependency: transitive description: @@ -121,6 +257,14 @@ packages: url: "https://pub.dev" source: hosted version: "1.0.2" + dart_style: + dependency: transitive + description: + name: dart_style + sha256: "59d53ef8eaed9d288ed9767618e2b31c4fa0383a127db59d5eb2e737a7638a60" + url: "https://pub.dev" + source: hosted + version: "3.1.9" dartx: dependency: "direct main" description: @@ -145,6 +289,30 @@ packages: url: "https://pub.dev" source: hosted version: "4.0.1" + drift: + dependency: "direct main" + description: + name: drift + sha256: "8033500116b24398fba0cca0369cc31678cd627c01e41753a61186911cea743e" + url: "https://pub.dev" + source: hosted + version: "2.33.0" + drift_dev: + dependency: "direct dev" + description: + name: drift_dev + sha256: "042a5fb507ab5697f67eb55b75cfff2f665701f6606926136d6d4e85f81ff837" + url: "https://pub.dev" + source: hosted + version: "2.34.1+1" + drift_flutter: + dependency: "direct main" + description: + name: drift_flutter + sha256: "887fdec622174dc7eaefd0048403e34ee07cc18626ac8a7544cc3b8a4a172166" + url: "https://pub.dev" + source: hosted + version: "0.3.0" duration_picker: dependency: "direct main" description: @@ -157,10 +325,10 @@ packages: dependency: "direct main" description: name: dynamic_system_colors - sha256: "43794e658fa88cbdec9f397dd1afd2eb69b6c9717e99b93b16ba37c3aa3b3a8c" + sha256: "6cda994363fe362e3c433e068af83feffc2c9a26ba0be7ecdb2b96f676d2dfd8" url: "https://pub.dev" source: hosted - version: "1.8.0" + version: "1.9.0" equatable: dependency: transitive description: @@ -315,10 +483,10 @@ packages: dependency: "direct main" description: name: flutter_install_referrer - sha256: "8f994ff0653be0483303af0a6f4ecbe2fc643a61456e5bd3bf5e28e38b035c77" + sha256: "4a82a6b09e9a3e38b41522e1d44413fe20f7dd48faa636412d0af2c8821d4de4" url: "https://pub.dev" source: hosted - version: "2.0.2" + version: "2.1.1" flutter_link_previewer: dependency: transitive description: @@ -348,6 +516,14 @@ packages: description: flutter source: sdk version: "0.0.0" + flutter_markdown: + dependency: "direct main" + description: + name: flutter_markdown + sha256: "08fb8315236099ff8e90cb87bb2b935e0a724a3af1623000a9cec930468e0f27" + url: "https://pub.dev" + source: hosted + version: "0.7.7+1" flutter_math_fork: dependency: "direct main" description: @@ -389,23 +565,47 @@ packages: dependency: "direct main" description: name: flutter_tts - sha256: bdf2fc4483e74450dc9fc6fe6a9b6a5663e108d4d0dad3324a22c8e26bf48af4 + sha256: ce5eb209b40e95f2f4a1397116c87ab2fcdff32257d04ed7a764e75894c03775 url: "https://pub.dev" source: hosted - version: "4.2.3" + version: "4.2.5" flutter_web_plugins: - dependency: transitive + dependency: "direct main" description: flutter source: sdk version: "0.0.0" - freezed_annotation: + frontend_server_client: dependency: transitive description: - name: freezed_annotation - sha256: c2e2d632dd9b8a2b7751117abcfc2b4888ecfe181bd9fca7170d9ef02e595fe2 + name: frontend_server_client + sha256: f64a0333a82f30b0cca061bc3d143813a486dc086b574bfb233b7c1372427694 url: "https://pub.dev" source: hosted - version: "2.4.4" + version: "4.0.0" + get_time_ago: + dependency: "direct main" + description: + name: get_time_ago + sha256: "010abc128f885c223ccf4f86a88d4789bb4dd5cecdddd87fee600549dde5d4fb" + url: "https://pub.dev" + source: hosted + version: "2.4.1" + glob: + dependency: transitive + description: + name: glob + sha256: c3f1ee72c96f8f78935e18aa8cecced9ab132419e8625dc187e1c2408efc20de + url: "https://pub.dev" + source: hosted + version: "2.1.3" + graphs: + dependency: transitive + description: + name: graphs + sha256: "741bbf84165310a68ff28fe9e727332eef1407342fca52759cb21ad8177bb8d0" + url: "https://pub.dev" + source: hosted + version: "2.3.2" highlight: dependency: transitive description: @@ -414,6 +614,22 @@ packages: url: "https://pub.dev" source: hosted version: "0.7.0" + hooks: + dependency: transitive + description: + name: hooks + sha256: "9a62a50b50b769a737bc0a8ff381f333529df3ab746b2f6b02e83760231455ba" + url: "https://pub.dev" + source: hosted + version: "2.0.2" + hotreloader: + dependency: transitive + description: + name: hotreloader + sha256: "66871df468fc24eee81f1a0a7cb98acc104716f9b7376d355437b48d633c4ebf" + url: "https://pub.dev" + source: hosted + version: "4.4.0" html: dependency: transitive description: @@ -426,10 +642,18 @@ packages: dependency: "direct main" description: name: http - sha256: bb2ce4590bc2667c96f318d68cac1b5a7987ec819351d32b1c987239a815e007 + sha256: "87721a4a50b19c7f1d49001e51409bddc46303966ce89a65af4f4e6004896412" url: "https://pub.dev" source: hosted - version: "1.5.0" + version: "1.6.0" + http_multi_server: + dependency: transitive + description: + name: http_multi_server + sha256: aa6199f908078bb1c5efb8d8638d4ae191aac11b311132c3ef48ce352fb52ef8 + url: "https://pub.dev" + source: hosted + version: "3.2.2" http_parser: dependency: transitive description: @@ -438,6 +662,14 @@ packages: url: "https://pub.dev" source: hosted version: "4.1.2" + image: + dependency: "direct main" + description: + name: image + sha256: "6300175e00616bbc832e2fc91bfa4d776af5402c81c7151bee6905bb08473c52" + url: "https://pub.dev" + source: hosted + version: "4.9.1" image_picker: dependency: "direct main" description: @@ -510,6 +742,30 @@ packages: url: "https://pub.dev" source: hosted version: "0.20.2" + io: + dependency: transitive + description: + name: io + sha256: dfd5a80599cf0165756e3181807ed3e77daf6dd4137caaad72d0b7931597650b + url: "https://pub.dev" + source: hosted + version: "1.0.5" + jni: + dependency: transitive + description: + name: jni + sha256: c2230682d5bc2362c1c9e8d3c7f406d9cbba23ab3f2e203a025dd47e0fb2e68f + url: "https://pub.dev" + source: hosted + version: "1.0.0" + jni_flutter: + dependency: transitive + description: + name: jni_flutter + sha256: "8b59e590786050b1cd866677dddaf76b1ade5e7bc751abe04b86e84d379d3ba6" + url: "https://pub.dev" + source: hosted + version: "1.0.1" js: dependency: transitive description: @@ -550,6 +806,14 @@ packages: url: "https://pub.dev" source: hosted version: "3.0.2" + lean_builder: + dependency: transitive + description: + name: lean_builder + sha256: "123eef9fba96ec12a18b8eb4889850368d78e8c0a6a63eaa45ec4cf3e73fbded" + url: "https://pub.dev" + source: hosted + version: "1.2.0" linkify: dependency: transitive description: @@ -566,6 +830,14 @@ packages: url: "https://pub.dev" source: hosted version: "4.0.0" + logging: + dependency: transitive + description: + name: logging + sha256: c8245ada5f1717ed44271ed1c26b8ce85ca3228fd2ffdb75468ab01979309d61 + url: "https://pub.dev" + source: hosted + version: "1.3.0" markdown: dependency: "direct main" description: @@ -578,34 +850,42 @@ packages: dependency: transitive description: name: matcher - sha256: dc58c723c3c24bf8d3e2d3ad3f2f9d7bd9cf43ec6feaa64181775e60190153f2 + sha256: dc0b7dc7651697ea4ff3e69ef44b0407ea32c487a39fff6a4004fa585e901861 url: "https://pub.dev" source: hosted - version: "0.12.17" + version: "0.12.19" material_color_utilities: dependency: transitive description: name: material_color_utilities - sha256: f7142bb1154231d7ea5f96bc7bde4bda2a0945d2806bb11670e30b850d56bdec + sha256: "9c337007e82b1889149c82ed242ed1cb24a66044e30979c44912381e9be4c48b" url: "https://pub.dev" source: hosted - version: "0.11.1" + version: "0.13.0" meta: dependency: transitive description: name: meta - sha256: e3641ec5d63ebf0d9b41bd43201a66e3fc79a65db5f61fc181f04cd27aab950c + sha256: "1741988757a65eb6b36abe716829688cf01910bbf91c34354ff7ec1c3de2b349" url: "https://pub.dev" source: hosted - version: "1.16.0" + version: "1.18.0" mime: - dependency: transitive + dependency: "direct main" description: name: mime sha256: "41a20518f0cb1256669420fdba0cd90d21561e560ac240f26ef8322e45bb7ed6" url: "https://pub.dev" source: hosted version: "2.0.0" + native_toolchain_c: + dependency: transitive + description: + name: native_toolchain_c + sha256: f59351d28f49520cd3a74eb1f41c5f19ae15e53c65a3231d14af672e46510a96 + url: "https://pub.dev" + source: hosted + version: "0.19.1" nested: dependency: transitive description: @@ -614,6 +894,14 @@ packages: url: "https://pub.dev" source: hosted version: "1.0.0" + objective_c: + dependency: transitive + description: + name: objective_c + sha256: "6cb691c686fa2838c6deb34980d426145c2a5d537491cb83d463c33cdbc726ed" + url: "https://pub.dev" + source: hosted + version: "9.4.1" ogp_data_extract: dependency: "direct main" description: @@ -626,18 +914,26 @@ packages: dependency: "direct main" description: name: ollama_dart - sha256: df4acde50fc4a055134f91919505d801170c2e1e5df798056a18e2811f20307b + sha256: dd60bf042a9deeb616f2e03ff6c189fabc72ed06c2762b48363dab4cb668684f url: "https://pub.dev" source: hosted - version: "0.2.4" + version: "2.2.0" + package_config: + dependency: transitive + description: + name: package_config + sha256: f096c55ebb7deb7e384101542bfba8c52696c1b56fca2eb62827989ef2353bbc + url: "https://pub.dev" + source: hosted + version: "2.2.0" package_info_plus: dependency: "direct main" description: name: package_info_plus - sha256: "16eee997588c60225bda0488b6dcfac69280a6b7a3cf02c741895dd370a02968" + sha256: "468c26b4254ab01979fa5e4a98cb343ea3631b9acee6f21028997419a80e1a20" url: "https://pub.dev" source: hosted - version: "8.3.1" + version: "9.0.1" package_info_plus_platform_interface: dependency: transitive description: @@ -662,6 +958,30 @@ packages: url: "https://pub.dev" source: hosted version: "1.1.0" + path_provider: + dependency: "direct main" + description: + name: path_provider + sha256: "50c5dd5b6e1aaf6fb3a78b33f6aa3afca52bf903a8a5298f53101fdaee55bbcd" + url: "https://pub.dev" + source: hosted + version: "2.1.5" + path_provider_android: + dependency: transitive + description: + name: path_provider_android + sha256: "69cbd515a62b94d32a7944f086b2f82b4ac40a1d45bebfc00813a430ab2dabcd" + url: "https://pub.dev" + source: hosted + version: "2.3.1" + path_provider_foundation: + dependency: transitive + description: + name: path_provider_foundation + sha256: "2a376b7d6392d80cd3705782d2caa734ca4727776db0b6ec36ef3f1855197699" + url: "https://pub.dev" + source: hosted + version: "2.6.0" path_provider_linux: dependency: transitive description: @@ -746,10 +1066,10 @@ packages: dependency: transitive description: name: petitparser - sha256: "1a97266a94f7350d30ae522c0af07890c70b8e62c71e8e3920d1db4d23c057d1" + sha256: "91bd59303e9f769f108f8df05e371341b15d59e995e6806aefab827b58336675" url: "https://pub.dev" source: hosted - version: "7.0.1" + version: "7.0.2" photo_view: dependency: transitive description: @@ -774,6 +1094,22 @@ packages: url: "https://pub.dev" source: hosted version: "2.1.8" + pool: + dependency: transitive + description: + name: pool + sha256: "978783255c543aa3586a1b3c21f6e9d720eb315376a915872c61ef8b5c20177d" + url: "https://pub.dev" + source: hosted + version: "1.5.2" + posix: + dependency: transitive + description: + name: posix + sha256: "185ef7606574f789b40f289c233efa52e96dead518aed988e040a10737febb07" + url: "https://pub.dev" + source: hosted + version: "6.5.0" provider: dependency: transitive description: @@ -782,6 +1118,22 @@ packages: url: "https://pub.dev" source: hosted version: "6.1.5+1" + pub_semver: + dependency: transitive + description: + name: pub_semver + sha256: "5bfcf68ca79ef689f8990d1160781b4bad40a3bd5e5218ad4076ddb7f4081585" + url: "https://pub.dev" + source: hosted + version: "2.2.0" + pubspec_parse: + dependency: transitive + description: + name: pubspec_parse + sha256: "0560ba233314abbed0a48a2956f7f022cce7c3e1e73df540277da7544cad4082" + url: "https://pub.dev" + source: hosted + version: "1.5.0" pwa_install: dependency: "direct main" description: @@ -790,6 +1142,22 @@ packages: url: "https://pub.dev" source: hosted version: "0.0.6" + recase: + dependency: transitive + description: + name: recase + sha256: e4eb4ec2dcdee52dcf99cb4ceabaffc631d7424ee55e56f280bc039737f89213 + url: "https://pub.dev" + source: hosted + version: "4.1.0" + record_use: + dependency: transitive + description: + name: record_use + sha256: "2551bd8eecfe95d14ae75f6021ad0248be5c27f138c2ec12fcb52b500b3ba1ed" + url: "https://pub.dev" + source: hosted + version: "0.6.0" scroll_to_index: dependency: "direct main" description: @@ -802,10 +1170,10 @@ packages: dependency: "direct main" description: name: shared_preferences - sha256: "6e8bf70b7fef813df4e9a36f658ac46d107db4b4cfe1048b477d4e453a8159f5" + sha256: c3025c5534b01739267eb7d76959bbc25a6d10f6988e1c2a3036940133dd10bf url: "https://pub.dev" source: hosted - version: "2.5.3" + version: "2.5.5" shared_preferences_android: dependency: transitive description: @@ -854,6 +1222,22 @@ packages: url: "https://pub.dev" source: hosted version: "2.4.1" + shelf: + dependency: transitive + description: + name: shelf + sha256: e7dd780a7ffb623c57850b33f43309312fc863fb6aa3d276a754bb299839ef12 + url: "https://pub.dev" + source: hosted + version: "1.4.2" + shelf_web_socket: + dependency: transitive + description: + name: shelf_web_socket + sha256: "3632775c8e90d6c9712f883e633716432a27758216dfb61bd86a8321c0580925" + url: "https://pub.dev" + source: hosted + version: "3.0.0" simple_icons: dependency: "direct main" description: @@ -875,14 +1259,22 @@ packages: url: "https://pub.dev" source: hosted version: "1.2.1" + source_gen: + dependency: transitive + description: + name: source_gen + sha256: ec37cc0e6694374cbef59ed79685572c870a54ede6fa30a3e420feb3adffea02 + url: "https://pub.dev" + source: hosted + version: "4.2.3" source_span: dependency: transitive description: name: source_span - sha256: "254ee5351d6cb365c859e20ee823c3bb479bf4a293c22d17a9f1bf144ce86f7c" + sha256: "56a02f1f4cd1a2d96303c0144c93bd6d909eea6bee6bf5a0e0b685edbd4c47ab" url: "https://pub.dev" source: hosted - version: "1.10.1" + version: "1.10.2" speech_to_text: dependency: "direct main" description: @@ -915,6 +1307,38 @@ packages: url: "https://pub.dev" source: hosted version: "7.0.0" + sqlcipher_flutter_libs: + dependency: transitive + description: + name: sqlcipher_flutter_libs + sha256: "38d62d659d2fb8739bf25a42c9a350d1fdd6c29a5a61f13a946778ec75d27929" + url: "https://pub.dev" + source: hosted + version: "0.7.0+eol" + sqlite3: + dependency: transitive + description: + name: sqlite3 + sha256: "9488c7d2cdb1091c91cacf7e207cff81b28bff8e366f042bad3afe7d34afe189" + url: "https://pub.dev" + source: hosted + version: "3.3.2" + sqlite3_flutter_libs: + dependency: transitive + description: + name: sqlite3_flutter_libs + sha256: "3ed7553eee7bb368f8950f58ba29f634e06e813c029aff6a0d60862b96de8454" + url: "https://pub.dev" + source: hosted + version: "0.6.0+eol" + sqlparser: + dependency: transitive + description: + name: sqlparser + sha256: ecdc06d4a7d79dcbc928d99afd2f7f5b0f98a637c46f89be83d911617f759978 + url: "https://pub.dev" + source: hosted + version: "0.44.4" stack_trace: dependency: transitive description: @@ -931,6 +1355,14 @@ packages: url: "https://pub.dev" source: hosted version: "2.1.4" + stream_transform: + dependency: transitive + description: + name: stream_transform + sha256: ad47125e588cfd37a9a7f86c7d6356dde8dfe89d071d293f80ca9e9273a33871 + url: "https://pub.dev" + source: hosted + version: "2.1.1" string_scanner: dependency: transitive description: @@ -959,10 +1391,10 @@ packages: dependency: transitive description: name: test_api - sha256: "522f00f556e73044315fa4585ec3270f1808a4b186c936e612cab0b565ff1e00" + sha256: "949a932224383300f01be9221c39180316445ecb8e7547f70a41a35bf421fb9e" url: "https://pub.dev" source: hosted - version: "0.7.6" + version: "0.7.11" time: dependency: transitive description: @@ -1103,10 +1535,10 @@ packages: dependency: transitive description: name: vector_graphics_compiler - sha256: d354a7ec6931e6047785f4db12a1f61ec3d43b207fc0790f863818543f8ff0dc + sha256: "7ee12e6dffe0fc8e755179d6d91b3b34f5924223fc104d85572ef9180d73d172" url: "https://pub.dev" source: hosted - version: "1.1.19" + version: "1.2.5" vector_math: dependency: transitive description: @@ -1139,6 +1571,14 @@ packages: url: "https://pub.dev" source: hosted version: "15.0.2" + watcher: + dependency: transitive + description: + name: watcher + sha256: "1398c9f081a753f9226febe8900fce8f7d0a67163334e1c94a2438339d79d635" + url: "https://pub.dev" + source: hosted + version: "1.2.1" web: dependency: transitive description: @@ -1147,6 +1587,22 @@ packages: url: "https://pub.dev" source: hosted version: "1.1.1" + web_socket: + dependency: transitive + description: + name: web_socket + sha256: "34d64019aa8e36bf9842ac014bb5d2f5586ca73df5e4d9bf5c936975cae6982c" + url: "https://pub.dev" + source: hosted + version: "1.0.1" + web_socket_channel: + dependency: transitive + description: + name: web_socket_channel + sha256: d645757fb0f4773d602444000a8131ff5d48c9e47adfe9772652dd1a4f2d45c8 + url: "https://pub.dev" + source: hosted + version: "3.0.3" win32: dependency: transitive description: @@ -1167,10 +1623,26 @@ packages: dependency: transitive description: name: xml - sha256: "971043b3a0d3da28727e40ed3e0b5d18b742fa5a68665cca88e74b7876d5e025" + sha256: "67f0aff7be013d107995e9b75bf4e7f2c3ef2dfdb2c8e68024bba0a7fd5756a4" url: "https://pub.dev" source: hosted - version: "6.6.1" + version: "7.0.1" + xxh3: + dependency: transitive + description: + name: xxh3 + sha256: "399a0438f5d426785723c99da6b16e136f4953fb1e9db0bf270bd41dd4619916" + url: "https://pub.dev" + source: hosted + version: "1.2.0" + yaml: + dependency: transitive + description: + name: yaml + sha256: b9da305ac7c39faa3f030eccd175340f968459dae4af175130b3fc47e40d76ce + url: "https://pub.dev" + source: hosted + version: "3.1.3" sdks: - dart: ">=3.8.1 <4.0.0" - flutter: ">=3.35.0" + dart: ">=3.12.0 <4.0.0" + flutter: "3.44.7" diff --git a/pubspec.yaml b/pubspec.yaml index e0c23c9..f2c805c 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -4,17 +4,22 @@ publish_to: 'none' version: 1.2.0+9 environment: - sdk: '>=3.8.1 <4.0.0' - flutter: 3.35.0 + sdk: '>=3.12.0 <4.0.0' + flutter: 3.44.7 dependencies: flutter: sdk: flutter flutter_localizations: sdk: flutter - ollama_dart: ^0.2.4 + flutter_web_plugins: + sdk: flutter + ollama_dart: ^2.2.0 intl: any - shared_preferences: ^2.2.3 + image: ^4.9.1 + mime: ^2.0.0 + get_time_ago: ^2.4.1 + shared_preferences: ^2.5.5 flutter_chat_ui: ^1.6.13 scroll_to_index: ^3.0.1 uuid: ^4.4.0 @@ -29,13 +34,13 @@ dependencies: file_picker: ^8.0.3 file_selector: ^1.0.3 bitsdojo_window: ^0.1.6 - flutter_install_referrer: ^2.0.2 - package_info_plus: ^8.0.0 + flutter_install_referrer: ^2.1.1 + package_info_plus: ^9.0.1 version: ^3.0.2 flutter_displaymode: ^0.6.0 duration_picker: ^1.2.0 - speech_to_text: ^7.0.0 - flutter_tts: ^4.2.3 + speech_to_text: ^7.3.0 + flutter_tts: ^4.2.5 permission_handler: ^11.3.1 datetime_loop: ^1.2.0 universal_html: ^2.2.4 @@ -48,14 +53,24 @@ dependencies: url: https://github.com/JHubi1/highlight.dart path: flutter_highlight ogp_data_extract: ^0.2.2 + auto_route: ^11.1.0 # dynamic_color: ^1.7.0 - dynamic_system_colors: ^1.8.0 + dynamic_system_colors: ^1.9.0 + drift: ^2.33.0 + drift_flutter: ^0.3.0 + path_provider: ^2.1.5 + + # dynamic_color: any + flutter_markdown: any dev_dependencies: flutter_test: sdk: flutter flutter_lints: ^4.0.0 + drift_dev: ^2.33.0 + auto_route_generator: ^10.6.0 + build_runner: ^2.15.0 flutter: uses-material-design: true @@ -69,3 +84,9 @@ flutter: - assets/welcome/1dark.png - assets/welcome/2dark.png - assets/welcome/3dark.png + fonts: + - family: GoogleSansCode + fonts: + - asset: assets/fonts/GoogleSansCode-VariableFont_MONO,wght.ttf + - asset: assets/fonts/GoogleSansCode-Italic-VariableFont_MONO,wght.ttf + style: italic diff --git a/scripts/build.dart b/scripts/build.dart index d28ce08..80a68ea 100644 --- a/scripts/build.dart +++ b/scripts/build.dart @@ -4,7 +4,7 @@ import 'dart:io'; void main() async { Directory.current = Directory(Platform.script.toFilePath()).parent.parent; - var flutterExecutable = Platform.isWindows ? 'flutter.bat' : 'flutter'; + final flutterExecutable = Platform.isWindows ? 'flutter.bat' : 'flutter'; print("Build script for Ollama App by JHubi1"); print("Report issues at: https://github.com/JHubi1/ollama-app/issues"); @@ -12,14 +12,15 @@ void main() async { print('----------'); print('Extracting version from pubspec.yaml ...'); - var pubspec = File('pubspec.yaml'); - var versionLine = await pubspec - .readAsLines() - .then((lines) => lines.firstWhere((line) => line.contains('version'))); - var version = versionLine.split(':').last.trim().split('+').first.trim(); - var versionCode = versionLine.split(':').last.trim().split('+')[1].trim(); + final pubspec = File('pubspec.yaml'); + final versionLine = await pubspec.readAsLines().then( + (lines) => lines.firstWhere((line) => line.contains('version')), + ); + final version = versionLine.split(':').last.trim().split('+').first.trim(); + final versionCode = versionLine.split(':').last.trim().split('+')[1].trim(); print( - "Building Ollama App v$version (build $versionCode) - this may take a while"); + "Building Ollama App v$version (build $versionCode) - this may take a while", + ); print('----------'); @@ -27,7 +28,7 @@ void main() async { 'build', 'apk', '--obfuscate', - '--split-debug-info=build\\debugAndroid' + '--split-debug-info=build\\debugAndroid', ]); // ---------- @@ -71,17 +72,20 @@ void main() async { stdout.write('Copying build output to build\\.output '); try { - var outputDir = Directory('build\\.output'); + final outputDir = Directory('build\\.output'); if (await outputDir.exists()) { await outputDir.delete(recursive: true); } await outputDir.create(); - await copyFile('build\\app\\outputs\\flutter-apk\\app-release.apk', - 'build\\.output\\ollama-android-v$version.apk'); await copyFile( - 'build\\windows\\x64\\runner\\ollama-windows-x64-v$version.exe', - 'build\\.output\\ollama-windows-x64-v$version.exe'); + 'build\\app\\outputs\\flutter-apk\\app-release.apk', + 'build\\.output\\ollama-android-v$version.apk', + ); + await copyFile( + 'build\\windows\\x64\\runner\\ollama-windows-x64-v$version.exe', + 'build\\.output\\ollama-windows-x64-v$version.exe', + ); print('- done'); } catch (_) { print('- failed'); @@ -95,14 +99,18 @@ void main() async { } Future copyFile(String sourcePath, String destinationPath) async { - var sourceFile = File(sourcePath); + final sourceFile = File(sourcePath); if (await sourceFile.exists()) { await sourceFile.copy(destinationPath); } } -Future execute(String title, String command, List arguments, - [String? errorText]) async { +Future execute( + String title, + String command, + List arguments, [ + String? errorText, +]) async { stdout.write('$title '); ProcessResult process; try { diff --git a/test/functions.dart b/test/functions.dart index 90dc745..680cf52 100644 --- a/test/functions.dart +++ b/test/functions.dart @@ -1,8 +1,8 @@ import 'dart:math'; String random(int length) { - var rand = Random(); - var codeUnits = List.generate(length, (index) { + final rand = Random(); + final codeUnits = List.generate(length, (index) { return rand.nextInt(33) + 89; }); diff --git a/test/widget_test.dart b/test/widget_test.dart index fc0cc4b..a961f52 100644 --- a/test/widget_test.dart +++ b/test/widget_test.dart @@ -6,13 +6,17 @@ import 'functions.dart'; void main() { testWidgets("Widget: button", (WidgetTester tester) async { - var text = random(10); + final text = random(10); var clicked = false; - await tester.pumpWidget(MaterialApp( + await tester.pumpWidget( + MaterialApp( home: Scaffold( - body: button(text, Icons.add_rounded, () { - clicked = true; - })))); + body: button(text, Icons.add_rounded, () { + clicked = true; + }), + ), + ), + ); expect(find.text(text), findsOneWidget); expect(find.byIcon(Icons.add_rounded), findsOneWidget); @@ -23,14 +27,21 @@ void main() { expect(clicked, true); }); testWidgets("Widget: toggle", (WidgetTester tester) async { - var text = random(10); + final text = random(10); var toggled = false; await tester.pumpWidget( - MaterialApp(home: Scaffold(body: Builder(builder: (context) { - return toggle(context, text, toggled, (value) { - toggled = value; - }); - })))); + MaterialApp( + home: Scaffold( + body: Builder( + builder: (context) { + return toggle(context, text, toggled, (value) { + toggled = value; + }); + }, + ), + ), + ), + ); expect(find.textContaining(text), findsOneWidget); expect(find.byType(Switch), findsOneWidget); diff --git a/tools/base64.dart b/tool/base64.dart similarity index 100% rename from tools/base64.dart rename to tool/base64.dart diff --git a/tools/logid.dart b/tool/logid.dart similarity index 96% rename from tools/logid.dart rename to tool/logid.dart index 0d94708..3b67338 100644 --- a/tools/logid.dart +++ b/tool/logid.dart @@ -4,7 +4,7 @@ import 'dart:math'; void main() { - var random = Random.secure(); + final random = Random.secure(); var logId = ""; const letters = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"; diff --git a/untranslated_messages.json b/untranslated_messages.json index fa1646c..22943e8 100644 --- a/untranslated_messages.json +++ b/untranslated_messages.json @@ -1,41 +1,181 @@ { "de": [ + "optionChatDetails", + "optionChatDetailsNoChat", + "newChatGreeting1", + "newChatGreeting2", + "newChatGreeting3", + "newChatGreeting4", + "newChatGreeting5", + "newChatGreetingNameFallback", + "modelDialogTitle", + "modelDialogRefresh", "errorGuardTitle", "errorGuardDetails", "errorGuardException", "errorGuardStackTrace", - "errorGuardReport" + "errorGuardReport", + "settingsTitleOverview", + "settingsDescriptionOverview", + "settingsHostHeaderHeaderName", + "settingsHostHeaderHeaderValue", + "settingsHostHeaderUnsupported", + "settingsHostHeaderDuplicate" ], "fa": [ + "learnMore", + "optionNoChatFoundSearch", + "optionSearchChats", + "optionChatDetails", + "optionChatDetailsNoChat", + "newChatGreeting1", + "newChatGreeting2", + "newChatGreeting3", + "newChatGreeting4", + "newChatGreeting5", + "newChatGreetingNameFallback", + "messageInputPlaceholderModelPlaceholder", + "tooltipMessageOptions", + "modelDialogTitle", + "modelDialogAdd", + "modelDialogRefresh", + "modelDialogAddCurrently", + "modelDialogAddPromptNotFound", + "modelDialogAddPromptNetworkError", + "modelDialogAddPromptCorruptionError", + "modelDialogAddPromptCorruptionErrorSolution", + "modelDialogAddCancelTitle", + "modelDialogAddCancelConfirm", + "modelDialogAddCancelHide", "errorGuardTitle", "errorGuardDetails", "errorGuardException", "errorGuardStackTrace", - "errorGuardReport" + "errorGuardReport", + "settingsTitleOverview", + "settingsDescriptionOverview", + "settingsHostMissing", + "settingsHostHeaderHeaderName", + "settingsHostHeaderHeaderValue", + "settingsHostHeaderUnsupported", + "settingsHostHeaderDuplicate" ], "it": [ + "learnMore", + "optionNoChatFoundSearch", + "optionSearchChats", + "optionChatDetails", + "optionChatDetailsNoChat", + "newChatGreeting1", + "newChatGreeting2", + "newChatGreeting3", + "newChatGreeting4", + "newChatGreeting5", + "newChatGreetingNameFallback", + "messageInputPlaceholderModelPlaceholder", + "tooltipMessageOptions", + "modelDialogTitle", + "modelDialogAdd", + "modelDialogRefresh", + "modelDialogAddCurrently", + "modelDialogAddPromptNotFound", + "modelDialogAddPromptNetworkError", + "modelDialogAddPromptCorruptionError", + "modelDialogAddPromptCorruptionErrorSolution", + "modelDialogAddCancelTitle", + "modelDialogAddCancelConfirm", + "modelDialogAddCancelHide", "errorGuardTitle", "errorGuardDetails", "errorGuardException", "errorGuardStackTrace", - "errorGuardReport" + "errorGuardReport", + "settingsTitleOverview", + "settingsDescriptionOverview", + "settingsHostMissing", + "settingsHostHeaderHeaderName", + "settingsHostHeaderHeaderValue", + "settingsHostHeaderUnsupported", + "settingsHostHeaderDuplicate" ], "tr": [ + "learnMore", + "optionNoChatFoundSearch", + "optionSearchChats", + "optionChatDetails", + "optionChatDetailsNoChat", + "newChatGreeting1", + "newChatGreeting2", + "newChatGreeting3", + "newChatGreeting4", + "newChatGreeting5", + "newChatGreetingNameFallback", + "messageInputPlaceholderModelPlaceholder", + "tooltipMessageOptions", + "modelDialogTitle", + "modelDialogAdd", + "modelDialogRefresh", + "modelDialogAddCurrently", + "modelDialogAddPromptNotFound", + "modelDialogAddPromptNetworkError", + "modelDialogAddPromptCorruptionError", + "modelDialogAddPromptCorruptionErrorSolution", + "modelDialogAddCancelTitle", + "modelDialogAddCancelConfirm", + "modelDialogAddCancelHide", "errorGuardTitle", "errorGuardDetails", "errorGuardException", "errorGuardStackTrace", - "errorGuardReport" + "errorGuardReport", + "settingsTitleOverview", + "settingsDescriptionOverview", + "settingsHostMissing", + "settingsHostHeaderHeaderName", + "settingsHostHeaderHeaderValue", + "settingsHostHeaderUnsupported", + "settingsHostHeaderDuplicate" ], "zh": [ + "learnMore", + "optionNoChatFoundSearch", + "optionSearchChats", + "optionChatDetails", + "optionChatDetailsNoChat", + "newChatGreeting1", + "newChatGreeting2", + "newChatGreeting3", + "newChatGreeting4", + "newChatGreeting5", + "newChatGreetingNameFallback", + "messageInputPlaceholderModelPlaceholder", + "tooltipMessageOptions", + "modelDialogTitle", + "modelDialogAdd", + "modelDialogRefresh", + "modelDialogAddCurrently", + "modelDialogAddPromptNotFound", + "modelDialogAddPromptNetworkError", + "modelDialogAddPromptCorruptionError", + "modelDialogAddPromptCorruptionErrorSolution", + "modelDialogAddCancelTitle", + "modelDialogAddCancelConfirm", + "modelDialogAddCancelHide", "errorGuardTitle", "errorGuardDetails", "errorGuardException", "errorGuardStackTrace", - "errorGuardReport" + "errorGuardReport", + "settingsTitleOverview", + "settingsDescriptionOverview", + "settingsHostMissing", + "settingsHostHeaderHeaderName", + "settingsHostHeaderHeaderValue", + "settingsHostHeaderUnsupported", + "settingsHostHeaderDuplicate" ] } diff --git a/web/drift_worker.js b/web/drift_worker.js new file mode 100644 index 0000000..ff67c77 --- /dev/null +++ b/web/drift_worker.js @@ -0,0 +1,13271 @@ +(function dartProgram(){function copyProperties(a,b){var s=Object.keys(a) +for(var r=0;r=0)return true +if(typeof version=="function"&&version.length==0){var q=version() +if(/^\d+\.\d+\.\d+\.\d+$/.test(q))return true}}catch(p){}return false}() +function inherit(a,b){a.prototype.constructor=a +a.prototype["$i"+a.name]=a +if(b!=null){if(z){Object.setPrototypeOf(a.prototype,b.prototype) +return}var s=Object.create(b.prototype) +copyProperties(a.prototype,s) +a.prototype=s}}function inheritMany(a,b){for(var s=0;s4294967295)throw A.b(A.S(a,0,4294967295,"length",null)) +return J.tW(new Array(a),b)}, +pA(a,b){if(a<0)throw A.b(A.J("Length must be a non-negative integer: "+a,null)) +return A.f(new Array(a),b.h("u<0>"))}, +tW(a,b){var s=A.f(a,b.h("u<0>")) +s.$flags=1 +return s}, +tX(a,b){return J.tl(a,b)}, +pB(a){if(a<256)switch(a){case 9:case 10:case 11:case 12:case 13:case 32:case 133:case 160:return!0 +default:return!1}switch(a){case 5760:case 8192:case 8193:case 8194:case 8195:case 8196:case 8197:case 8198:case 8199:case 8200:case 8201:case 8202:case 8232:case 8233:case 8239:case 8287:case 12288:case 65279:return!0 +default:return!1}}, +tY(a,b){var s,r +for(s=a.length;b0;b=s){s=b-1 +r=a.charCodeAt(s) +if(r!==32&&r!==13&&!J.pB(r))break}return b}, +cW(a){if(typeof a=="number"){if(Math.floor(a)==a)return J.et.prototype +return J.hl.prototype}if(typeof a=="string")return J.bX.prototype +if(a==null)return J.eu.prototype +if(typeof a=="boolean")return J.hk.prototype +if(Array.isArray(a))return J.u.prototype +if(typeof a!="object"){if(typeof a=="function")return J.bz.prototype +if(typeof a=="symbol")return J.d7.prototype +if(typeof a=="bigint")return J.aK.prototype +return a}if(a instanceof A.e)return a +return J.nG(a)}, +a0(a){if(typeof a=="string")return J.bX.prototype +if(a==null)return a +if(Array.isArray(a))return J.u.prototype +if(typeof a!="object"){if(typeof a=="function")return J.bz.prototype +if(typeof a=="symbol")return J.d7.prototype +if(typeof a=="bigint")return J.aK.prototype +return a}if(a instanceof A.e)return a +return J.nG(a)}, +aS(a){if(a==null)return a +if(Array.isArray(a))return J.u.prototype +if(typeof a!="object"){if(typeof a=="function")return J.bz.prototype +if(typeof a=="symbol")return J.d7.prototype +if(typeof a=="bigint")return J.aK.prototype +return a}if(a instanceof A.e)return a +return J.nG(a)}, +wR(a){if(typeof a=="number")return J.d6.prototype +if(typeof a=="string")return J.bX.prototype +if(a==null)return a +if(!(a instanceof A.e))return J.cE.prototype +return a}, +iY(a){if(typeof a=="string")return J.bX.prototype +if(a==null)return a +if(!(a instanceof A.e))return J.cE.prototype +return a}, +rk(a){if(a==null)return a +if(typeof a!="object"){if(typeof a=="function")return J.bz.prototype +if(typeof a=="symbol")return J.d7.prototype +if(typeof a=="bigint")return J.aK.prototype +return a}if(a instanceof A.e)return a +return J.nG(a)}, +aj(a,b){if(a==null)return b==null +if(typeof a!="object")return b!=null&&a===b +return J.cW(a).W(a,b)}, +aJ(a,b){if(typeof b==="number")if(Array.isArray(a)||typeof a=="string"||A.rn(a,a[v.dispatchPropertyName]))if(b>>>0===b&&b>>0===b&&b").H(c).h("f4<1,2>")) +return new A.co(a,b.h("@<0>").H(c).h("co<1,2>"))}, +pC(a){return new A.d8("Field '"+a+"' has been assigned during initialization.")}, +pD(a){return new A.d8("Field '"+a+"' has not been initialized.")}, +u_(a){return new A.d8("Field '"+a+"' has already been initialized.")}, +nH(a){var s,r=a^48 +if(r<=9)return r +s=a|32 +if(97<=s&&s<=102)return s-87 +return-1}, +c9(a,b){a=a+b&536870911 +a=a+((a&524287)<<10)&536870911 +return a^a>>>6}, +ok(a){a=a+((a&67108863)<<3)&536870911 +a^=a>>>11 +return a+((a&16383)<<15)&536870911}, +cU(a,b,c){return a}, +oW(a){var s,r +for(s=$.cT.length,r=0;rc)A.C(A.S(b,0,c,"start",null))}return new A.cC(a,b,c,d.h("cC<0>"))}, +ht(a,b,c,d){if(t.Q.b(a))return new A.ct(a,b,c.h("@<0>").H(d).h("ct<1,2>")) +return new A.aE(a,b,c.h("@<0>").H(d).h("aE<1,2>"))}, +ol(a,b,c){var s="takeCount" +A.bT(b,s) +A.ab(b,s) +if(t.Q.b(a))return new A.el(a,b,c.h("el<0>")) +return new A.cD(a,b,c.h("cD<0>"))}, +pY(a,b,c){var s="count" +if(t.Q.b(a)){A.bT(b,s) +A.ab(b,s) +return new A.d3(a,b,c.h("d3<0>"))}A.bT(b,s) +A.ab(b,s) +return new A.bJ(a,b,c.h("bJ<0>"))}, +tU(a,b,c){return new A.cs(a,b,c.h("cs<0>"))}, +az(){return new A.aQ("No element")}, +py(){return new A.aQ("Too few elements")}, +ce:function ce(){}, +fU:function fU(a,b){this.a=a +this.$ti=b}, +co:function co(a,b){this.a=a +this.$ti=b}, +f4:function f4(a,b){this.a=a +this.$ti=b}, +f_:function f_(){}, +ak:function ak(a,b){this.a=a +this.$ti=b}, +d8:function d8(a){this.a=a}, +fV:function fV(a){this.a=a}, +nO:function nO(){}, +kR:function kR(){}, +q:function q(){}, +M:function M(){}, +cC:function cC(a,b,c,d){var _=this +_.a=a +_.b=b +_.c=c +_.$ti=d}, +b3:function b3(a,b,c){var _=this +_.a=a +_.b=b +_.c=0 +_.d=null +_.$ti=c}, +aE:function aE(a,b,c){this.a=a +this.b=b +this.$ti=c}, +ct:function ct(a,b,c){this.a=a +this.b=b +this.$ti=c}, +d9:function d9(a,b,c){var _=this +_.a=null +_.b=a +_.c=b +_.$ti=c}, +E:function E(a,b,c){this.a=a +this.b=b +this.$ti=c}, +aY:function aY(a,b,c){this.a=a +this.b=b +this.$ti=c}, +eU:function eU(a,b){this.a=a +this.b=b}, +en:function en(a,b,c){this.a=a +this.b=b +this.$ti=c}, +ha:function ha(a,b,c,d){var _=this +_.a=a +_.b=b +_.c=c +_.d=null +_.$ti=d}, +cD:function cD(a,b,c){this.a=a +this.b=b +this.$ti=c}, +el:function el(a,b,c){this.a=a +this.b=b +this.$ti=c}, +hR:function hR(a,b,c){this.a=a +this.b=b +this.$ti=c}, +bJ:function bJ(a,b,c){this.a=a +this.b=b +this.$ti=c}, +d3:function d3(a,b,c){this.a=a +this.b=b +this.$ti=c}, +hM:function hM(a,b){this.a=a +this.b=b}, +eK:function eK(a,b,c){this.a=a +this.b=b +this.$ti=c}, +hN:function hN(a,b){this.a=a +this.b=b +this.c=!1}, +cu:function cu(a){this.$ti=a}, +h7:function h7(){}, +eV:function eV(a,b){this.a=a +this.$ti=b}, +i7:function i7(a,b){this.a=a +this.$ti=b}, +by:function by(a,b,c){this.a=a +this.b=b +this.$ti=c}, +cs:function cs(a,b,c){this.a=a +this.b=b +this.$ti=c}, +er:function er(a,b){this.a=a +this.b=b +this.c=-1}, +eo:function eo(){}, +hV:function hV(){}, +dr:function dr(){}, +eI:function eI(a,b){this.a=a +this.$ti=b}, +hQ:function hQ(a){this.a=a}, +fz:function fz(){}, +rw(a){var s=v.mangledGlobalNames[a] +if(s!=null)return s +return"minified:"+a}, +rn(a,b){var s +if(b!=null){s=b.x +if(s!=null)return s}return t.aU.b(a)}, +t(a){var s +if(typeof a=="string")return a +if(typeof a=="number"){if(a!==0)return""+a}else if(!0===a)return"true" +else if(!1===a)return"false" +else if(a==null)return"null" +s=J.b1(a) +return s}, +eG(a){var s,r=$.pI +if(r==null)r=$.pI=Symbol("identityHashCode") +s=a[r] +if(s==null){s=Math.random()*0x3fffffff|0 +a[r]=s}return s}, +pP(a,b){var s,r,q,p,o,n=null,m=/^\s*[+-]?((0x[a-f0-9]+)|(\d+)|([a-z0-9]+))\s*$/i.exec(a) +if(m==null)return n +s=m[3] +if(b==null){if(s!=null)return parseInt(a,10) +if(m[2]!=null)return parseInt(a,16) +return n}if(b<2||b>36)throw A.b(A.S(b,2,36,"radix",n)) +if(b===10&&s!=null)return parseInt(a,10) +if(b<10||s==null){r=b<=10?47+b:86+b +q=m[1] +for(p=q.length,o=0;or)return n}return parseInt(a,b)}, +hG(a){var s,r,q,p +if(a instanceof A.e)return A.aZ(A.aT(a),null) +s=J.cW(a) +if(s===B.aB||s===B.aE||t.ak.b(a)){r=B.P(a) +if(r!=="Object"&&r!=="")return r +q=a.constructor +if(typeof q=="function"){p=q.name +if(typeof p=="string"&&p!=="Object"&&p!=="")return p}}return A.aZ(A.aT(a),null)}, +pQ(a){var s,r,q +if(a==null||typeof a=="number"||A.bQ(a))return J.b1(a) +if(typeof a=="string")return JSON.stringify(a) +if(a instanceof A.cp)return a.i(0) +if(a instanceof A.fi)return a.fO(!0) +s=$.t6() +for(r=0;r<1;++r){q=s[r].l9(a) +if(q!=null)return q}return"Instance of '"+A.hG(a)+"'"}, +u9(){if(!!self.location)return self.location.href +return null}, +pH(a){var s,r,q,p,o=a.length +if(o<=500)return String.fromCharCode.apply(null,a) +for(s="",r=0;r65535)return A.ud(a)}return A.pH(a)}, +ue(a,b,c){var s,r,q,p +if(c<=500&&b===0&&c===a.length)return String.fromCharCode.apply(null,a) +for(s=b,r="";s>>0,s&1023|56320)}}throw A.b(A.S(a,0,1114111,null,null))}, +aF(a){if(a.date===void 0)a.date=new Date(a.a) +return a.date}, +pO(a){return a.c?A.aF(a).getUTCFullYear()+0:A.aF(a).getFullYear()+0}, +pM(a){return a.c?A.aF(a).getUTCMonth()+1:A.aF(a).getMonth()+1}, +pJ(a){return a.c?A.aF(a).getUTCDate()+0:A.aF(a).getDate()+0}, +pK(a){return a.c?A.aF(a).getUTCHours()+0:A.aF(a).getHours()+0}, +pL(a){return a.c?A.aF(a).getUTCMinutes()+0:A.aF(a).getMinutes()+0}, +pN(a){return a.c?A.aF(a).getUTCSeconds()+0:A.aF(a).getSeconds()+0}, +ub(a){return a.c?A.aF(a).getUTCMilliseconds()+0:A.aF(a).getMilliseconds()+0}, +uc(a){return B.b.ac((a.c?A.aF(a).getUTCDay()+0:A.aF(a).getDay()+0)+6,7)+1}, +ua(a){var s=a.$thrownJsError +if(s==null)return null +return A.a1(s)}, +eH(a,b){var s +if(a.$thrownJsError==null){s=new Error() +A.aa(a,s) +a.$thrownJsError=s +s.stack=b.i(0)}}, +e3(a,b){var s,r="index" +if(!A.bv(b))return new A.bb(!0,b,r,null) +s=J.at(a) +if(b<0||b>=s)return A.hf(b,s,a,null,r) +return A.kN(b,r)}, +wL(a,b,c){if(a>c)return A.S(a,0,c,"start",null) +if(b!=null)if(bc)return A.S(b,a,c,"end",null) +return new A.bb(!0,b,"end",null)}, +e0(a){return new A.bb(!0,a,null,null)}, +b(a){return A.aa(a,new Error())}, +aa(a,b){var s +if(a==null)a=new A.bL() +b.dartException=a +s=A.xp +if("defineProperty" in Object){Object.defineProperty(b,"message",{get:s}) +b.name=""}else b.toString=s +return b}, +xp(){return J.b1(this.dartException)}, +C(a,b){throw A.aa(a,b==null?new Error():b)}, +y(a,b,c){var s +if(b==null)b=0 +if(c==null)c=0 +s=Error() +A.C(A.vA(a,b,c),s)}, +vA(a,b,c){var s,r,q,p,o,n,m,l,k +if(typeof b=="string")s=b +else{r="[]=;add;removeWhere;retainWhere;removeRange;setRange;setInt8;setInt16;setInt32;setUint8;setUint16;setUint32;setFloat32;setFloat64".split(";") +q=r.length +p=b +if(p>q){c=p/q|0 +p%=q}s=r[p]}o=typeof c=="string"?c:"modify;remove from;add to".split(";")[c] +n=t.j.b(a)?"list":"ByteData" +m=a.$flags|0 +l="a " +if((m&4)!==0)k="constant " +else if((m&2)!==0){k="unmodifiable " +l="an "}else k=(m&1)!==0?"fixed-length ":"" +return new A.eR("'"+s+"': Cannot "+o+" "+l+k+n)}, +a2(a){throw A.b(A.au(a))}, +bM(a){var s,r,q,p,o,n +a=A.rv(a.replace(String({}),"$receiver$")) +s=a.match(/\\\$[a-zA-Z]+\\\$/g) +if(s==null)s=A.f([],t.s) +r=s.indexOf("\\$arguments\\$") +q=s.indexOf("\\$argumentsExpr\\$") +p=s.indexOf("\\$expr\\$") +o=s.indexOf("\\$method\\$") +n=s.indexOf("\\$receiver\\$") +return new A.lv(a.replace(new RegExp("\\\\\\$arguments\\\\\\$","g"),"((?:x|[^x])*)").replace(new RegExp("\\\\\\$argumentsExpr\\\\\\$","g"),"((?:x|[^x])*)").replace(new RegExp("\\\\\\$expr\\\\\\$","g"),"((?:x|[^x])*)").replace(new RegExp("\\\\\\$method\\\\\\$","g"),"((?:x|[^x])*)").replace(new RegExp("\\\\\\$receiver\\\\\\$","g"),"((?:x|[^x])*)"),r,q,p,o,n)}, +lw(a){return function($expr$){var $argumentsExpr$="$arguments$" +try{$expr$.$method$($argumentsExpr$)}catch(s){return s.message}}(a)}, +q6(a){return function($expr$){try{$expr$.$method$}catch(s){return s.message}}(a)}, +oa(a,b){var s=b==null,r=s?null:b.method +return new A.hn(a,r,s?null:b.receiver)}, +G(a){if(a==null)return new A.hD(a) +if(a instanceof A.em)return A.cl(a,a.a) +if(typeof a!=="object")return a +if("dartException" in a)return A.cl(a,a.dartException) +return A.wi(a)}, +cl(a,b){if(t.C.b(b))if(b.$thrownJsError==null)b.$thrownJsError=a +return b}, +wi(a){var s,r,q,p,o,n,m,l,k,j,i,h,g +if(!("message" in a))return a +s=a.message +if("number" in a&&typeof a.number=="number"){r=a.number +q=r&65535 +if((B.b.O(r,16)&8191)===10)switch(q){case 438:return A.cl(a,A.oa(A.t(s)+" (Error "+q+")",null)) +case 445:case 5007:A.t(s) +return A.cl(a,new A.eC())}}if(a instanceof TypeError){p=$.rC() +o=$.rD() +n=$.rE() +m=$.rF() +l=$.rI() +k=$.rJ() +j=$.rH() +$.rG() +i=$.rL() +h=$.rK() +g=p.ar(s) +if(g!=null)return A.cl(a,A.oa(s,g)) +else{g=o.ar(s) +if(g!=null){g.method="call" +return A.cl(a,A.oa(s,g))}else if(n.ar(s)!=null||m.ar(s)!=null||l.ar(s)!=null||k.ar(s)!=null||j.ar(s)!=null||m.ar(s)!=null||i.ar(s)!=null||h.ar(s)!=null)return A.cl(a,new A.eC())}return A.cl(a,new A.hU(typeof s=="string"?s:""))}if(a instanceof RangeError){if(typeof s=="string"&&s.indexOf("call stack")!==-1)return new A.eM() +s=function(b){try{return String(b)}catch(f){}return null}(a) +return A.cl(a,new A.bb(!1,null,null,typeof s=="string"?s.replace(/^RangeError:\s*/,""):s))}if(typeof InternalError=="function"&&a instanceof InternalError)if(typeof s=="string"&&s==="too much recursion")return new A.eM() +return a}, +a1(a){var s +if(a instanceof A.em)return a.b +if(a==null)return new A.fm(a) +s=a.$cachedTrace +if(s!=null)return s +s=new A.fm(a) +if(typeof a==="object")a.$cachedTrace=s +return s}, +oY(a){if(a==null)return J.aC(a) +if(typeof a=="object")return A.eG(a) +return J.aC(a)}, +wN(a,b){var s,r,q,p=a.length +for(s=0;s=0 +else if(b instanceof A.cw){s=B.a.N(a,c) +return b.b.test(s)}else return!J.nX(b,B.a.N(a,c)).gB(0)}, +oT(a){if(a.indexOf("$",0)>=0)return a.replace(/\$/g,"$$$$") +return a}, +xl(a,b,c,d){var s=b.fd(a,d) +if(s==null)return a +return A.p2(a,s.b.index,s.gbw(),c)}, +rv(a){if(/[[\]{}()*+?.\\^$|]/.test(a))return a.replace(/[[\]{}()*+?.\\^$|]/g,"\\$&") +return a}, +bi(a,b,c){var s +if(typeof b=="string")return A.xk(a,b,c) +if(b instanceof A.cw){s=b.gfo() +s.lastIndex=0 +return a.replace(s,A.oT(c))}return A.xj(a,b,c)}, +xj(a,b,c){var s,r,q,p +for(s=J.nX(b,a),s=s.gq(s),r=0,q="";s.k();){p=s.gm() +q=q+a.substring(r,p.gco())+c +r=p.gbw()}s=q+a.substring(r) +return s.charCodeAt(0)==0?s:s}, +xk(a,b,c){var s,r,q +if(b===""){if(a==="")return c +s=a.length +for(r=c,q=0;q=0)return a.split(b).join(c) +return a.replace(new RegExp(A.rv(b),"g"),A.oT(c))}, +xm(a,b,c,d){var s,r,q,p +if(typeof b=="string"){s=a.indexOf(b,d) +if(s<0)return a +return A.p2(a,s,s+b.length,c)}if(b instanceof A.cw)return d===0?a.replace(b.b,A.oT(c)):A.xl(a,b,c,d) +r=J.ti(b,a,d) +q=r.gq(r) +if(!q.k())return a +p=q.gm() +return B.a.aL(a,p.gco(),p.gbw(),c)}, +p2(a,b,c,d){return a.substring(0,b)+d+a.substring(c)}, +ah:function ah(a,b){this.a=a +this.b=b}, +cO:function cO(a,b){this.a=a +this.b=b}, +iD:function iD(a,b){this.a=a +this.b=b}, +eg:function eg(){}, +eh:function eh(a,b,c){this.a=a +this.b=b +this.$ti=c}, +cM:function cM(a,b){this.a=a +this.$ti=b}, +iw:function iw(a,b,c){var _=this +_.a=a +_.b=b +_.c=0 +_.d=null +_.$ti=c}, +kp:function kp(){}, +es:function es(a,b){this.a=a +this.$ti=b}, +eJ:function eJ(){}, +lv:function lv(a,b,c,d,e,f){var _=this +_.a=a +_.b=b +_.c=c +_.d=d +_.e=e +_.f=f}, +eC:function eC(){}, +hn:function hn(a,b,c){this.a=a +this.b=b +this.c=c}, +hU:function hU(a){this.a=a}, +hD:function hD(a){this.a=a}, +em:function em(a,b){this.a=a +this.b=b}, +fm:function fm(a){this.a=a +this.b=null}, +cp:function cp(){}, +jj:function jj(){}, +jk:function jk(){}, +ll:function ll(){}, +lb:function lb(){}, +eb:function eb(a,b){this.a=a +this.b=b}, +hJ:function hJ(a){this.a=a}, +bA:function bA(a){var _=this +_.a=0 +_.f=_.e=_.d=_.c=_.b=null +_.r=0 +_.$ti=a}, +kw:function kw(a){this.a=a}, +kz:function kz(a,b){var _=this +_.a=a +_.b=b +_.d=_.c=null}, +bB:function bB(a,b){this.a=a +this.$ti=b}, +hr:function hr(a,b,c){var _=this +_.a=a +_.b=b +_.c=c +_.d=null}, +ex:function ex(a,b){this.a=a +this.$ti=b}, +cx:function cx(a,b,c){var _=this +_.a=a +_.b=b +_.c=c +_.d=null}, +ew:function ew(a,b){this.a=a +this.$ti=b}, +hq:function hq(a,b,c,d){var _=this +_.a=a +_.b=b +_.c=c +_.d=null +_.$ti=d}, +nI:function nI(a){this.a=a}, +nJ:function nJ(a){this.a=a}, +nK:function nK(a){this.a=a}, +fi:function fi(){}, +iC:function iC(){}, +cw:function cw(a,b){var _=this +_.a=a +_.b=b +_.e=_.d=_.c=null}, +dH:function dH(a){this.b=a}, +i8:function i8(a,b,c){this.a=a +this.b=b +this.c=c}, +m7:function m7(a,b,c){var _=this +_.a=a +_.b=b +_.c=c +_.d=null}, +dp:function dp(a,b){this.a=a +this.c=b}, +iL:function iL(a,b,c){this.a=a +this.b=b +this.c=c}, +n6:function n6(a,b,c){var _=this +_.a=a +_.b=b +_.c=c +_.d=null}, +xo(a){throw A.aa(A.pC(a),new Error())}, +x(){throw A.aa(A.pD(""),new Error())}, +j_(){throw A.aa(A.u_(""),new Error())}, +p4(){throw A.aa(A.pC(""),new Error())}, +mo(a){var s=new A.mn(a) +return s.b=s}, +mn:function mn(a){this.a=a +this.b=null}, +vy(a){return a}, +fA(a,b,c){}, +iV(a){var s,r,q +if(t.aP.b(a))return a +s=J.a0(a) +r=A.b4(s.gl(a),null,!1,t.z) +for(q=0;q>>0!==a||a>=c)throw A.b(A.e3(b,a))}, +ci(a,b,c){var s +if(!(a>>>0!==a))s=b>>>0!==b||a>b||b>c +else s=!0 +if(s)throw A.b(A.wL(a,b,c)) +return b}, +db:function db(){}, +da:function da(){}, +eA:function eA(){}, +iR:function iR(a){this.a=a}, +cy:function cy(){}, +dd:function dd(){}, +c_:function c_(){}, +aW:function aW(){}, +hu:function hu(){}, +hv:function hv(){}, +hw:function hw(){}, +dc:function dc(){}, +hx:function hx(){}, +hy:function hy(){}, +hz:function hz(){}, +eB:function eB(){}, +c0:function c0(){}, +fd:function fd(){}, +fe:function fe(){}, +ff:function ff(){}, +fg:function fg(){}, +og(a,b){var s=b.c +return s==null?b.c=A.fs(a,"D",[b.x]):s}, +pW(a){var s=a.w +if(s===6||s===7)return A.pW(a.x) +return s===11||s===12}, +ui(a){return a.as}, +aB(a){return A.nd(v.typeUniverse,a,!1)}, +wZ(a,b){var s,r,q,p,o +if(a==null)return null +s=b.y +r=a.Q +if(r==null)r=a.Q=new Map() +q=b.as +p=r.get(q) +if(p!=null)return p +o=A.cj(v.typeUniverse,a.x,s,0) +r.set(q,o) +return o}, +cj(a1,a2,a3,a4){var s,r,q,p,o,n,m,l,k,j,i,h,g,f,e,d,c,b,a,a0=a2.w +switch(a0){case 5:case 1:case 2:case 3:case 4:return a2 +case 6:s=a2.x +r=A.cj(a1,s,a3,a4) +if(r===s)return a2 +return A.qz(a1,r,!0) +case 7:s=a2.x +r=A.cj(a1,s,a3,a4) +if(r===s)return a2 +return A.qy(a1,r,!0) +case 8:q=a2.y +p=A.dY(a1,q,a3,a4) +if(p===q)return a2 +return A.fs(a1,a2.x,p) +case 9:o=a2.x +n=A.cj(a1,o,a3,a4) +m=a2.y +l=A.dY(a1,m,a3,a4) +if(n===o&&l===m)return a2 +return A.oA(a1,n,l) +case 10:k=a2.x +j=a2.y +i=A.dY(a1,j,a3,a4) +if(i===j)return a2 +return A.qA(a1,k,i) +case 11:h=a2.x +g=A.cj(a1,h,a3,a4) +f=a2.y +e=A.wf(a1,f,a3,a4) +if(g===h&&e===f)return a2 +return A.qx(a1,g,e) +case 12:d=a2.y +a4+=d.length +c=A.dY(a1,d,a3,a4) +o=a2.x +n=A.cj(a1,o,a3,a4) +if(c===d&&n===o)return a2 +return A.oB(a1,n,c,!0) +case 13:b=a2.x +if(b") +for(r=1;r=0)p+=" "+r[q];++q}return p+"})"}, +qZ(a1,a2,a3){var s,r,q,p,o,n,m,l,k,j,i,h,g,f,e,d,c,b,a=", ",a0=null +if(a3!=null){s=a3.length +if(a2==null)a2=A.f([],t.s) +else a0=a2.length +r=a2.length +for(q=s;q>0;--q)a2.push("T"+(r+q)) +for(p=t.X,o="<",n="",q=0;q0){c+=b+"[" +for(b="",q=0;q0){c+=b+"{" +for(b="",q=0;q "+d}, +aZ(a,b){var s,r,q,p,o,n,m=a.w +if(m===5)return"erased" +if(m===2)return"dynamic" +if(m===3)return"void" +if(m===1)return"Never" +if(m===4)return"any" +if(m===6){s=a.x +r=A.aZ(s,b) +q=s.w +return(q===11||q===12?"("+r+")":r)+"?"}if(m===7)return"FutureOr<"+A.aZ(a.x,b)+">" +if(m===8){p=A.wh(a.x) +o=a.y +return o.length>0?p+("<"+A.r8(o,b)+">"):p}if(m===10)return A.w1(a,b) +if(m===11)return A.qZ(a,b,null) +if(m===12)return A.qZ(a.x,b,a.y) +if(m===13){n=a.x +return b[b.length-1-n]}return"?"}, +wh(a){var s=v.mangledGlobalNames[a] +if(s!=null)return s +return"minified:"+a}, +v4(a,b){var s=a.tR[b] +while(typeof s=="string")s=a.tR[s] +return s}, +v3(a,b){var s,r,q,p,o,n=a.eT,m=n[b] +if(m==null)return A.nd(a,b,!1) +else if(typeof m=="number"){s=m +r=A.ft(a,5,"#") +q=A.nl(s) +for(p=0;p0)p+="<"+A.fr(c)+">" +s=a.eC.get(p) +if(s!=null)return s +r=new A.be(null,null) +r.w=8 +r.x=b +r.y=c +if(c.length>0)r.c=c[0] +r.as=p +q=A.ch(a,r) +a.eC.set(p,q) +return q}, +oA(a,b,c){var s,r,q,p,o,n +if(b.w===9){s=b.x +r=b.y.concat(c)}else{r=c +s=b}q=s.as+(";<"+A.fr(r)+">") +p=a.eC.get(q) +if(p!=null)return p +o=new A.be(null,null) +o.w=9 +o.x=s +o.y=r +o.as=q +n=A.ch(a,o) +a.eC.set(q,n) +return n}, +qA(a,b,c){var s,r,q="+"+(b+"("+A.fr(c)+")"),p=a.eC.get(q) +if(p!=null)return p +s=new A.be(null,null) +s.w=10 +s.x=b +s.y=c +s.as=q +r=A.ch(a,s) +a.eC.set(q,r) +return r}, +qx(a,b,c){var s,r,q,p,o,n=b.as,m=c.a,l=m.length,k=c.b,j=k.length,i=c.c,h=i.length,g="("+A.fr(m) +if(j>0){s=l>0?",":"" +g+=s+"["+A.fr(k)+"]"}if(h>0){s=l>0?",":"" +g+=s+"{"+A.uX(i)+"}"}r=n+(g+")") +q=a.eC.get(r) +if(q!=null)return q +p=new A.be(null,null) +p.w=11 +p.x=b +p.y=c +p.as=r +o=A.ch(a,p) +a.eC.set(r,o) +return o}, +oB(a,b,c,d){var s,r=b.as+("<"+A.fr(c)+">"),q=a.eC.get(r) +if(q!=null)return q +s=A.uZ(a,b,c,r,d) +a.eC.set(r,s) +return s}, +uZ(a,b,c,d,e){var s,r,q,p,o,n,m,l +if(e){s=c.length +r=A.nl(s) +for(q=0,p=0;p0){n=A.cj(a,b,r,0) +m=A.dY(a,c,r,0) +return A.oB(a,n,m,c!==m)}}l=new A.be(null,null) +l.w=12 +l.x=b +l.y=c +l.as=d +return A.ch(a,l)}, +qr(a,b,c,d){return{u:a,e:b,r:c,s:[],p:0,n:d}}, +qt(a){var s,r,q,p,o,n,m,l=a.r,k=a.s +for(s=l.length,r=0;r=48&&q<=57)r=A.uP(r+1,q,l,k) +else if((((q|32)>>>0)-97&65535)<26||q===95||q===36||q===124)r=A.qs(a,r,l,k,!1) +else if(q===46)r=A.qs(a,r,l,k,!0) +else{++r +switch(q){case 44:break +case 58:k.push(!1) +break +case 33:k.push(!0) +break +case 59:k.push(A.cN(a.u,a.e,k.pop())) +break +case 94:k.push(A.v0(a.u,k.pop())) +break +case 35:k.push(A.ft(a.u,5,"#")) +break +case 64:k.push(A.ft(a.u,2,"@")) +break +case 126:k.push(A.ft(a.u,3,"~")) +break +case 60:k.push(a.p) +a.p=k.length +break +case 62:A.uR(a,k) +break +case 38:A.uQ(a,k) +break +case 63:p=a.u +k.push(A.qz(p,A.cN(p,a.e,k.pop()),a.n)) +break +case 47:p=a.u +k.push(A.qy(p,A.cN(p,a.e,k.pop()),a.n)) +break +case 40:k.push(-3) +k.push(a.p) +a.p=k.length +break +case 41:A.uO(a,k) +break +case 91:k.push(a.p) +a.p=k.length +break +case 93:o=k.splice(a.p) +A.qu(a.u,a.e,o) +a.p=k.pop() +k.push(o) +k.push(-1) +break +case 123:k.push(a.p) +a.p=k.length +break +case 125:o=k.splice(a.p) +A.uT(a.u,a.e,o) +a.p=k.pop() +k.push(o) +k.push(-2) +break +case 43:n=l.indexOf("(",r) +k.push(l.substring(r,n)) +k.push(-4) +k.push(a.p) +a.p=k.length +r=n+1 +break +default:throw"Bad character "+q}}}m=k.pop() +return A.cN(a.u,a.e,m)}, +uP(a,b,c,d){var s,r,q=b-48 +for(s=c.length;a=48&&r<=57))break +q=q*10+(r-48)}d.push(q) +return a}, +qs(a,b,c,d,e){var s,r,q,p,o,n,m=b+1 +for(s=c.length;m>>0)-97&65535)<26||r===95||r===36||r===124))q=r>=48&&r<=57 +else q=!0 +if(!q)break}}p=c.substring(b,m) +if(e){s=a.u +o=a.e +if(o.w===9)o=o.x +n=A.v4(s,o.x)[p] +if(n==null)A.C('No "'+p+'" in "'+A.ui(o)+'"') +d.push(A.fu(s,o,n))}else d.push(p) +return m}, +uR(a,b){var s,r=a.u,q=A.qq(a,b),p=b.pop() +if(typeof p=="string")b.push(A.fs(r,p,q)) +else{s=A.cN(r,a.e,p) +switch(s.w){case 11:b.push(A.oB(r,s,q,a.n)) +break +default:b.push(A.oA(r,s,q)) +break}}}, +uO(a,b){var s,r,q,p=a.u,o=b.pop(),n=null,m=null +if(typeof o=="number")switch(o){case-1:n=b.pop() +break +case-2:m=b.pop() +break +default:b.push(o) +break}else b.push(o) +s=A.qq(a,b) +o=b.pop() +switch(o){case-3:o=b.pop() +if(n==null)n=p.sEA +if(m==null)m=p.sEA +r=A.cN(p,a.e,o) +q=new A.iq() +q.a=s +q.b=n +q.c=m +b.push(A.qx(p,r,q)) +return +case-4:b.push(A.qA(p,b.pop(),s)) +return +default:throw A.b(A.e8("Unexpected state under `()`: "+A.t(o)))}}, +uQ(a,b){var s=b.pop() +if(0===s){b.push(A.ft(a.u,1,"0&")) +return}if(1===s){b.push(A.ft(a.u,4,"1&")) +return}throw A.b(A.e8("Unexpected extended operation "+A.t(s)))}, +qq(a,b){var s=b.splice(a.p) +A.qu(a.u,a.e,s) +a.p=b.pop() +return s}, +cN(a,b,c){if(typeof c=="string")return A.fs(a,c,a.sEA) +else if(typeof c=="number"){b.toString +return A.uS(a,b,c)}else return c}, +qu(a,b,c){var s,r=c.length +for(s=0;sn)return!1 +m=n-o +l=s.b +k=r.b +j=l.length +i=k.length +if(o+j=d)return!1 +a1=f[b] +b+=3 +if(a00?new Array(q):v.typeUniverse.sEA +for(o=0;o0?new Array(a):v.typeUniverse.sEA}, +be:function be(a,b){var _=this +_.a=a +_.b=b +_.r=_.f=_.d=_.c=null +_.w=0 +_.as=_.Q=_.z=_.y=_.x=null}, +iq:function iq(){this.c=this.b=this.a=null}, +nc:function nc(a){this.a=a}, +il:function il(){}, +fq:function fq(a){this.a=a}, +uC(){var s,r,q +if(self.scheduleImmediate!=null)return A.wl() +if(self.MutationObserver!=null&&self.document!=null){s={} +r=self.document.createElement("div") +q=self.document.createElement("span") +s.a=null +new self.MutationObserver(A.ck(new A.m9(s),1)).observe(r,{childList:true}) +return new A.m8(s,r,q)}else if(self.setImmediate!=null)return A.wm() +return A.wn()}, +uD(a){self.scheduleImmediate(A.ck(new A.ma(a),0))}, +uE(a){self.setImmediate(A.ck(new A.mb(a),0))}, +uF(a){A.om(B.y,a)}, +om(a,b){var s=B.b.J(a.a,1000) +return A.uV(s<0?0:s,b)}, +uV(a,b){var s=new A.iO() +s.hV(a,b) +return s}, +uW(a,b){var s=new A.iO() +s.hW(a,b) +return s}, +l(a){return new A.i9(new A.n($.h,a.h("n<0>")),a.h("i9<0>"))}, +k(a,b){a.$2(0,null) +b.b=!0 +return b.a}, +c(a,b){A.vp(a,b)}, +j(a,b){b.P(a)}, +i(a,b){b.bv(A.G(a),A.a1(a))}, +vp(a,b){var s,r,q=new A.nm(b),p=new A.nn(b) +if(a instanceof A.n)a.fM(q,p,t.z) +else{s=t.z +if(a instanceof A.n)a.bE(q,p,s) +else{r=new A.n($.h,t.eI) +r.a=8 +r.c=a +r.fM(q,p,s)}}}, +m(a){var s=function(b,c){return function(d,e){while(true){try{b(d,e) +break}catch(r){e=r +d=c}}}}(a,1) +return $.h.d6(new A.nA(s),t.H,t.S,t.z)}, +qw(a,b,c){return 0}, +fO(a){var s +if(t.C.b(a)){s=a.gbi() +if(s!=null)return s}return B.v}, +tS(a,b){var s=new A.n($.h,b.h("n<0>")) +A.q0(B.y,new A.ki(a,s)) +return s}, +kh(a,b){var s,r,q,p,o,n,m,l=null +try{l=a.$0()}catch(q){s=A.G(q) +r=A.a1(q) +p=new A.n($.h,b.h("n<0>")) +o=s +n=r +m=A.cS(o,n) +if(m==null)o=new A.U(o,n==null?A.fO(o):n) +else o=m +p.aN(o) +return p}return b.h("D<0>").b(l)?l:A.dC(l,b)}, +bc(a,b){var s=a==null?b.a(a):a,r=new A.n($.h,b.h("n<0>")) +r.b0(s) +return r}, +pu(a,b){var s +if(!b.b(null))throw A.b(A.ad(null,"computation","The type parameter is not nullable")) +s=new A.n($.h,b.h("n<0>")) +A.q0(a,new A.kg(null,s,b)) +return s}, +o4(a,b){var s,r,q,p,o,n,m,l,k,j,i={},h=null,g=!1,f=new A.n($.h,b.h("n>")) +i.a=null +i.b=0 +i.c=i.d=null +s=new A.kk(i,h,g,f) +try{for(n=J.a4(a),m=t.P;n.k();){r=n.gm() +q=i.b +r.bE(new A.kj(i,q,f,b,h,g),s,m);++i.b}n=i.b +if(n===0){n=f +n.bI(A.f([],b.h("u<0>"))) +return n}i.a=A.b4(n,null,!1,b.h("0?"))}catch(l){p=A.G(l) +o=A.a1(l) +if(i.b===0||g){n=f +m=p +k=o +j=A.cS(m,k) +if(j==null)m=new A.U(m,k==null?A.fO(m):k) +else m=j +n.aN(m) +return n}else{i.d=p +i.c=o}}return f}, +cS(a,b){var s,r,q,p=$.h +if(p===B.d)return null +s=p.h3(a,b) +if(s==null)return null +r=s.a +q=s.b +if(t.C.b(r))A.eH(r,q) +return s}, +ns(a,b){var s +if($.h!==B.d){s=A.cS(a,b) +if(s!=null)return s}if(b==null)if(t.C.b(a)){b=a.gbi() +if(b==null){A.eH(a,B.v) +b=B.v}}else b=B.v +else if(t.C.b(a))A.eH(a,b) +return new A.U(a,b)}, +uN(a,b,c){var s=new A.n(b,c.h("n<0>")) +s.a=8 +s.c=a +return s}, +dC(a,b){var s=new A.n($.h,b.h("n<0>")) +s.a=8 +s.c=a +return s}, +mH(a,b,c){var s,r,q,p={},o=p.a=a +while(s=o.a,(s&4)!==0){o=o.c +p.a=o}if(o===b){s=A.la() +b.aN(new A.U(new A.bb(!0,o,null,"Cannot complete a future with itself"),s)) +return}r=b.a&1 +s=o.a=s|r +if((s&24)===0){q=b.c +b.a=b.a&1|4 +b.c=o +o.fq(q) +return}if(!c)if(b.c==null)o=(s&16)===0||r!==0 +else o=!1 +else o=!0 +if(o){q=b.bP() +b.cs(p.a) +A.cJ(b,q) +return}b.a^=2 +b.b.aY(new A.mI(p,b))}, +cJ(a,b){var s,r,q,p,o,n,m,l,k,j,i,h,g={},f=g.a=a +for(;;){s={} +r=f.a +q=(r&16)===0 +p=!q +if(b==null){if(p&&(r&1)===0){r=f.c +f.b.c1(r.a,r.b)}return}s.a=b +o=b.a +for(f=b;o!=null;f=o,o=n){f.a=null +A.cJ(g.a,f) +s.a=o +n=o.a}r=g.a +m=r.c +s.b=p +s.c=m +if(q){l=f.c +l=(l&1)!==0||(l&15)===8}else l=!0 +if(l){k=f.b.b +if(p){f=r.b +f=!(f===k||f.gaI()===k.gaI())}else f=!1 +if(f){f=g.a +r=f.c +f.b.c1(r.a,r.b) +return}j=$.h +if(j!==k)$.h=k +else j=null +f=s.a.c +if((f&15)===8)new A.mM(s,g,p).$0() +else if(q){if((f&1)!==0)new A.mL(s,m).$0()}else if((f&2)!==0)new A.mK(g,s).$0() +if(j!=null)$.h=j +f=s.c +if(f instanceof A.n){r=s.a.$ti +r=r.h("D<2>").b(f)||!r.y[1].b(f)}else r=!1 +if(r){i=s.a.b +if((f.a&24)!==0){h=i.c +i.c=null +b=i.cC(h) +i.a=f.a&30|i.a&1 +i.c=f.c +g.a=f +continue}else A.mH(f,i,!0) +return}}i=s.a.b +h=i.c +i.c=null +b=i.cC(h) +f=s.b +r=s.c +if(!f){i.a=8 +i.c=r}else{i.a=i.a&1|16 +i.c=r}g.a=i +f=i}}, +w3(a,b){if(t._.b(a))return b.d6(a,t.z,t.K,t.l) +if(t.bI.b(a))return b.b9(a,t.z,t.K) +throw A.b(A.ad(a,"onError",u.c))}, +vW(){var s,r +for(s=$.dX;s!=null;s=$.dX){$.fC=null +r=s.b +$.dX=r +if(r==null)$.fB=null +s.a.$0()}}, +we(){$.oL=!0 +try{A.vW()}finally{$.fC=null +$.oL=!1 +if($.dX!=null)$.p7().$1(A.rg())}}, +ra(a){var s=new A.ia(a),r=$.fB +if(r==null){$.dX=$.fB=s +if(!$.oL)$.p7().$1(A.rg())}else $.fB=r.b=s}, +wb(a){var s,r,q,p=$.dX +if(p==null){A.ra(a) +$.fC=$.fB +return}s=new A.ia(a) +r=$.fC +if(r==null){s.b=p +$.dX=$.fC=s}else{q=r.b +s.b=q +$.fC=r.b=s +if(q==null)$.fB=s}}, +p_(a){var s,r=null,q=$.h +if(B.d===q){A.nx(r,r,B.d,a) +return}if(B.d===q.ge1().a)s=B.d.gaI()===q.gaI() +else s=!1 +if(s){A.nx(r,r,q,q.au(a,t.H)) +return}s=$.h +s.aY(s.cP(a))}, +xF(a){return new A.dO(A.cU(a,"stream",t.K))}, +eP(a,b,c,d){var s=null +return c?new A.dS(b,s,s,a,d.h("dS<0>")):new A.dw(b,s,s,a,d.h("dw<0>"))}, +iW(a){var s,r,q +if(a==null)return +try{a.$0()}catch(q){s=A.G(q) +r=A.a1(q) +$.h.c1(s,r)}}, +uM(a,b,c,d,e,f){var s=$.h,r=e?1:0,q=c!=null?32:0,p=A.ig(s,b,f),o=A.ih(s,c),n=d==null?A.rf():d +return new A.cf(a,p,o,s.au(n,t.H),s,r|q,f.h("cf<0>"))}, +ig(a,b,c){var s=b==null?A.wo():b +return a.b9(s,t.H,c)}, +ih(a,b){if(b==null)b=A.wp() +if(t.da.b(b))return a.d6(b,t.z,t.K,t.l) +if(t.d5.b(b))return a.b9(b,t.z,t.K) +throw A.b(A.J("handleError callback must take either an Object (the error), or both an Object (the error) and a StackTrace.",null))}, +vX(a){}, +vZ(a,b){$.h.c1(a,b)}, +vY(){}, +w9(a,b,c){var s,r,q,p +try{b.$1(a.$0())}catch(p){s=A.G(p) +r=A.a1(p) +q=A.cS(s,r) +if(q!=null)c.$2(q.a,q.b) +else c.$2(s,r)}}, +vv(a,b,c){var s=a.K() +if(s!==$.cm())s.ai(new A.np(b,c)) +else b.X(c)}, +vw(a,b){return new A.no(a,b)}, +qS(a,b,c){var s=a.K() +if(s!==$.cm())s.ai(new A.nq(b,c)) +else b.b1(c)}, +uU(a,b,c){return new A.dM(new A.n5(null,null,a,c,b),b.h("@<0>").H(c).h("dM<1,2>"))}, +q0(a,b){var s=$.h +if(s===B.d)return s.eh(a,b) +return s.eh(a,s.cP(b))}, +xf(a,b,c){return A.wa(a,b,null,c)}, +wa(a,b,c,d){return $.h.h6(c,b).bb(a,d)}, +w7(a,b,c,d,e){A.fD(d,e)}, +fD(a,b){A.wb(new A.nt(a,b))}, +nu(a,b,c,d){var s,r=$.h +if(r===c)return d.$0() +$.h=c +s=r +try{r=d.$0() +return r}finally{$.h=s}}, +nw(a,b,c,d,e){var s,r=$.h +if(r===c)return d.$1(e) +$.h=c +s=r +try{r=d.$1(e) +return r}finally{$.h=s}}, +nv(a,b,c,d,e,f){var s,r=$.h +if(r===c)return d.$2(e,f) +$.h=c +s=r +try{r=d.$2(e,f) +return r}finally{$.h=s}}, +r6(a,b,c,d){return d}, +r7(a,b,c,d){return d}, +r5(a,b,c,d){return d}, +w6(a,b,c,d,e){return null}, +nx(a,b,c,d){var s,r +if(B.d!==c){s=B.d.gaI() +r=c.gaI() +d=s!==r?c.cP(d):c.ed(d,t.H)}A.ra(d)}, +w5(a,b,c,d,e){return A.om(d,B.d!==c?c.ed(e,t.H):e)}, +w4(a,b,c,d,e){var s +if(B.d!==c)e=c.fW(e,t.H,t.aF) +s=B.b.J(d.a,1000) +return A.uW(s<0?0:s,e)}, +w8(a,b,c,d){A.oZ(d)}, +w0(a){$.h.hi(a)}, +r4(a,b,c,d,e){var s,r,q +$.rt=A.wq() +if(d==null)d=B.bC +if(e==null)s=c.gfl() +else{r=t.X +s=A.tT(e,r,r)}r=new A.ii(c.gfD(),c.gfF(),c.gfE(),c.gfz(),c.gfA(),c.gfw(),c.gfc(),c.ge1(),c.gf7(),c.gf6(),c.gfs(),c.gff(),c.gdS(),c,s) +q=d.a +if(q!=null)r.as=new A.ay(r,q) +return r}, +m9:function m9(a){this.a=a}, +m8:function m8(a,b,c){this.a=a +this.b=b +this.c=c}, +ma:function ma(a){this.a=a}, +mb:function mb(a){this.a=a}, +iO:function iO(){this.c=0}, +nb:function nb(a,b){this.a=a +this.b=b}, +na:function na(a,b,c,d){var _=this +_.a=a +_.b=b +_.c=c +_.d=d}, +i9:function i9(a,b){this.a=a +this.b=!1 +this.$ti=b}, +nm:function nm(a){this.a=a}, +nn:function nn(a){this.a=a}, +nA:function nA(a){this.a=a}, +iM:function iM(a){var _=this +_.a=a +_.e=_.d=_.c=_.b=null}, +dR:function dR(a,b){this.a=a +this.$ti=b}, +U:function U(a,b){this.a=a +this.b=b}, +eZ:function eZ(a,b){this.a=a +this.$ti=b}, +cG:function cG(a,b,c,d,e,f,g){var _=this +_.ay=0 +_.CW=_.ch=null +_.w=a +_.a=b +_.b=c +_.c=d +_.d=e +_.e=f +_.r=_.f=null +_.$ti=g}, +cF:function cF(){}, +fp:function fp(a,b,c){var _=this +_.a=a +_.b=b +_.c=0 +_.r=_.f=_.e=_.d=null +_.$ti=c}, +n7:function n7(a,b){this.a=a +this.b=b}, +n9:function n9(a,b,c){this.a=a +this.b=b +this.c=c}, +n8:function n8(a){this.a=a}, +ki:function ki(a,b){this.a=a +this.b=b}, +kg:function kg(a,b,c){this.a=a +this.b=b +this.c=c}, +kk:function kk(a,b,c,d){var _=this +_.a=a +_.b=b +_.c=c +_.d=d}, +kj:function kj(a,b,c,d,e,f){var _=this +_.a=a +_.b=b +_.c=c +_.d=d +_.e=e +_.f=f}, +dx:function dx(){}, +a6:function a6(a,b){this.a=a +this.$ti=b}, +a8:function a8(a,b){this.a=a +this.$ti=b}, +cg:function cg(a,b,c,d,e){var _=this +_.a=null +_.b=a +_.c=b +_.d=c +_.e=d +_.$ti=e}, +n:function n(a,b){var _=this +_.a=0 +_.b=a +_.c=null +_.$ti=b}, +mE:function mE(a,b){this.a=a +this.b=b}, +mJ:function mJ(a,b){this.a=a +this.b=b}, +mI:function mI(a,b){this.a=a +this.b=b}, +mG:function mG(a,b){this.a=a +this.b=b}, +mF:function mF(a,b){this.a=a +this.b=b}, +mM:function mM(a,b,c){this.a=a +this.b=b +this.c=c}, +mN:function mN(a,b){this.a=a +this.b=b}, +mO:function mO(a){this.a=a}, +mL:function mL(a,b){this.a=a +this.b=b}, +mK:function mK(a,b){this.a=a +this.b=b}, +ia:function ia(a){this.a=a +this.b=null}, +V:function V(){}, +li:function li(a,b){this.a=a +this.b=b}, +lj:function lj(a,b){this.a=a +this.b=b}, +lg:function lg(a){this.a=a}, +lh:function lh(a,b,c){this.a=a +this.b=b +this.c=c}, +le:function le(a,b){this.a=a +this.b=b}, +lf:function lf(a,b,c,d){var _=this +_.a=a +_.b=b +_.c=c +_.d=d}, +lc:function lc(a,b){this.a=a +this.b=b}, +ld:function ld(a,b,c){this.a=a +this.b=b +this.c=c}, +hP:function hP(){}, +cP:function cP(){}, +n4:function n4(a){this.a=a}, +n3:function n3(a){this.a=a}, +iN:function iN(){}, +ib:function ib(){}, +dw:function dw(a,b,c,d,e){var _=this +_.a=null +_.b=0 +_.c=null +_.d=a +_.e=b +_.f=c +_.r=d +_.$ti=e}, +dS:function dS(a,b,c,d,e){var _=this +_.a=null +_.b=0 +_.c=null +_.d=a +_.e=b +_.f=c +_.r=d +_.$ti=e}, +ar:function ar(a,b){this.a=a +this.$ti=b}, +cf:function cf(a,b,c,d,e,f,g){var _=this +_.w=a +_.a=b +_.b=c +_.c=d +_.d=e +_.e=f +_.r=_.f=null +_.$ti=g}, +dP:function dP(a){this.a=a}, +ag:function ag(){}, +mm:function mm(a,b,c){this.a=a +this.b=b +this.c=c}, +ml:function ml(a){this.a=a}, +dN:function dN(){}, +ik:function ik(){}, +dy:function dy(a){this.b=a +this.a=null}, +f2:function f2(a,b){this.b=a +this.c=b +this.a=null}, +mw:function mw(){}, +fh:function fh(){this.a=0 +this.c=this.b=null}, +mU:function mU(a,b){this.a=a +this.b=b}, +f3:function f3(a){this.a=1 +this.b=a +this.c=null}, +dO:function dO(a){this.a=null +this.b=a +this.c=!1}, +np:function np(a,b){this.a=a +this.b=b}, +no:function no(a,b){this.a=a +this.b=b}, +nq:function nq(a,b){this.a=a +this.b=b}, +f8:function f8(){}, +dA:function dA(a,b,c,d,e,f,g){var _=this +_.w=a +_.x=null +_.a=b +_.b=c +_.c=d +_.d=e +_.e=f +_.r=_.f=null +_.$ti=g}, +fc:function fc(a,b,c){this.b=a +this.a=b +this.$ti=c}, +f5:function f5(a){this.a=a}, +dL:function dL(a,b,c,d,e,f){var _=this +_.w=$ +_.x=null +_.a=a +_.b=b +_.c=c +_.d=d +_.e=e +_.r=_.f=null +_.$ti=f}, +fo:function fo(){}, +eY:function eY(a,b,c){this.a=a +this.b=b +this.$ti=c}, +dD:function dD(a,b,c,d,e){var _=this +_.a=a +_.b=b +_.c=c +_.d=d +_.$ti=e}, +dM:function dM(a,b){this.a=a +this.$ti=b}, +n5:function n5(a,b,c,d,e){var _=this +_.a=a +_.b=b +_.c=c +_.d=d +_.e=e}, +ay:function ay(a,b){this.a=a +this.b=b}, +iT:function iT(){}, +ii:function ii(a,b,c,d,e,f,g,h,i,j,k,l,m,n,o){var _=this +_.a=a +_.b=b +_.c=c +_.d=d +_.e=e +_.f=f +_.r=g +_.w=h +_.x=i +_.y=j +_.z=k +_.Q=l +_.as=m +_.at=null +_.ax=n +_.ay=o}, +mt:function mt(a,b,c){this.a=a +this.b=b +this.c=c}, +mv:function mv(a,b,c,d){var _=this +_.a=a +_.b=b +_.c=c +_.d=d}, +ms:function ms(a,b){this.a=a +this.b=b}, +mu:function mu(a,b,c){this.a=a +this.b=b +this.c=c}, +iH:function iH(){}, +mZ:function mZ(a,b,c){this.a=a +this.b=b +this.c=c}, +n0:function n0(a,b,c,d){var _=this +_.a=a +_.b=b +_.c=c +_.d=d}, +mY:function mY(a,b){this.a=a +this.b=b}, +n_:function n_(a,b,c){this.a=a +this.b=b +this.c=c}, +dU:function dU(a){this.a=a}, +nt:function nt(a,b){this.a=a +this.b=b}, +iU:function iU(a,b,c,d,e,f,g,h,i,j,k,l,m){var _=this +_.a=a +_.b=b +_.c=c +_.d=d +_.e=e +_.f=f +_.r=g +_.w=h +_.x=i +_.y=j +_.z=k +_.Q=l +_.as=m}, +pw(a,b){return new A.cK(a.h("@<0>").H(b).h("cK<1,2>"))}, +qp(a,b){var s=a[b] +return s===a?null:s}, +oy(a,b,c){if(c==null)a[b]=a +else a[b]=c}, +ox(){var s=Object.create(null) +A.oy(s,"",s) +delete s[""] +return s}, +u0(a,b){return new A.bA(a.h("@<0>").H(b).h("bA<1,2>"))}, +u1(a,b,c){return A.wN(a,new A.bA(b.h("@<0>").H(c).h("bA<1,2>")))}, +al(a,b){return new A.bA(a.h("@<0>").H(b).h("bA<1,2>"))}, +ob(a){return new A.fa(a.h("fa<0>"))}, +oz(){var s=Object.create(null) +s[""]=s +delete s[""] +return s}, +ix(a,b,c){var s=new A.dG(a,b,c.h("dG<0>")) +s.c=a.e +return s}, +tT(a,b,c){var s=A.pw(b,c) +a.ap(0,new A.kn(s,b,c)) +return s}, +oc(a){var s,r +if(A.oW(a))return"{...}" +s=new A.aA("") +try{r={} +$.cT.push(a) +s.a+="{" +r.a=!0 +a.ap(0,new A.kE(r,s)) +s.a+="}"}finally{$.cT.pop()}r=s.a +return r.charCodeAt(0)==0?r:r}, +cK:function cK(a){var _=this +_.a=0 +_.e=_.d=_.c=_.b=null +_.$ti=a}, +mP:function mP(a){this.a=a}, +dE:function dE(a){var _=this +_.a=0 +_.e=_.d=_.c=_.b=null +_.$ti=a}, +cL:function cL(a,b){this.a=a +this.$ti=b}, +ir:function ir(a,b,c){var _=this +_.a=a +_.b=b +_.c=0 +_.d=null +_.$ti=c}, +fa:function fa(a){var _=this +_.a=0 +_.f=_.e=_.d=_.c=_.b=null +_.r=0 +_.$ti=a}, +mT:function mT(a){this.a=a +this.c=this.b=null}, +dG:function dG(a,b,c){var _=this +_.a=a +_.b=b +_.d=_.c=null +_.$ti=c}, +kn:function kn(a,b,c){this.a=a +this.b=b +this.c=c}, +ey:function ey(a){var _=this +_.b=_.a=0 +_.c=null +_.$ti=a}, +iy:function iy(a,b,c,d){var _=this +_.a=a +_.b=b +_.c=null +_.d=c +_.e=!1 +_.$ti=d}, +aL:function aL(){}, +v:function v(){}, +Q:function Q(){}, +kD:function kD(a){this.a=a}, +kE:function kE(a,b){this.a=a +this.b=b}, +fb:function fb(a,b){this.a=a +this.$ti=b}, +iz:function iz(a,b,c){var _=this +_.a=a +_.b=b +_.c=null +_.$ti=c}, +dl:function dl(){}, +fk:function fk(){}, +vh(a,b,c){var s,r,q,p,o=c-b +if(o<=4096)s=$.rW() +else s=new Uint8Array(o) +for(r=J.a0(a),q=0;q=16)return null +r=r*16+o}n=h-1 +i[h]=r +for(;s=16)return null +r=r*16+o}m=n-1 +i[n]=r}if(j===1&&i[0]===0)return $.ba() +l=A.aR(j,i) +return new A.a7(l===0?!1:c,i,l)}, +qn(a,b){var s,r,q,p,o +if(a==="")return null +s=$.rP().a8(a) +if(s==null)return null +r=s.b +q=r[1]==="-" +p=r[4] +o=r[3] +if(p!=null)return A.uJ(p,q) +if(o!=null)return A.uK(o,2,q) +return null}, +aR(a,b){for(;;){if(!(a>0&&b[a-1]===0))break;--a}return a}, +ou(a,b,c,d){var s,r=new Uint16Array(d),q=c-b +for(s=0;s>>0)+(o>>>4)-1075 +m=new Uint16Array(4) +m[0]=(r[1]<<8>>>0)+r[0] +m[1]=(r[3]<<8>>>0)+r[2] +m[2]=(r[5]<<8>>>0)+r[4] +m[3]=o&15|16 +l=new A.a7(!1,m,4) +if(n<0)k=l.bh(0,-n) +else k=n>0?l.b_(0,n):l +if(s)return k.aA(0) +return k}, +ov(a,b,c,d){var s,r,q +if(b===0)return 0 +if(c===0&&d===a)return b +for(s=b-1,r=d.$flags|0;s>=0;--s){q=a[s] +r&2&&A.y(d) +d[s+c]=q}for(s=c-1;s>=0;--s){r&2&&A.y(d) +d[s]=0}return b+c}, +ql(a,b,c,d){var s,r,q,p,o,n=B.b.J(c,16),m=B.b.ac(c,16),l=16-m,k=B.b.b_(1,l)-1 +for(s=b-1,r=d.$flags|0,q=0;s>=0;--s){p=a[s] +o=B.b.bh(p,l) +r&2&&A.y(d) +d[s+n+1]=(o|q)>>>0 +q=B.b.b_((p&k)>>>0,m)}r&2&&A.y(d) +d[n]=q}, +qg(a,b,c,d){var s,r,q,p,o=B.b.J(c,16) +if(B.b.ac(c,16)===0)return A.ov(a,b,o,d) +s=b+o+1 +A.ql(a,b,c,d) +for(r=d.$flags|0,q=o;--q,q>=0;){r&2&&A.y(d) +d[q]=0}p=s-1 +return d[p]===0?p:s}, +uL(a,b,c,d){var s,r,q,p,o=B.b.J(c,16),n=B.b.ac(c,16),m=16-n,l=B.b.b_(1,n)-1,k=B.b.bh(a[o],n),j=b-o-1 +for(s=d.$flags|0,r=0;r>>0,m) +s&2&&A.y(d) +d[r]=(p|k)>>>0 +k=B.b.bh(q,n)}s&2&&A.y(d) +d[j]=k}, +mi(a,b,c,d){var s,r=b-d +if(r===0)for(s=b-1;s>=0;--s){r=a[s]-c[s] +if(r!==0)return r}return r}, +uH(a,b,c,d,e){var s,r,q +for(s=e.$flags|0,r=0,q=0;q=0;e=o,c=q){q=c+1 +p=a*b[c]+d[e]+r +o=e+1 +s&2&&A.y(d) +d[e]=p&65535 +r=B.b.J(p,65536)}for(;r!==0;e=o){n=d[e]+r +o=e+1 +s&2&&A.y(d) +d[e]=n&65535 +r=B.b.J(n,65536)}}, +uI(a,b,c){var s,r=b[c] +if(r===a)return 65535 +s=B.b.eW((r<<16|b[c-1])>>>0,a) +if(s>65535)return 65535 +return s}, +tJ(a){throw A.b(A.ad(a,"object","Expandos are not allowed on strings, numbers, bools, records or null"))}, +mD(a,b){var s=$.rR() +s=s==null?null:new s(A.ck(A.xs(a,b),1)) +return new A.ip(s,b.h("ip<0>"))}, +bh(a,b){var s=A.pP(a,b) +if(s!=null)return s +throw A.b(A.af(a,null,null))}, +tI(a,b){a=A.aa(a,new Error()) +a.stack=b.i(0) +throw a}, +b4(a,b,c,d){var s,r=c?J.pA(a,d):J.pz(a,d) +if(a!==0&&b!=null)for(s=0;s")) +for(s=J.a4(a);s.k();)r.push(s.gm()) +r.$flags=1 +return r}, +aw(a,b){var s,r +if(Array.isArray(a))return A.f(a.slice(0),b.h("u<0>")) +s=A.f([],b.h("u<0>")) +for(r=J.a4(a);r.k();)s.push(r.gm()) +return s}, +aM(a,b){var s=A.u3(a,!1,b) +s.$flags=3 +return s}, +q_(a,b,c){var s,r,q,p,o +A.ab(b,"start") +s=c==null +r=!s +if(r){q=c-b +if(q<0)throw A.b(A.S(c,b,null,"end",null)) +if(q===0)return""}if(Array.isArray(a)){p=a +o=p.length +if(s)c=o +return A.pR(b>0||c0)a=J.e7(a,b) +s=A.aw(a,t.S) +return A.pR(s)}, +pZ(a){return A.aP(a)}, +um(a,b,c){var s=a.length +if(b>=s)return"" +return A.ue(a,b,c==null||c>s?s:c)}, +H(a,b,c,d,e){return new A.cw(a,A.o8(a,d,b,e,c,""))}, +oj(a,b,c){var s=J.a4(b) +if(!s.k())return a +if(c.length===0){do a+=A.t(s.gm()) +while(s.k())}else{a+=A.t(s.gm()) +while(s.k())a=a+c+A.t(s.gm())}return a}, +eS(){var s,r,q=A.u9() +if(q==null)throw A.b(A.a3("'Uri.base' is not supported")) +s=$.qb +if(s!=null&&q===$.qa)return s +r=A.bt(q) +$.qb=r +$.qa=q +return r}, +vf(a,b,c,d){var s,r,q,p,o,n="0123456789ABCDEF" +if(c===B.j){s=$.rT() +s=s.b.test(b)}else s=!1 +if(s)return b +r=B.i.a5(b) +for(s=r.length,q=0,p="";q>>4&15]+n[o&15]}return p.charCodeAt(0)==0?p:p}, +la(){return A.a1(new Error())}, +pn(a,b,c){var s="microsecond" +if(b>999)throw A.b(A.S(b,0,999,s,null)) +if(a<-864e13||a>864e13)throw A.b(A.S(a,-864e13,864e13,"millisecondsSinceEpoch",null)) +if(a===864e13&&b!==0)throw A.b(A.ad(b,s,"Time including microseconds is outside valid range")) +A.cU(c,"isUtc",t.y) +return a}, +tE(a){var s=Math.abs(a),r=a<0?"-":"" +if(s>=1000)return""+a +if(s>=100)return r+"0"+s +if(s>=10)return r+"00"+s +return r+"000"+s}, +pm(a){if(a>=100)return""+a +if(a>=10)return"0"+a +return"00"+a}, +h0(a){if(a>=10)return""+a +return"0"+a}, +po(a,b){return new A.bx(a+1000*b)}, +o1(a,b){var s,r +for(s=0;s<5;++s){r=a[s] +if(r.b===b)return r}throw A.b(A.ad(b,"name","No enum value with that name"))}, +tH(a,b){var s,r,q=A.al(t.N,b) +for(s=0;s<2;++s){r=a[s] +q.t(0,r.b,r)}return q}, +h9(a){if(typeof a=="number"||A.bQ(a)||a==null)return J.b1(a) +if(typeof a=="string")return JSON.stringify(a) +return A.pQ(a)}, +pr(a,b){A.cU(a,"error",t.K) +A.cU(b,"stackTrace",t.l) +A.tI(a,b)}, +e8(a){return new A.fN(a)}, +J(a,b){return new A.bb(!1,null,b,a)}, +ad(a,b,c){return new A.bb(!0,a,b,c)}, +bT(a,b){return a}, +kN(a,b){return new A.dh(null,null,!0,a,b,"Value not in range")}, +S(a,b,c,d,e){return new A.dh(b,c,!0,a,d,"Invalid value")}, +pU(a,b,c,d){if(ac)throw A.b(A.S(a,b,c,d,null)) +return a}, +ug(a,b,c,d){if(0>a||a>=d)A.C(A.hf(a,d,b,null,c)) +return a}, +bd(a,b,c){if(0>a||a>c)throw A.b(A.S(a,0,c,"start",null)) +if(b!=null){if(a>b||b>c)throw A.b(A.S(b,a,c,"end",null)) +return b}return c}, +ab(a,b){if(a<0)throw A.b(A.S(a,0,null,b,null)) +return a}, +px(a,b){var s=b.b +return new A.eq(s,!0,a,null,"Index out of range")}, +hf(a,b,c,d,e){return new A.eq(b,!0,a,e,"Index out of range")}, +a3(a){return new A.eR(a)}, +q7(a){return new A.hT(a)}, +B(a){return new A.aQ(a)}, +au(a){return new A.fW(a)}, +k6(a){return new A.io(a)}, +af(a,b,c){return new A.aD(a,b,c)}, +tV(a,b,c){var s,r +if(A.oW(a)){if(b==="("&&c===")")return"(...)" +return b+"..."+c}s=A.f([],t.s) +$.cT.push(a) +try{A.vV(a,s)}finally{$.cT.pop()}r=A.oj(b,s,", ")+c +return r.charCodeAt(0)==0?r:r}, +o7(a,b,c){var s,r +if(A.oW(a))return b+"..."+c +s=new A.aA(b) +$.cT.push(a) +try{r=s +r.a=A.oj(r.a,a,", ")}finally{$.cT.pop()}s.a+=c +r=s.a +return r.charCodeAt(0)==0?r:r}, +vV(a,b){var s,r,q,p,o,n,m,l=a.gq(a),k=0,j=0 +for(;;){if(!(k<80||j<3))break +if(!l.k())return +s=A.t(l.gm()) +b.push(s) +k+=s.length+2;++j}if(!l.k()){if(j<=5)return +r=b.pop() +q=b.pop()}else{p=l.gm();++j +if(!l.k()){if(j<=4){b.push(A.t(p)) +return}r=A.t(p) +q=b.pop() +k+=r.length+2}else{o=l.gm();++j +for(;l.k();p=o,o=n){n=l.gm();++j +if(j>100){for(;;){if(!(k>75&&j>3))break +k-=b.pop().length+2;--j}b.push("...") +return}}q=A.t(p) +r=A.t(o) +k+=r.length+q.length+4}}if(j>b.length+2){k+=5 +m="..."}else m=null +for(;;){if(!(k>80&&b.length>3))break +k-=b.pop().length+2 +if(m==null){k+=5 +m="..."}}if(m!=null)b.push(m) +b.push(q) +b.push(r)}, +eD(a,b,c,d){var s +if(B.f===c){s=J.aC(a) +b=J.aC(b) +return A.ok(A.c9(A.c9($.nV(),s),b))}if(B.f===d){s=J.aC(a) +b=J.aC(b) +c=J.aC(c) +return A.ok(A.c9(A.c9(A.c9($.nV(),s),b),c))}s=J.aC(a) +b=J.aC(b) +c=J.aC(c) +d=J.aC(d) +d=A.ok(A.c9(A.c9(A.c9(A.c9($.nV(),s),b),c),d)) +return d}, +xd(a){var s=A.t(a),r=$.rt +if(r==null)A.oZ(s) +else r.$1(s)}, +q9(a){var s,r=null,q=new A.aA(""),p=A.f([-1],t.t) +A.uv(r,r,r,q,p) +p.push(q.a.length) +q.a+="," +A.uu(256,B.ak.kr(a),q) +s=q.a +return new A.hY(s.charCodeAt(0)==0?s:s,p,r).geM()}, +bt(a5){var s,r,q,p,o,n,m,l,k,j,i,h,g,f,e,d,c,b,a,a0,a1,a2,a3=null,a4=a5.length +if(a4>=5){s=((a5.charCodeAt(4)^58)*3|a5.charCodeAt(0)^100|a5.charCodeAt(1)^97|a5.charCodeAt(2)^116|a5.charCodeAt(3)^97)>>>0 +if(s===0)return A.q8(a4=14)r[7]=a4 +q=r[1] +if(q>=0)if(A.r9(a5,0,q,20,r)===20)r[7]=q +p=r[2]+1 +o=r[3] +n=r[4] +m=r[5] +l=r[6] +if(lq+3)){i=o>0 +if(!(i&&o+1===n)){if(!B.a.C(a5,"\\",n))if(p>0)h=B.a.C(a5,"\\",p-1)||B.a.C(a5,"\\",p-2) +else h=!1 +else h=!0 +if(!h){if(!(mn+2&&B.a.C(a5,"/..",m-3) +else h=!0 +if(!h)if(q===4){if(B.a.C(a5,"file",0)){if(p<=0){if(!B.a.C(a5,"/",n)){g="file:///" +s=3}else{g="file://" +s=2}a5=g+B.a.p(a5,n,a4) +m+=s +l+=s +a4=a5.length +p=7 +o=7 +n=7}else if(n===m){++l +f=m+1 +a5=B.a.aL(a5,n,m,"/");++a4 +m=f}j="file"}else if(B.a.C(a5,"http",0)){if(i&&o+3===n&&B.a.C(a5,"80",o+1)){l-=3 +e=n-3 +m-=3 +a5=B.a.aL(a5,o,n,"") +a4-=3 +n=e}j="http"}}else if(q===5&&B.a.C(a5,"https",0)){if(i&&o+4===n&&B.a.C(a5,"443",o+1)){l-=4 +e=n-4 +m-=4 +a5=B.a.aL(a5,o,n,"") +a4-=3 +n=e}j="https"}k=!h}}}}if(k)return new A.b6(a40)j=A.nh(a5,0,q) +else{if(q===0)A.dT(a5,0,"Invalid empty scheme") +j=""}d=a3 +if(p>0){c=q+3 +b=c=c?0:a.charCodeAt(q) +m=n^48 +if(m<=9){if(o!==0||q===r){o=o*10+m +if(o<=255){++q +continue}A.hZ("each part must be in the range 0..255",a,r)}A.hZ("parts must not have leading zeros",a,r)}if(q===r){if(q===c)break +A.hZ(k,a,q)}l=p+1 +s&2&&A.y(d) +d[e+p]=o +if(n===46){if(l<4){++q +p=l +r=q +o=0 +continue}break}if(q===c){if(l===4)return +break}A.hZ(k,a,q) +p=l}A.hZ("IPv4 address should contain exactly 4 parts",a,q)}, +ux(a,b,c){var s +if(b===c)throw A.b(A.af("Empty IP address",a,b)) +if(a.charCodeAt(b)===118){s=A.uy(a,b,c) +if(s!=null)throw A.b(s) +return!1}A.qc(a,b,c) +return!0}, +uy(a,b,c){var s,r,q,p,o="Missing hex-digit in IPvFuture address";++b +for(s=b;;s=r){if(s=97&&p<=102)continue +if(q===46){if(r-1===b)return new A.aD(o,a,r) +s=r +break}return new A.aD("Unexpected character",a,r-1)}if(s-1===b)return new A.aD(o,a,s) +return new A.aD("Missing '.' in IPvFuture address",a,s)}if(s===c)return new A.aD("Missing address in IPvFuture address, host, cursor",null,null) +for(;;){if((u.v.charCodeAt(a.charCodeAt(s))&16)!==0){++s +if(s=a3?0:a1.charCodeAt(p) +A:{k=l^48 +j=!1 +if(k<=9)i=k +else{h=l|32 +if(h>=97&&h<=102)i=h-87 +else break A +m=j}if(po){if(l===46){if(m){if(q<=6){A.uw(a1,o,a3,s,q*2) +q+=2 +p=a3 +break}a0.$2(a,o)}break}g=q*2 +s[g]=B.b.O(n,8) +s[g+1]=n&255;++q +if(l===58){if(q<8){++p +o=p +n=0 +m=!0 +continue}a0.$2(a,p)}break}if(l===58){if(r<0){f=q+1;++p +r=q +q=f +o=p +continue}a0.$2("only one wildcard `::` is allowed",p)}if(r!==q-1)a0.$2("missing part",p) +break}if(p0){c=e*2 +b=16-d*2 +B.e.M(s,b,16,s,c) +B.e.el(s,c,b,0)}}return s}, +fw(a,b,c,d,e,f,g){return new A.fv(a,b,c,d,e,f,g)}, +am(a,b,c,d){var s,r,q,p,o,n,m,l,k=null +d=d==null?"":A.nh(d,0,d.length) +s=A.qK(k,0,0) +a=A.qH(a,0,a==null?0:a.length,!1) +r=A.qJ(k,0,0,k) +q=A.qG(k,0,0) +p=A.ng(k,d) +o=d==="file" +if(a==null)n=s.length!==0||p!=null||o +else n=!1 +if(n)a="" +n=a==null +m=!n +b=A.qI(b,0,b==null?0:b.length,c,d,m) +l=d.length===0 +if(l&&n&&!B.a.u(b,"/"))b=A.oE(b,!l||m) +else b=A.cQ(b) +return A.fw(d,s,n&&B.a.u(b,"//")?"":a,p,b,r,q)}, +qD(a){if(a==="http")return 80 +if(a==="https")return 443 +return 0}, +dT(a,b,c){throw A.b(A.af(c,a,b))}, +qC(a,b){return b?A.vb(a,!1):A.va(a,!1)}, +v6(a,b){var s,r,q +for(s=a.length,r=0;r")),r=r.h("M.E");s.k();){q=s.d +if(q==null)q=r.a(q) +if(B.a.I(q,A.H('["*/:<>?\\\\|]',!0,!1,!1,!1)))if(b)throw A.b(A.J("Illegal character in path",null)) +else throw A.b(A.a3("Illegal character in path: "+q))}}, +v7(a,b){var s,r="Illegal drive letter " +if(!(65<=a&&a<=90))s=97<=a&&a<=122 +else s=!0 +if(s)return +if(b)throw A.b(A.J(r+A.pZ(a),null)) +else throw A.b(A.a3(r+A.pZ(a)))}, +va(a,b){var s=null,r=A.f(a.split("/"),t.s) +if(B.a.u(a,"/"))return A.am(s,s,r,"file") +else return A.am(s,s,r,s)}, +vb(a,b){var s,r,q,p,o="\\",n=null,m="file" +if(B.a.u(a,"\\\\?\\"))if(B.a.C(a,"UNC\\",4))a=B.a.aL(a,0,7,o) +else{a=B.a.N(a,4) +if(a.length<3||a.charCodeAt(1)!==58||a.charCodeAt(2)!==92)throw A.b(A.ad(a,"path","Windows paths with \\\\?\\ prefix must be absolute"))}else a=A.bi(a,"/",o) +s=a.length +if(s>1&&a.charCodeAt(1)===58){A.v7(a.charCodeAt(0),!0) +if(s===2||a.charCodeAt(2)!==92)throw A.b(A.ad(a,"path","Windows paths with drive letter must be absolute")) +r=A.f(a.split(o),t.s) +A.ne(r,!0,1) +return A.am(n,n,r,m)}if(B.a.u(a,o))if(B.a.C(a,o,1)){q=B.a.aU(a,o,2) +s=q<0 +p=s?B.a.N(a,2):B.a.p(a,2,q) +r=A.f((s?"":B.a.N(a,q+1)).split(o),t.s) +A.ne(r,!0,0) +return A.am(p,n,r,m)}else{r=A.f(a.split(o),t.s) +A.ne(r,!0,0) +return A.am(n,n,r,m)}else{r=A.f(a.split(o),t.s) +A.ne(r,!0,0) +return A.am(n,n,r,n)}}, +ng(a,b){if(a!=null&&a===A.qD(b))return null +return a}, +qH(a,b,c,d){var s,r,q,p,o,n,m,l +if(a==null)return null +if(b===c)return"" +if(a.charCodeAt(b)===91){s=c-1 +if(a.charCodeAt(s)!==93)A.dT(a,b,"Missing end `]` to match `[` in host") +r=b+1 +q="" +if(a.charCodeAt(r)!==118){p=A.v8(a,r,s) +if(p=b&&s=b&&s=p){if(i==null)i=new A.aA("") +if(r=o){if(q==null)q=new A.aA("") +if(r")).aq(0,"/")}else if(d!=null)throw A.b(A.J("Both path and pathSegments specified",null)) +else s=A.fx(a,b,c,128,!0,!0) +if(s.length===0){if(r)return"/"}else if(q&&!B.a.u(s,"/"))s="/"+s +return A.vc(s,e,f)}, +vc(a,b,c){var s=b.length===0 +if(s&&!c&&!B.a.u(a,"/")&&!B.a.u(a,"\\"))return A.oE(a,!s||c) +return A.cQ(a)}, +qJ(a,b,c,d){if(a!=null)return A.fx(a,b,c,256,!0,!1) +return null}, +qG(a,b,c){if(a==null)return null +return A.fx(a,b,c,256,!0,!1)}, +oD(a,b,c){var s,r,q,p,o,n=b+2 +if(n>=a.length)return"%" +s=a.charCodeAt(b+1) +r=a.charCodeAt(n) +q=A.nH(s) +p=A.nH(r) +if(q<0||p<0)return"%" +o=q*16+p +if(o<127&&(u.v.charCodeAt(o)&1)!==0)return A.aP(c&&65<=o&&90>=o?(o|32)>>>0:o) +if(s>=97||r>=97)return B.a.p(a,b,b+3).toUpperCase() +return null}, +oC(a){var s,r,q,p,o,n="0123456789ABCDEF" +if(a<=127){s=new Uint8Array(3) +s[0]=37 +s[1]=n.charCodeAt(a>>>4) +s[2]=n.charCodeAt(a&15)}else{if(a>2047)if(a>65535){r=240 +q=4}else{r=224 +q=3}else{r=192 +q=2}s=new Uint8Array(3*q) +for(p=0;--q,q>=0;r=128){o=B.b.jl(a,6*q)&63|r +s[p]=37 +s[p+1]=n.charCodeAt(o>>>4) +s[p+2]=n.charCodeAt(o&15) +p+=3}}return A.q_(s,0,null)}, +fx(a,b,c,d,e,f){var s=A.qM(a,b,c,d,e,f) +return s==null?B.a.p(a,b,c):s}, +qM(a,b,c,d,e,f){var s,r,q,p,o,n,m,l,k,j=null,i=u.v +for(s=!e,r=b,q=r,p=j;r=2&&A.qF(a.charCodeAt(0)))for(s=1;s127||(u.v.charCodeAt(r)&8)===0)break}return a}, +ve(a,b){if(a.kC("package")&&a.c==null)return A.rb(b,0,b.length) +return-1}, +v9(a,b){var s,r,q +for(s=0,r=0;r<2;++r){q=a.charCodeAt(b+r) +if(48<=q&&q<=57)s=s*16+q-48 +else{q|=32 +if(97<=q&&q<=102)s=s*16+q-87 +else throw A.b(A.J("Invalid URL encoding",null))}}return s}, +oF(a,b,c,d,e){var s,r,q,p,o=b +for(;;){if(!(o127)throw A.b(A.J("Illegal percent encoding in URI",null)) +if(r===37){if(o+3>q)throw A.b(A.J("Truncated URI",null)) +p.push(A.v9(a,o+1)) +o+=2}else p.push(r)}}return d.cS(p)}, +qF(a){var s=a|32 +return 97<=s&&s<=122}, +uv(a,b,c,d,e){d.a=d.a}, +q8(a,b,c){var s,r,q,p,o,n,m,l,k="Invalid MIME type",j=A.f([b-1],t.t) +for(s=a.length,r=b,q=-1,p=null;rb)throw A.b(A.af(k,a,r)) +while(p!==44){j.push(r);++r +for(o=-1;r=0)j.push(o) +else{n=B.c.gE(j) +if(p!==44||r!==n+7||!B.a.C(a,"base64",n+1))throw A.b(A.af("Expecting '='",a,r)) +break}}j.push(r) +m=r+1 +if((j.length&1)===1)a=B.al.kM(a,m,s) +else{l=A.qM(a,m,s,256,!0,!1) +if(l!=null)a=B.a.aL(a,m,s,l)}return new A.hY(a,j,c)}, +uu(a,b,c){var s,r,q,p,o,n="0123456789ABCDEF" +for(s=b.length,r=0,q=0;q>>4)) +c.a+=o +o=A.aP(n.charCodeAt(p&15)) +c.a+=o}}if((r&4294967040)!==0)for(q=0;q255)throw A.b(A.ad(p,"non-byte value",null))}}, +r9(a,b,c,d,e){var s,r,q +for(s=b;s95)r=31 +q='\xe1\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\xe1\xe1\xe1\x01\xe1\xe1\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\xe1\xe3\xe1\xe1\x01\xe1\x01\xe1\xcd\x01\xe1\x01\x01\x01\x01\x01\x01\x01\x01\x0e\x03\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01"\x01\xe1\x01\xe1\xac\xe1\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\xe1\xe1\xe1\x01\xe1\xe1\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\xe1\xea\xe1\xe1\x01\xe1\x01\xe1\xcd\x01\xe1\x01\x01\x01\x01\x01\x01\x01\x01\x01\n\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01"\x01\xe1\x01\xe1\xac\xeb\x8b\x8b\x8b\x8b\x8b\x8b\x8b\x8b\x8b\x8b\x8b\x8b\x8b\x8b\x8b\x8b\x8b\x8b\x8b\x8b\x8b\x8b\x8b\x8b\x8b\x8b\xeb\xeb\xeb\x8b\xeb\xeb\x8b\x8b\x8b\x8b\x8b\x8b\x8b\x8b\x8b\x8b\x8b\x8b\x8b\x8b\x8b\x8b\x8b\x8b\x8b\x8b\x8b\x8b\x8b\x8b\x8b\x8b\xeb\x83\xeb\xeb\x8b\xeb\x8b\xeb\xcd\x8b\xeb\x8b\x8b\x8b\x8b\x8b\x8b\x8b\x8b\x92\x83\x8b\x8b\x8b\x8b\x8b\x8b\x8b\x8b\x8b\x8b\xeb\x8b\xeb\x8b\xeb\xac\xeb\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\xeb\xeb\xeb\v\xeb\xeb\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\xebD\xeb\xeb\v\xeb\v\xeb\xcd\v\xeb\v\v\v\v\v\v\v\v\x12D\v\v\v\v\v\v\v\v\v\v\xeb\v\xeb\v\xeb\xac\xe5\x05\x05\x05\x05\x05\x05\x05\x05\x05\x05\x05\x05\x05\x05\x05\x05\x05\x05\x05\x05\x05\x05\x05\x05\x05\x05\xe5\xe5\xe5\x05\xe5D\xe5\xe5\xe5\xe5\xe5\xe5\xe5\xe5\xe5\xe5\xe5\xe5\xe5\xe5\xe5\xe5\xe5\xe5\xe5\xe5\xe5\xe5\xe5\xe5\xe5\xe5\xe8\x8a\xe5\xe5\x05\xe5\x05\xe5\xcd\x05\xe5\x05\x05\x05\x05\x05\x05\x05\x05\x05\x8a\x05\x05\x05\x05\x05\x05\x05\x05\x05\x05f\x05\xe5\x05\xe5\xac\xe5\x05\x05\x05\x05\x05\x05\x05\x05\x05\x05\x05\x05\x05\x05\x05\x05\x05\x05\x05\x05\x05\x05\x05\x05\x05\x05\xe5\xe5\xe5\x05\xe5D\xe5\xe5\xe5\xe5\xe5\xe5\xe5\xe5\xe5\xe5\xe5\xe5\xe5\xe5\xe5\xe5\xe5\xe5\xe5\xe5\xe5\xe5\xe5\xe5\xe5\xe5\xe5\x8a\xe5\xe5\x05\xe5\x05\xe5\xcd\x05\xe5\x05\x05\x05\x05\x05\x05\x05\x05\x05\x8a\x05\x05\x05\x05\x05\x05\x05\x05\x05\x05f\x05\xe5\x05\xe5\xac\xe7\xe7\xe7\xe7\xe7\xe7\xe7\xe7\xe7\xe7\xe7\xe7\xe7\xe7\xe7\xe7\xe7\xe7\xe7\xe7\xe7\xe7\xe7\xe7\xe7\xe7\xe7\xe7\xe7\xe7\xe7\xe7D\xe7\xe7\xe7\xe7\xe7\xe7\xe7\xe7\xe7\xe7\xe7\xe7\xe7\xe7\xe7\xe7\xe7\xe7\xe7\xe7\xe7\xe7\xe7\xe7\xe7\xe7\xe7\x8a\xe7\xe7\xe7\xe7\xe7\xe7\xcd\xe7\xe7\xe7\xe7\xe7\xe7\xe7\xe7\xe7\xe7\xe7\x8a\xe7\x07\x07\x07\x07\x07\x07\x07\x07\x07\xe7\xe7\xe7\xe7\xe7\xac\xe7\xe7\xe7\xe7\xe7\xe7\xe7\xe7\xe7\xe7\xe7\xe7\xe7\xe7\xe7\xe7\xe7\xe7\xe7\xe7\xe7\xe7\xe7\xe7\xe7\xe7\xe7\xe7\xe7\xe7\xe7\xe7D\xe7\xe7\xe7\xe7\xe7\xe7\xe7\xe7\xe7\xe7\xe7\xe7\xe7\xe7\xe7\xe7\xe7\xe7\xe7\xe7\xe7\xe7\xe7\xe7\xe7\xe7\xe7\x8a\xe7\xe7\xe7\xe7\xe7\xe7\xcd\xe7\xe7\xe7\xe7\xe7\xe7\xe7\xe7\xe7\xe7\xe7\x8a\x07\x07\x07\x07\x07\x07\x07\x07\x07\x07\xe7\xe7\xe7\xe7\xe7\xac\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\x05\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\xeb\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\xeb\xeb\xeb\v\xeb\xeb\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\xeb\xea\xeb\xeb\v\xeb\v\xeb\xcd\v\xeb\v\v\v\v\v\v\v\v\x10\xea\v\v\v\v\v\v\v\v\v\v\xeb\v\xeb\v\xeb\xac\xeb\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\xeb\xeb\xeb\v\xeb\xeb\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\xeb\xea\xeb\xeb\v\xeb\v\xeb\xcd\v\xeb\v\v\v\v\v\v\v\v\x12\n\v\v\v\v\v\v\v\v\v\v\xeb\v\xeb\v\xeb\xac\xeb\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\xeb\xeb\xeb\v\xeb\xeb\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\xeb\xea\xeb\xeb\v\xeb\v\xeb\xcd\v\xeb\v\v\v\v\v\v\v\v\v\n\v\v\v\v\v\v\v\v\v\v\xeb\v\xeb\v\xeb\xac\xec\f\f\f\f\f\f\f\f\f\f\f\f\f\f\f\f\f\f\f\f\f\f\f\f\f\f\xec\xec\xec\f\xec\xec\f\f\f\f\f\f\f\f\f\f\f\f\f\f\f\f\f\f\f\f\f\f\f\f\f\f\xec\xec\xec\xec\f\xec\f\xec\xcd\f\xec\f\f\f\f\f\f\f\f\f\xec\f\f\f\f\f\f\f\f\f\f\xec\f\xec\f\xec\f\xed\r\r\r\r\r\r\r\r\r\r\r\r\r\r\r\r\r\r\r\r\r\r\r\r\r\r\xed\xed\xed\r\xed\xed\r\r\r\r\r\r\r\r\r\r\r\r\r\r\r\r\r\r\r\r\r\r\r\r\r\r\xed\xed\xed\xed\r\xed\r\xed\xed\r\xed\r\r\r\r\r\r\r\r\r\xed\r\r\r\r\r\r\r\r\r\r\xed\r\xed\r\xed\r\xe1\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\xe1\xe1\xe1\x01\xe1\xe1\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\xe1\xea\xe1\xe1\x01\xe1\x01\xe1\xcd\x01\xe1\x01\x01\x01\x01\x01\x01\x01\x01\x0f\xea\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01"\x01\xe1\x01\xe1\xac\xe1\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\xe1\xe1\xe1\x01\xe1\xe1\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\xe1\xe9\xe1\xe1\x01\xe1\x01\xe1\xcd\x01\xe1\x01\x01\x01\x01\x01\x01\x01\x01\x01\t\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01"\x01\xe1\x01\xe1\xac\xeb\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\xeb\xeb\xeb\v\xeb\xeb\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\xeb\xea\xeb\xeb\v\xeb\v\xeb\xcd\v\xeb\v\v\v\v\v\v\v\v\x11\xea\v\v\v\v\v\v\v\v\v\v\xeb\v\xeb\v\xeb\xac\xeb\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\xeb\xeb\xeb\v\xeb\xeb\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\xeb\xe9\xeb\xeb\v\xeb\v\xeb\xcd\v\xeb\v\v\v\v\v\v\v\v\v\t\v\v\v\v\v\v\v\v\v\v\xeb\v\xeb\v\xeb\xac\xeb\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\xeb\xeb\xeb\v\xeb\xeb\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\xeb\xea\xeb\xeb\v\xeb\v\xeb\xcd\v\xeb\v\v\v\v\v\v\v\v\x13\xea\v\v\v\v\v\v\v\v\v\v\xeb\v\xeb\v\xeb\xac\xeb\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\xeb\xeb\xeb\v\xeb\xeb\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\xeb\xea\xeb\xeb\v\xeb\v\xeb\xcd\v\xeb\v\v\v\v\v\v\v\v\v\xea\v\v\v\v\v\v\v\v\v\v\xeb\v\xeb\v\xeb\xac\xf5\x15\x15\x15\x15\x15\x15\x15\x15\x15\x15\x15\x15\x15\x15\x15\x15\x15\x15\x15\x15\x15\x15\x15\x15\x15\x15\xf5\xf5\xf5\xf5\xf5\xf5\xf5\xf5\xf5\xf5\xf5\xf5\xf5\xf5\xf5\xf5\xf5\xf5\xf5\xf5\xf5\xf5\xf5\xf5\xf5\xf5\xf5\xf5\xf5\xf5\xf5\xf5\xf5\xf5\xf5\xf5\xf5\xf5\xf5\xf5\xf5\xf5\xf5\xf5\xf5\xf5\xf5\xf5\xf5\xf5\xf5\xf5\xf5\xf5\xf5\xf5\xf5\xf5\xf5\xf5\xf5\xf5\xf5\xf5\xf5\xf5\xf5\xf5\xf5\xf5\x15\x15\x15\x15\x15\x15\x15\x15\x15\x15\x15\x15\x15\x15\x15\x15\x15\x15\x15\x15\x15\x15\x15\x15\x15\x15\xf5\xf5\xf5\xf5\xf5\xf5\xf5\xf5\xf5\xf5\xf5\xf5\xf5\xf5\xf5\xf5\xf5\xf5\xf5\xf5\xf5\xf5\xf5\xf5\xf5\xf5\xf5\xf5\xf5\xf5\xf5\xf5\xf5\xf5\xf5\xf5\xf5\xf5\xf5\xf5\xf5\xf5\xf5\xf5\xf5\xf5\xf5\xf5\x15\xf5\x15\x15\xf5\x15\x15\x15\x15\x15\x15\x15\x15\x15\x15\xf5\xf5\xf5\xf5\xf5\xf5'.charCodeAt(d*96+r) +d=q&31 +e[q>>>5]=s}return d}, +qv(a){if(a.b===7&&B.a.u(a.a,"package")&&a.c<=0)return A.rb(a.a,a.e,a.f) +return-1}, +rb(a,b,c){var s,r,q +for(s=b,r=0;s=1)return a.$1(b) +return a.$0()}, +vr(a,b,c,d){if(d>=2)return a.$2(b,c) +if(d===1)return a.$1(b) +return a.$0()}, +vs(a,b,c,d,e){if(e>=3)return a.$3(b,c,d) +if(e===2)return a.$2(b,c) +if(e===1)return a.$1(b) +return a.$0()}, +vt(a,b,c,d,e,f){if(f>=4)return a.$4(b,c,d,e) +if(f===3)return a.$3(b,c,d) +if(f===2)return a.$2(b,c) +if(f===1)return a.$1(b) +return a.$0()}, +vu(a,b,c,d,e,f,g){if(g>=5)return a.$5(b,c,d,e,f) +if(g===4)return a.$4(b,c,d,e) +if(g===3)return a.$3(b,c,d) +if(g===2)return a.$2(b,c) +if(g===1)return a.$1(b) +return a.$0()}, +r3(a){return a==null||A.bQ(a)||typeof a=="number"||typeof a=="string"||t.gj.b(a)||t.p.b(a)||t.go.b(a)||t.dQ.b(a)||t.h7.b(a)||t.an.b(a)||t.bv.b(a)||t.h4.b(a)||t.gN.b(a)||t.E.b(a)||t.fd.b(a)}, +x0(a){if(A.r3(a))return a +return new A.nM(new A.dE(t.hg)).$1(a)}, +oO(a,b,c){return a[b].apply(a,c)}, +e1(a,b){var s,r +if(b==null)return new a() +if(b instanceof Array)switch(b.length){case 0:return new a() +case 1:return new a(b[0]) +case 2:return new a(b[0],b[1]) +case 3:return new a(b[0],b[1],b[2]) +case 4:return new a(b[0],b[1],b[2],b[3])}s=[null] +B.c.aG(s,b) +r=a.bind.apply(a,s) +String(r) +return new r()}, +T(a,b){var s=new A.n($.h,b.h("n<0>")),r=new A.a6(s,b.h("a6<0>")) +a.then(A.ck(new A.nQ(r),1),A.ck(new A.nR(r),1)) +return s}, +r2(a){return a==null||typeof a==="boolean"||typeof a==="number"||typeof a==="string"||a instanceof Int8Array||a instanceof Uint8Array||a instanceof Uint8ClampedArray||a instanceof Int16Array||a instanceof Uint16Array||a instanceof Int32Array||a instanceof Uint32Array||a instanceof Float32Array||a instanceof Float64Array||a instanceof ArrayBuffer||a instanceof DataView}, +rh(a){if(A.r2(a))return a +return new A.nD(new A.dE(t.hg)).$1(a)}, +nM:function nM(a){this.a=a}, +nQ:function nQ(a){this.a=a}, +nR:function nR(a){this.a=a}, +nD:function nD(a){this.a=a}, +ro(a,b){return Math.max(a,b)}, +xh(a){return Math.sqrt(a)}, +xg(a){return Math.sin(a)}, +wI(a){return Math.cos(a)}, +xn(a){return Math.tan(a)}, +wj(a){return Math.acos(a)}, +wk(a){return Math.asin(a)}, +wE(a){return Math.atan(a)}, +mR:function mR(a){this.a=a}, +d2:function d2(){}, +h1:function h1(){}, +hs:function hs(){}, +hB:function hB(){}, +hW:function hW(){}, +tF(a,b){var s=new A.ek(a,b,A.al(t.S,t.aR),A.eP(null,null,!0,t.al),new A.a6(new A.n($.h,t.D),t.h)) +s.hP(a,!1,b) +return s}, +ek:function ek(a,b,c,d,e){var _=this +_.a=a +_.c=b +_.d=0 +_.e=c +_.f=d +_.r=!1 +_.w=e}, +jW:function jW(a){this.a=a}, +jX:function jX(a,b){this.a=a +this.b=b}, +iB:function iB(a,b){this.a=a +this.b=b}, +fX:function fX(){}, +h5:function h5(a){this.a=a}, +h4:function h4(){}, +jY:function jY(a){this.a=a}, +jZ:function jZ(a){this.a=a}, +bZ:function bZ(){}, +ap:function ap(a,b){this.a=a +this.b=b}, +bf:function bf(a,b){this.a=a +this.b=b}, +aO:function aO(a){this.a=a}, +bm:function bm(a,b,c){this.a=a +this.b=b +this.c=c}, +bw:function bw(a){this.a=a}, +de:function de(a,b){this.a=a +this.b=b}, +cB:function cB(a,b){this.a=a +this.b=b}, +bW:function bW(a,b,c,d){var _=this +_.a=a +_.b=b +_.c=c +_.d=d}, +c2:function c2(a){this.a=a}, +bn:function bn(a,b){this.a=a +this.b=b}, +c1:function c1(a,b){this.a=a +this.b=b}, +c4:function c4(a,b){this.a=a +this.b=b}, +bV:function bV(a,b){this.a=a +this.b=b}, +c5:function c5(a){this.a=a}, +c3:function c3(a,b){this.a=a +this.b=b}, +bF:function bF(a){this.a=a}, +bI:function bI(a){this.a=a}, +uj(a,b,c){var s=null,r=t.S,q=A.f([],t.t) +r=new A.kS(a,!1,!0,A.al(r,t.x),A.al(r,t.g1),q,new A.fp(s,s,t.dn),A.ob(t.gw),new A.a6(new A.n($.h,t.D),t.h),A.eP(s,s,!1,t.bw)) +r.hR(a,!1,!0) +return r}, +kS:function kS(a,b,c,d,e,f,g,h,i,j){var _=this +_.a=a +_.b=b +_.c=c +_.d=d +_.f=_.e=0 +_.r=e +_.w=f +_.x=g +_.y=!1 +_.z=h +_.Q=i +_.as=j}, +kX:function kX(a){this.a=a}, +kY:function kY(a,b){this.a=a +this.b=b}, +kZ:function kZ(a,b){this.a=a +this.b=b}, +kT:function kT(a,b){this.a=a +this.b=b}, +kU:function kU(a,b){this.a=a +this.b=b}, +kW:function kW(a,b){this.a=a +this.b=b}, +kV:function kV(a){this.a=a}, +fj:function fj(a,b,c){this.a=a +this.b=b +this.c=c}, +i6:function i6(a){this.a=a}, +m3:function m3(a,b){this.a=a +this.b=b}, +m4:function m4(a,b){this.a=a +this.b=b}, +m1:function m1(){}, +lY:function lY(a,b){this.a=a +this.b=b}, +lZ:function lZ(){}, +m_:function m_(){}, +lX:function lX(){}, +m2:function m2(){}, +m0:function m0(){}, +ds:function ds(a,b){this.a=a +this.b=b}, +bK:function bK(a,b){this.a=a +this.b=b}, +xe(a,b){var s,r,q={} +q.a=s +q.a=null +s=new A.bU(new A.a8(new A.n($.h,b.h("n<0>")),b.h("a8<0>")),A.f([],t.bT),b.h("bU<0>")) +q.a=s +r=t.X +A.xf(new A.nS(q,a,b),A.u1([B.a_,s],r,r),t.H) +return q.a}, +oP(){var s=$.h.j(0,B.a_) +if(s instanceof A.bU&&s.c)throw A.b(B.M)}, +nS:function nS(a,b,c){this.a=a +this.b=b +this.c=c}, +bU:function bU(a,b,c){var _=this +_.a=a +_.b=b +_.c=!1 +_.$ti=c}, +ed:function ed(){}, +ao:function ao(){}, +ea:function ea(a,b){this.a=a +this.b=b}, +d0:function d0(a,b){this.a=a +this.b=b}, +qW(a){return"SAVEPOINT s"+a}, +qU(a){return"RELEASE s"+a}, +qV(a){return"ROLLBACK TO s"+a}, +jN:function jN(){}, +kK:function kK(){}, +lu:function lu(){}, +kF:function kF(){}, +jQ:function jQ(){}, +hA:function hA(){}, +k4:function k4(){}, +ic:function ic(){}, +mc:function mc(a,b,c){this.a=a +this.b=b +this.c=c}, +mh:function mh(a,b,c){this.a=a +this.b=b +this.c=c}, +mf:function mf(a,b,c){this.a=a +this.b=b +this.c=c}, +mg:function mg(a,b,c){this.a=a +this.b=b +this.c=c}, +me:function me(a,b,c){this.a=a +this.b=b +this.c=c}, +md:function md(a,b){this.a=a +this.b=b}, +iP:function iP(){}, +fn:function fn(a,b,c,d,e,f,g,h,i){var _=this +_.y=a +_.z=null +_.Q=b +_.as=c +_.at=d +_.ax=e +_.ay=f +_.ch=g +_.e=h +_.a=i +_.b=0 +_.d=_.c=!1}, +n1:function n1(a){this.a=a}, +n2:function n2(a){this.a=a}, +h2:function h2(){}, +jV:function jV(a,b){this.a=a +this.b=b}, +jU:function jU(a){this.a=a}, +id:function id(a,b){var _=this +_.e=a +_.a=b +_.b=0 +_.d=_.c=!1}, +f7:function f7(a,b,c){var _=this +_.e=a +_.f=null +_.r=b +_.a=c +_.b=0 +_.d=_.c=!1}, +mA:function mA(a,b){this.a=a +this.b=b}, +pT(a,b){var s,r,q,p=A.al(t.N,t.S) +for(s=a.length,r=0;r3)throw A.b("Expected two or three arguments to regexp") +s=a.j(0,0) +q=a.j(0,1) +if(s==null||q==null)return null +if(typeof s!="string"||typeof q!="string")throw A.b("Expected two strings as parameters to regexp") +if(g===3){p=a.j(0,2) +if(A.bv(p)){k=(p&1)===1 +j=(p&2)!==2 +i=(p&4)===4 +h=(p&8)===8}}r=null +try{o=k +n=j +m=i +r=A.H(s,n,h,o,m)}catch(l){if(A.G(l) instanceof A.aD)throw A.b("Invalid regex") +else throw l}o=r.b +return o.test(q)}, +vz(a){var s,r,q=a.a.b +if(q<2||q>3)throw A.b("Expected 2 or 3 arguments to moor_contains") +s=a.j(0,0) +r=a.j(0,1) +if(s==null||r==null)return null +if(typeof s!="string"||typeof r!="string")throw A.b("First two args to contains must be strings") +return q===3&&a.j(0,2)===1?B.a.I(s,r):B.a.I(s.toLowerCase(),r.toLowerCase())}, +k5:function k5(){}, +ny:function ny(a){this.a=a}, +ho:function ho(a){var _=this +_.a=$ +_.b=!1 +_.d=null +_.e=a}, +kx:function kx(a,b){this.a=a +this.b=b}, +ky:function ky(a,b){this.a=a +this.b=b}, +bo:function bo(){this.a=null}, +kA:function kA(a,b,c,d,e){var _=this +_.a=a +_.b=b +_.c=c +_.d=d +_.e=e}, +kB:function kB(a,b,c){this.a=a +this.b=b +this.c=c}, +kC:function kC(a,b){this.a=a +this.b=b}, +uB(a,b,c,d){var s,r=null,q=new A.hO(t.a7),p=t.X,o=A.eP(r,r,!1,p),n=A.eP(r,r,!1,p),m=A.pv(new A.ar(n,A.r(n).h("ar<1>")),new A.dP(o),!0,p) +q.a=m +p=A.pv(new A.ar(o,A.r(o).h("ar<1>")),new A.dP(n),!0,p) +q.b=p +s=new A.i6(A.od(c)) +a.onmessage=A.bu(new A.lU(b,q,d,s)) +m=m.b +m===$&&A.x() +new A.ar(m,A.r(m).h("ar<1>")).ez(new A.lV(d,s,a),new A.lW(b,a)) +return p}, +lU:function lU(a,b,c,d){var _=this +_.a=a +_.b=b +_.c=c +_.d=d}, +lV:function lV(a,b,c){this.a=a +this.b=b +this.c=c}, +lW:function lW(a,b){this.a=a +this.b=b}, +jR:function jR(a,b,c){var _=this +_.a=a +_.b=b +_.c=c +_.d=null}, +jT:function jT(a){this.a=a}, +jS:function jS(a,b){this.a=a +this.b=b}, +od(a){var s +A:{if(a<=0){s=B.r +break A}if(1===a){s=B.aR +break A}if(2===a){s=B.aS +break A}if(3===a){s=B.aT +break A}if(a>3){s=B.t +break A}s=A.C(A.e8(null))}return s}, +pS(a){if("v" in a)return A.od(A.A(A.X(a.v))) +else return B.r}, +on(a){var s,r,q,p,o,n,m,l,k,j=A.a_(a.type),i=a.payload +A:{if("Error"===j){s=new A.dv(A.a_(A.a9(i))) +break A}if("ServeDriftDatabase"===j){A.a9(i) +r=A.pS(i) +s=A.bt(A.a_(i.sqlite)) +q=A.a9(i.port) +p=A.o1(B.aG,A.a_(i.storage)) +o=A.a_(i.database) +n=A.oG(i.initPort) +m=r.c +l=m<2||A.bg(i.migrations) +s=new A.dk(s,q,p,o,n,r,l,m<3||A.bg(i.new_serialization)) +break A}if("StartFileSystemServer"===j){s=new A.eN(A.a9(i)) +break A}if("RequestCompatibilityCheck"===j){s=new A.di(A.a_(i)) +break A}if("DedicatedWorkerCompatibilityResult"===j){A.a9(i) +k=A.f([],t.L) +if("existing" in i)B.c.aG(k,A.pq(t.c.a(i.existing))) +s=A.bg(i.supportsNestedWorkers) +q=A.bg(i.canAccessOpfs) +p=A.bg(i.supportsSharedArrayBuffers) +o=A.bg(i.supportsIndexedDb) +n=A.bg(i.indexedDbExists) +m=A.bg(i.opfsExists) +m=new A.ej(s,q,p,o,k,A.pS(i),n,m) +s=m +break A}if("SharedWorkerCompatibilityResult"===j){s=A.uk(t.c.a(i)) +break A}if("DeleteDatabase"===j){s=i==null?A.oH(i):i +t.c.a(s) +q=$.p6().j(0,A.a_(s[0])) +q.toString +s=new A.h3(new A.ah(q,A.a_(s[1]))) +break A}s=A.C(A.J("Unknown type "+j,null))}return s}, +uk(a){var s,r,q=new A.l5(a) +if(a.length>5){s=A.pq(t.c.a(a[5])) +r=a.length>6?A.od(A.A(A.X(a[6]))):B.r}else{s=B.B +r=B.r}return new A.c6(q.$1(0),q.$1(1),q.$1(2),s,r,q.$1(3),q.$1(4))}, +pq(a){var s,r,q=A.f([],t.L),p=B.c.bu(a,t.m),o=p.$ti +p=new A.b3(p,p.gl(0),o.h("b3")) +o=o.h("v.E") +while(p.k()){s=p.d +if(s==null)s=o.a(s) +r=$.p6().j(0,A.a_(s.l)) +r.toString +q.push(new A.ah(r,A.a_(s.n)))}return q}, +pp(a){var s,r,q,p,o=A.f([],t.W) +for(s=a.length,r=0;r"))) +while(i.k()){l=i.gm() +if(J.aj(l.name,a)){q=!0 +s=1 +break A}}q=!1 +s=1 +break +case 8:k=n.open(a,1) +k.onupgradeneeded=A.bu(new A.nB(g,k)) +s=10 +return A.c(A.jm(k,t.m),$async$e2) +case 10:j=c +if(g.a==null)g.a=!0 +j.close() +s=g.a===!1?11:12 +break +case 11:s=13 +return A.c(A.jm(n.deleteDatabase(a),t.X),$async$e2) +case 13:case 12:p=2 +s=6 +break +case 4:p=3 +f=o.pop() +s=6 +break +case 3:s=2 +break +case 6:i=g.a +q=i===!0 +s=1 +break +case 1:return A.j(q,r) +case 2:return A.i(o.at(-1),r)}}) +return A.k($async$e2,r)}, +nE(a){var s=0,r=A.l(t.H),q +var $async$nE=A.m(function(b,c){if(b===1)return A.i(c,r) +for(;;)switch(s){case 0:q=v.G +s="indexedDB" in q?2:3 +break +case 2:s=4 +return A.c(A.jm(A.a9(q.indexedDB).deleteDatabase(a),t.X),$async$nE) +case 4:case 3:return A.j(null,r)}}) +return A.k($async$nE,r)}, +iZ(){var s=null +return A.xc()}, +xc(){var s=0,r=A.l(t.A),q,p=2,o=[],n,m,l,k,j,i,h +var $async$iZ=A.m(function(a,b){if(a===1){o.push(b) +s=p}for(;;)switch(s){case 0:j=null +i=A.p1() +if(i==null){q=null +s=1 +break}m=t.m +s=3 +return A.c(A.T(i.getDirectory(),m),$async$iZ) +case 3:n=b +p=5 +l=j +if(l==null)l={} +s=8 +return A.c(A.T(n.getDirectoryHandle("drift_db",l),m),$async$iZ) +case 8:m=b +q=m +s=1 +break +p=2 +s=7 +break +case 5:p=4 +h=o.pop() +q=null +s=1 +break +s=7 +break +case 4:s=2 +break +case 7:case 1:return A.j(q,r) +case 2:return A.i(o.at(-1),r)}}) +return A.k($async$iZ,r)}, +e5(){var s=0,r=A.l(t.u),q,p=2,o=[],n=[],m,l,k,j,i,h,g,f +var $async$e5=A.m(function(a,b){if(a===1){o.push(b) +s=p}for(;;)switch(s){case 0:s=3 +return A.c(A.iZ(),$async$e5) +case 3:g=b +if(g==null){q=B.A +s=1 +break}j=t.cO +if(!(v.G.Symbol.asyncIterator in g))A.C(A.J("Target object does not implement the async iterable interface",null)) +m=new A.fc(new A.nP(),new A.e9(g,j),j.h("fc")) +l=A.f([],t.s) +j=new A.dO(A.cU(m,"stream",t.K)) +p=4 +i=t.m +case 7:s=9 +return A.c(j.k(),$async$e5) +case 9:if(!b){s=8 +break}k=j.gm() +s=J.aj(k.kind,"directory")?10:11 +break +case 10:p=13 +s=16 +return A.c(A.T(k.getFileHandle("database"),i),$async$e5) +case 16:J.nW(l,k.name) +p=4 +s=15 +break +case 13:p=12 +f=o.pop() +s=15 +break +case 12:s=4 +break +case 15:case 11:s=7 +break +case 8:n.push(6) +s=5 +break +case 4:n=[2] +case 5:p=2 +s=17 +return A.c(j.K(),$async$e5) +case 17:s=n.pop() +break +case 6:q=l +s=1 +break +case 1:return A.j(q,r) +case 2:return A.i(o.at(-1),r)}}) +return A.k($async$e5,r)}, +fE(a){return A.wK(a)}, +wK(a){var s=0,r=A.l(t.H),q,p=2,o=[],n,m,l,k,j +var $async$fE=A.m(function(b,c){if(b===1){o.push(c) +s=p}for(;;)switch(s){case 0:k=A.p1() +if(k==null){s=1 +break}m=t.m +s=3 +return A.c(A.T(k.getDirectory(),m),$async$fE) +case 3:n=c +p=5 +s=8 +return A.c(A.T(n.getDirectoryHandle("drift_db"),m),$async$fE) +case 8:n=c +s=9 +return A.c(A.T(n.removeEntry(a,{recursive:!0}),t.X),$async$fE) +case 9:p=2 +s=7 +break +case 5:p=4 +j=o.pop() +s=7 +break +case 4:s=2 +break +case 7:case 1:return A.j(q,r) +case 2:return A.i(o.at(-1),r)}}) +return A.k($async$fE,r)}, +jm(a,b){var s=new A.n($.h,b.h("n<0>")),r=new A.a8(s,b.h("a8<0>")) +A.aI(a,"success",new A.jp(r,a,b),!1) +A.aI(a,"error",new A.jq(r,a),!1) +A.aI(a,"blocked",new A.jr(r,a),!1) +return s}, +nB:function nB(a,b){this.a=a +this.b=b}, +nP:function nP(){}, +h6:function h6(a,b){this.a=a +this.b=b}, +k3:function k3(a,b){this.a=a +this.b=b}, +k0:function k0(a){this.a=a}, +k_:function k_(a){this.a=a}, +k1:function k1(a,b,c){this.a=a +this.b=b +this.c=c}, +k2:function k2(a,b,c){this.a=a +this.b=b +this.c=c}, +mp:function mp(a,b){this.a=a +this.b=b}, +dj:function dj(a,b,c){var _=this +_.a=a +_.b=b +_.c=0 +_.d=c}, +kQ:function kQ(a){this.a=a}, +lH:function lH(a,b){this.a=a +this.b=b}, +jp:function jp(a,b,c){this.a=a +this.b=b +this.c=c}, +jq:function jq(a,b){this.a=a +this.b=b}, +jr:function jr(a,b){this.a=a +this.b=b}, +l_:function l_(a,b){this.a=a +this.b=null +this.c=b}, +l4:function l4(a){this.a=a}, +l0:function l0(a,b){this.a=a +this.b=b}, +l3:function l3(a,b,c){this.a=a +this.b=b +this.c=c}, +l1:function l1(a){this.a=a}, +l2:function l2(a,b,c){this.a=a +this.b=b +this.c=c}, +cc:function cc(a,b){this.a=a +this.b=b}, +bO:function bO(a,b){this.a=a +this.b=b}, +i3:function i3(a,b,c,d,e){var _=this +_.e=a +_.f=null +_.r=b +_.w=c +_.x=d +_.a=e +_.b=0 +_.d=_.c=!1}, +iS:function iS(a,b,c,d,e,f,g){var _=this +_.Q=a +_.as=b +_.at=c +_.b=null +_.d=_.c=!1 +_.e=d +_.f=e +_.r=f +_.x=g +_.y=$ +_.a=!1}, +jv(a,b){if(a==null)a="." +return new A.fY(b,a)}, +oM(a){return a}, +rc(a,b){var s,r,q,p,o,n,m,l +for(s=b.length,r=1;r=1;s=q){q=s-1 +if(b[q]!=null)break}p=new A.aA("") +o=a+"(" +p.a=o +n=A.N(b) +m=n.h("cC<1>") +l=new A.cC(b,0,s,m) +l.hS(b,0,s,n.c) +m=o+new A.E(l,new A.nz(),m.h("E")).aq(0,", ") +p.a=m +p.a=m+("): part "+(r-1)+" was null, but part "+r+" was not.") +throw A.b(A.J(p.i(0),null))}}, +fY:function fY(a,b){this.a=a +this.b=b}, +jw:function jw(){}, +jx:function jx(){}, +nz:function nz(){}, +dJ:function dJ(a){this.a=a}, +dK:function dK(a){this.a=a}, +kt:function kt(){}, +df(a,b){var s,r,q,p,o,n=b.hz(a) +b.a9(a) +if(n!=null)a=B.a.N(a,n.length) +s=t.s +r=A.f([],s) +q=A.f([],s) +s=a.length +if(s!==0&&b.D(a.charCodeAt(0))){q.push(a[0]) +p=1}else{q.push("") +p=0}for(o=p;o")),r=new A.a8(s,b.h("a8<0>")) +A.aI(a,"success",new A.jn(r,a,b),!1) +A.aI(a,"error",new A.jo(r,a),!1) +return s}, +tD(a,b){var s=new A.n($.h,b.h("n<0>")),r=new A.a8(s,b.h("a8<0>")) +A.aI(a,"success",new A.js(r,a,b),!1) +A.aI(a,"error",new A.jt(r,a),!1) +A.aI(a,"blocked",new A.ju(r,a),!1) +return s}, +cI:function cI(a,b){var _=this +_.c=_.b=_.a=null +_.d=a +_.$ti=b}, +mq:function mq(a,b){this.a=a +this.b=b}, +mr:function mr(a,b){this.a=a +this.b=b}, +jn:function jn(a,b,c){this.a=a +this.b=b +this.c=c}, +jo:function jo(a,b){this.a=a +this.b=b}, +js:function js(a,b,c){this.a=a +this.b=b +this.c=c}, +jt:function jt(a,b){this.a=a +this.b=b}, +ju:function ju(a,b){this.a=a +this.b=b}, +lN:function lN(a){this.a=a}, +lO:function lO(a){this.a=a}, +lQ(a){var s=0,r=A.l(t.ab),q,p,o,n +var $async$lQ=A.m(function(b,c){if(b===1)return A.i(c,r) +for(;;)switch(s){case 0:p=v.G +o=a.gha()?new p.URL(a.i(0)):new p.URL(a.i(0),A.eS().i(0)) +n=A +s=3 +return A.c(A.T(p.fetch(o,null),t.m),$async$lQ) +case 3:q=n.lP(c,null) +s=1 +break +case 1:return A.j(q,r)}}) +return A.k($async$lQ,r)}, +lP(a,b){var s=0,r=A.l(t.ab),q,p,o,n,m +var $async$lP=A.m(function(c,d){if(c===1)return A.i(d,r) +for(;;)switch(s){case 0:p=new A.fZ(A.al(t.S,t.b9)) +o=A +n=A +m=A +s=3 +return A.c(new A.lN(p).d2(a),$async$lP) +case 3:q=new o.i5(new n.lR(m.uA(d,p))) +s=1 +break +case 1:return A.j(q,r)}}) +return A.k($async$lP,r)}, +i5:function i5(a){this.a=a}, +du:function du(a,b,c,d,e){var _=this +_.d=a +_.e=b +_.r=c +_.b=d +_.a=e}, +i4:function i4(a,b){this.a=a +this.b=b +this.c=0}, +pV(a){var s=J.aj(a.byteLength,8) +if(!s)throw A.b(A.J("Must be 8 in length",null)) +s=v.G.Int32Array +return new A.kP(t.ha.a(A.e1(s,[a])))}, +u4(a){return B.h}, +u5(a){var s=a.b +return new A.P(s.getInt32(0,!1),s.getInt32(4,!1),s.getInt32(8,!1))}, +u6(a){var s=a.b +return new A.aV(B.j.cS(A.oi(a.a,16,s.getInt32(12,!1))),s.getInt32(0,!1),s.getInt32(4,!1),s.getInt32(8,!1))}, +kP:function kP(a){this.b=a}, +bp:function bp(a,b,c){this.a=a +this.b=b +this.c=c}, +ac:function ac(a,b,c,d,e){var _=this +_.c=a +_.d=b +_.a=c +_.b=d +_.$ti=e}, +bC:function bC(){}, +b2:function b2(){}, +P:function P(a,b,c){this.a=a +this.b=b +this.c=c}, +aV:function aV(a,b,c,d){var _=this +_.d=a +_.a=b +_.b=c +_.c=d}, +i2(a){var s=0,r=A.l(t.ei),q,p,o,n,m,l,k,j,i +var $async$i2=A.m(function(b,c){if(b===1)return A.i(c,r) +for(;;)switch(s){case 0:k=t.m +s=3 +return A.c(A.T(A.p0().getDirectory(),k),$async$i2) +case 3:j=c +i=$.fJ().aM(0,a.root) +p=i.length,o=0 +case 4:if(!(o")),A.xr(),r.h("aE<1,Z>")),t.a))}if(!B.a.I(a,q))return new A.bk(A.aM(A.f([A.q5(a)],t.J),t.a)) +return new A.bk(A.aM(new A.E(A.f(a.split(q),t.s),A.xq(),t.fe),t.a))}, +bk:function bk(a){this.a=a}, +jd:function jd(){}, +ji:function ji(){}, +jh:function jh(){}, +jf:function jf(){}, +jg:function jg(a){this.a=a}, +je:function je(a){this.a=a}, +tR(a){return A.pt(a)}, +pt(a){return A.hc(a,new A.kf(a))}, +tQ(a){return A.tN(a)}, +tN(a){return A.hc(a,new A.kd(a))}, +tK(a){return A.hc(a,new A.ka(a))}, +tO(a){return A.tL(a)}, +tL(a){return A.hc(a,new A.kb(a))}, +tP(a){return A.tM(a)}, +tM(a){return A.hc(a,new A.kc(a))}, +hd(a){if(B.a.I(a,$.rx()))return A.bt(a) +else if(B.a.I(a,$.ry()))return A.qC(a,!0) +else if(B.a.u(a,"/"))return A.qC(a,!1) +if(B.a.I(a,"\\"))return $.th().hs(a) +return A.bt(a)}, +hc(a,b){var s,r +try{s=b.$0() +return s}catch(r){if(A.G(r) instanceof A.aD)return new A.bs(A.am(null,"unparsed",null,null),a) +else throw r}}, +L:function L(a,b,c,d){var _=this +_.a=a +_.b=b +_.c=c +_.d=d}, +kf:function kf(a){this.a=a}, +kd:function kd(a){this.a=a}, +ke:function ke(a){this.a=a}, +ka:function ka(a){this.a=a}, +kb:function kb(a){this.a=a}, +kc:function kc(a){this.a=a}, +hp:function hp(a){this.a=a +this.b=$}, +q4(a){if(t.a.b(a))return a +if(a instanceof A.bk)return a.hr() +return new A.hp(new A.lq(a))}, +q5(a){var s,r,q +try{if(a.length===0){r=A.q1(A.f([],t.e),null) +return r}if(B.a.I(a,$.ta())){r=A.uq(a) +return r}if(B.a.I(a,"\tat ")){r=A.up(a) +return r}if(B.a.I(a,$.t0())||B.a.I(a,$.rZ())){r=A.uo(a) +return r}if(B.a.I(a,u.q)){r=A.tx(a).hr() +return r}if(B.a.I(a,$.t3())){r=A.q2(a) +return r}r=A.q3(a) +return r}catch(q){r=A.G(q) +if(r instanceof A.aD){s=r +throw A.b(A.af(s.a+"\nStack trace:\n"+a,null,null))}else throw q}}, +us(a){return A.q3(a)}, +q3(a){var s=A.aM(A.ut(a),t.B) +return new A.Z(s)}, +ut(a){var s,r=B.a.eL(a),q=$.pa(),p=t.U,o=new A.aY(A.f(A.bi(r,q,"").split("\n"),t.s),new A.lr(),p) +if(!o.gq(0).k())return A.f([],t.e) +r=A.ol(o,o.gl(0)-1,p.h("d.E")) +r=A.ht(r,A.wQ(),A.r(r).h("d.E"),t.B) +s=A.aw(r,A.r(r).h("d.E")) +if(!B.a.ej(o.gE(0),".da"))s.push(A.pt(o.gE(0))) +return s}, +uq(a){var s=A.b5(A.f(a.split("\n"),t.s),1,null,t.N).hK(0,new A.lp()),r=t.B +r=A.aM(A.ht(s,A.rj(),s.$ti.h("d.E"),r),r) +return new A.Z(r)}, +up(a){var s=A.aM(new A.aE(new A.aY(A.f(a.split("\n"),t.s),new A.lo(),t.U),A.rj(),t.M),t.B) +return new A.Z(s)}, +uo(a){var s=A.aM(new A.aE(new A.aY(A.f(B.a.eL(a).split("\n"),t.s),new A.lm(),t.U),A.wO(),t.M),t.B) +return new A.Z(s)}, +ur(a){return A.q2(a)}, +q2(a){var s=a.length===0?A.f([],t.e):new A.aE(new A.aY(A.f(B.a.eL(a).split("\n"),t.s),new A.ln(),t.U),A.wP(),t.M) +s=A.aM(s,t.B) +return new A.Z(s)}, +q1(a,b){var s=A.aM(a,t.B) +return new A.Z(s)}, +Z:function Z(a){this.a=a}, +lq:function lq(a){this.a=a}, +lr:function lr(){}, +lp:function lp(){}, +lo:function lo(){}, +lm:function lm(){}, +ln:function ln(){}, +lt:function lt(){}, +ls:function ls(a){this.a=a}, +bs:function bs(a,b){this.a=a +this.w=b}, +ef:function ef(a){var _=this +_.b=_.a=$ +_.c=null +_.d=!1 +_.$ti=a}, +f1:function f1(a,b,c){this.a=a +this.b=b +this.$ti=c}, +f0:function f0(a,b){this.b=a +this.a=b}, +pv(a,b,c,d){var s,r={} +r.a=a +s=new A.ep(d.h("ep<0>")) +s.hQ(b,!0,r,d) +return s}, +ep:function ep(a){var _=this +_.b=_.a=$ +_.c=null +_.d=!1 +_.$ti=a}, +km:function km(a,b){this.a=a +this.b=b}, +kl:function kl(a){this.a=a}, +f9:function f9(a,b,c,d){var _=this +_.a=a +_.b=b +_.c=c +_.e=_.d=!1 +_.r=_.f=null +_.w=d}, +hO:function hO(a){this.b=this.a=$ +this.$ti=a}, +eO:function eO(){}, +dq:function dq(){}, +iu:function iu(){}, +br:function br(a,b){this.a=a +this.b=b}, +aI(a,b,c,d){var s +if(c==null)s=null +else{s=A.rd(new A.my(c),t.m) +s=s==null?null:A.bu(s)}s=new A.im(a,b,s,!1) +s.e3() +return s}, +rd(a,b){var s=$.h +if(s===B.d)return a +return s.ee(a,b)}, +o2:function o2(a,b){this.a=a +this.$ti=b}, +f6:function f6(a,b,c,d){var _=this +_.a=a +_.b=b +_.c=c +_.$ti=d}, +im:function im(a,b,c,d){var _=this +_.a=0 +_.b=a +_.c=b +_.d=c +_.e=d}, +my:function my(a){this.a=a}, +mz:function mz(a){this.a=a}, +oZ(a){if(typeof dartPrint=="function"){dartPrint(a) +return}if(typeof console=="object"&&typeof console.log!="undefined"){console.log(a) +return}if(typeof print=="function"){print(a) +return}throw"Unable to print message: "+String(a)}, +hm(a,b,c,d,e,f){var s +if(c==null)return a[b]() +else if(d==null)return a[b](c) +else if(e==null)return a[b](c,d) +else{s=a[b](c,d,e) +return s}}, +oS(){var s,r,q,p,o=null +try{o=A.eS()}catch(s){if(t.g8.b(A.G(s))){r=$.nr +if(r!=null)return r +throw s}else throw s}if(J.aj(o,$.qT)){r=$.nr +r.toString +return r}$.qT=o +if($.p5()===$.cY())r=$.nr=o.hp(".").i(0) +else{q=o.eK() +p=q.length-1 +r=$.nr=p===0?q:B.a.p(q,0,p)}return r}, +rm(a){var s +if(!(a>=65&&a<=90))s=a>=97&&a<=122 +else s=!0 +return s}, +ri(a,b){var s,r,q=null,p=a.length,o=b+2 +if(p0)throw A.b(A.k6("BigInt value exceeds the range of 64 bits")) +return a}, +uh(a){var s,r=a.a,q=a.b,p=r.d,o=p.sqlite3_value_type(q) +A:{s=null +if(1===o){r=A.A(v.G.Number(p.sqlite3_value_int64(q))) +break A}if(2===o){r=p.sqlite3_value_double(q) +break A}if(3===o){o=p.sqlite3_value_bytes(q) +o=A.cd(r.b,p.sqlite3_value_text(q),o) +r=o +break A}if(4===o){o=p.sqlite3_value_bytes(q) +o=A.qd(r.b,p.sqlite3_value_blob(q),o) +r=o +break A}r=s +break A}return r}, +o5(a,b){var s,r +for(s=b,r=0;r<16;++r)s+=A.aP("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ012346789".charCodeAt(a.hf(61))) +return s.charCodeAt(0)==0?s:s}, +kO(a){var s=0,r=A.l(t.E),q +var $async$kO=A.m(function(b,c){if(b===1)return A.i(c,r) +for(;;)switch(s){case 0:s=3 +return A.c(A.T(a.arrayBuffer(),t.v),$async$kO) +case 3:q=c +s=1 +break +case 1:return A.j(q,r)}}) +return A.k($async$kO,r)}, +pX(a,b,c){var s=v.G.DataView,r=[a] +r.push(b) +r.push(c) +return t.gT.a(A.e1(s,r))}, +oi(a,b,c){var s=v.G.Uint8Array,r=[a] +r.push(b) +r.push(c) +return t.Z.a(A.e1(s,r))}, +tu(a,b){v.G.Atomics.notify(a,b,1/0)}, +p0(){var s=v.G.navigator +if("storage" in s)return s.storage +return null}, +k7(a,b,c){var s=a.read(b,c) +return s}, +o3(a,b,c){var s=a.write(b,c) +return s}, +ps(a,b){return A.T(a.removeEntry(b,{recursive:!1}),t.X)}, +x2(){var s=v.G +if(A.ku(s,"DedicatedWorkerGlobalScope"))new A.jR(s,new A.bo(),new A.h6(A.al(t.N,t.fE),null)).T() +else if(A.ku(s,"SharedWorkerGlobalScope"))new A.l_(s,new A.h6(A.al(t.N,t.fE),null)).T()}},B={} +var w=[A,J,B] +var $={} +A.o9.prototype={} +J.hi.prototype={ +W(a,b){return a===b}, +gA(a){return A.eG(a)}, +i(a){return"Instance of '"+A.hG(a)+"'"}, +gV(a){return A.bR(A.oK(this))}} +J.hk.prototype={ +i(a){return String(a)}, +gA(a){return a?519018:218159}, +gV(a){return A.bR(t.y)}, +$iI:1, +$iK:1} +J.eu.prototype={ +W(a,b){return null==b}, +i(a){return"null"}, +gA(a){return 0}, +$iI:1, +$iR:1} +J.ev.prototype={$iz:1} +J.bY.prototype={ +gA(a){return 0}, +i(a){return String(a)}} +J.hF.prototype={} +J.cE.prototype={} +J.bz.prototype={ +i(a){var s=a[$.e6()] +if(s==null)return this.hL(a) +return"JavaScript function for "+J.b1(s)}} +J.aK.prototype={ +gA(a){return 0}, +i(a){return String(a)}} +J.d7.prototype={ +gA(a){return 0}, +i(a){return String(a)}} +J.u.prototype={ +bu(a,b){return new A.ak(a,A.N(a).h("@<1>").H(b).h("ak<1,2>"))}, +v(a,b){a.$flags&1&&A.y(a,29) +a.push(b)}, +d7(a,b){var s +a.$flags&1&&A.y(a,"removeAt",1) +s=a.length +if(b>=s)throw A.b(A.kN(b,null)) +return a.splice(b,1)[0]}, +cY(a,b,c){var s +a.$flags&1&&A.y(a,"insert",2) +s=a.length +if(b>s)throw A.b(A.kN(b,null)) +a.splice(b,0,c)}, +es(a,b,c){var s,r +a.$flags&1&&A.y(a,"insertAll",2) +A.pU(b,0,a.length,"index") +if(!t.Q.b(c))c=J.j4(c) +s=J.at(c) +a.length=a.length+s +r=b+s +this.M(a,r,a.length,a,b) +this.ad(a,b,r,c)}, +hl(a){a.$flags&1&&A.y(a,"removeLast",1) +if(a.length===0)throw A.b(A.e3(a,-1)) +return a.pop()}, +G(a,b){var s +a.$flags&1&&A.y(a,"remove",1) +for(s=0;s").H(c).h("E<1,2>"))}, +aq(a,b){var s,r=A.b4(a.length,"",!1,t.N) +for(s=0;ss)throw A.b(A.S(b,0,s,"start",null)) +if(cs)throw A.b(A.S(c,b,s,"end",null)) +if(b===c)return A.f([],A.N(a)) +return A.f(a.slice(b,c),A.N(a))}, +cm(a,b,c){A.bd(b,c,a.length) +return A.b5(a,b,c,A.N(a).c)}, +gF(a){if(a.length>0)return a[0] +throw A.b(A.az())}, +gE(a){var s=a.length +if(s>0)return a[s-1] +throw A.b(A.az())}, +M(a,b,c,d,e){var s,r,q,p,o +a.$flags&2&&A.y(a,5) +A.bd(b,c,a.length) +s=c-b +if(s===0)return +A.ab(e,"skipCount") +if(t.j.b(d)){r=d +q=e}else{r=J.e7(d,e).az(0,!1) +q=0}p=J.a0(r) +if(q+s>p.gl(r))throw A.b(A.py()) +if(q=0;--o)a[b+o]=p.j(r,q+o) +else for(o=0;o0){a[0]=q +a[1]=r}return}p=0 +if(A.N(a).c.b(null))for(o=0;o0)this.j6(a,p)}, +hG(a){return this.hH(a,null)}, +j6(a,b){var s,r=a.length +for(;s=r-1,r>0;r=s)if(a[s]===null){a[s]=void 0;--b +if(b===0)break}}, +d0(a,b){var s,r=a.length,q=r-1 +if(q<0)return-1 +q=0;--s)if(J.aj(a[s],b))return s +return-1}, +gB(a){return a.length===0}, +i(a){return A.o7(a,"[","]")}, +az(a,b){var s=A.f(a.slice(0),A.N(a)) +return s}, +cf(a){return this.az(a,!0)}, +gq(a){return new J.fK(a,a.length,A.N(a).h("fK<1>"))}, +gA(a){return A.eG(a)}, +gl(a){return a.length}, +j(a,b){if(!(b>=0&&b=0&&b=p){r.d=null +return!1}r.d=q[s] +r.c=s+1 +return!0}} +J.d6.prototype={ +ag(a,b){var s +if(ab)return 1 +else if(a===b){if(a===0){s=this.gew(b) +if(this.gew(a)===s)return 0 +if(this.gew(a))return-1 +return 1}return 0}else if(isNaN(a)){if(isNaN(b))return 0 +return 1}else return-1}, +gew(a){return a===0?1/a<0:a<0}, +l7(a){var s +if(a>=-2147483648&&a<=2147483647)return a|0 +if(isFinite(a)){s=a<0?Math.ceil(a):Math.floor(a) +return s+0}throw A.b(A.a3(""+a+".toInt()"))}, +jQ(a){var s,r +if(a>=0){if(a<=2147483647){s=a|0 +return a===s?s:s+1}}else if(a>=-2147483648)return a|0 +r=Math.ceil(a) +if(isFinite(r))return r +throw A.b(A.a3(""+a+".ceil()"))}, +i(a){if(a===0&&1/a<0)return"-0.0" +else return""+a}, +gA(a){var s,r,q,p,o=a|0 +if(a===o)return o&536870911 +s=Math.abs(a) +r=Math.log(s)/0.6931471805599453|0 +q=Math.pow(2,r) +p=s<1?s/q:q/s +return((p*9007199254740992|0)+(p*3542243181176521|0))*599197+r*1259&536870911}, +ac(a,b){var s=a%b +if(s===0)return 0 +if(s>0)return s +return s+b}, +eW(a,b){if((a|0)===a)if(b>=1||b<-1)return a/b|0 +return this.fK(a,b)}, +J(a,b){return(a|0)===a?a/b|0:this.fK(a,b)}, +fK(a,b){var s=a/b +if(s>=-2147483648&&s<=2147483647)return s|0 +if(s>0){if(s!==1/0)return Math.floor(s)}else if(s>-1/0)return Math.ceil(s) +throw A.b(A.a3("Result of truncating division is "+A.t(s)+": "+A.t(a)+" ~/ "+b))}, +b_(a,b){if(b<0)throw A.b(A.e0(b)) +return b>31?0:a<>>0}, +bh(a,b){var s +if(b<0)throw A.b(A.e0(b)) +if(a>0)s=this.e2(a,b) +else{s=b>31?31:b +s=a>>s>>>0}return s}, +O(a,b){var s +if(a>0)s=this.e2(a,b) +else{s=b>31?31:b +s=a>>s>>>0}return s}, +jl(a,b){if(0>b)throw A.b(A.e0(b)) +return this.e2(a,b)}, +e2(a,b){return b>31?0:a>>>b}, +gV(a){return A.bR(t.o)}, +$iF:1, +$ib0:1} +J.et.prototype={ +gfX(a){var s,r=a<0?-a-1:a,q=r +for(s=32;q>=4294967296;){q=this.J(q,4294967296) +s+=32}return s-Math.clz32(q)}, +gV(a){return A.bR(t.S)}, +$iI:1, +$ia:1} +J.hl.prototype={ +gV(a){return A.bR(t.i)}, +$iI:1} +J.bX.prototype={ +jS(a,b){if(b<0)throw A.b(A.e3(a,b)) +if(b>=a.length)A.C(A.e3(a,b)) +return a.charCodeAt(b)}, +cL(a,b,c){var s=b.length +if(c>s)throw A.b(A.S(c,0,s,null,null)) +return new A.iL(b,a,c)}, +eb(a,b){return this.cL(a,b,0)}, +hd(a,b,c){var s,r,q=null +if(c<0||c>b.length)throw A.b(A.S(c,0,b.length,q,q)) +s=a.length +if(c+s>b.length)return q +for(r=0;rr)return!1 +return b===this.N(a,r-s)}, +ho(a,b,c){A.pU(0,0,a.length,"startIndex") +return A.xm(a,b,c,0)}, +aM(a,b){var s +if(typeof b=="string")return A.f(a.split(b),t.s) +else{if(b instanceof A.cw){s=b.e +s=!(s==null?b.e=b.i9():s)}else s=!1 +if(s)return A.f(a.split(b.b),t.s) +else return this.ii(a,b)}}, +aL(a,b,c,d){var s=A.bd(b,c,a.length) +return A.p2(a,b,s,d)}, +ii(a,b){var s,r,q,p,o,n,m=A.f([],t.s) +for(s=J.nX(b,a),s=s.gq(s),r=0,q=1;s.k();){p=s.gm() +o=p.gco() +n=p.gbw() +q=n-o +if(q===0&&r===o)continue +m.push(this.p(a,r,o)) +r=n}if(r0)m.push(this.N(a,r)) +return m}, +C(a,b,c){var s +if(c<0||c>a.length)throw A.b(A.S(c,0,a.length,null,null)) +if(typeof b=="string"){s=c+b.length +if(s>a.length)return!1 +return b===a.substring(c,s)}return J.to(b,a,c)!=null}, +u(a,b){return this.C(a,b,0)}, +p(a,b,c){return a.substring(b,A.bd(b,c,a.length))}, +N(a,b){return this.p(a,b,null)}, +eL(a){var s,r,q,p=a.trim(),o=p.length +if(o===0)return p +if(p.charCodeAt(0)===133){s=J.tY(p,1) +if(s===o)return""}else s=0 +r=o-1 +q=p.charCodeAt(r)===133?J.tZ(p,r):o +if(s===0&&q===o)return p +return p.substring(s,q)}, +bG(a,b){var s,r +if(0>=b)return"" +if(b===1||a.length===0)return a +if(b!==b>>>0)throw A.b(B.aw) +for(s=a,r="";;){if((b&1)===1)r=s+r +b=b>>>1 +if(b===0)break +s+=s}return r}, +kR(a,b,c){var s=b-a.length +if(s<=0)return a +return this.bG(c,s)+a}, +hg(a,b){var s=b-a.length +if(s<=0)return a +return a+this.bG(" ",s)}, +aU(a,b,c){var s +if(c<0||c>a.length)throw A.b(A.S(c,0,a.length,null,null)) +s=a.indexOf(b,c) +return s}, +kx(a,b){return this.aU(a,b,0)}, +hc(a,b,c){var s,r +if(c==null)c=a.length +else if(c<0||c>a.length)throw A.b(A.S(c,0,a.length,null,null)) +s=b.length +r=a.length +if(c+s>r)c=r-s +return a.lastIndexOf(b,c)}, +d0(a,b){return this.hc(a,b,null)}, +I(a,b){return A.xi(a,b,0)}, +ag(a,b){var s +if(a===b)s=0 +else s=a>6}r=r+((r&67108863)<<3)&536870911 +r^=r>>11 +return r+((r&16383)<<15)&536870911}, +gV(a){return A.bR(t.N)}, +gl(a){return a.length}, +j(a,b){if(!(b>=0&&b"))}, +gl(a){return J.at(this.gam())}, +gB(a){return J.nY(this.gam())}, +Y(a,b){var s=A.r(this) +return A.ee(J.e7(this.gam(),b),s.c,s.y[1])}, +ah(a,b){var s=A.r(this) +return A.ee(J.j3(this.gam(),b),s.c,s.y[1])}, +L(a,b){return A.r(this).y[1].a(J.j1(this.gam(),b))}, +gF(a){return A.r(this).y[1].a(J.j2(this.gam()))}, +gE(a){return A.r(this).y[1].a(J.nZ(this.gam()))}, +i(a){return J.b1(this.gam())}} +A.fU.prototype={ +k(){return this.a.k()}, +gm(){return this.$ti.y[1].a(this.a.gm())}} +A.co.prototype={ +gam(){return this.a}} +A.f4.prototype={$iq:1} +A.f_.prototype={ +j(a,b){return this.$ti.y[1].a(J.aJ(this.a,b))}, +t(a,b,c){J.pb(this.a,b,this.$ti.c.a(c))}, +cm(a,b,c){var s=this.$ti +return A.ee(J.tn(this.a,b,c),s.c,s.y[1])}, +M(a,b,c,d,e){var s=this.$ti +J.tp(this.a,b,c,A.ee(d,s.y[1],s.c),e)}, +ad(a,b,c,d){return this.M(0,b,c,d,0)}, +$iq:1, +$ip:1} +A.ak.prototype={ +bu(a,b){return new A.ak(this.a,this.$ti.h("@<1>").H(b).h("ak<1,2>"))}, +gam(){return this.a}} +A.d8.prototype={ +i(a){return"LateInitializationError: "+this.a}} +A.fV.prototype={ +gl(a){return this.a.length}, +j(a,b){return this.a.charCodeAt(b)}} +A.nO.prototype={ +$0(){return A.bc(null,t.H)}, +$S:2} +A.kR.prototype={} +A.q.prototype={} +A.M.prototype={ +gq(a){var s=this +return new A.b3(s,s.gl(s),A.r(s).h("b3"))}, +gB(a){return this.gl(this)===0}, +gF(a){if(this.gl(this)===0)throw A.b(A.az()) +return this.L(0,0)}, +gE(a){var s=this +if(s.gl(s)===0)throw A.b(A.az()) +return s.L(0,s.gl(s)-1)}, +aq(a,b){var s,r,q,p=this,o=p.gl(p) +if(b.length!==0){if(o===0)return"" +s=A.t(p.L(0,0)) +if(o!==p.gl(p))throw A.b(A.au(p)) +for(r=s,q=1;q").H(c).h("E<1,2>"))}, +kv(a,b,c){var s,r,q=this,p=q.gl(q) +for(s=b,r=0;rs)throw A.b(A.S(r,0,s,"start",null))}}, +giq(){var s=J.at(this.a),r=this.c +if(r==null||r>s)return s +return r}, +gjq(){var s=J.at(this.a),r=this.b +if(r>s)return s +return r}, +gl(a){var s,r=J.at(this.a),q=this.b +if(q>=r)return 0 +s=this.c +if(s==null||s>=r)return r-q +return s-q}, +L(a,b){var s=this,r=s.gjq()+b +if(b<0||r>=s.giq())throw A.b(A.hf(b,s.gl(0),s,null,"index")) +return J.j1(s.a,r)}, +Y(a,b){var s,r,q=this +A.ab(b,"count") +s=q.b+b +r=q.c +if(r!=null&&s>=r)return new A.cu(q.$ti.h("cu<1>")) +return A.b5(q.a,s,r,q.$ti.c)}, +ah(a,b){var s,r,q,p=this +A.ab(b,"count") +s=p.c +r=p.b +q=r+b +if(s==null)return A.b5(p.a,r,q,p.$ti.c) +else{if(s=o){r.d=null +return!1}r.d=p.L(q,s);++r.c +return!0}} +A.aE.prototype={ +gq(a){var s=this.a +return new A.d9(s.gq(s),this.b,A.r(this).h("d9<1,2>"))}, +gl(a){var s=this.a +return s.gl(s)}, +gB(a){var s=this.a +return s.gB(s)}, +gF(a){var s=this.a +return this.b.$1(s.gF(s))}, +gE(a){var s=this.a +return this.b.$1(s.gE(s))}, +L(a,b){var s=this.a +return this.b.$1(s.L(s,b))}} +A.ct.prototype={$iq:1} +A.d9.prototype={ +k(){var s=this,r=s.b +if(r.k()){s.a=s.c.$1(r.gm()) +return!0}s.a=null +return!1}, +gm(){var s=this.a +return s==null?this.$ti.y[1].a(s):s}} +A.E.prototype={ +gl(a){return J.at(this.a)}, +L(a,b){return this.b.$1(J.j1(this.a,b))}} +A.aY.prototype={ +gq(a){return new A.eU(J.a4(this.a),this.b)}, +b8(a,b,c){return new A.aE(this,b,this.$ti.h("@<1>").H(c).h("aE<1,2>"))}} +A.eU.prototype={ +k(){var s,r +for(s=this.a,r=this.b;s.k();)if(r.$1(s.gm()))return!0 +return!1}, +gm(){return this.a.gm()}} +A.en.prototype={ +gq(a){return new A.ha(J.a4(this.a),this.b,B.O,this.$ti.h("ha<1,2>"))}} +A.ha.prototype={ +gm(){var s=this.d +return s==null?this.$ti.y[1].a(s):s}, +k(){var s,r,q=this,p=q.c +if(p==null)return!1 +for(s=q.a,r=q.b;!p.k();){q.d=null +if(s.k()){q.c=null +p=J.a4(r.$1(s.gm())) +q.c=p}else return!1}q.d=q.c.gm() +return!0}} +A.cD.prototype={ +gq(a){var s=this.a +return new A.hR(s.gq(s),this.b,A.r(this).h("hR<1>"))}} +A.el.prototype={ +gl(a){var s=this.a,r=s.gl(s) +s=this.b +if(r>s)return s +return r}, +$iq:1} +A.hR.prototype={ +k(){if(--this.b>=0)return this.a.k() +this.b=-1 +return!1}, +gm(){if(this.b<0){this.$ti.c.a(null) +return null}return this.a.gm()}} +A.bJ.prototype={ +Y(a,b){A.bT(b,"count") +A.ab(b,"count") +return new A.bJ(this.a,this.b+b,A.r(this).h("bJ<1>"))}, +gq(a){var s=this.a +return new A.hM(s.gq(s),this.b)}} +A.d3.prototype={ +gl(a){var s=this.a,r=s.gl(s)-this.b +if(r>=0)return r +return 0}, +Y(a,b){A.bT(b,"count") +A.ab(b,"count") +return new A.d3(this.a,this.b+b,this.$ti)}, +$iq:1} +A.hM.prototype={ +k(){var s,r +for(s=this.a,r=0;r"))}, +Y(a,b){A.ab(b,"count") +return this}, +ah(a,b){A.ab(b,"count") +return this}} +A.h7.prototype={ +k(){return!1}, +gm(){throw A.b(A.az())}} +A.eV.prototype={ +gq(a){return new A.i7(J.a4(this.a),this.$ti.h("i7<1>"))}} +A.i7.prototype={ +k(){var s,r +for(s=this.a,r=this.$ti.c;s.k();)if(r.b(s.gm()))return!0 +return!1}, +gm(){return this.$ti.c.a(this.a.gm())}} +A.by.prototype={ +gl(a){return J.at(this.a)}, +gB(a){return J.nY(this.a)}, +gF(a){return new A.ah(this.b,J.j2(this.a))}, +L(a,b){return new A.ah(b+this.b,J.j1(this.a,b))}, +ah(a,b){A.bT(b,"count") +A.ab(b,"count") +return new A.by(J.j3(this.a,b),this.b,A.r(this).h("by<1>"))}, +Y(a,b){A.bT(b,"count") +A.ab(b,"count") +return new A.by(J.e7(this.a,b),b+this.b,A.r(this).h("by<1>"))}, +gq(a){return new A.er(J.a4(this.a),this.b)}} +A.cs.prototype={ +gE(a){var s,r=this.a,q=J.a0(r),p=q.gl(r) +if(p<=0)throw A.b(A.az()) +s=q.gE(r) +if(p!==q.gl(r))throw A.b(A.au(this)) +return new A.ah(p-1+this.b,s)}, +ah(a,b){A.bT(b,"count") +A.ab(b,"count") +return new A.cs(J.j3(this.a,b),this.b,this.$ti)}, +Y(a,b){A.bT(b,"count") +A.ab(b,"count") +return new A.cs(J.e7(this.a,b),this.b+b,this.$ti)}, +$iq:1} +A.er.prototype={ +k(){if(++this.c>=0&&this.a.k())return!0 +this.c=-2 +return!1}, +gm(){var s=this.c +return s>=0?new A.ah(this.b+s,this.a.gm()):A.C(A.az())}} +A.eo.prototype={} +A.hV.prototype={ +t(a,b,c){throw A.b(A.a3("Cannot modify an unmodifiable list"))}, +M(a,b,c,d,e){throw A.b(A.a3("Cannot modify an unmodifiable list"))}, +ad(a,b,c,d){return this.M(0,b,c,d,0)}} +A.dr.prototype={} +A.eI.prototype={ +gl(a){return J.at(this.a)}, +L(a,b){var s=this.a,r=J.a0(s) +return r.L(s,r.gl(s)-1-b)}} +A.hQ.prototype={ +gA(a){var s=this._hashCode +if(s!=null)return s +s=664597*B.a.gA(this.a)&536870911 +this._hashCode=s +return s}, +i(a){return'Symbol("'+this.a+'")'}, +W(a,b){if(b==null)return!1 +return b instanceof A.hQ&&this.a===b.a}} +A.fz.prototype={} +A.ah.prototype={$r:"+(1,2)",$s:1} +A.cO.prototype={$r:"+file,outFlags(1,2)",$s:2} +A.iD.prototype={$r:"+result,resultCode(1,2)",$s:3} +A.eg.prototype={ +i(a){return A.oc(this)}, +gcU(){return new A.dR(this.ks(),A.r(this).h("dR>"))}, +ks(){var s=this +return function(){var r=0,q=1,p=[],o,n,m +return function $async$gcU(a,b,c){if(b===1){p.push(c) +r=q}for(;;)switch(r){case 0:o=s.ga_(),o=o.gq(o),n=A.r(s).h("aN<1,2>") +case 2:if(!o.k()){r=3 +break}m=o.gm() +r=4 +return a.b=new A.aN(m,s.j(0,m),n),1 +case 4:r=2 +break +case 3:return 0 +case 1:return a.c=p.at(-1),3}}}}, +$ian:1} +A.eh.prototype={ +gl(a){return this.b.length}, +gfk(){var s=this.$keys +if(s==null){s=Object.keys(this.a) +this.$keys=s}return s}, +a4(a){if(typeof a!="string")return!1 +if("__proto__"===a)return!1 +return this.a.hasOwnProperty(a)}, +j(a,b){if(!this.a4(b))return null +return this.b[this.a[b]]}, +ap(a,b){var s,r,q=this.gfk(),p=this.b +for(s=q.length,r=0;r"))}, +gbF(){return new A.cM(this.b,this.$ti.h("cM<2>"))}} +A.cM.prototype={ +gl(a){return this.a.length}, +gB(a){return 0===this.a.length}, +gq(a){var s=this.a +return new A.iw(s,s.length,this.$ti.h("iw<1>"))}} +A.iw.prototype={ +gm(){var s=this.d +return s==null?this.$ti.c.a(s):s}, +k(){var s=this,r=s.c +if(r>=s.b){s.d=null +return!1}s.d=s.a[r] +s.c=r+1 +return!0}} +A.kp.prototype={ +W(a,b){if(b==null)return!1 +return b instanceof A.es&&this.a.W(0,b.a)&&A.oU(this)===A.oU(b)}, +gA(a){return A.eD(this.a,A.oU(this),B.f,B.f)}, +i(a){var s=B.c.aq([A.bR(this.$ti.c)],", ") +return this.a.i(0)+" with "+("<"+s+">")}} +A.es.prototype={ +$2(a,b){return this.a.$1$2(a,b,this.$ti.y[0])}, +$4(a,b,c,d){return this.a.$1$4(a,b,c,d,this.$ti.y[0])}, +$S(){return A.wZ(A.nC(this.a),this.$ti)}} +A.eJ.prototype={} +A.lv.prototype={ +ar(a){var s,r,q=this,p=new RegExp(q.a).exec(a) +if(p==null)return null +s=Object.create(null) +r=q.b +if(r!==-1)s.arguments=p[r+1] +r=q.c +if(r!==-1)s.argumentsExpr=p[r+1] +r=q.d +if(r!==-1)s.expr=p[r+1] +r=q.e +if(r!==-1)s.method=p[r+1] +r=q.f +if(r!==-1)s.receiver=p[r+1] +return s}} +A.eC.prototype={ +i(a){return"Null check operator used on a null value"}} +A.hn.prototype={ +i(a){var s,r=this,q="NoSuchMethodError: method not found: '",p=r.b +if(p==null)return"NoSuchMethodError: "+r.a +s=r.c +if(s==null)return q+p+"' ("+r.a+")" +return q+p+"' on '"+s+"' ("+r.a+")"}} +A.hU.prototype={ +i(a){var s=this.a +return s.length===0?"Error":"Error: "+s}} +A.hD.prototype={ +i(a){return"Throw of null ('"+(this.a===null?"null":"undefined")+"' from JavaScript)"}, +$ia5:1} +A.em.prototype={} +A.fm.prototype={ +i(a){var s,r=this.b +if(r!=null)return r +r=this.a +s=r!==null&&typeof r==="object"?r.stack:null +return this.b=s==null?"":s}, +$iY:1} +A.cp.prototype={ +i(a){var s=this.constructor,r=s==null?null:s.name +return"Closure '"+A.rw(r==null?"unknown":r)+"'"}, +glI(){return this}, +$C:"$1", +$R:1, +$D:null} +A.jj.prototype={$C:"$0",$R:0} +A.jk.prototype={$C:"$2",$R:2} +A.ll.prototype={} +A.lb.prototype={ +i(a){var s=this.$static_name +if(s==null)return"Closure of unknown static method" +return"Closure '"+A.rw(s)+"'"}} +A.eb.prototype={ +W(a,b){if(b==null)return!1 +if(this===b)return!0 +if(!(b instanceof A.eb))return!1 +return this.$_target===b.$_target&&this.a===b.a}, +gA(a){return(A.oY(this.a)^A.eG(this.$_target))>>>0}, +i(a){return"Closure '"+this.$_name+"' of "+("Instance of '"+A.hG(this.a)+"'")}} +A.hJ.prototype={ +i(a){return"RuntimeError: "+this.a}} +A.bA.prototype={ +gl(a){return this.a}, +gB(a){return this.a===0}, +ga_(){return new A.bB(this,A.r(this).h("bB<1>"))}, +gbF(){return new A.ex(this,A.r(this).h("ex<2>"))}, +gcU(){return new A.ew(this,A.r(this).h("ew<1,2>"))}, +a4(a){var s,r +if(typeof a=="string"){s=this.b +if(s==null)return!1 +return s[a]!=null}else if(typeof a=="number"&&(a&0x3fffffff)===a){r=this.c +if(r==null)return!1 +return r[a]!=null}else return this.ky(a)}, +ky(a){var s=this.d +if(s==null)return!1 +return this.d_(s[this.cZ(a)],a)>=0}, +aG(a,b){b.ap(0,new A.kw(this))}, +j(a,b){var s,r,q,p,o=null +if(typeof b=="string"){s=this.b +if(s==null)return o +r=s[b] +q=r==null?o:r.b +return q}else if(typeof b=="number"&&(b&0x3fffffff)===b){p=this.c +if(p==null)return o +r=p[b] +q=r==null?o:r.b +return q}else return this.kz(b)}, +kz(a){var s,r,q=this.d +if(q==null)return null +s=q[this.cZ(a)] +r=this.d_(s,a) +if(r<0)return null +return s[r].b}, +t(a,b,c){var s,r,q=this +if(typeof b=="string"){s=q.b +q.eX(s==null?q.b=q.dW():s,b,c)}else if(typeof b=="number"&&(b&0x3fffffff)===b){r=q.c +q.eX(r==null?q.c=q.dW():r,b,c)}else q.kB(b,c)}, +kB(a,b){var s,r,q,p=this,o=p.d +if(o==null)o=p.d=p.dW() +s=p.cZ(a) +r=o[s] +if(r==null)o[s]=[p.dq(a,b)] +else{q=p.d_(r,a) +if(q>=0)r[q].b=b +else r.push(p.dq(a,b))}}, +hj(a,b){var s,r,q=this +if(q.a4(a)){s=q.j(0,a) +return s==null?A.r(q).y[1].a(s):s}r=b.$0() +q.t(0,a,r) +return r}, +G(a,b){var s=this +if(typeof b=="string")return s.eY(s.b,b) +else if(typeof b=="number"&&(b&0x3fffffff)===b)return s.eY(s.c,b) +else return s.kA(b)}, +kA(a){var s,r,q,p,o=this,n=o.d +if(n==null)return null +s=o.cZ(a) +r=n[s] +q=o.d_(r,a) +if(q<0)return null +p=r.splice(q,1)[0] +o.eZ(p) +if(r.length===0)delete n[s] +return p.b}, +ef(a){var s=this +if(s.a>0){s.b=s.c=s.d=s.e=s.f=null +s.a=0 +s.dn()}}, +ap(a,b){var s=this,r=s.e,q=s.r +while(r!=null){b.$2(r.a,r.b) +if(q!==s.r)throw A.b(A.au(s)) +r=r.c}}, +eX(a,b,c){var s=a[b] +if(s==null)a[b]=this.dq(b,c) +else s.b=c}, +eY(a,b){var s +if(a==null)return null +s=a[b] +if(s==null)return null +this.eZ(s) +delete a[b] +return s.b}, +dn(){this.r=this.r+1&1073741823}, +dq(a,b){var s,r=this,q=new A.kz(a,b) +if(r.e==null)r.e=r.f=q +else{s=r.f +s.toString +q.d=s +r.f=s.c=q}++r.a +r.dn() +return q}, +eZ(a){var s=this,r=a.d,q=a.c +if(r==null)s.e=q +else r.c=q +if(q==null)s.f=r +else q.d=r;--s.a +s.dn()}, +cZ(a){return J.aC(a)&1073741823}, +d_(a,b){var s,r +if(a==null)return-1 +s=a.length +for(r=0;r"]=s +delete s[""] +return s}} +A.kw.prototype={ +$2(a,b){this.a.t(0,a,b)}, +$S(){return A.r(this.a).h("~(1,2)")}} +A.kz.prototype={} +A.bB.prototype={ +gl(a){return this.a.a}, +gB(a){return this.a.a===0}, +gq(a){var s=this.a +return new A.hr(s,s.r,s.e)}} +A.hr.prototype={ +gm(){return this.d}, +k(){var s,r=this,q=r.a +if(r.b!==q.r)throw A.b(A.au(q)) +s=r.c +if(s==null){r.d=null +return!1}else{r.d=s.a +r.c=s.c +return!0}}} +A.ex.prototype={ +gl(a){return this.a.a}, +gB(a){return this.a.a===0}, +gq(a){var s=this.a +return new A.cx(s,s.r,s.e)}} +A.cx.prototype={ +gm(){return this.d}, +k(){var s,r=this,q=r.a +if(r.b!==q.r)throw A.b(A.au(q)) +s=r.c +if(s==null){r.d=null +return!1}else{r.d=s.b +r.c=s.c +return!0}}} +A.ew.prototype={ +gl(a){return this.a.a}, +gB(a){return this.a.a===0}, +gq(a){var s=this.a +return new A.hq(s,s.r,s.e,this.$ti.h("hq<1,2>"))}} +A.hq.prototype={ +gm(){var s=this.d +s.toString +return s}, +k(){var s,r=this,q=r.a +if(r.b!==q.r)throw A.b(A.au(q)) +s=r.c +if(s==null){r.d=null +return!1}else{r.d=new A.aN(s.a,s.b,r.$ti.h("aN<1,2>")) +r.c=s.c +return!0}}} +A.nI.prototype={ +$1(a){return this.a(a)}, +$S:114} +A.nJ.prototype={ +$2(a,b){return this.a(a,b)}, +$S:39} +A.nK.prototype={ +$1(a){return this.a(a)}, +$S:45} +A.fi.prototype={ +i(a){return this.fO(!1)}, +fO(a){var s,r,q,p,o,n=this.is(),m=this.fh(),l=(a?"Record ":"")+"(" +for(s=n.length,r="",q=0;q0;){--q;--s +k[q]=r[s]}}return A.aM(k,t.K)}} +A.iC.prototype={ +fh(){return[this.a,this.b]}, +W(a,b){if(b==null)return!1 +return b instanceof A.iC&&this.$s===b.$s&&J.aj(this.a,b.a)&&J.aj(this.b,b.b)}, +gA(a){return A.eD(this.$s,this.a,this.b,B.f)}} +A.cw.prototype={ +i(a){return"RegExp/"+this.a+"/"+this.b.flags}, +gfo(){var s=this,r=s.c +if(r!=null)return r +r=s.b +return s.c=A.o8(s.a,r.multiline,!r.ignoreCase,r.unicode,r.dotAll,"g")}, +giK(){var s=this,r=s.d +if(r!=null)return r +r=s.b +return s.d=A.o8(s.a,r.multiline,!r.ignoreCase,r.unicode,r.dotAll,"y")}, +i9(){var s,r=this.a +if(!B.a.I(r,"("))return!1 +s=this.b.unicode?"u":"" +return new RegExp("(?:)|"+r,s).exec("").length>1}, +a8(a){var s=this.b.exec(a) +if(s==null)return null +return new A.dH(s)}, +cL(a,b,c){var s=b.length +if(c>s)throw A.b(A.S(c,0,s,null,null)) +return new A.i8(this,b,c)}, +eb(a,b){return this.cL(0,b,0)}, +fd(a,b){var s,r=this.gfo() +r.lastIndex=b +s=r.exec(a) +if(s==null)return null +return new A.dH(s)}, +ir(a,b){var s,r=this.giK() +r.lastIndex=b +s=r.exec(a) +if(s==null)return null +return new A.dH(s)}, +hd(a,b,c){if(c<0||c>b.length)throw A.b(A.S(c,0,b.length,null,null)) +return this.ir(b,c)}} +A.dH.prototype={ +gco(){return this.b.index}, +gbw(){var s=this.b +return s.index+s[0].length}, +j(a,b){return this.b[b]}, +aK(a){var s,r=this.b.groups +if(r!=null){s=r[a] +if(s!=null||a in r)return s}throw A.b(A.ad(a,"name","Not a capture group name"))}, +$iez:1, +$ihH:1} +A.i8.prototype={ +gq(a){return new A.m7(this.a,this.b,this.c)}} +A.m7.prototype={ +gm(){var s=this.d +return s==null?t.cz.a(s):s}, +k(){var s,r,q,p,o,n,m=this,l=m.b +if(l==null)return!1 +s=m.c +r=l.length +if(s<=r){q=m.a +p=q.fd(l,s) +if(p!=null){m.d=p +o=p.gbw() +if(p.b.index===o){s=!1 +if(q.b.unicode){q=m.c +n=q+1 +if(n=55296&&r<=56319){s=l.charCodeAt(n) +s=s>=56320&&s<=57343}}}o=(s?o+1:o)+1}m.c=o +return!0}}m.b=m.d=null +return!1}} +A.dp.prototype={ +gbw(){return this.a+this.c.length}, +j(a,b){if(b!==0)A.C(A.kN(b,null)) +return this.c}, +$iez:1, +gco(){return this.a}} +A.iL.prototype={ +gq(a){return new A.n6(this.a,this.b,this.c)}, +gF(a){var s=this.b,r=this.a.indexOf(s,this.c) +if(r>=0)return new A.dp(r,s) +throw A.b(A.az())}} +A.n6.prototype={ +k(){var s,r,q=this,p=q.c,o=q.b,n=o.length,m=q.a,l=m.length +if(p+n>l){q.d=null +return!1}s=m.indexOf(o,p) +if(s<0){q.c=l+1 +q.d=null +return!1}r=s+n +q.d=new A.dp(s,o) +q.c=r===q.c?r+1:r +return!0}, +gm(){var s=this.d +s.toString +return s}} +A.mn.prototype={ +af(){var s=this.b +if(s===this)throw A.b(A.pD(this.a)) +return s}} +A.db.prototype={ +gV(a){return B.b2}, +fU(a,b,c){A.fA(a,b,c) +return c==null?new Uint8Array(a,b):new Uint8Array(a,b,c)}, +jM(a,b,c){var s +A.fA(a,b,c) +s=new DataView(a,b) +return s}, +fT(a){return this.jM(a,0,null)}, +$iI:1, +$iec:1} +A.da.prototype={$ida:1} +A.eA.prototype={ +gaS(a){if(((a.$flags|0)&2)!==0)return new A.iR(a.buffer) +else return a.buffer}, +iE(a,b,c,d){var s=A.S(b,0,c,d,null) +throw A.b(s)}, +f4(a,b,c,d){if(b>>>0!==b||b>c)this.iE(a,b,c,d)}} +A.iR.prototype={ +fU(a,b,c){var s=A.bE(this.a,b,c) +s.$flags=3 +return s}, +fT(a){var s=A.pE(this.a,0,null) +s.$flags=3 +return s}, +$iec:1} +A.cy.prototype={ +gV(a){return B.b3}, +$iI:1, +$icy:1, +$io_:1} +A.dd.prototype={ +gl(a){return a.length}, +fG(a,b,c,d,e){var s,r,q=a.length +this.f4(a,b,q,"start") +this.f4(a,c,q,"end") +if(b>c)throw A.b(A.S(b,0,c,null,null)) +s=c-b +if(e<0)throw A.b(A.J(e,null)) +r=d.length +if(r-e0){s=Date.now()-r.c +if(s>(p+1)*o)p=B.b.eW(s,o)}q.c=p +r.d.$1(q)}, +$S:5} +A.i9.prototype={ +P(a){var s,r=this +if(a==null)a=r.$ti.c.a(a) +if(!r.b)r.a.b0(a) +else{s=r.a +if(r.$ti.h("D<1>").b(a))s.f3(a) +else s.bI(a)}}, +bv(a,b){var s=this.a +if(this.b)s.X(new A.U(a,b)) +else s.aN(new A.U(a,b))}} +A.nm.prototype={ +$1(a){return this.a.$2(0,a)}, +$S:14} +A.nn.prototype={ +$2(a,b){this.a.$2(1,new A.em(a,b))}, +$S:40} +A.nA.prototype={ +$2(a,b){this.a(a,b)}, +$S:48} +A.iM.prototype={ +gm(){return this.b}, +j8(a,b){var s,r,q +a=a +b=b +s=this.a +for(;;)try{r=s(this,a,b) +return r}catch(q){b=q +a=1}}, +k(){var s,r,q,p,o=this,n=null,m=0 +for(;;){s=o.d +if(s!=null)try{if(s.k()){o.b=s.gm() +return!0}else o.d=null}catch(r){n=r +m=1 +o.d=null}q=o.j8(m,n) +if(1===q)return!0 +if(0===q){o.b=null +p=o.e +if(p==null||p.length===0){o.a=A.qw +return!1}o.a=p.pop() +m=0 +n=null +continue}if(2===q){m=0 +n=null +continue}if(3===q){n=o.c +o.c=null +p=o.e +if(p==null||p.length===0){o.b=null +o.a=A.qw +throw n +return!1}o.a=p.pop() +m=1 +continue}throw A.b(A.B("sync*"))}return!1}, +lJ(a){var s,r,q=this +if(a instanceof A.dR){s=a.a() +r=q.e +if(r==null)r=q.e=[] +r.push(q.a) +q.a=s +return 2}else{q.d=J.a4(a) +return 2}}} +A.dR.prototype={ +gq(a){return new A.iM(this.a())}} +A.U.prototype={ +i(a){return A.t(this.a)}, +$iO:1, +gbi(){return this.b}} +A.eZ.prototype={} +A.cG.prototype={ +ak(){}, +al(){}} +A.cF.prototype={ +gbK(){return this.c<4}, +fB(a){var s=a.CW,r=a.ch +if(s==null)this.d=r +else s.ch=r +if(r==null)this.e=s +else r.CW=s +a.CW=a +a.ch=a}, +fI(a,b,c,d){var s,r,q,p,o,n,m,l,k,j=this +if((j.c&4)!==0){s=$.h +r=new A.f3(s) +A.p_(r.gfp()) +if(c!=null)r.c=s.au(c,t.H) +return r}s=A.r(j) +r=$.h +q=d?1:0 +p=b!=null?32:0 +o=A.ig(r,a,s.c) +n=A.ih(r,b) +m=c==null?A.rf():c +l=new A.cG(j,o,n,r.au(m,t.H),r,q|p,s.h("cG<1>")) +l.CW=l +l.ch=l +l.ay=j.c&1 +k=j.e +j.e=l +l.ch=null +l.CW=k +if(k==null)j.d=l +else k.ch=l +if(j.d===l)A.iW(j.a) +return l}, +ft(a){var s,r=this +A.r(r).h("cG<1>").a(a) +if(a.ch===a)return null +s=a.ay +if((s&2)!==0)a.ay=s|4 +else{r.fB(a) +if((r.c&2)===0&&r.d==null)r.du()}return null}, +fu(a){}, +fv(a){}, +bH(){if((this.c&4)!==0)return new A.aQ("Cannot add new events after calling close") +return new A.aQ("Cannot add new events while doing an addStream")}, +v(a,b){if(!this.gbK())throw A.b(this.bH()) +this.b2(b)}, +a3(a,b){var s +if(!this.gbK())throw A.b(this.bH()) +s=A.ns(a,b) +this.b4(s.a,s.b)}, +n(){var s,r,q=this +if((q.c&4)!==0){s=q.r +s.toString +return s}if(!q.gbK())throw A.b(q.bH()) +q.c|=4 +r=q.r +if(r==null)r=q.r=new A.n($.h,t.D) +q.b3() +return r}, +dK(a){var s,r,q,p=this,o=p.c +if((o&2)!==0)throw A.b(A.B(u.o)) +s=p.d +if(s==null)return +r=o&1 +p.c=o^3 +while(s!=null){o=s.ay +if((o&1)===r){s.ay=o|2 +a.$1(s) +o=s.ay^=1 +q=s.ch +if((o&4)!==0)p.fB(s) +s.ay&=4294967293 +s=q}else s=s.ch}p.c&=4294967293 +if(p.d==null)p.du()}, +du(){if((this.c&4)!==0){var s=this.r +if((s.a&30)===0)s.b0(null)}A.iW(this.b)}, +$iae:1} +A.fp.prototype={ +gbK(){return A.cF.prototype.gbK.call(this)&&(this.c&2)===0}, +bH(){if((this.c&2)!==0)return new A.aQ(u.o) +return this.hN()}, +b2(a){var s=this,r=s.d +if(r==null)return +if(r===s.e){s.c|=2 +r.bm(a) +s.c&=4294967293 +if(s.d==null)s.du() +return}s.dK(new A.n7(s,a))}, +b4(a,b){if(this.d==null)return +this.dK(new A.n9(this,a,b))}, +b3(){var s=this +if(s.d!=null)s.dK(new A.n8(s)) +else s.r.b0(null)}} +A.n7.prototype={ +$1(a){a.bm(this.b)}, +$S(){return this.a.$ti.h("~(ag<1>)")}} +A.n9.prototype={ +$1(a){a.bk(this.b,this.c)}, +$S(){return this.a.$ti.h("~(ag<1>)")}} +A.n8.prototype={ +$1(a){a.ct()}, +$S(){return this.a.$ti.h("~(ag<1>)")}} +A.ki.prototype={ +$0(){var s,r,q,p,o,n,m=null +try{m=this.a.$0()}catch(q){s=A.G(q) +r=A.a1(q) +p=s +o=r +n=A.cS(p,o) +if(n==null)p=new A.U(p,o) +else p=n +this.b.X(p) +return}this.b.b1(m)}, +$S:0} +A.kg.prototype={ +$0(){this.c.a(null) +this.b.b1(null)}, +$S:0} +A.kk.prototype={ +$2(a,b){var s=this,r=s.a,q=--r.b +if(r.a!=null){r.a=null +r.d=a +r.c=b +if(q===0||s.c)s.d.X(new A.U(a,b))}else if(q===0&&!s.c){q=r.d +q.toString +r=r.c +r.toString +s.d.X(new A.U(q,r))}}, +$S:6} +A.kj.prototype={ +$1(a){var s,r,q,p,o,n,m=this,l=m.a,k=--l.b,j=l.a +if(j!=null){J.pb(j,m.b,a) +if(J.aj(k,0)){l=m.d +s=A.f([],l.h("u<0>")) +for(q=j,p=q.length,o=0;o")) +r=b==null?1:3 +this.cr(new A.cg(s,r,a,b,this.$ti.h("@<1>").H(c).h("cg<1,2>"))) +return s}, +ce(a,b){return this.bE(a,null,b)}, +fM(a,b,c){var s=new A.n($.h,c.h("n<0>")) +this.cr(new A.cg(s,19,a,b,this.$ti.h("@<1>").H(c).h("cg<1,2>"))) +return s}, +ai(a){var s=this.$ti,r=$.h,q=new A.n(r,s) +if(r!==B.d)a=r.au(a,t.z) +this.cr(new A.cg(q,8,a,null,s.h("cg<1,1>"))) +return q}, +jj(a){this.a=this.a&1|16 +this.c=a}, +cs(a){this.a=a.a&30|this.a&1 +this.c=a.c}, +cr(a){var s=this,r=s.a +if(r<=3){a.a=s.c +s.c=a}else{if((r&4)!==0){r=s.c +if((r.a&24)===0){r.cr(a) +return}s.cs(r)}s.b.aY(new A.mE(s,a))}}, +fq(a){var s,r,q,p,o,n=this,m={} +m.a=a +if(a==null)return +s=n.a +if(s<=3){r=n.c +n.c=a +if(r!=null){q=a.a +for(p=a;q!=null;p=q,q=o)o=q.a +p.a=r}}else{if((s&4)!==0){s=n.c +if((s.a&24)===0){s.fq(a) +return}n.cs(s)}m.a=n.cC(a) +n.b.aY(new A.mJ(m,n))}}, +bP(){var s=this.c +this.c=null +return this.cC(s)}, +cC(a){var s,r,q +for(s=a,r=null;s!=null;r=s,s=q){q=s.a +s.a=r}return r}, +b1(a){var s,r=this +if(r.$ti.h("D<1>").b(a))A.mH(a,r,!0) +else{s=r.bP() +r.a=8 +r.c=a +A.cJ(r,s)}}, +bI(a){var s=this,r=s.bP() +s.a=8 +s.c=a +A.cJ(s,r)}, +i7(a){var s,r,q,p=this +if((a.a&16)!==0){s=p.b +r=a.b +s=!(s===r||s.gaI()===r.gaI())}else s=!1 +if(s)return +q=p.bP() +p.cs(a) +A.cJ(p,q)}, +X(a){var s=this.bP() +this.jj(a) +A.cJ(this,s)}, +i6(a,b){this.X(new A.U(a,b))}, +b0(a){if(this.$ti.h("D<1>").b(a)){this.f3(a) +return}this.f2(a)}, +f2(a){this.a^=2 +this.b.aY(new A.mG(this,a))}, +f3(a){A.mH(a,this,!1) +return}, +aN(a){this.a^=2 +this.b.aY(new A.mF(this,a))}, +$iD:1} +A.mE.prototype={ +$0(){A.cJ(this.a,this.b)}, +$S:0} +A.mJ.prototype={ +$0(){A.cJ(this.b,this.a.a)}, +$S:0} +A.mI.prototype={ +$0(){A.mH(this.a.a,this.b,!0)}, +$S:0} +A.mG.prototype={ +$0(){this.a.bI(this.b)}, +$S:0} +A.mF.prototype={ +$0(){this.a.X(this.b)}, +$S:0} +A.mM.prototype={ +$0(){var s,r,q,p,o,n,m,l,k=this,j=null +try{q=k.a.a +j=q.b.b.bb(q.d,t.z)}catch(p){s=A.G(p) +r=A.a1(p) +if(k.c&&k.b.a.c.a===s){q=k.a +q.c=k.b.a.c}else{q=s +o=r +if(o==null)o=A.fO(q) +n=k.a +n.c=new A.U(q,o) +q=n}q.b=!0 +return}if(j instanceof A.n&&(j.a&24)!==0){if((j.a&16)!==0){q=k.a +q.c=j.c +q.b=!0}return}if(j instanceof A.n){m=k.b.a +l=new A.n(m.b,m.$ti) +j.bE(new A.mN(l,m),new A.mO(l),t.H) +q=k.a +q.c=l +q.b=!1}}, +$S:0} +A.mN.prototype={ +$1(a){this.a.i7(this.b)}, +$S:26} +A.mO.prototype={ +$2(a,b){this.a.X(new A.U(a,b))}, +$S:58} +A.mL.prototype={ +$0(){var s,r,q,p,o,n +try{q=this.a +p=q.a +o=p.$ti +q.c=p.b.b.bc(p.d,this.b,o.h("2/"),o.c)}catch(n){s=A.G(n) +r=A.a1(n) +q=s +p=r +if(p==null)p=A.fO(q) +o=this.a +o.c=new A.U(q,p) +o.b=!0}}, +$S:0} +A.mK.prototype={ +$0(){var s,r,q,p,o,n,m,l=this +try{s=l.a.a.c +p=l.b +if(p.a.kL(s)&&p.a.e!=null){p.c=p.a.kw(s) +p.b=!1}}catch(o){r=A.G(o) +q=A.a1(o) +p=l.a.a.c +if(p.a===r){n=l.b +n.c=p +p=n}else{p=r +n=q +if(n==null)n=A.fO(p) +m=l.b +m.c=new A.U(p,n) +p=m}p.b=!0}}, +$S:0} +A.ia.prototype={} +A.V.prototype={ +gl(a){var s={},r=new A.n($.h,t.gR) +s.a=0 +this.R(new A.li(s,this),!0,new A.lj(s,r),r.gdB()) +return r}, +gF(a){var s=new A.n($.h,A.r(this).h("n")),r=this.R(null,!0,new A.lg(s),s.gdB()) +r.c6(new A.lh(this,r,s)) +return s}, +ku(a,b){var s=new A.n($.h,A.r(this).h("n")),r=this.R(null,!0,new A.le(null,s),s.gdB()) +r.c6(new A.lf(this,b,r,s)) +return s}} +A.li.prototype={ +$1(a){++this.a.a}, +$S(){return A.r(this.b).h("~(V.T)")}} +A.lj.prototype={ +$0(){this.b.b1(this.a.a)}, +$S:0} +A.lg.prototype={ +$0(){var s,r=A.la(),q=new A.aQ("No element") +A.eH(q,r) +s=A.cS(q,r) +if(s==null)s=new A.U(q,r) +this.a.X(s)}, +$S:0} +A.lh.prototype={ +$1(a){A.qS(this.b,this.c,a)}, +$S(){return A.r(this.a).h("~(V.T)")}} +A.le.prototype={ +$0(){var s,r=A.la(),q=new A.aQ("No element") +A.eH(q,r) +s=A.cS(q,r) +if(s==null)s=new A.U(q,r) +this.b.X(s)}, +$S:0} +A.lf.prototype={ +$1(a){var s=this.c,r=this.d +A.w9(new A.lc(this.b,a),new A.ld(s,r,a),A.vw(s,r))}, +$S(){return A.r(this.a).h("~(V.T)")}} +A.lc.prototype={ +$0(){return this.a.$1(this.b)}, +$S:29} +A.ld.prototype={ +$1(a){if(a)A.qS(this.a,this.b,this.c)}, +$S:72} +A.hP.prototype={} +A.cP.prototype={ +giX(){if((this.b&8)===0)return this.a +return this.a.ge6()}, +dH(){var s,r=this +if((r.b&8)===0){s=r.a +return s==null?r.a=new A.fh():s}s=r.a.ge6() +return s}, +gaQ(){var s=this.a +return(this.b&8)!==0?s.ge6():s}, +ds(){if((this.b&4)!==0)return new A.aQ("Cannot add event after closing") +return new A.aQ("Cannot add event while adding a stream")}, +fa(){var s=this.c +if(s==null)s=this.c=(this.b&2)!==0?$.cm():new A.n($.h,t.D) +return s}, +v(a,b){var s=this,r=s.b +if(r>=4)throw A.b(s.ds()) +if((r&1)!==0)s.b2(b) +else if((r&3)===0)s.dH().v(0,new A.dy(b))}, +a3(a,b){var s,r,q=this +if(q.b>=4)throw A.b(q.ds()) +s=A.ns(a,b) +a=s.a +b=s.b +r=q.b +if((r&1)!==0)q.b4(a,b) +else if((r&3)===0)q.dH().v(0,new A.f2(a,b))}, +jK(a){return this.a3(a,null)}, +n(){var s=this,r=s.b +if((r&4)!==0)return s.fa() +if(r>=4)throw A.b(s.ds()) +r=s.b=r|4 +if((r&1)!==0)s.b3() +else if((r&3)===0)s.dH().v(0,B.x) +return s.fa()}, +fI(a,b,c,d){var s,r,q,p=this +if((p.b&3)!==0)throw A.b(A.B("Stream has already been listened to.")) +s=A.uM(p,a,b,c,d,A.r(p).c) +r=p.giX() +if(((p.b|=1)&8)!==0){q=p.a +q.se6(s) +q.ba()}else p.a=s +s.jk(r) +s.dL(new A.n4(p)) +return s}, +ft(a){var s,r,q,p,o,n,m,l=this,k=null +if((l.b&8)!==0)k=l.a.K() +l.a=null +l.b=l.b&4294967286|2 +s=l.r +if(s!=null)if(k==null)try{r=s.$0() +if(r instanceof A.n)k=r}catch(o){q=A.G(o) +p=A.a1(o) +n=new A.n($.h,t.D) +n.aN(new A.U(q,p)) +k=n}else k=k.ai(s) +m=new A.n3(l) +if(k!=null)k=k.ai(m) +else m.$0() +return k}, +fu(a){if((this.b&8)!==0)this.a.bA() +A.iW(this.e)}, +fv(a){if((this.b&8)!==0)this.a.ba() +A.iW(this.f)}, +$iae:1} +A.n4.prototype={ +$0(){A.iW(this.a.d)}, +$S:0} +A.n3.prototype={ +$0(){var s=this.a.c +if(s!=null&&(s.a&30)===0)s.b0(null)}, +$S:0} +A.iN.prototype={ +b2(a){this.gaQ().bm(a)}, +b4(a,b){this.gaQ().bk(a,b)}, +b3(){this.gaQ().ct()}} +A.ib.prototype={ +b2(a){this.gaQ().bl(new A.dy(a))}, +b4(a,b){this.gaQ().bl(new A.f2(a,b))}, +b3(){this.gaQ().bl(B.x)}} +A.dw.prototype={} +A.dS.prototype={} +A.ar.prototype={ +gA(a){return(A.eG(this.a)^892482866)>>>0}, +W(a,b){if(b==null)return!1 +if(this===b)return!0 +return b instanceof A.ar&&b.a===this.a}} +A.cf.prototype={ +cz(){return this.w.ft(this)}, +ak(){this.w.fu(this)}, +al(){this.w.fv(this)}} +A.dP.prototype={ +v(a,b){this.a.v(0,b)}, +a3(a,b){this.a.a3(a,b)}, +n(){return this.a.n()}, +$iae:1} +A.ag.prototype={ +jk(a){var s=this +if(a==null)return +s.r=a +if(a.c!=null){s.e=(s.e|128)>>>0 +a.cn(s)}}, +c6(a){this.a=A.ig(this.d,a,A.r(this).h("ag.T"))}, +eD(a){var s=this +s.e=(s.e&4294967263)>>>0 +s.b=A.ih(s.d,a)}, +bA(){var s,r,q=this,p=q.e +if((p&8)!==0)return +s=(p+256|4)>>>0 +q.e=s +if(p<256){r=q.r +if(r!=null)if(r.a===1)r.a=3}if((p&4)===0&&(s&64)===0)q.dL(q.gbL())}, +ba(){var s=this,r=s.e +if((r&8)!==0)return +if(r>=256){r=s.e=r-256 +if(r<256)if((r&128)!==0&&s.r.c!=null)s.r.cn(s) +else{r=(r&4294967291)>>>0 +s.e=r +if((r&64)===0)s.dL(s.gbM())}}}, +K(){var s=this,r=(s.e&4294967279)>>>0 +s.e=r +if((r&8)===0)s.dv() +r=s.f +return r==null?$.cm():r}, +dv(){var s,r=this,q=r.e=(r.e|8)>>>0 +if((q&128)!==0){s=r.r +if(s.a===1)s.a=3}if((q&64)===0)r.r=null +r.f=r.cz()}, +bm(a){var s=this.e +if((s&8)!==0)return +if(s<64)this.b2(a) +else this.bl(new A.dy(a))}, +bk(a,b){var s +if(t.C.b(a))A.eH(a,b) +s=this.e +if((s&8)!==0)return +if(s<64)this.b4(a,b) +else this.bl(new A.f2(a,b))}, +ct(){var s=this,r=s.e +if((r&8)!==0)return +r=(r|2)>>>0 +s.e=r +if(r<64)s.b3() +else s.bl(B.x)}, +ak(){}, +al(){}, +cz(){return null}, +bl(a){var s,r=this,q=r.r +if(q==null)q=r.r=new A.fh() +q.v(0,a) +s=r.e +if((s&128)===0){s=(s|128)>>>0 +r.e=s +if(s<256)q.cn(r)}}, +b2(a){var s=this,r=s.e +s.e=(r|64)>>>0 +s.d.cd(s.a,a,A.r(s).h("ag.T")) +s.e=(s.e&4294967231)>>>0 +s.dw((r&4)!==0)}, +b4(a,b){var s,r=this,q=r.e,p=new A.mm(r,a,b) +if((q&1)!==0){r.e=(q|16)>>>0 +r.dv() +s=r.f +if(s!=null&&s!==$.cm())s.ai(p) +else p.$0()}else{p.$0() +r.dw((q&4)!==0)}}, +b3(){var s,r=this,q=new A.ml(r) +r.dv() +r.e=(r.e|16)>>>0 +s=r.f +if(s!=null&&s!==$.cm())s.ai(q) +else q.$0()}, +dL(a){var s=this,r=s.e +s.e=(r|64)>>>0 +a.$0() +s.e=(s.e&4294967231)>>>0 +s.dw((r&4)!==0)}, +dw(a){var s,r,q=this,p=q.e +if((p&128)!==0&&q.r.c==null){p=q.e=(p&4294967167)>>>0 +s=!1 +if((p&4)!==0)if(p<256){s=q.r +s=s==null?null:s.c==null +s=s!==!1}if(s){p=(p&4294967291)>>>0 +q.e=p}}for(;;a=r){if((p&8)!==0){q.r=null +return}r=(p&4)!==0 +if(a===r)break +q.e=(p^64)>>>0 +if(r)q.ak() +else q.al() +p=(q.e&4294967231)>>>0 +q.e=p}if((p&128)!==0&&p<256)q.r.cn(q)}} +A.mm.prototype={ +$0(){var s,r,q,p=this.a,o=p.e +if((o&8)!==0&&(o&16)===0)return +p.e=(o|64)>>>0 +s=p.b +o=this.b +r=t.K +q=p.d +if(t.da.b(s))q.hq(s,o,this.c,r,t.l) +else q.cd(s,o,r) +p.e=(p.e&4294967231)>>>0}, +$S:0} +A.ml.prototype={ +$0(){var s=this.a,r=s.e +if((r&16)===0)return +s.e=(r|74)>>>0 +s.d.cc(s.c) +s.e=(s.e&4294967231)>>>0}, +$S:0} +A.dN.prototype={ +R(a,b,c,d){return this.a.fI(a,d,c,b===!0)}, +aV(a,b,c){return this.R(a,null,b,c)}, +kF(a){return this.R(a,null,null,null)}, +ez(a,b){return this.R(a,null,b,null)}} +A.ik.prototype={ +gc5(){return this.a}, +sc5(a){return this.a=a}} +A.dy.prototype={ +eF(a){a.b2(this.b)}} +A.f2.prototype={ +eF(a){a.b4(this.b,this.c)}} +A.mw.prototype={ +eF(a){a.b3()}, +gc5(){return null}, +sc5(a){throw A.b(A.B("No events after a done."))}} +A.fh.prototype={ +cn(a){var s=this,r=s.a +if(r===1)return +if(r>=1){s.a=1 +return}A.p_(new A.mU(s,a)) +s.a=1}, +v(a,b){var s=this,r=s.c +if(r==null)s.b=s.c=b +else{r.sc5(b) +s.c=b}}} +A.mU.prototype={ +$0(){var s,r,q=this.a,p=q.a +q.a=0 +if(p===3)return +s=q.b +r=s.gc5() +q.b=r +if(r==null)q.c=null +s.eF(this.b)}, +$S:0} +A.f3.prototype={ +c6(a){}, +eD(a){}, +bA(){var s=this.a +if(s>=0)this.a=s+2}, +ba(){var s=this,r=s.a-2 +if(r<0)return +if(r===0){s.a=1 +A.p_(s.gfp())}else s.a=r}, +K(){this.a=-1 +this.c=null +return $.cm()}, +iT(){var s,r=this,q=r.a-1 +if(q===0){r.a=-1 +s=r.c +if(s!=null){r.c=null +r.b.cc(s)}}else r.a=q}} +A.dO.prototype={ +gm(){if(this.c)return this.b +return null}, +k(){var s,r=this,q=r.a +if(q!=null){if(r.c){s=new A.n($.h,t.k) +r.b=s +r.c=!1 +q.ba() +return s}throw A.b(A.B("Already waiting for next."))}return r.iD()}, +iD(){var s,r,q=this,p=q.b +if(p!=null){s=new A.n($.h,t.k) +q.b=s +r=p.R(q.giN(),!0,q.giP(),q.giR()) +if(q.b!=null)q.a=r +return s}return $.rz()}, +K(){var s=this,r=s.a,q=s.b +s.b=null +if(r!=null){s.a=null +if(!s.c)q.b0(!1) +else s.c=!1 +return r.K()}return $.cm()}, +iO(a){var s,r,q=this +if(q.a==null)return +s=q.b +q.b=a +q.c=!0 +s.b1(!0) +if(q.c){r=q.a +if(r!=null)r.bA()}}, +iS(a,b){var s=this,r=s.a,q=s.b +s.b=s.a=null +if(r!=null)q.X(new A.U(a,b)) +else q.aN(new A.U(a,b))}, +iQ(){var s=this,r=s.a,q=s.b +s.b=s.a=null +if(r!=null)q.bI(!1) +else q.f2(!1)}} +A.np.prototype={ +$0(){return this.a.X(this.b)}, +$S:0} +A.no.prototype={ +$2(a,b){A.vv(this.a,this.b,new A.U(a,b))}, +$S:6} +A.nq.prototype={ +$0(){return this.a.b1(this.b)}, +$S:0} +A.f8.prototype={ +R(a,b,c,d){var s=this.$ti,r=$.h,q=b===!0?1:0,p=d!=null?32:0,o=A.ig(r,a,s.y[1]),n=A.ih(r,d) +s=new A.dA(this,o,n,r.au(c,t.H),r,q|p,s.h("dA<1,2>")) +s.x=this.a.aV(s.gdM(),s.gdO(),s.gdQ()) +return s}, +aV(a,b,c){return this.R(a,null,b,c)}} +A.dA.prototype={ +bm(a){if((this.e&2)!==0)return +this.dm(a)}, +bk(a,b){if((this.e&2)!==0)return +this.bj(a,b)}, +ak(){var s=this.x +if(s!=null)s.bA()}, +al(){var s=this.x +if(s!=null)s.ba()}, +cz(){var s=this.x +if(s!=null){this.x=null +return s.K()}return null}, +dN(a){this.w.ix(a,this)}, +dR(a,b){this.bk(a,b)}, +dP(){this.ct()}} +A.fc.prototype={ +ix(a,b){var s,r,q,p,o,n,m=null +try{m=this.b.$1(a)}catch(q){s=A.G(q) +r=A.a1(q) +p=s +o=r +n=A.cS(p,o) +if(n!=null){p=n.a +o=n.b}b.bk(p,o) +return}b.bm(m)}} +A.f5.prototype={ +v(a,b){var s=this.a +if((s.e&2)!==0)A.C(A.B("Stream is already closed")) +s.dm(b)}, +a3(a,b){var s=this.a +if((s.e&2)!==0)A.C(A.B("Stream is already closed")) +s.bj(a,b)}, +n(){var s=this.a +if((s.e&2)!==0)A.C(A.B("Stream is already closed")) +s.eV()}, +$iae:1} +A.dL.prototype={ +ak(){var s=this.x +if(s!=null)s.bA()}, +al(){var s=this.x +if(s!=null)s.ba()}, +cz(){var s=this.x +if(s!=null){this.x=null +return s.K()}return null}, +dN(a){var s,r,q,p +try{q=this.w +q===$&&A.x() +q.v(0,a)}catch(p){s=A.G(p) +r=A.a1(p) +if((this.e&2)!==0)A.C(A.B("Stream is already closed")) +this.bj(s,r)}}, +dR(a,b){var s,r,q,p,o=this,n="Stream is already closed" +try{q=o.w +q===$&&A.x() +q.a3(a,b)}catch(p){s=A.G(p) +r=A.a1(p) +if(s===a){if((o.e&2)!==0)A.C(A.B(n)) +o.bj(a,b)}else{if((o.e&2)!==0)A.C(A.B(n)) +o.bj(s,r)}}}, +dP(){var s,r,q,p,o=this +try{o.x=null +q=o.w +q===$&&A.x() +q.n()}catch(p){s=A.G(p) +r=A.a1(p) +if((o.e&2)!==0)A.C(A.B("Stream is already closed")) +o.bj(s,r)}}} +A.fo.prototype={ +ec(a){return new A.eY(this.a,a,this.$ti.h("eY<1,2>"))}} +A.eY.prototype={ +R(a,b,c,d){var s=this.$ti,r=$.h,q=b===!0?1:0,p=d!=null?32:0,o=A.ig(r,a,s.y[1]),n=A.ih(r,d),m=new A.dL(o,n,r.au(c,t.H),r,q|p,s.h("dL<1,2>")) +m.w=this.a.$1(new A.f5(m)) +m.x=this.b.aV(m.gdM(),m.gdO(),m.gdQ()) +return m}, +aV(a,b,c){return this.R(a,null,b,c)}} +A.dD.prototype={ +v(a,b){var s,r=this.d +if(r==null)throw A.b(A.B("Sink is closed")) +this.$ti.y[1].a(b) +s=r.a +if((s.e&2)!==0)A.C(A.B("Stream is already closed")) +s.dm(b)}, +a3(a,b){var s=this.d +if(s==null)throw A.b(A.B("Sink is closed")) +s.a3(a,b)}, +n(){var s=this.d +if(s==null)return +this.d=null +this.c.$1(s)}, +$iae:1} +A.dM.prototype={ +ec(a){return this.hO(a)}} +A.n5.prototype={ +$1(a){var s=this +return new A.dD(s.a,s.b,s.c,a,s.e.h("@<0>").H(s.d).h("dD<1,2>"))}, +$S(){return this.e.h("@<0>").H(this.d).h("dD<1,2>(ae<2>)")}} +A.ay.prototype={} +A.iT.prototype={ +bN(a,b,c){var s,r,q,p,o,n,m,l,k=this.gdS(),j=k.a +if(j===B.d){A.fD(b,c) +return}s=k.b +r=j.ga1() +m=j.ghh() +m.toString +q=m +p=$.h +try{$.h=q +s.$5(j,r,a,b,c) +$.h=p}catch(l){o=A.G(l) +n=A.a1(l) +$.h=p +m=b===o?c:n +q.bN(j,o,m)}}, +$iw:1} +A.ii.prototype={ +gf1(){var s=this.at +return s==null?this.at=new A.dU(this):s}, +ga1(){return this.ax.gf1()}, +gaI(){return this.as.a}, +cc(a){var s,r,q +try{this.bb(a,t.H)}catch(q){s=A.G(q) +r=A.a1(q) +this.bN(this,s,r)}}, +cd(a,b,c){var s,r,q +try{this.bc(a,b,t.H,c)}catch(q){s=A.G(q) +r=A.a1(q) +this.bN(this,s,r)}}, +hq(a,b,c,d,e){var s,r,q +try{this.eJ(a,b,c,t.H,d,e)}catch(q){s=A.G(q) +r=A.a1(q) +this.bN(this,s,r)}}, +ed(a,b){return new A.mt(this,this.au(a,b),b)}, +fW(a,b,c){return new A.mv(this,this.b9(a,b,c),c,b)}, +cP(a){return new A.ms(this,this.au(a,t.H))}, +ee(a,b){return new A.mu(this,this.b9(a,t.H,b),b)}, +j(a,b){var s,r=this.ay,q=r.j(0,b) +if(q!=null||r.a4(b))return q +s=this.ax.j(0,b) +if(s!=null)r.t(0,b,s) +return s}, +c1(a,b){this.bN(this,a,b)}, +h6(a,b){var s=this.Q,r=s.a +return s.b.$5(r,r.ga1(),this,a,b)}, +bb(a){var s=this.a,r=s.a +return s.b.$4(r,r.ga1(),this,a)}, +bc(a,b){var s=this.b,r=s.a +return s.b.$5(r,r.ga1(),this,a,b)}, +eJ(a,b,c){var s=this.c,r=s.a +return s.b.$6(r,r.ga1(),this,a,b,c)}, +au(a){var s=this.d,r=s.a +return s.b.$4(r,r.ga1(),this,a)}, +b9(a){var s=this.e,r=s.a +return s.b.$4(r,r.ga1(),this,a)}, +d6(a){var s=this.f,r=s.a +return s.b.$4(r,r.ga1(),this,a)}, +h3(a,b){var s=this.r,r=s.a +if(r===B.d)return null +return s.b.$5(r,r.ga1(),this,a,b)}, +aY(a){var s=this.w,r=s.a +return s.b.$4(r,r.ga1(),this,a)}, +eh(a,b){var s=this.x,r=s.a +return s.b.$5(r,r.ga1(),this,a,b)}, +hi(a){var s=this.z,r=s.a +return s.b.$4(r,r.ga1(),this,a)}, +gfD(){return this.a}, +gfF(){return this.b}, +gfE(){return this.c}, +gfz(){return this.d}, +gfA(){return this.e}, +gfw(){return this.f}, +gfc(){return this.r}, +ge1(){return this.w}, +gf7(){return this.x}, +gf6(){return this.y}, +gfs(){return this.z}, +gff(){return this.Q}, +gdS(){return this.as}, +ghh(){return this.ax}, +gfl(){return this.ay}} +A.mt.prototype={ +$0(){return this.a.bb(this.b,this.c)}, +$S(){return this.c.h("0()")}} +A.mv.prototype={ +$1(a){var s=this +return s.a.bc(s.b,a,s.d,s.c)}, +$S(){return this.d.h("@<0>").H(this.c).h("1(2)")}} +A.ms.prototype={ +$0(){return this.a.cc(this.b)}, +$S:0} +A.mu.prototype={ +$1(a){return this.a.cd(this.b,a,this.c)}, +$S(){return this.c.h("~(0)")}} +A.iH.prototype={ +gfD(){return B.bx}, +gfF(){return B.bz}, +gfE(){return B.by}, +gfz(){return B.bw}, +gfA(){return B.br}, +gfw(){return B.bB}, +gfc(){return B.bt}, +ge1(){return B.bA}, +gf7(){return B.bs}, +gf6(){return B.bq}, +gfs(){return B.bv}, +gff(){return B.bu}, +gdS(){return B.bp}, +ghh(){return null}, +gfl(){return $.rS()}, +gf1(){var s=$.mX +return s==null?$.mX=new A.dU(this):s}, +ga1(){var s=$.mX +return s==null?$.mX=new A.dU(this):s}, +gaI(){return this}, +cc(a){var s,r,q +try{if(B.d===$.h){a.$0() +return}A.nu(null,null,this,a)}catch(q){s=A.G(q) +r=A.a1(q) +A.fD(s,r)}}, +cd(a,b){var s,r,q +try{if(B.d===$.h){a.$1(b) +return}A.nw(null,null,this,a,b)}catch(q){s=A.G(q) +r=A.a1(q) +A.fD(s,r)}}, +hq(a,b,c){var s,r,q +try{if(B.d===$.h){a.$2(b,c) +return}A.nv(null,null,this,a,b,c)}catch(q){s=A.G(q) +r=A.a1(q) +A.fD(s,r)}}, +ed(a,b){return new A.mZ(this,a,b)}, +fW(a,b,c){return new A.n0(this,a,c,b)}, +cP(a){return new A.mY(this,a)}, +ee(a,b){return new A.n_(this,a,b)}, +j(a,b){return null}, +c1(a,b){A.fD(a,b)}, +h6(a,b){return A.r4(null,null,this,a,b)}, +bb(a){if($.h===B.d)return a.$0() +return A.nu(null,null,this,a)}, +bc(a,b){if($.h===B.d)return a.$1(b) +return A.nw(null,null,this,a,b)}, +eJ(a,b,c){if($.h===B.d)return a.$2(b,c) +return A.nv(null,null,this,a,b,c)}, +au(a){return a}, +b9(a){return a}, +d6(a){return a}, +h3(a,b){return null}, +aY(a){A.nx(null,null,this,a)}, +eh(a,b){return A.om(a,b)}, +hi(a){A.oZ(a)}} +A.mZ.prototype={ +$0(){return this.a.bb(this.b,this.c)}, +$S(){return this.c.h("0()")}} +A.n0.prototype={ +$1(a){var s=this +return s.a.bc(s.b,a,s.d,s.c)}, +$S(){return this.d.h("@<0>").H(this.c).h("1(2)")}} +A.mY.prototype={ +$0(){return this.a.cc(this.b)}, +$S:0} +A.n_.prototype={ +$1(a){return this.a.cd(this.b,a,this.c)}, +$S(){return this.c.h("~(0)")}} +A.dU.prototype={$iW:1} +A.nt.prototype={ +$0(){A.pr(this.a,this.b)}, +$S:0} +A.iU.prototype={$ioq:1} +A.cK.prototype={ +gl(a){return this.a}, +gB(a){return this.a===0}, +ga_(){return new A.cL(this,A.r(this).h("cL<1>"))}, +gbF(){var s=A.r(this) +return A.ht(new A.cL(this,s.h("cL<1>")),new A.mP(this),s.c,s.y[1])}, +a4(a){var s,r +if(typeof a=="string"&&a!=="__proto__"){s=this.b +return s==null?!1:s[a]!=null}else if(typeof a=="number"&&(a&1073741823)===a){r=this.c +return r==null?!1:r[a]!=null}else return this.ic(a)}, +ic(a){var s=this.d +if(s==null)return!1 +return this.aO(this.fg(s,a),a)>=0}, +j(a,b){var s,r,q +if(typeof b=="string"&&b!=="__proto__"){s=this.b +r=s==null?null:A.qp(s,b) +return r}else if(typeof b=="number"&&(b&1073741823)===b){q=this.c +r=q==null?null:A.qp(q,b) +return r}else return this.iv(b)}, +iv(a){var s,r,q=this.d +if(q==null)return null +s=this.fg(q,a) +r=this.aO(s,a) +return r<0?null:s[r+1]}, +t(a,b,c){var s,r,q=this +if(typeof b=="string"&&b!=="__proto__"){s=q.b +q.f0(s==null?q.b=A.ox():s,b,c)}else if(typeof b=="number"&&(b&1073741823)===b){r=q.c +q.f0(r==null?q.c=A.ox():r,b,c)}else q.ji(b,c)}, +ji(a,b){var s,r,q,p=this,o=p.d +if(o==null)o=p.d=A.ox() +s=p.dC(a) +r=o[s] +if(r==null){A.oy(o,s,[a,b]);++p.a +p.e=null}else{q=p.aO(r,a) +if(q>=0)r[q+1]=b +else{r.push(a,b);++p.a +p.e=null}}}, +ap(a,b){var s,r,q,p,o,n=this,m=n.f5() +for(s=m.length,r=A.r(n).y[1],q=0;q"))}} +A.ir.prototype={ +gm(){var s=this.d +return s==null?this.$ti.c.a(s):s}, +k(){var s=this,r=s.b,q=s.c,p=s.a +if(r!==p.e)throw A.b(A.au(p)) +else if(q>=r.length){s.d=null +return!1}else{s.d=r[q] +s.c=q+1 +return!0}}} +A.fa.prototype={ +gq(a){var s=this,r=new A.dG(s,s.r,s.$ti.h("dG<1>")) +r.c=s.e +return r}, +gl(a){return this.a}, +gB(a){return this.a===0}, +I(a,b){var s,r +if(b!=="__proto__"){s=this.b +if(s==null)return!1 +return s[b]!=null}else{r=this.ib(b) +return r}}, +ib(a){var s=this.d +if(s==null)return!1 +return this.aO(s[B.a.gA(a)&1073741823],a)>=0}, +gF(a){var s=this.e +if(s==null)throw A.b(A.B("No elements")) +return s.a}, +gE(a){var s=this.f +if(s==null)throw A.b(A.B("No elements")) +return s.a}, +v(a,b){var s,r,q=this +if(typeof b=="string"&&b!=="__proto__"){s=q.b +return q.f_(s==null?q.b=A.oz():s,b)}else if(typeof b=="number"&&(b&1073741823)===b){r=q.c +return q.f_(r==null?q.c=A.oz():r,b)}else return q.hX(b)}, +hX(a){var s,r,q=this,p=q.d +if(p==null)p=q.d=A.oz() +s=J.aC(a)&1073741823 +r=p[s] +if(r==null)p[s]=[q.dX(a)] +else{if(q.aO(r,a)>=0)return!1 +r.push(q.dX(a))}return!0}, +G(a,b){var s +if(typeof b=="string"&&b!=="__proto__")return this.j5(this.b,b) +else{s=this.j4(b) +return s}}, +j4(a){var s,r,q,p,o=this.d +if(o==null)return!1 +s=J.aC(a)&1073741823 +r=o[s] +q=this.aO(r,a) +if(q<0)return!1 +p=r.splice(q,1)[0] +if(0===r.length)delete o[s] +this.fQ(p) +return!0}, +f_(a,b){if(a[b]!=null)return!1 +a[b]=this.dX(b) +return!0}, +j5(a,b){var s +if(a==null)return!1 +s=a[b] +if(s==null)return!1 +this.fQ(s) +delete a[b] +return!0}, +fn(){this.r=this.r+1&1073741823}, +dX(a){var s,r=this,q=new A.mT(a) +if(r.e==null)r.e=r.f=q +else{s=r.f +s.toString +q.c=s +r.f=s.b=q}++r.a +r.fn() +return q}, +fQ(a){var s=this,r=a.c,q=a.b +if(r==null)s.e=q +else r.b=q +if(q==null)s.f=r +else q.c=r;--s.a +s.fn()}, +aO(a,b){var s,r +if(a==null)return-1 +s=a.length +for(r=0;r"))}, +gl(a){return this.b}, +gF(a){var s +if(this.b===0)throw A.b(A.B("No such element")) +s=this.c +s.toString +return s}, +gE(a){var s +if(this.b===0)throw A.b(A.B("No such element")) +s=this.c.c +s.toString +return s}, +gB(a){return this.b===0}, +dT(a,b,c){var s,r,q=this +if(b.a!=null)throw A.b(A.B("LinkedListEntry is already in a LinkedList"));++q.a +b.a=q +s=q.b +if(s===0){b.b=b +q.c=b.c=b +q.b=s+1 +return}r=a.c +r.toString +b.c=r +b.b=a +a.c=r.b=b +q.b=s+1}, +e4(a){var s,r,q=this;++q.a +s=a.b +s.c=a.c +a.c.b=s +r=--q.b +a.a=a.b=a.c=null +if(r===0)q.c=null +else if(a===q.c)q.c=s}} +A.iy.prototype={ +gm(){var s=this.c +return s==null?this.$ti.c.a(s):s}, +k(){var s=this,r=s.a +if(s.b!==r.a)throw A.b(A.au(s)) +if(r.b!==0)r=s.e&&s.d===r.gF(0) +else r=!0 +if(r){s.c=null +return!1}s.e=!0 +r=s.d +s.c=r +s.d=r.b +return!0}} +A.aL.prototype={ +gc8(){var s=this.a +if(s==null||this===s.gF(0))return null +return this.c}} +A.v.prototype={ +gq(a){return new A.b3(a,this.gl(a),A.aT(a).h("b3"))}, +L(a,b){return this.j(a,b)}, +gB(a){return this.gl(a)===0}, +gF(a){if(this.gl(a)===0)throw A.b(A.az()) +return this.j(a,0)}, +gE(a){if(this.gl(a)===0)throw A.b(A.az()) +return this.j(a,this.gl(a)-1)}, +b8(a,b,c){return new A.E(a,b,A.aT(a).h("@").H(c).h("E<1,2>"))}, +Y(a,b){return A.b5(a,b,null,A.aT(a).h("v.E"))}, +ah(a,b){return A.b5(a,0,A.cU(b,"count",t.S),A.aT(a).h("v.E"))}, +az(a,b){var s,r,q,p,o=this +if(o.gB(a)){s=J.pA(0,A.aT(a).h("v.E")) +return s}r=o.j(a,0) +q=A.b4(o.gl(a),r,!0,A.aT(a).h("v.E")) +for(p=1;p").H(b).h("ak<1,2>"))}, +a0(a,b,c){var s,r=this.gl(a) +A.bd(b,c,r) +s=A.aw(this.cm(a,b,c),A.aT(a).h("v.E")) +return s}, +cm(a,b,c){A.bd(b,c,this.gl(a)) +return A.b5(a,b,c,A.aT(a).h("v.E"))}, +el(a,b,c,d){var s +A.bd(b,c,this.gl(a)) +for(s=b;sp.gl(q))throw A.b(A.py()) +if(r=0;--o)this.t(a,b+o,p.j(q,r+o)) +else for(o=0;o"))}, +gl(a){return J.at(this.ga_())}, +gB(a){return J.nY(this.ga_())}, +gbF(){return new A.fb(this,A.r(this).h("fb"))}, +i(a){return A.oc(this)}, +$ian:1} +A.kD.prototype={ +$1(a){var s=this.a,r=s.j(0,a) +if(r==null)r=A.r(s).h("Q.V").a(r) +return new A.aN(a,r,A.r(s).h("aN"))}, +$S(){return A.r(this.a).h("aN(Q.K)")}} +A.kE.prototype={ +$2(a,b){var s,r=this.a +if(!r.a)this.b.a+=", " +r.a=!1 +r=this.b +s=A.t(a) +r.a=(r.a+=s)+": " +s=A.t(b) +r.a+=s}, +$S:113} +A.fb.prototype={ +gl(a){var s=this.a +return s.gl(s)}, +gB(a){var s=this.a +return s.gB(s)}, +gF(a){var s=this.a +s=s.j(0,J.j2(s.ga_())) +return s==null?this.$ti.y[1].a(s):s}, +gE(a){var s=this.a +s=s.j(0,J.nZ(s.ga_())) +return s==null?this.$ti.y[1].a(s):s}, +gq(a){var s=this.a +return new A.iz(J.a4(s.ga_()),s,this.$ti.h("iz<1,2>"))}} +A.iz.prototype={ +k(){var s=this,r=s.a +if(r.k()){s.c=s.b.j(0,r.gm()) +return!0}s.c=null +return!1}, +gm(){var s=this.c +return s==null?this.$ti.y[1].a(s):s}} +A.dl.prototype={ +gB(a){return this.a===0}, +b8(a,b,c){return new A.ct(this,b,this.$ti.h("@<1>").H(c).h("ct<1,2>"))}, +i(a){return A.o7(this,"{","}")}, +ah(a,b){return A.ol(this,b,this.$ti.c)}, +Y(a,b){return A.pY(this,b,this.$ti.c)}, +gF(a){var s,r=A.ix(this,this.r,this.$ti.c) +if(!r.k())throw A.b(A.az()) +s=r.d +return s==null?r.$ti.c.a(s):s}, +gE(a){var s,r,q=A.ix(this,this.r,this.$ti.c) +if(!q.k())throw A.b(A.az()) +s=q.$ti.c +do{r=q.d +if(r==null)r=s.a(r)}while(q.k()) +return r}, +L(a,b){var s,r,q,p=this +A.ab(b,"index") +s=A.ix(p,p.r,p.$ti.c) +for(r=b;s.k();){if(r===0){q=s.d +return q==null?s.$ti.c.a(q):q}--r}throw A.b(A.hf(b,b-r,p,null,"index"))}, +$iq:1, +$id:1} +A.fk.prototype={} +A.nj.prototype={ +$0(){var s,r +try{s=new TextDecoder("utf-8",{fatal:true}) +return s}catch(r){}return null}, +$S:21} +A.ni.prototype={ +$0(){var s,r +try{s=new TextDecoder("utf-8",{fatal:false}) +return s}catch(r){}return null}, +$S:21} +A.fL.prototype={ +kr(a){return B.aj.a5(a)}} +A.iQ.prototype={ +a5(a){var s,r,q,p=A.bd(0,null,a.length),o=new Uint8Array(p) +for(s=~this.a,r=0;r=0){g="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/".charCodeAt(f) +if(g===k)continue +k=g}else{if(f===-1){if(o<0){e=p==null?null:p.a.length +if(e==null)e=0 +o=e+(r-q) +n=r}++m +if(k===61)continue}k=g}if(f!==-2){if(p==null){p=new A.aA("") +e=p}else e=p +e.a+=B.a.p(a0,q,r) +d=A.aP(k) +e.a+=d +q=l +continue}}throw A.b(A.af("Invalid base64 data",a0,r))}if(p!=null){e=B.a.p(a0,q,a2) +e=p.a+=e +d=e.length +if(o>=0)A.pd(a0,n,a2,o,m,d) +else{c=B.b.ac(d-1,4)+1 +if(c===1)throw A.b(A.af(a,a0,a2)) +while(c<4){e+="=" +p.a=e;++c}}e=p.a +return B.a.aL(a0,a1,a2,e.charCodeAt(0)==0?e:e)}b=a2-a1 +if(o>=0)A.pd(a0,n,a2,o,m,b) +else{c=B.b.ac(b,4) +if(c===1)throw A.b(A.af(a,a0,a2)) +if(c>1)a0=B.a.aL(a0,a2,a2,c===2?"==":"=")}return a0}} +A.fR.prototype={} +A.cq.prototype={} +A.cr.prototype={} +A.h8.prototype={} +A.i_.prototype={ +cS(a){return new A.fy(!1).dD(a,0,null,!0)}} +A.i0.prototype={ +a5(a){var s,r,q=A.bd(0,null,a.length) +if(q===0)return new Uint8Array(0) +s=new Uint8Array(q*3) +r=new A.nk(s) +if(r.iu(a,0,q)!==q)r.e7() +return B.e.a0(s,0,r.b)}} +A.nk.prototype={ +e7(){var s=this,r=s.c,q=s.b,p=s.b=q+1 +r.$flags&2&&A.y(r) +r[q]=239 +q=s.b=p+1 +r[p]=191 +s.b=q+1 +r[q]=189}, +jx(a,b){var s,r,q,p,o=this +if((b&64512)===56320){s=65536+((a&1023)<<10)|b&1023 +r=o.c +q=o.b +p=o.b=q+1 +r.$flags&2&&A.y(r) +r[q]=s>>>18|240 +q=o.b=p+1 +r[p]=s>>>12&63|128 +p=o.b=q+1 +r[q]=s>>>6&63|128 +o.b=p+1 +r[p]=s&63|128 +return!0}else{o.e7() +return!1}}, +iu(a,b,c){var s,r,q,p,o,n,m,l,k=this +if(b!==c&&(a.charCodeAt(c-1)&64512)===55296)--c +for(s=k.c,r=s.$flags|0,q=s.length,p=b;p=q)break +k.b=n+1 +r&2&&A.y(s) +s[n]=o}else{n=o&64512 +if(n===55296){if(k.b+4>q)break +m=p+1 +if(k.jx(o,a.charCodeAt(m)))p=m}else if(n===56320){if(k.b+3>q)break +k.e7()}else if(o<=2047){n=k.b +l=n+1 +if(l>=q)break +k.b=l +r&2&&A.y(s) +s[n]=o>>>6|192 +k.b=l+1 +s[l]=o&63|128}else{n=k.b +if(n+2>=q)break +l=k.b=n+1 +r&2&&A.y(s) +s[n]=o>>>12|224 +n=k.b=l+1 +s[l]=o>>>6&63|128 +k.b=n+1 +s[n]=o&63|128}}}return p}} +A.fy.prototype={ +dD(a,b,c,d){var s,r,q,p,o,n,m=this,l=A.bd(b,c,J.at(a)) +if(b===l)return"" +if(a instanceof Uint8Array){s=a +r=s +q=0}else{r=A.vh(a,b,l) +l-=b +q=b +b=0}if(d&&l-b>=15){p=m.a +o=A.vg(p,r,b,l) +if(o!=null){if(!p)return o +if(o.indexOf("\ufffd")<0)return o}}o=m.dF(r,b,l,d) +p=m.b +if((p&1)!==0){n=A.vi(p) +m.b=0 +throw A.b(A.af(n,a,q+m.c))}return o}, +dF(a,b,c,d){var s,r,q=this +if(c-b>1000){s=B.b.J(b+c,2) +r=q.dF(a,b,s,!1) +if((q.b&1)!==0)return r +return r+q.dF(a,s,c,d)}return q.jW(a,b,c,d)}, +jW(a,b,c,d){var s,r,q,p,o,n,m,l=this,k=65533,j=l.b,i=l.c,h=new A.aA(""),g=b+1,f=a[b] +A:for(s=l.a;;){for(;;g=p){r="AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFFFFFFFFFFFFFFFFGGGGGGGGGGGGGGGGHHHHHHHHHHHHHHHHHHHHHHHHHHHIHHHJEEBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBKCCCCCCCCCCCCDCLONNNMEEEEEEEEEEE".charCodeAt(f)&31 +i=j<=32?f&61694>>>r:(f&63|i<<6)>>>0 +j=" \x000:XECCCCCN:lDb \x000:XECCCCCNvlDb \x000:XECCCCCN:lDb AAAAA\x00\x00\x00\x00\x00AAAAA00000AAAAA:::::AAAAAGG000AAAAA00KKKAAAAAG::::AAAAA:IIIIAAAAA000\x800AAAAA\x00\x00\x00\x00 AAAAA".charCodeAt(j+r) +if(j===0){q=A.aP(i) +h.a+=q +if(g===c)break A +break}else if((j&1)!==0){if(s)switch(j){case 69:case 67:q=A.aP(k) +h.a+=q +break +case 65:q=A.aP(k) +h.a+=q;--g +break +default:q=A.aP(k) +h.a=(h.a+=q)+q +break}else{l.b=j +l.c=g-1 +return""}j=0}if(g===c)break A +p=g+1 +f=a[g]}p=g+1 +f=a[g] +if(f<128){for(;;){if(!(p=128){o=n-1 +p=n +break}p=n}if(o-g<20)for(m=g;m32)if(s){s=A.aP(k) +h.a+=s}else{l.b=77 +l.c=c +return""}l.b=j +l.c=i +s=h.a +return s.charCodeAt(0)==0?s:s}} +A.a7.prototype={ +aA(a){var s,r,q=this,p=q.c +if(p===0)return q +s=!q.a +r=q.b +p=A.aR(p,r) +return new A.a7(p===0?!1:s,r,p)}, +io(a){var s,r,q,p,o,n,m=this.c +if(m===0)return $.ba() +s=m+a +r=this.b +q=new Uint16Array(s) +for(p=m-1;p>=0;--p)q[p+a]=r[p] +o=this.a +n=A.aR(s,q) +return new A.a7(n===0?!1:o,q,n)}, +ip(a){var s,r,q,p,o,n,m,l=this,k=l.c +if(k===0)return $.ba() +s=k-a +if(s<=0)return l.a?$.p9():$.ba() +r=l.b +q=new Uint16Array(s) +for(p=a;p>>0!==0)return l.dl(0,$.fI()) +for(k=0;k=0)return q.cq(b,r) +return b.cq(q,!r)}, +dl(a,b){var s,r,q=this,p=q.c +if(p===0)return b.aA(0) +s=b.c +if(s===0)return q +r=q.a +if(r!==b.a)return q.dr(b,r) +if(A.mi(q.b,p,b.b,s)>=0)return q.cq(b,r) +return b.cq(q,!r)}, +bG(a,b){var s,r,q,p,o,n,m,l=this.c,k=b.c +if(l===0||k===0)return $.ba() +s=l+k +r=this.b +q=b.b +p=new Uint16Array(s) +for(o=0;o0?p.aA(0):p}, +j3(a){var s,r,q,p=this +if(p.c0)q=q.bh(0,$.ot.af()) +return p.a&&q.c>0?q.aA(0):q}, +f9(a){var s,r,q,p,o,n,m,l,k,j,i,h,g,f,e,d,c=this,b=c.c +if(b===$.qi&&a.c===$.qk&&c.b===$.qh&&a.b===$.qj)return +s=a.b +r=a.c +q=16-B.b.gfX(s[r-1]) +if(q>0){p=new Uint16Array(r+5) +o=A.qg(s,r,q,p) +n=new Uint16Array(b+5) +m=A.qg(c.b,b,q,n)}else{n=A.ou(c.b,0,b,b+2) +o=r +p=s +m=b}l=p[o-1] +k=m-o +j=new Uint16Array(m) +i=A.ov(p,o,k,j) +h=m+1 +g=n.$flags|0 +if(A.mi(n,m,j,i)>=0){g&2&&A.y(n) +n[m]=1 +A.ie(n,h,j,i,n)}else{g&2&&A.y(n) +n[m]=0}f=new Uint16Array(o+2) +f[o]=1 +A.ie(f,o+1,p,o,f) +e=m-1 +while(k>0){d=A.uI(l,n,e);--k +A.qm(d,f,0,n,k,o) +if(n[e]1){q=$.p8() +if(q.c===0)A.C(B.an) +p=r.j3(q).i(0) +s.push(p) +o=p.length +if(o===1)s.push("000") +if(o===2)s.push("00") +if(o===3)s.push("0") +r=r.im(q)}s.push(B.b.i(r.b[0])) +if(m)s.push("-") +return new A.eI(s,t.bJ).c2(0)}} +A.mj.prototype={ +$2(a,b){a=a+b&536870911 +a=a+((a&524287)<<10)&536870911 +return a^a>>>6}, +$S:87} +A.mk.prototype={ +$1(a){a=a+((a&67108863)<<3)&536870911 +a^=a>>>11 +return a+((a&16383)<<15)&536870911}, +$S:28} +A.ip.prototype={ +fV(a,b,c){var s=this.a +if(s!=null)s.register(a,b,c)}, +h1(a){var s=this.a +if(s!=null)s.unregister(a)}} +A.ei.prototype={ +W(a,b){if(b==null)return!1 +return b instanceof A.ei&&this.a===b.a&&this.b===b.b&&this.c===b.c}, +gA(a){return A.eD(this.a,this.b,B.f,B.f)}, +ag(a,b){var s=B.b.ag(this.a,b.a) +if(s!==0)return s +return B.b.ag(this.b,b.b)}, +i(a){var s=this,r=A.tE(A.pO(s)),q=A.h0(A.pM(s)),p=A.h0(A.pJ(s)),o=A.h0(A.pK(s)),n=A.h0(A.pL(s)),m=A.h0(A.pN(s)),l=A.pm(A.ub(s)),k=s.b,j=k===0?"":A.pm(k) +k=r+"-"+q +if(s.c)return k+"-"+p+" "+o+":"+n+":"+m+"."+l+j+"Z" +else return k+"-"+p+" "+o+":"+n+":"+m+"."+l+j}} +A.bx.prototype={ +W(a,b){if(b==null)return!1 +return b instanceof A.bx&&this.a===b.a}, +gA(a){return B.b.gA(this.a)}, +ag(a,b){return B.b.ag(this.a,b.a)}, +i(a){var s,r,q,p,o,n=this.a,m=B.b.J(n,36e8),l=n%36e8 +if(n<0){m=0-m +n=0-l +s="-"}else{n=l +s=""}r=B.b.J(n,6e7) +n%=6e7 +q=r<10?"0":"" +p=B.b.J(n,1e6) +o=p<10?"0":"" +return s+m+":"+q+r+":"+o+p+"."+B.a.kR(B.b.i(n%1e6),6,"0")}} +A.mx.prototype={ +i(a){return this.ae()}} +A.O.prototype={ +gbi(){return A.ua(this)}} +A.fN.prototype={ +i(a){var s=this.a +if(s!=null)return"Assertion failed: "+A.h9(s) +return"Assertion failed"}} +A.bL.prototype={} +A.bb.prototype={ +gdJ(){return"Invalid argument"+(!this.a?"(s)":"")}, +gdI(){return""}, +i(a){var s=this,r=s.c,q=r==null?"":" ("+r+")",p=s.d,o=p==null?"":": "+A.t(p),n=s.gdJ()+q+o +if(!s.a)return n +return n+s.gdI()+": "+A.h9(s.gev())}, +gev(){return this.b}} +A.dh.prototype={ +gev(){return this.b}, +gdJ(){return"RangeError"}, +gdI(){var s,r=this.e,q=this.f +if(r==null)s=q!=null?": Not less than or equal to "+A.t(q):"" +else if(q==null)s=": Not greater than or equal to "+A.t(r) +else if(q>r)s=": Not in inclusive range "+A.t(r)+".."+A.t(q) +else s=qe.length +else s=!1 +if(s)f=null +if(f==null){if(e.length>78)e=B.a.p(e,0,75)+"..." +return g+"\n"+e}for(r=1,q=0,p=!1,o=0;o1?g+(" (at line "+r+", character "+(f-q+1)+")\n"):g+(" (at character "+(f+1)+")\n") +m=e.length +for(o=f;o78){k="..." +if(f-q<75){j=q+75 +i=q}else{if(m-f<75){i=m-75 +j=m +k=""}else{i=f-36 +j=f+36}l="..."}}else{j=m +i=q +k=""}return g+l+B.a.p(e,i,j)+k+"\n"+B.a.bG(" ",f-i+l.length)+"^\n"}else return f!=null?g+(" (at offset "+A.t(f)+")"):g}, +$ia5:1} +A.hh.prototype={ +gbi(){return null}, +i(a){return"IntegerDivisionByZeroException"}, +$iO:1, +$ia5:1} +A.d.prototype={ +bu(a,b){return A.ee(this,A.r(this).h("d.E"),b)}, +b8(a,b,c){return A.ht(this,b,A.r(this).h("d.E"),c)}, +az(a,b){var s=A.r(this).h("d.E") +if(b)s=A.aw(this,s) +else{s=A.aw(this,s) +s.$flags=1 +s=s}return s}, +cf(a){return this.az(0,!0)}, +gl(a){var s,r=this.gq(this) +for(s=0;r.k();)++s +return s}, +gB(a){return!this.gq(this).k()}, +ah(a,b){return A.ol(this,b,A.r(this).h("d.E"))}, +Y(a,b){return A.pY(this,b,A.r(this).h("d.E"))}, +hF(a,b){return new A.eK(this,b,A.r(this).h("eK"))}, +gF(a){var s=this.gq(this) +if(!s.k())throw A.b(A.az()) +return s.gm()}, +gE(a){var s,r=this.gq(this) +if(!r.k())throw A.b(A.az()) +do s=r.gm() +while(r.k()) +return s}, +L(a,b){var s,r +A.ab(b,"index") +s=this.gq(this) +for(r=b;s.k();){if(r===0)return s.gm();--r}throw A.b(A.hf(b,b-r,this,null,"index"))}, +i(a){return A.tV(this,"(",")")}} +A.aN.prototype={ +i(a){return"MapEntry("+A.t(this.a)+": "+A.t(this.b)+")"}} +A.R.prototype={ +gA(a){return A.e.prototype.gA.call(this,0)}, +i(a){return"null"}} +A.e.prototype={$ie:1, +W(a,b){return this===b}, +gA(a){return A.eG(this)}, +i(a){return"Instance of '"+A.hG(this)+"'"}, +gV(a){return A.wT(this)}, +toString(){return this.i(this)}} +A.dQ.prototype={ +i(a){return this.a}, +$iY:1} +A.aA.prototype={ +gl(a){return this.a.length}, +i(a){var s=this.a +return s.charCodeAt(0)==0?s:s}} +A.lA.prototype={ +$2(a,b){throw A.b(A.af("Illegal IPv6 address, "+a,this.a,b))}, +$S:66} +A.fv.prototype={ +gfL(){var s,r,q,p,o=this,n=o.w +if(n===$){s=o.a +r=s.length!==0?s+":":"" +q=o.c +p=q==null +if(!p||s==="file"){s=r+"//" +r=o.b +if(r.length!==0)s=s+r+"@" +if(!p)s+=q +r=o.d +if(r!=null)s=s+":"+A.t(r)}else s=r +s+=o.e +r=o.f +if(r!=null)s=s+"?"+r +r=o.r +if(r!=null)s=s+"#"+r +n=o.w=s.charCodeAt(0)==0?s:s}return n}, +gkS(){var s,r,q=this,p=q.x +if(p===$){s=q.e +if(s.length!==0&&s.charCodeAt(0)===47)s=B.a.N(s,1) +r=s.length===0?B.A:A.aM(new A.E(A.f(s.split("/"),t.s),A.wH(),t.do),t.N) +q.x!==$&&A.p4() +p=q.x=r}return p}, +gA(a){var s,r=this,q=r.y +if(q===$){s=B.a.gA(r.gfL()) +r.y!==$&&A.p4() +r.y=s +q=s}return q}, +geN(){return this.b}, +gb7(){var s=this.c +if(s==null)return"" +if(B.a.u(s,"[")&&!B.a.C(s,"v",1))return B.a.p(s,1,s.length-1) +return s}, +gc7(){var s=this.d +return s==null?A.qD(this.a):s}, +gc9(){var s=this.f +return s==null?"":s}, +gcW(){var s=this.r +return s==null?"":s}, +kC(a){var s=this.a +if(a.length!==s.length)return!1 +return A.vx(a,s,0)>=0}, +hn(a){var s,r,q,p,o,n,m,l=this +a=A.nh(a,0,a.length) +s=a==="file" +r=l.b +q=l.d +if(a!==l.a)q=A.ng(q,a) +p=l.c +if(!(p!=null))p=r.length!==0||q!=null||s?"":null +o=l.e +if(!s)n=p!=null&&o.length!==0 +else n=!0 +if(n&&!B.a.u(o,"/"))o="/"+o +m=o +return A.fw(a,r,p,q,m,l.f,l.r)}, +gha(){if(this.a!==""){var s=this.r +s=(s==null?"":s)===""}else s=!1 +return s}, +fm(a,b){var s,r,q,p,o,n,m +for(s=0,r=0;B.a.C(b,"../",r);){r+=3;++s}q=B.a.d0(a,"/") +for(;;){if(!(q>0&&s>0))break +p=B.a.hc(a,"/",q-1) +if(p<0)break +o=q-p +n=o!==2 +m=!1 +if(!n||o===3)if(a.charCodeAt(p+1)===46)n=!n||a.charCodeAt(p+2)===46 +else n=m +else n=m +if(n)break;--s +q=p}return B.a.aL(a,q+1,null,B.a.N(b,r-3*s))}, +hp(a){return this.ca(A.bt(a))}, +ca(a){var s,r,q,p,o,n,m,l,k,j,i,h=this +if(a.gZ().length!==0)return a +else{s=h.a +if(a.geo()){r=a.hn(s) +return r}else{q=h.b +p=h.c +o=h.d +n=h.e +if(a.gh7())m=a.gcX()?a.gc9():h.f +else{l=A.ve(h,n) +if(l>0){k=B.a.p(n,0,l) +n=a.gen()?k+A.cQ(a.gaa()):k+A.cQ(h.fm(B.a.N(n,k.length),a.gaa()))}else if(a.gen())n=A.cQ(a.gaa()) +else if(n.length===0)if(p==null)n=s.length===0?a.gaa():A.cQ(a.gaa()) +else n=A.cQ("/"+a.gaa()) +else{j=h.fm(n,a.gaa()) +r=s.length===0 +if(!r||p!=null||B.a.u(n,"/"))n=A.cQ(j) +else n=A.oE(j,!r||p!=null)}m=a.gcX()?a.gc9():null}}}i=a.gep()?a.gcW():null +return A.fw(s,q,p,o,n,m,i)}, +geo(){return this.c!=null}, +gcX(){return this.f!=null}, +gep(){return this.r!=null}, +gh7(){return this.e.length===0}, +gen(){return B.a.u(this.e,"/")}, +eK(){var s,r=this,q=r.a +if(q!==""&&q!=="file")throw A.b(A.a3("Cannot extract a file path from a "+q+" URI")) +q=r.f +if((q==null?"":q)!=="")throw A.b(A.a3(u.y)) +q=r.r +if((q==null?"":q)!=="")throw A.b(A.a3(u.l)) +if(r.c!=null&&r.gb7()!=="")A.C(A.a3(u.j)) +s=r.gkS() +A.v6(s,!1) +q=A.oj(B.a.u(r.e,"/")?"/":"",s,"/") +q=q.charCodeAt(0)==0?q:q +return q}, +i(a){return this.gfL()}, +W(a,b){var s,r,q,p=this +if(b==null)return!1 +if(p===b)return!0 +s=!1 +if(t.dD.b(b))if(p.a===b.gZ())if(p.c!=null===b.geo())if(p.b===b.geN())if(p.gb7()===b.gb7())if(p.gc7()===b.gc7())if(p.e===b.gaa()){r=p.f +q=r==null +if(!q===b.gcX()){if(q)r="" +if(r===b.gc9()){r=p.r +q=r==null +if(!q===b.gep()){s=q?"":r +s=s===b.gcW()}}}}return s}, +$ihX:1, +gZ(){return this.a}, +gaa(){return this.e}} +A.nf.prototype={ +$1(a){return A.vf(64,a,B.j,!1)}, +$S:8} +A.hY.prototype={ +geM(){var s,r,q,p,o=this,n=null,m=o.c +if(m==null){m=o.a +s=o.b[0]+1 +r=B.a.aU(m,"?",s) +q=m.length +if(r>=0){p=A.fx(m,r+1,q,256,!1,!1) +q=r}else p=n +m=o.c=new A.ij("data","",n,n,A.fx(m,s,q,128,!1,!1),p,n)}return m}, +i(a){var s=this.a +return this.b[0]===-1?"data:"+s:s}} +A.b6.prototype={ +geo(){return this.c>0}, +geq(){return this.c>0&&this.d+10&&this.r>=this.a.length}, +gZ(){var s=this.w +return s==null?this.w=this.ia():s}, +ia(){var s,r=this,q=r.b +if(q<=0)return"" +s=q===4 +if(s&&B.a.u(r.a,"http"))return"http" +if(q===5&&B.a.u(r.a,"https"))return"https" +if(s&&B.a.u(r.a,"file"))return"file" +if(q===7&&B.a.u(r.a,"package"))return"package" +return B.a.p(r.a,0,q)}, +geN(){var s=this.c,r=this.b+3 +return s>r?B.a.p(this.a,r,s-1):""}, +gb7(){var s=this.c +return s>0?B.a.p(this.a,s,this.d):""}, +gc7(){var s,r=this +if(r.geq())return A.bh(B.a.p(r.a,r.d+1,r.e),null) +s=r.b +if(s===4&&B.a.u(r.a,"http"))return 80 +if(s===5&&B.a.u(r.a,"https"))return 443 +return 0}, +gaa(){return B.a.p(this.a,this.e,this.f)}, +gc9(){var s=this.f,r=this.r +return s=q.length)return s +return new A.b6(B.a.p(q,0,r),s.b,s.c,s.d,s.e,s.f,r,s.w)}, +hn(a){var s,r,q,p,o,n,m,l,k,j,i,h=this,g=null +a=A.nh(a,0,a.length) +s=!(h.b===a.length&&B.a.u(h.a,a)) +r=a==="file" +q=h.c +p=q>0?B.a.p(h.a,h.b+3,q):"" +o=h.geq()?h.gc7():g +if(s)o=A.ng(o,a) +q=h.c +if(q>0)n=B.a.p(h.a,q,h.d) +else n=p.length!==0||o!=null||r?"":g +q=h.a +m=h.f +l=B.a.p(q,h.e,m) +if(!r)k=n!=null&&l.length!==0 +else k=!0 +if(k&&!B.a.u(l,"/"))l="/"+l +k=h.r +j=m0)return b +s=b.c +if(s>0){r=a.b +if(r<=0)return b +q=r===4 +if(q&&B.a.u(a.a,"file"))p=b.e!==b.f +else if(q&&B.a.u(a.a,"http"))p=!b.fj("80") +else p=!(r===5&&B.a.u(a.a,"https"))||!b.fj("443") +if(p){o=r+1 +return new A.b6(B.a.p(a.a,0,o)+B.a.N(b.a,c+1),r,s+o,b.d+o,b.e+o,b.f+o,b.r+o,a.w)}else return this.fN().ca(b)}n=b.e +c=b.f +if(n===c){s=b.r +if(c0?l:m +o=k-n +return new A.b6(B.a.p(a.a,0,k)+B.a.N(s,n),a.b,a.c,a.d,m,c+o,b.r+o,a.w)}j=a.e +i=a.f +if(j===i&&a.c>0){while(B.a.C(s,"../",n))n+=3 +o=j-n+1 +return new A.b6(B.a.p(a.a,0,j)+"/"+B.a.N(s,n),a.b,a.c,a.d,j,c+o,b.r+o,a.w)}h=a.a +l=A.qv(this) +if(l>=0)g=l +else for(g=j;B.a.C(h,"../",g);)g+=3 +f=0 +for(;;){e=n+3 +if(!(e<=c&&B.a.C(s,"../",n)))break;++f +n=e}for(d="";i>g;){--i +if(h.charCodeAt(i)===47){if(f===0){d="/" +break}--f +d="/"}}if(i===g&&a.b<=0&&!B.a.C(h,"/",j)){n-=f*3 +d=""}o=i-n+d.length +return new A.b6(B.a.p(h,0,i)+d+B.a.N(s,n),a.b,a.c,a.d,j,c+o,b.r+o,a.w)}, +eK(){var s,r=this,q=r.b +if(q>=0){s=!(q===4&&B.a.u(r.a,"file")) +q=s}else q=!1 +if(q)throw A.b(A.a3("Cannot extract a file path from a "+r.gZ()+" URI")) +q=r.f +s=r.a +if(q0?s.gb7():r,n=s.geq()?s.gc7():r,m=s.a,l=s.f,k=B.a.p(m,s.e,l),j=s.r +l=l4294967296)throw A.b(new A.dh(k,k,!1,k,k,"max must be in range 0 < max \u2264 2^32, was "+a)) +if(a>255)if(a>65535)s=a>16777215?4:3 +else s=2 +else s=1 +r=this.a +r.$flags&2&&A.y(r,11) +r.setUint32(0,0,!1) +q=4-s +p=A.A(Math.pow(256,s)) +for(o=a-1,n=(a&o)===0;;){crypto.getRandomValues(J.cZ(B.aN.gaS(r),q,s)) +m=r.getUint32(0,!1) +if(n)return(m&o)>>>0 +l=m%a +if(m-l+a>>0)&2147483647 +r^=r>>>6}r=r+(r<<3>>>0)&2147483647 +r^=r>>>11 +return r+(r<<15>>>0)&2147483647}} +A.hB.prototype={} +A.hW.prototype={} +A.ek.prototype={ +hP(a,b,c){var s=this.a.a +s===$&&A.x() +s.ez(this.giz(),new A.jW(this))}, +he(){return this.d++}, +n(){var s=0,r=A.l(t.H),q,p=this,o +var $async$n=A.m(function(a,b){if(a===1)return A.i(b,r) +for(;;)switch(s){case 0:if(p.r||(p.w.a.a&30)!==0){s=1 +break}p.r=!0 +o=p.a.b +o===$&&A.x() +o.n() +s=3 +return A.c(p.w.a,$async$n) +case 3:case 1:return A.j(q,r)}}) +return A.k($async$n,r)}, +iA(a){var s,r=this +if(r.c){a.toString +a=B.N.ei(a)}if(a instanceof A.bf){s=r.e.G(0,a.a) +if(s!=null)s.a.P(a.b)}else if(a instanceof A.bm){s=r.e.G(0,a.a) +if(s!=null)s.fZ(new A.h5(a.b),a.c)}else if(a instanceof A.ap)r.f.v(0,a) +else if(a instanceof A.bw){s=r.e.G(0,a.a) +if(s!=null)s.fY(B.M)}}, +br(a){var s,r,q=this +if(q.r||(q.w.a.a&30)!==0)throw A.b(A.B("Tried to send "+a.i(0)+" over isolate channel, but the connection was closed!")) +s=q.a.b +s===$&&A.x() +r=q.c?B.N.dk(a):a +s.a.v(0,r)}, +kY(a,b,c){var s,r=this +if(r.r||(r.w.a.a&30)!==0)return +s=a.a +if(b instanceof A.ed)r.br(new A.bw(s)) +else r.br(new A.bm(s,b,c))}, +hC(a){var s=this.f +new A.ar(s,A.r(s).h("ar<1>")).kF(new A.jX(this,a))}} +A.jW.prototype={ +$0(){var s,r,q +for(s=this.a,r=s.e,q=new A.cx(r,r.r,r.e);q.k();)q.d.fY(B.am) +r.ef(0) +s.w.aT()}, +$S:0} +A.jX.prototype={ +$1(a){return this.hw(a)}, +hw(a){var s=0,r=A.l(t.H),q,p=2,o=[],n=this,m,l,k,j,i,h +var $async$$1=A.m(function(b,c){if(b===1){o.push(c) +s=p}for(;;)switch(s){case 0:i=null +p=4 +k=n.b.$1(a) +s=7 +return A.c(t.cG.b(k)?k:A.dC(k,t.O),$async$$1) +case 7:i=c +p=2 +s=6 +break +case 4:p=3 +h=o.pop() +m=A.G(h) +l=A.a1(h) +k=n.a.kY(a,m,l) +q=k +s=1 +break +s=6 +break +case 3:s=2 +break +case 6:k=n.a +if(!(k.r||(k.w.a.a&30)!==0))k.br(new A.bf(a.a,i)) +case 1:return A.j(q,r) +case 2:return A.i(o.at(-1),r)}}) +return A.k($async$$1,r)}, +$S:49} +A.iB.prototype={ +fZ(a,b){var s +if(b==null)s=this.b +else{s=A.f([],t.J) +if(b instanceof A.bk)B.c.aG(s,b.a) +else s.push(A.q4(b)) +s.push(A.q4(this.b)) +s=new A.bk(A.aM(s,t.a))}this.a.bv(a,s)}, +fY(a){return this.fZ(a,null)}} +A.fX.prototype={ +i(a){return"Channel was closed before receiving a response"}, +$ia5:1} +A.h5.prototype={ +i(a){return J.b1(this.a)}, +$ia5:1} +A.h4.prototype={ +dk(a){var s,r +if(a instanceof A.ap)return[0,a.a,this.h2(a.b)] +else if(a instanceof A.bm){s=J.b1(a.b) +r=a.c +r=r==null?null:r.i(0) +return[2,a.a,s,r]}else if(a instanceof A.bf)return[1,a.a,this.h2(a.b)] +else if(a instanceof A.bw)return A.f([3,a.a],t.t) +else return null}, +ei(a){var s,r,q,p +if(!t.j.b(a))throw A.b(B.aA) +s=J.a0(a) +r=A.A(s.j(a,0)) +q=A.A(s.j(a,1)) +switch(r){case 0:return new A.ap(q,t.ah.a(this.h0(s.j(a,2)))) +case 2:p=A.qR(s.j(a,3)) +s=s.j(a,2) +if(s==null)s=A.oH(s) +return new A.bm(q,s,p!=null?new A.dQ(p):null) +case 1:return new A.bf(q,t.O.a(this.h0(s.j(a,2)))) +case 3:return new A.bw(q)}throw A.b(B.az)}, +h2(a){var s,r,q,p,o,n,m,l,k,j,i,h,g,f +if(a==null)return a +if(a instanceof A.de)return a.a +else if(a instanceof A.bW){s=a.a +r=a.b +q=[] +for(p=a.c,o=p.length,n=0;n")),g=g.h("M.E");s.k();){a8=s.d +h.push(this.dE(a8==null?g.a(a8):a8))}m.push(new A.d0(i,h))}f=J.nZ(a7.a) +A:{if(f==null){s=a6 +break A}A.A(f) +s=f +break A}return new A.bn(new A.ea(n,m),s) +case 5:return new A.c4(B.V[q.$1(1)],p.$1(2)) +case 6:return new A.bV(q.$1(1),p.$1(2)) +case 13:s.toString +return new A.c5(A.o1(B.T,A.a_(J.aJ(s,1)))) +case 7:return new A.c3(new A.eE(p.$1(1),q.$1(2)),q.$1(3)) +case 8:e=A.f([],t.be) +s=t.j +k=1 +for(;;){l=a7.a +l.toString +if(!(k")).ku(0,new A.kV(r))}, +k9(a,b){var s,r,q +for(s=this.z,s=A.ix(s,s.r,s.$ti.c),r=s.$ti.c;s.k();){q=s.d +if(q==null)q=r.a(q) +if(q!==b)q.br(new A.ap(q.d++,a))}}} +A.kX.prototype={ +$1(a){var s=this.a +s.i4() +s.as.n()}, +$S:55} +A.kY.prototype={ +$1(a){return this.a.iC(this.b,a)}, +$S:62} +A.kZ.prototype={ +$1(a){return this.a.z.G(0,this.b)}, +$S:23} +A.kT.prototype={ +$0(){var s=this.b +return this.a.aD(s.a,s.b,s.c,s.d)}, +$S:68} +A.kU.prototype={ +$0(){return this.a.r.G(0,this.b.a)}, +$S:69} +A.kW.prototype={ +$0(){var s,r=this.b +if(r==null)return this.a.w.length===0 +else{s=this.a.w +return s.length!==0&&B.c.gF(s)===r}}, +$S:29} +A.kV.prototype={ +$1(a){return this.a.$0()}, +$S:23} +A.fj.prototype={ +cM(a,b){return this.jO(a,b)}, +jO(a,b){var s=0,r=A.l(t.H),q=1,p=[],o=[],n=this,m,l,k,j,i +var $async$cM=A.m(function(c,d){if(c===1){p.push(d) +s=q}for(;;)switch(s){case 0:j=n.a +i=j.dY(a,!0) +q=2 +m=n.b +l=m.he() +k=new A.n($.h,t.D) +m.e.t(0,l,new A.iB(new A.a6(k,t.h),A.la())) +m.br(new A.ap(l,new A.c3(b,i))) +s=5 +return A.c(k,$async$cM) +case 5:o.push(4) +s=3 +break +case 2:o=[1] +case 3:q=1 +j.cB(i) +s=o.pop() +break +case 4:return A.j(null,r) +case 1:return A.i(p.at(-1),r)}}) +return A.k($async$cM,r)}} +A.i6.prototype={ +dk(a1){var s,r,q,p,o,n,m,l,k,j,i,h,g,f,e,d,c,b,a=this,a0=null +A:{if(a1 instanceof A.ap){s=new A.ah(0,{i:a1.a,p:a.jf(a1.b)}) +break A}if(a1 instanceof A.bf){s=new A.ah(1,{i:a1.a,p:a.jg(a1.b)}) +break A}r=a1 instanceof A.bm +q=a0 +p=a0 +o=!1 +n=a0 +m=a0 +s=!1 +if(r){l=a1.a +q=a1.b +o=q instanceof A.c8 +if(o){t.f_.a(q) +p=a1.c +s=a.a.c>=4 +m=p +n=q}k=l}else{k=a0 +l=k}if(s){s=m==null?a0:m.i(0) +j=n.a +i=n.b +if(i==null)i=a0 +h=n.c +g=n.e +if(g==null)g=a0 +f=n.f +if(f==null)f=a0 +e=n.r +B:{if(e==null){d=a0 +break B}d=[] +for(c=e.length,b=0;b") +s=A.aw(new A.E(s,new A.lX(),q),q.h("M.E")) +s=new A.bF(s) +break A}s=A.C(A.J("Unknown request tag "+r,m))}return s}, +jg(a){var s,r +A:{s=null +if(a==null)break A +if(a instanceof A.aO){r=a.a +s=A.bQ(r)?r:A.A(r) +break A}if(a instanceof A.bI){s=this.jh(a) +break A}}return s}, +jh(a){var s,r,q,p=a.a,o=J.a0(p) +if(o.gB(p)){p=v.G +return{c:new p.Array(),r:new p.Array()}}else{s=J.d_(o.gF(p).ga_(),new A.m2(),t.N).cf(0) +r=A.f([],t.fk) +for(p=o.gq(p);p.k();){q=[] +for(o=J.a4(p.gm().gbF());o.k();)q.push(this.cE(o.gm())) +r.push(q)}return{c:s,r:r}}}, +il(a){var s,r,q,p,o,n,m,l,k,j +if(a==null)return null +else if(typeof a==="boolean")return new A.aO(A.bg(a)) +else if(typeof a==="number")return new A.aO(A.A(A.X(a))) +else{A.a9(a) +s=a.c +s=t.u.b(s)?s:new A.ak(s,A.N(s).h("ak<1,o>")) +r=t.N +s=J.d_(s,new A.m0(),r) +q=A.aw(s,s.$ti.h("M.E")) +p=A.f([],t.d) +s=a.r +s=J.a4(t.e9.b(s)?s:new A.ak(s,A.N(s).h("ak<1,u>"))) +o=t.X +while(s.k()){n=s.gm() +m=A.al(r,o) +n=A.tU(n,0,o) +l=J.a4(n.a) +n=n.b +k=new A.er(l,n) +while(k.k()){j=k.c +j=j>=0?new A.ah(n+j,l.gm()):A.C(A.az()) +m.t(0,q[j.a],this.cD(j.b))}p.push(m)}return new A.bI(p)}}, +cE(a){var s +A:{if(a==null){s=null +break A}if(A.bv(a)){s=a +break A}if(A.bQ(a)){s=a +break A}if(typeof a=="string"){s=a +break A}if(typeof a=="number"){s=A.f([15,a],t.n) +break A}if(a instanceof A.a7){s=A.f([14,a.i(0)],t.f) +break A}if(t.I.b(a)){s=new Uint8Array(A.iV(a)) +break A}s=A.C(A.J("Unknown db value: "+A.t(a),null))}return s}, +cD(a){var s,r,q,p=null +if(a!=null)if(typeof a==="number")return A.A(A.X(a)) +else if(typeof a==="boolean")return A.bg(a) +else if(typeof a==="string")return A.a_(a) +else if(A.ku(a,"Uint8Array"))return t.Z.a(a) +else{t.c.a(a) +s=a.length===2 +if(s){r=a[0] +q=a[1]}else{q=p +r=q}if(!s)throw A.b(A.B("Pattern matching error")) +if(r==14)return A.ow(A.a_(q),p) +else return A.X(q)}else return p}, +f8(a){var s,r=a!=null?A.a_(a):null +A:{if(r!=null){s=new A.dQ(r) +break A}s=null +break A}return s}, +ih(a){var s,r,q,p,o=null,n=a.length>=8,m=o,l=o,k=o,j=o,i=o,h=o,g=o +if(n){s=a[0] +m=a[1] +l=a[2] +k=a[3] +j=a[4] +i=a[5] +h=a[6] +g=a[7]}else s=o +if(!n)throw A.b(A.B("Pattern matching error")) +s=A.A(A.X(s)) +j=A.A(A.X(j)) +A.a_(l) +n=k!=null?A.a_(k):o +r=h!=null?A.a_(h):o +if(g!=null){q=[] +t.c.a(g) +p=B.c.gq(g) +while(p.k())q.push(this.cD(p.gm()))}else q=o +p=i!=null?A.a_(i):o +return new A.bm(s,new A.c8(l,n,j,o,p,r,q),this.f8(m))}} +A.m3.prototype={ +$0(){var s=A.a9(this.a.a) +return new A.ap(s.i,this.b.ik(s.p))}, +$S:70} +A.m4.prototype={ +$0(){var s=A.a9(this.a.a) +return new A.bf(s.i,this.b.il(s.p))}, +$S:77} +A.m1.prototype={ +$1(a){return a}, +$S:8} +A.lY.prototype={ +$0(){var s,r,q,p,o,n,m=this.b,l=J.a0(m),k=t.c,j=k.a(l.j(m,1)),i=t.u.b(j)?j:new A.ak(j,A.N(j).h("ak<1,o>")) +i=J.d_(i,new A.lZ(),t.N) +s=A.aw(i,i.$ti.h("M.E")) +i=l.gl(m) +r=A.f([],t.b) +for(i=l.Y(m,2).ah(0,i-3),k=A.ee(i,i.$ti.h("d.E"),k),k=A.ht(k,new A.m_(),A.r(k).h("d.E"),t.ee),i=k.a,q=A.r(k),k=new A.d9(i.gq(i),k.b,q.h("d9<1,2>")),i=this.a.gjv(),q=q.y[1];k.k();){p=k.a +if(p==null)p=q.a(p) +o=J.a0(p) +n=A.A(A.X(o.j(p,0))) +p=o.Y(p,1) +o=p.$ti.h("E") +p=A.aw(new A.E(p,i,o),o.h("M.E")) +r.push(new A.d0(n,p))}m=l.j(m,l.gl(m)-1) +m=m==null?null:A.A(A.X(m)) +return new A.bn(new A.ea(s,r),m)}, +$S:80} +A.lZ.prototype={ +$1(a){return a}, +$S:8} +A.m_.prototype={ +$1(a){return a}, +$S:91} +A.lX.prototype={ +$1(a){var s,r,q +t.c.a(a) +s=a.length===2 +if(s){r=a[0] +q=a[1]}else{r=null +q=null}if(!s)throw A.b(A.B("Pattern matching error")) +A.a_(r) +return new A.bK(q==null?null:B.R[A.A(A.X(q))],r)}, +$S:93} +A.m2.prototype={ +$1(a){return a}, +$S:8} +A.m0.prototype={ +$1(a){return a}, +$S:8} +A.ds.prototype={ +ae(){return"UpdateKind."+this.b}} +A.bK.prototype={ +gA(a){return A.eD(this.a,this.b,B.f,B.f)}, +W(a,b){if(b==null)return!1 +return b instanceof A.bK&&b.a==this.a&&b.b===this.b}, +i(a){return"TableUpdate("+this.b+", kind: "+A.t(this.a)+")"}} +A.nS.prototype={ +$0(){return this.a.a.a.P(A.kh(this.b,this.c))}, +$S:0} +A.bU.prototype={ +K(){var s,r +if(this.c)return +for(s=this.b,r=0;!1;++r)s[r].$0() +this.c=!0}} +A.ed.prototype={ +i(a){return"Operation was cancelled"}, +$ia5:1} +A.ao.prototype={ +n(){var s=0,r=A.l(t.H) +var $async$n=A.m(function(a,b){if(a===1)return A.i(b,r) +for(;;)switch(s){case 0:return A.j(null,r)}}) +return A.k($async$n,r)}} +A.ea.prototype={ +gA(a){return A.eD(B.o.h8(this.a),B.o.h8(this.b),B.f,B.f)}, +W(a,b){if(b==null)return!1 +return b instanceof A.ea&&B.o.ek(b.a,this.a)&&B.o.ek(b.b,this.b)}, +i(a){return"BatchedStatements("+A.t(this.a)+", "+A.t(this.b)+")"}} +A.d0.prototype={ +gA(a){return A.eD(this.a,B.o,B.f,B.f)}, +W(a,b){if(b==null)return!1 +return b instanceof A.d0&&b.a===this.a&&B.o.ek(b.b,this.b)}, +i(a){return"ArgumentsForBatchedStatement("+this.a+", "+A.t(this.b)+")"}} +A.jN.prototype={} +A.kK.prototype={} +A.lu.prototype={} +A.kF.prototype={} +A.jQ.prototype={} +A.hA.prototype={} +A.k4.prototype={} +A.ic.prototype={ +gex(){return!1}, +gc3(){return!1}, +fJ(a,b,c){if(this.gex()||this.b>0)return this.a.cp(new A.mc(b,a,c),c) +else return a.$0()}, +bs(a,b){return this.fJ(a,!0,b)}, +cv(a,b){this.gc3()}, +ab(a,b){return this.l4(a,b)}, +l4(a,b){var s=0,r=A.l(t.aS),q,p=this,o +var $async$ab=A.m(function(c,d){if(c===1)return A.i(d,r) +for(;;)switch(s){case 0:s=3 +return A.c(p.bs(new A.mh(p,a,b),t.aj),$async$ab) +case 3:o=d.gjN(0) +o=A.aw(o,o.$ti.h("M.E")) +q=o +s=1 +break +case 1:return A.j(q,r)}}) +return A.k($async$ab,r)}, +cb(a,b){return this.bs(new A.mf(this,a,b),t.S)}, +aw(a,b){return this.bs(new A.mg(this,a,b),t.S)}, +a7(a,b){return this.bs(new A.me(this,b,a),t.H)}, +l0(a){return this.a7(a,null)}, +av(a){return this.bs(new A.md(this,a),t.H)}, +cN(){return new A.f7(this,new A.a6(new A.n($.h,t.D),t.h),new A.bo())}, +cO(){return this.aR(this)}} +A.mc.prototype={ +$0(){return this.hy(this.c)}, +hy(a){var s=0,r=A.l(a),q,p=this +var $async$$0=A.m(function(b,c){if(b===1)return A.i(c,r) +for(;;)switch(s){case 0:if(p.a)A.oP() +s=3 +return A.c(p.b.$0(),$async$$0) +case 3:q=c +s=1 +break +case 1:return A.j(q,r)}}) +return A.k($async$$0,r)}, +$S(){return this.c.h("D<0>()")}} +A.mh.prototype={ +$0(){var s=this.a,r=this.b,q=this.c +s.cv(r,q) +return s.gaJ().ab(r,q)}, +$S:38} +A.mf.prototype={ +$0(){var s=this.a,r=this.b,q=this.c +s.cv(r,q) +return s.gaJ().d9(r,q)}, +$S:24} +A.mg.prototype={ +$0(){var s=this.a,r=this.b,q=this.c +s.cv(r,q) +return s.gaJ().aw(r,q)}, +$S:24} +A.me.prototype={ +$0(){var s,r,q=this.b +if(q==null)q=B.q +s=this.a +r=this.c +s.cv(r,q) +return s.gaJ().a7(r,q)}, +$S:2} +A.md.prototype={ +$0(){var s=this.a +s.gc3() +return s.gaJ().av(this.b)}, +$S:2} +A.iP.prototype={ +i3(){this.c=!0 +if(this.d)throw A.b(A.B("A transaction was used after being closed. Please check that you're awaiting all database operations inside a `transaction` block."))}, +aR(a){throw A.b(A.a3("Nested transactions aren't supported."))}, +gan(){return B.m}, +gc3(){return!1}, +gex(){return!0}, +$ihS:1} +A.fn.prototype={ +ao(a){var s,r,q=this +q.i3() +s=q.z +if(s==null){s=q.z=new A.a6(new A.n($.h,t.k),t.co) +r=q.as;++r.b +r.fJ(new A.n1(q),!1,t.P).ai(new A.n2(r))}return s.a}, +gaJ(){return this.e.e}, +aR(a){var s=this.at+1 +return new A.fn(this.y,new A.a6(new A.n($.h,t.D),t.h),a,s,A.qW(s),A.qU(s),A.qV(s),this.e,new A.bo())}, +bf(){var s=0,r=A.l(t.H),q,p=this +var $async$bf=A.m(function(a,b){if(a===1)return A.i(b,r) +for(;;)switch(s){case 0:if(!p.c){s=1 +break}s=3 +return A.c(p.a7(p.ay,B.q),$async$bf) +case 3:p.e0() +case 1:return A.j(q,r)}}) +return A.k($async$bf,r)}, +bC(){var s=0,r=A.l(t.H),q,p=2,o=[],n=[],m=this +var $async$bC=A.m(function(a,b){if(a===1){o.push(b) +s=p}for(;;)switch(s){case 0:if(!m.c){s=1 +break}p=3 +s=6 +return A.c(m.a7(m.ch,B.q),$async$bC) +case 6:n.push(5) +s=4 +break +case 3:n=[2] +case 4:p=2 +m.e0() +s=n.pop() +break +case 5:case 1:return A.j(q,r) +case 2:return A.i(o.at(-1),r)}}) +return A.k($async$bC,r)}, +e0(){var s=this +if(s.at===0)s.e.e.a=!1 +s.Q.aT() +s.d=!0}} +A.n1.prototype={ +$0(){var s=0,r=A.l(t.P),q=1,p=[],o=this,n,m,l,k,j +var $async$$0=A.m(function(a,b){if(a===1){p.push(b) +s=q}for(;;)switch(s){case 0:q=3 +A.oP() +l=o.a +s=6 +return A.c(l.l0(l.ax),$async$$0) +case 6:l.e.e.a=!0 +l.z.P(!0) +q=1 +s=5 +break +case 3:q=2 +j=p.pop() +n=A.G(j) +m=A.a1(j) +l=o.a +l.z.bv(n,m) +l.e0() +s=5 +break +case 2:s=1 +break +case 5:s=7 +return A.c(o.a.Q.a,$async$$0) +case 7:return A.j(null,r) +case 1:return A.i(p.at(-1),r)}}) +return A.k($async$$0,r)}, +$S:17} +A.n2.prototype={ +$0(){return this.a.b--}, +$S:41} +A.h2.prototype={ +gaJ(){return this.e}, +gan(){return B.m}, +ao(a){return this.x.cp(new A.jV(this,a),t.y)}, +bp(a){return this.jb(a)}, +jb(a){var s=0,r=A.l(t.H),q=this,p,o,n,m +var $async$bp=A.m(function(b,c){if(b===1)return A.i(c,r) +for(;;)switch(s){case 0:n=q.e +m=n.y +m===$&&A.x() +p=a.c +s=m instanceof A.hA?2:4 +break +case 2:o=p +s=3 +break +case 4:s=m instanceof A.fl?5:7 +break +case 5:s=8 +return A.c(A.bc(m.a.gla(),t.S),$async$bp) +case 8:o=c +s=6 +break +case 7:throw A.b(A.k6("Invalid delegate: "+n.i(0)+". The versionDelegate getter must not subclass DBVersionDelegate directly")) +case 6:case 3:if(o===0)o=null +s=9 +return A.c(a.cM(new A.id(q,new A.bo()),new A.eE(o,p)),$async$bp) +case 9:s=m instanceof A.fl&&o!==p?10:11 +break +case 10:m.a.h4("PRAGMA user_version = "+p+";") +s=12 +return A.c(A.bc(null,t.H),$async$bp) +case 12:case 11:return A.j(null,r)}}) +return A.k($async$bp,r)}, +aR(a){var s=$.h +return new A.fn(B.au,new A.a6(new A.n(s,t.D),t.h),a,0,"BEGIN TRANSACTION","COMMIT TRANSACTION","ROLLBACK TRANSACTION",this,new A.bo())}, +n(){return this.x.cp(new A.jU(this),t.H)}, +gc3(){return this.r}, +gex(){return this.w}} +A.jV.prototype={ +$0(){var s=0,r=A.l(t.y),q,p=2,o=[],n=this,m,l,k,j,i,h,g,f,e +var $async$$0=A.m(function(a,b){if(a===1){o.push(b) +s=p}for(;;)switch(s){case 0:f=n.a +if(f.d){f=A.ns(new A.aQ("Can't re-open a database after closing it. Please create a new database connection and open that instead."),null) +k=new A.n($.h,t.k) +k.aN(f) +q=k +s=1 +break}j=f.f +if(j!=null)A.pr(j.a,j.b) +k=f.e +i=t.y +h=A.bc(k.d,i) +s=3 +return A.c(t.bF.b(h)?h:A.dC(h,i),$async$$0) +case 3:if(b){q=f.c=!0 +s=1 +break}i=n.b +s=4 +return A.c(k.bz(i),$async$$0) +case 4:f.c=!0 +p=6 +s=9 +return A.c(f.bp(i),$async$$0) +case 9:q=!0 +s=1 +break +p=2 +s=8 +break +case 6:p=5 +e=o.pop() +m=A.G(e) +l=A.a1(e) +f.f=new A.ah(m,l) +throw e +s=8 +break +case 5:s=2 +break +case 8:case 1:return A.j(q,r) +case 2:return A.i(o.at(-1),r)}}) +return A.k($async$$0,r)}, +$S:42} +A.jU.prototype={ +$0(){var s=this.a +if(s.c&&!s.d){s.d=!0 +s.c=!1 +return s.e.n()}else return A.bc(null,t.H)}, +$S:2} +A.id.prototype={ +aR(a){return this.e.aR(a)}, +ao(a){this.c=!0 +return A.bc(!0,t.y)}, +gaJ(){return this.e.e}, +gc3(){return!1}, +gan(){return B.m}} +A.f7.prototype={ +gan(){return this.e.gan()}, +ao(a){var s,r,q,p=this,o=p.f +if(o!=null)return o.a +else{p.c=!0 +s=new A.n($.h,t.k) +r=new A.a6(s,t.co) +p.f=r +q=p.e;++q.b +q.bs(new A.mA(p,r),t.P) +return s}}, +gaJ(){return this.e.gaJ()}, +aR(a){return this.e.aR(a)}, +n(){this.r.aT() +return A.bc(null,t.H)}} +A.mA.prototype={ +$0(){var s=0,r=A.l(t.P),q=this,p +var $async$$0=A.m(function(a,b){if(a===1)return A.i(b,r) +for(;;)switch(s){case 0:q.b.P(!0) +p=q.a +s=2 +return A.c(p.r.a,$async$$0) +case 2:--p.e.b +return A.j(null,r)}}) +return A.k($async$$0,r)}, +$S:17} +A.dg.prototype={ +gjN(a){var s=this.b +return new A.E(s,new A.kM(this),A.N(s).h("E<1,an>"))}} +A.kM.prototype={ +$1(a){var s,r,q,p,o,n,m,l=A.al(t.N,t.z) +for(s=this.a,r=s.a,q=r.length,s=s.c,p=J.a0(a),o=0;o")).gF(0)).n() +q.t(0,a,s)}return new A.ah(s,o.sqlite3_stmt_isexplain(r)===0)}} +A.fl.prototype={} +A.kJ.prototype={ +kq(){var s,r,q,p +for(s=this.b,r=new A.cx(s,s.r,s.e);r.k();){q=r.d +if(!q.r){q.r=!0 +if(!q.f){p=q.a +p.c.d.sqlite3_reset(p.b) +q.f=!0}q=q.a +p=q.c +p.d.sqlite3_finalize(q.b) +p=p.w +if(p!=null){p=p.a +if(p!=null)p.unregister(q.d)}}}s.ef(0)}} +A.k5.prototype={ +$1(a){return Date.now()}, +$S:44} +A.ny.prototype={ +$1(a){var s=a.j(0,0) +if(typeof s=="number")return this.a.$1(s) +else return null}, +$S:25} +A.ho.prototype={ +gij(){var s=this.a +s===$&&A.x() +return s}, +gan(){if(this.b){var s=this.a +s===$&&A.x() +s=B.m!==s.gan()}else s=!1 +if(s)throw A.b(A.k6("LazyDatabase created with "+B.m.i(0)+", but underlying database is "+this.gij().gan().i(0)+".")) +return B.m}, +hZ(){var s,r,q=this +if(q.b)return A.bc(null,t.H) +else{s=q.d +if(s!=null)return s.a +else{s=new A.n($.h,t.D) +r=q.d=new A.a6(s,t.h) +A.kh(q.e,t.x).bE(new A.kx(q,r),r.gjT(),t.P) +return s}}}, +cN(){var s=this.a +s===$&&A.x() +return s.cN()}, +cO(){var s=this.a +s===$&&A.x() +return s.cO()}, +ao(a){return this.hZ().ce(new A.ky(this,a),t.y)}, +av(a){var s=this.a +s===$&&A.x() +return s.av(a)}, +a7(a,b){var s=this.a +s===$&&A.x() +return s.a7(a,b)}, +cb(a,b){var s=this.a +s===$&&A.x() +return s.cb(a,b)}, +aw(a,b){var s=this.a +s===$&&A.x() +return s.aw(a,b)}, +ab(a,b){var s=this.a +s===$&&A.x() +return s.ab(a,b)}, +n(){var s=0,r=A.l(t.H),q,p=this,o,n +var $async$n=A.m(function(a,b){if(a===1)return A.i(b,r) +for(;;)switch(s){case 0:s=p.b?3:5 +break +case 3:o=p.a +o===$&&A.x() +s=6 +return A.c(o.n(),$async$n) +case 6:q=b +s=1 +break +s=4 +break +case 5:n=p.d +s=n!=null?7:8 +break +case 7:s=9 +return A.c(n.a,$async$n) +case 9:o=p.a +o===$&&A.x() +s=10 +return A.c(o.n(),$async$n) +case 10:case 8:case 4:case 1:return A.j(q,r)}}) +return A.k($async$n,r)}} +A.kx.prototype={ +$1(a){var s=this.a +s.a!==$&&A.j_() +s.a=a +s.b=!0 +this.b.aT()}, +$S:46} +A.ky.prototype={ +$1(a){var s=this.a.a +s===$&&A.x() +return s.ao(this.b)}, +$S:47} +A.bo.prototype={ +cp(a,b){var s,r=this.a,q=new A.n($.h,t.D) +this.a=q +s=new A.kA(this,a,new A.a6(q,t.h),q,b) +if(r!=null)return r.ce(new A.kC(s,b),b) +else return s.$0()}} +A.kA.prototype={ +$0(){var s=this +return A.kh(s.b,s.e).ai(new A.kB(s.a,s.c,s.d))}, +$S(){return this.e.h("D<0>()")}} +A.kB.prototype={ +$0(){this.b.aT() +var s=this.a +if(s.a===this.c)s.a=null}, +$S:5} +A.kC.prototype={ +$1(a){return this.a.$0()}, +$S(){return this.b.h("D<0>(~)")}} +A.lU.prototype={ +$1(a){var s,r=this,q=a.data +if(r.a&&J.aj(q,"_disconnect")){s=r.b.a +s===$&&A.x() +s=s.a +s===$&&A.x() +s.n()}else{s=r.b.a +if(r.c){s===$&&A.x() +s=s.a +s===$&&A.x() +s.v(0,r.d.ei(t.c.a(q)))}else{s===$&&A.x() +s=s.a +s===$&&A.x() +s.v(0,A.rh(q))}}}, +$S:9} +A.lV.prototype={ +$1(a){var s=this.c +if(this.a)s.postMessage(this.b.dk(t.fJ.a(a))) +else s.postMessage(A.x0(a))}, +$S:7} +A.lW.prototype={ +$0(){if(this.a)this.b.postMessage("_disconnect") +this.b.close()}, +$S:0} +A.jR.prototype={ +T(){A.aI(this.a,"message",new A.jT(this),!1)}, +aj(a){return this.iB(a)}, +iB(a6){var s=0,r=A.l(t.H),q=1,p=[],o=this,n,m,l,k,j,i,h,g,f,e,d,c,b,a,a0,a1,a2,a3,a4,a5 +var $async$aj=A.m(function(a7,a8){if(a7===1){p.push(a8) +s=q}for(;;)switch(s){case 0:k=a6 instanceof A.di +j=k?a6.a:null +s=k?3:4 +break +case 3:i={} +i.a=i.b=!1 +s=5 +return A.c(o.b.cp(new A.jS(i,o),t.P),$async$aj) +case 5:h=o.c.a.j(0,j) +g=A.f([],t.L) +f=!1 +s=i.b?6:7 +break +case 6:a5=J +s=8 +return A.c(A.e5(),$async$aj) +case 8:k=a5.a4(a8) +case 9:if(!k.k()){s=10 +break}e=k.gm() +g.push(new A.ah(B.F,e)) +if(e===j)f=!0 +s=9 +break +case 10:case 7:s=h!=null?11:13 +break +case 11:k=h.a +d=k===B.u||k===B.E +f=k===B.a2||k===B.a3 +s=12 +break +case 13:a5=i.a +if(a5){s=14 +break}else a8=a5 +s=15 +break +case 14:s=16 +return A.c(A.e2(j),$async$aj) +case 16:case 15:d=a8 +case 12:k=v.G +c="Worker" in k +e=i.b +b=i.a +new A.ej(c,e,"SharedArrayBuffer" in k,b,g,B.t,d,f).di(o.a) +s=2 +break +case 4:if(a6 instanceof A.dk){o.c.eR(a6) +s=2 +break}k=a6 instanceof A.eN +a=k?a6.a:null +s=k?17:18 +break +case 17:s=19 +return A.c(A.i2(a),$async$aj) +case 19:a0=a8 +o.a.postMessage(!0) +s=20 +return A.c(a0.T(),$async$aj) +case 20:s=2 +break +case 18:n=null +m=null +a1=a6 instanceof A.h3 +if(a1){a2=a6.a +n=a2.a +m=a2.b}s=a1?21:22 +break +case 21:q=24 +case 27:switch(n){case B.a4:s=29 +break +case B.F:s=30 +break +default:s=28 +break}break +case 29:s=31 +return A.c(A.nE(m),$async$aj) +case 31:s=28 +break +case 30:s=32 +return A.c(A.fE(m),$async$aj) +case 32:s=28 +break +case 28:a6.di(o.a) +q=1 +s=26 +break +case 24:q=23 +a4=p.pop() +l=A.G(a4) +new A.dv(J.b1(l)).di(o.a) +s=26 +break +case 23:s=1 +break +case 26:s=2 +break +case 22:s=2 +break +case 2:return A.j(null,r) +case 1:return A.i(p.at(-1),r)}}) +return A.k($async$aj,r)}} +A.jT.prototype={ +$1(a){this.a.aj(A.on(A.a9(a.data)))}, +$S:1} +A.jS.prototype={ +$0(){var s=0,r=A.l(t.P),q=this,p,o,n,m,l +var $async$$0=A.m(function(a,b){if(a===1)return A.i(b,r) +for(;;)switch(s){case 0:o=q.b +n=o.d +m=q.a +s=n!=null?2:4 +break +case 2:m.b=n.b +m.a=n.a +s=3 +break +case 4:l=m +s=5 +return A.c(A.cV(),$async$$0) +case 5:l.b=b +s=6 +return A.c(A.iX(),$async$$0) +case 6:p=b +m.a=p +o.d=new A.lH(p,m.b) +case 3:return A.j(null,r)}}) +return A.k($async$$0,r)}, +$S:17} +A.cz.prototype={ +ae(){return"ProtocolVersion."+this.b}} +A.lJ.prototype={ +dj(a){this.aB(new A.lM(a))}, +eQ(a){this.aB(new A.lL(a))}, +di(a){this.aB(new A.lK(a))}} +A.lM.prototype={ +$2(a,b){var s=b==null?B.z:b +this.a.postMessage(a,s)}, +$S:18} +A.lL.prototype={ +$2(a,b){var s=b==null?B.z:b +this.a.postMessage(a,s)}, +$S:18} +A.lK.prototype={ +$2(a,b){var s=b==null?B.z:b +this.a.postMessage(a,s)}, +$S:18} +A.jl.prototype={} +A.c6.prototype={ +aB(a){var s=this +A.dV(a,"SharedWorkerCompatibilityResult",A.f([s.e,s.f,s.r,s.c,s.d,A.pp(s.a),s.b.c],t.f),null)}} +A.l5.prototype={ +$1(a){return A.bg(J.aJ(this.a,a))}, +$S:51} +A.dv.prototype={ +aB(a){A.dV(a,"Error",this.a,null)}, +i(a){return"Error in worker: "+this.a}, +$ia5:1} +A.dk.prototype={ +aB(a){var s,r,q=this,p={} +p.sqlite=q.a.i(0) +s=q.b +p.port=s +p.storage=q.c.b +p.database=q.d +r=q.e +p.initPort=r +p.migrations=q.r +p.new_serialization=q.w +p.v=q.f.c +s=A.f([s],t.W) +if(r!=null)s.push(r) +A.dV(a,"ServeDriftDatabase",p,s)}} +A.di.prototype={ +aB(a){A.dV(a,"RequestCompatibilityCheck",this.a,null)}} +A.ej.prototype={ +aB(a){var s=this,r={} +r.supportsNestedWorkers=s.e +r.canAccessOpfs=s.f +r.supportsIndexedDb=s.w +r.supportsSharedArrayBuffers=s.r +r.indexedDbExists=s.c +r.opfsExists=s.d +r.existing=A.pp(s.a) +r.v=s.b.c +A.dV(a,"DedicatedWorkerCompatibilityResult",r,null)}} +A.eN.prototype={ +aB(a){A.dV(a,"StartFileSystemServer",this.a,null)}} +A.h3.prototype={ +aB(a){var s=this.a +A.dV(a,"DeleteDatabase",A.f([s.a.b,s.b],t.s),null)}} +A.nB.prototype={ +$1(a){this.b.transaction.abort() +this.a.a=!1}, +$S:9} +A.nP.prototype={ +$1(a){return A.a9(a[1])}, +$S:52} +A.h6.prototype={ +eR(a){var s=a.f.c,r=a.w +this.a.hj(a.d,new A.k3(this,a)).hA(A.uB(a.b,s>=1,s,r),!r)}, +aW(a,b,c,d,e){return this.kP(a,b,c,d,e)}, +kP(a,b,c,d,e){var s=0,r=A.l(t.x),q,p=this,o,n,m,l,k,j,i,h +var $async$aW=A.m(function(f,g){if(f===1)return A.i(g,r) +for(;;)switch(s){case 0:s=3 +return A.c(A.lQ(d),$async$aW) +case 3:i=g +h=null +case 4:switch(e.a){case 0:s=6 +break +case 1:s=7 +break +case 3:s=8 +break +case 2:s=9 +break +case 4:s=10 +break +default:s=11 +break}break +case 6:s=12 +return A.c(A.l7("drift_db/"+a),$async$aW) +case 12:o=g +h=o.gb6() +s=5 +break +case 7:s=13 +return A.c(p.cu(a),$async$aW) +case 13:o=g +h=o.gb6() +s=5 +break +case 8:case 9:s=14 +return A.c(A.hg(a),$async$aW) +case 14:o=g +h=o.gb6() +s=5 +break +case 10:o=A.o6(null) +s=5 +break +case 11:o=null +case 5:s=c!=null&&o.cg("/database",0)===0?15:16 +break +case 15:n=c.$0() +s=17 +return A.c(t.eY.b(n)?n:A.dC(n,t.aD),$async$aW) +case 17:m=g +if(m!=null){l=o.aX(new A.eL("/database"),4).a +l.be(m,0) +l.ci()}case 16:i.h9() +n=i.a +n=n.a +k=n.d.dart_sqlite3_register_vfs(n.bZ(B.i.a5(o.a),1),o,1) +if(k===0)A.C(A.B("could not register vfs")) +n=$.rM() +n.a.set(o,k) +n=A.u0(t.N,t.eT) +j=new A.i3(new A.iS(i,"/database",null,p.b,!0,b,new A.kJ(n)),!1,!0,new A.bo(),new A.bo()) +if(h!=null){q=A.tt(j,new A.mp(h,j)) +s=1 +break}else{q=j +s=1 +break}case 1:return A.j(q,r)}}) +return A.k($async$aW,r)}, +cu(a){return this.iI(a)}, +iI(a){var s=0,r=A.l(t.aT),q,p,o,n,m,l,k,j,i +var $async$cu=A.m(function(b,c){if(b===1)return A.i(c,r) +for(;;)switch(s){case 0:k=v.G +j=new k.SharedArrayBuffer(8) +i=k.Int32Array +i=t.ha.a(A.e1(i,[j])) +k.Atomics.store(i,0,-1) +i={clientVersion:1,root:"drift_db/"+a,synchronizationBuffer:j,communicationBuffer:new k.SharedArrayBuffer(67584)} +p=new k.Worker(A.eS().i(0)) +new A.eN(i).dj(p) +s=3 +return A.c(new A.f6(p,"message",!1,t.fF).gF(0),$async$cu) +case 3:o=A.pV(i.synchronizationBuffer) +i=i.communicationBuffer +n=A.pX(i,65536,2048) +k=k.Uint8Array +k=t.Z.a(A.e1(k,[i])) +m=A.jv("/",$.cY()) +l=$.fG() +q=new A.du(o,new A.bp(i,n,k),m,l,"dart-sqlite3-vfs") +s=1 +break +case 1:return A.j(q,r)}}) +return A.k($async$cu,r)}} +A.k3.prototype={ +$0(){var s=this.b,r=s.e,q=r!=null?new A.k0(r):null,p=this.a,o=A.uj(new A.ho(new A.k1(p,s,q)),!1,!0),n=new A.n($.h,t.D),m=new A.dj(s.c,o,new A.a8(n,t.F)) +n.ai(new A.k2(p,s,m)) +return m}, +$S:53} +A.k0.prototype={ +$0(){var s=new A.n($.h,t.fX),r=this.a +r.postMessage(!0) +r.onmessage=A.bu(new A.k_(new A.a6(s,t.fu))) +return s}, +$S:54} +A.k_.prototype={ +$1(a){var s=t.dE.a(a.data),r=s==null?null:s +this.a.P(r)}, +$S:9} +A.k1.prototype={ +$0(){var s=this.b +return this.a.aW(s.d,s.r,this.c,s.a,s.c)}, +$S:37} +A.k2.prototype={ +$0(){this.a.a.G(0,this.b.d) +this.c.b.hD()}, +$S:5} +A.mp.prototype={ +c_(a){return this.jR(a)}, +jR(a){var s=0,r=A.l(t.H),q=this,p +var $async$c_=A.m(function(b,c){if(b===1)return A.i(c,r) +for(;;)switch(s){case 0:s=2 +return A.c(a.n(),$async$c_) +case 2:s=q.b===a?3:4 +break +case 3:p=q.a.$0() +s=5 +return A.c(p instanceof A.n?p:A.dC(p,t.H),$async$c_) +case 5:case 4:return A.j(null,r)}}) +return A.k($async$c_,r)}} +A.dj.prototype={ +hA(a,b){var s,r,q;++this.c +s=t.X +s=A.uU(new A.kQ(this),s,s).gjP().$1(a.ghI()) +r=a.$ti +q=new A.ef(r.h("ef<1>")) +q.b=new A.f0(q,a.ghE()) +q.a=new A.f1(s,q,r.h("f1<1>")) +this.b.hB(q,b)}} +A.kQ.prototype={ +$1(a){var s=this.a +if(--s.c===0)s.d.aT() +s=a.a +if((s.e&2)!==0)A.C(A.B("Stream is already closed")) +s.eV()}, +$S:56} +A.lH.prototype={} +A.jp.prototype={ +$1(a){this.a.P(this.c.a(this.b.result))}, +$S:1} +A.jq.prototype={ +$1(a){var s=this.b.error +if(s==null)s=a +this.a.aH(s)}, +$S:1} +A.jr.prototype={ +$1(a){var s=this.b.error +if(s==null)s=a +this.a.aH(s)}, +$S:1} +A.l_.prototype={ +T(){A.aI(this.a,"connect",new A.l4(this),!1)}, +dV(a){return this.iM(a)}, +iM(a){var s=0,r=A.l(t.H),q=this,p,o +var $async$dV=A.m(function(b,c){if(b===1)return A.i(c,r) +for(;;)switch(s){case 0:p=a.ports +o=J.aJ(t.cl.b(p)?p:new A.ak(p,A.N(p).h("ak<1,z>")),0) +o.start() +A.aI(o,"message",new A.l0(q,o),!1) +return A.j(null,r)}}) +return A.k($async$dV,r)}, +cw(a,b){return this.iJ(a,b)}, +iJ(a,b){var s=0,r=A.l(t.H),q=1,p=[],o=this,n,m,l,k,j,i,h,g +var $async$cw=A.m(function(c,d){if(c===1){p.push(d) +s=q}for(;;)switch(s){case 0:q=3 +n=A.on(A.a9(b.data)) +m=n +l=null +i=m instanceof A.di +if(i)l=m.a +s=i?7:8 +break +case 7:s=9 +return A.c(o.bU(l),$async$cw) +case 9:k=d +k.eQ(a) +s=6 +break +case 8:if(m instanceof A.dk&&B.u===m.c){o.c.eR(n) +s=6 +break}if(m instanceof A.dk){i=o.b +i.toString +n.dj(i) +s=6 +break}i=A.J("Unknown message",null) +throw A.b(i) +case 6:q=1 +s=5 +break +case 3:q=2 +g=p.pop() +j=A.G(g) +new A.dv(J.b1(j)).eQ(a) +a.close() +s=5 +break +case 2:s=1 +break +case 5:return A.j(null,r) +case 1:return A.i(p.at(-1),r)}}) +return A.k($async$cw,r)}, +bU(a){return this.jp(a)}, +jp(a){var s=0,r=A.l(t.fL),q,p=this,o,n,m,l,k,j,i,h,g,f,e,d,c +var $async$bU=A.m(function(b,a0){if(b===1)return A.i(a0,r) +for(;;)switch(s){case 0:k=v.G +j="Worker" in k +s=3 +return A.c(A.iX(),$async$bU) +case 3:i=a0 +s=!j?4:6 +break +case 4:k=p.c.a.j(0,a) +if(k==null)o=null +else{k=k.a +k=k===B.u||k===B.E +o=k}h=A +g=!1 +f=!1 +e=i +d=B.B +c=B.t +s=o==null?7:9 +break +case 7:s=10 +return A.c(A.e2(a),$async$bU) +case 10:s=8 +break +case 9:a0=o +case 8:q=new h.c6(g,f,e,d,c,a0,!1) +s=1 +break +s=5 +break +case 6:n={} +m=p.b +if(m==null)m=p.b=new k.Worker(A.eS().i(0)) +new A.di(a).dj(m) +k=new A.n($.h,t.a9) +n.a=n.b=null +l=new A.l3(n,new A.a6(k,t.bi),i) +n.b=A.aI(m,"message",new A.l1(l),!1) +n.a=A.aI(m,"error",new A.l2(p,l,m),!1) +q=k +s=1 +break +case 5:case 1:return A.j(q,r)}}) +return A.k($async$bU,r)}} +A.l4.prototype={ +$1(a){return this.a.dV(a)}, +$S:1} +A.l0.prototype={ +$1(a){return this.a.cw(this.b,a)}, +$S:1} +A.l3.prototype={ +$4(a,b,c,d){var s,r=this.b +if((r.a.a&30)===0){r.P(new A.c6(!0,a,this.c,d,B.t,c,b)) +r=this.a +s=r.b +if(s!=null)s.K() +r=r.a +if(r!=null)r.K()}}, +$S:57} +A.l1.prototype={ +$1(a){var s=t.ed.a(A.on(A.a9(a.data))) +this.a.$4(s.f,s.d,s.c,s.a)}, +$S:1} +A.l2.prototype={ +$1(a){this.b.$4(!1,!1,!1,B.B) +this.c.terminate() +this.a.b=null}, +$S:1} +A.cc.prototype={ +ae(){return"WasmStorageImplementation."+this.b}} +A.bO.prototype={ +ae(){return"WebStorageApi."+this.b}} +A.i3.prototype={} +A.iS.prototype={ +kQ(){var s=this.Q.bz(this.as) +return s}, +bo(){var s=0,r=A.l(t.H),q +var $async$bo=A.m(function(a,b){if(a===1)return A.i(b,r) +for(;;)switch(s){case 0:q=A.dC(null,t.H) +s=2 +return A.c(q,$async$bo) +case 2:return A.j(null,r)}}) +return A.k($async$bo,r)}, +bq(a,b){return this.jd(a,b)}, +jd(a,b){var s=0,r=A.l(t.z),q=this +var $async$bq=A.m(function(c,d){if(c===1)return A.i(d,r) +for(;;)switch(s){case 0:q.l6(a,b) +s=!q.a?2:3 +break +case 2:s=4 +return A.c(q.bo(),$async$bq) +case 4:case 3:return A.j(null,r)}}) +return A.k($async$bq,r)}, +a7(a,b){return this.l1(a,b)}, +l1(a,b){var s=0,r=A.l(t.H),q=this +var $async$a7=A.m(function(c,d){if(c===1)return A.i(d,r) +for(;;)switch(s){case 0:s=2 +return A.c(q.bq(a,b),$async$a7) +case 2:return A.j(null,r)}}) +return A.k($async$a7,r)}, +aw(a,b){return this.l2(a,b)}, +l2(a,b){var s=0,r=A.l(t.S),q,p=this,o +var $async$aw=A.m(function(c,d){if(c===1)return A.i(d,r) +for(;;)switch(s){case 0:s=3 +return A.c(p.bq(a,b),$async$aw) +case 3:o=p.b.b +q=A.A(v.G.Number(o.a.d.sqlite3_last_insert_rowid(o.b))) +s=1 +break +case 1:return A.j(q,r)}}) +return A.k($async$aw,r)}, +d9(a,b){return this.l5(a,b)}, +l5(a,b){var s=0,r=A.l(t.S),q,p=this,o +var $async$d9=A.m(function(c,d){if(c===1)return A.i(d,r) +for(;;)switch(s){case 0:s=3 +return A.c(p.bq(a,b),$async$d9) +case 3:o=p.b.b +q=o.a.d.sqlite3_changes(o.b) +s=1 +break +case 1:return A.j(q,r)}}) +return A.k($async$d9,r)}, +av(a){return this.l_(a)}, +l_(a){var s=0,r=A.l(t.H),q=this +var $async$av=A.m(function(b,c){if(b===1)return A.i(c,r) +for(;;)switch(s){case 0:q.kZ(a) +s=!q.a?2:3 +break +case 2:s=4 +return A.c(q.bo(),$async$av) +case 4:case 3:return A.j(null,r)}}) +return A.k($async$av,r)}, +n(){var s=0,r=A.l(t.H),q=this +var $async$n=A.m(function(a,b){if(a===1)return A.i(b,r) +for(;;)switch(s){case 0:s=2 +return A.c(q.hM(),$async$n) +case 2:q.b.n() +s=3 +return A.c(q.bo(),$async$n) +case 3:return A.j(null,r)}}) +return A.k($async$n,r)}} +A.fY.prototype={ +fR(a,b,c,d,e,f,g,h,i,j,k,l,m,n,o){var s +A.rc("absolute",A.f([a,b,c,d,e,f,g,h,i,j,k,l,m,n,o],t.d4)) +s=this.a +s=s.S(a)>0&&!s.a9(a) +if(s)return a +s=this.b +return this.hb(0,s==null?A.oS():s,a,b,c,d,e,f,g,h,i,j,k,l,m,n,o)}, +aF(a){var s=null +return this.fR(a,s,s,s,s,s,s,s,s,s,s,s,s,s,s)}, +hb(a,b,c,d,e,f,g,h,i,j,k,l,m,n,o,p,q){var s=A.f([b,c,d,e,f,g,h,i,j,k,l,m,n,o,p,q],t.d4) +A.rc("join",s) +return this.kE(new A.eV(s,t.eJ))}, +kD(a,b,c){var s=null +return this.hb(0,b,c,s,s,s,s,s,s,s,s,s,s,s,s,s,s)}, +kE(a){var s,r,q,p,o,n,m,l,k +for(s=a.gq(0),r=new A.eU(s,new A.jw()),q=this.a,p=!1,o=!1,n="";r.k();){m=s.gm() +if(q.a9(m)&&o){l=A.df(m,q) +k=n.charCodeAt(0)==0?n:n +n=B.a.p(k,0,q.bD(k,!0)) +l.b=n +if(q.c4(n))l.e[0]=q.gbg() +n=l.i(0)}else if(q.S(m)>0){o=!q.a9(m) +n=m}else{if(!(m.length!==0&&q.eg(m[0])))if(p)n+=q.gbg() +n+=m}p=q.c4(m)}return n.charCodeAt(0)==0?n:n}, +aM(a,b){var s=A.df(b,this.a),r=s.d,q=A.N(r).h("aY<1>") +r=A.aw(new A.aY(r,new A.jx(),q),q.h("d.E")) +s.d=r +q=s.b +if(q!=null)B.c.cY(r,0,q) +return s.d}, +by(a){var s +if(!this.iL(a))return a +s=A.df(a,this.a) +s.eC() +return s.i(0)}, +iL(a){var s,r,q,p,o,n,m,l=this.a,k=l.S(a) +if(k!==0){if(l===$.fH())for(s=0;s0)return o.by(a) +if(m.S(a)<=0||m.a9(a))a=o.aF(a) +if(m.S(a)<=0&&m.S(b)>0)throw A.b(A.pG(n+a+'" from "'+b+'".')) +s=A.df(b,m) +s.eC() +r=A.df(a,m) +r.eC() +q=s.d +if(q.length!==0&&q[0]===".")return r.i(0) +q=s.b +p=r.b +if(q!=p)q=q==null||p==null||!m.eE(q,p) +else q=!1 +if(q)return r.i(0) +for(;;){q=s.d +if(q.length!==0){p=r.d +q=p.length!==0&&m.eE(q[0],p[0])}else q=!1 +if(!q)break +B.c.d7(s.d,0) +B.c.d7(s.e,1) +B.c.d7(r.d,0) +B.c.d7(r.e,1)}q=s.d +p=q.length +if(p!==0&&q[0]==="..")throw A.b(A.pG(n+a+'" from "'+b+'".')) +q=t.N +B.c.es(r.d,0,A.b4(p,"..",!1,q)) +p=r.e +p[0]="" +B.c.es(p,1,A.b4(s.d.length,m.gbg(),!1,q)) +m=r.d +q=m.length +if(q===0)return"." +if(q>1&&B.c.gE(m)==="."){B.c.hl(r.d) +m=r.e +m.pop() +m.pop() +m.push("")}r.b="" +r.hm() +return r.i(0)}, +kW(a){return this.eH(a,null)}, +iF(a,b){var s,r,q,p,o,n,m,l,k=this +a=a +b=b +r=k.a +q=r.S(a)>0 +p=r.S(b)>0 +if(q&&!p){b=k.aF(b) +if(r.a9(a))a=k.aF(a)}else if(p&&!q){a=k.aF(a) +if(r.a9(b))b=k.aF(b)}else if(p&&q){o=r.a9(b) +n=r.a9(a) +if(o&&!n)b=k.aF(b) +else if(n&&!o)a=k.aF(a)}m=k.iG(a,b) +if(m!==B.n)return m +s=null +try{s=k.eH(b,a)}catch(l){if(A.G(l) instanceof A.eF)return B.k +else throw l}if(r.S(s)>0)return B.k +if(J.aj(s,"."))return B.J +if(J.aj(s,".."))return B.k +return J.at(s)>=3&&J.tq(s,"..")&&r.D(J.tk(s,2))?B.k:B.K}, +iG(a,b){var s,r,q,p,o,n,m,l,k,j,i,h,g,f,e=this +if(a===".")a="" +s=e.a +r=s.S(a) +q=s.S(b) +if(r!==q)return B.k +for(p=0;pq.aM(0,s).length?s:r}} +A.jw.prototype={ +$1(a){return a!==""}, +$S:3} +A.jx.prototype={ +$1(a){return a.length!==0}, +$S:3} +A.nz.prototype={ +$1(a){return a==null?"null":'"'+a+'"'}, +$S:59} +A.dJ.prototype={ +i(a){return this.a}} +A.dK.prototype={ +i(a){return this.a}} +A.kt.prototype={ +hz(a){var s=this.S(a) +if(s>0)return B.a.p(a,0,s) +return this.a9(a)?a[0]:null}, +hk(a){var s,r=null,q=a.length +if(q===0)return A.am(r,r,r,r) +s=A.jv(r,this).aM(0,a) +if(this.D(a.charCodeAt(q-1)))B.c.v(s,"") +return A.am(r,r,s,r)}, +cQ(a,b){return a===b}, +eE(a,b){return a===b}} +A.kH.prototype={ +ger(){var s=this.d +if(s.length!==0)s=B.c.gE(s)===""||B.c.gE(this.e)!=="" +else s=!1 +return s}, +hm(){var s,r,q=this +for(;;){s=q.d +if(!(s.length!==0&&B.c.gE(s)===""))break +B.c.hl(q.d) +q.e.pop()}s=q.e +r=s.length +if(r!==0)s[r-1]=""}, +eC(){var s,r,q,p,o,n=this,m=A.f([],t.s) +for(s=n.d,r=s.length,q=0,p=0;p0){s=B.a.aU(a,"\\",s+1) +if(s>0)return s}return r}if(r<3)return 0 +if(!A.rm(a.charCodeAt(0)))return 0 +if(a.charCodeAt(1)!==58)return 0 +r=a.charCodeAt(2) +if(!(r===47||r===92))return 0 +return 3}, +S(a){return this.bD(a,!1)}, +a9(a){return this.S(a)===1}, +d4(a){var s,r +if(a.gZ()!==""&&a.gZ()!=="file")throw A.b(A.J("Uri "+a.i(0)+" must have scheme 'file:'.",null)) +s=a.gaa() +if(a.gb7()===""){if(s.length>=3&&B.a.u(s,"/")&&A.ri(s,1)!=null)s=B.a.ho(s,"/","")}else s="\\\\"+a.gb7()+s +r=A.bi(s,"/","\\") +return A.oF(r,0,r.length,B.j,!1)}, +ea(a){var s,r,q=A.df(a,this),p=q.b +p.toString +if(B.a.u(p,"\\\\")){s=new A.aY(A.f(p.split("\\"),t.s),new A.m6(),t.U) +B.c.cY(q.d,0,s.gE(0)) +if(q.ger())B.c.v(q.d,"") +return A.am(s.gF(0),null,q.d,"file")}else{if(q.d.length===0||q.ger())B.c.v(q.d,"") +p=q.d +r=q.b +r.toString +r=A.bi(r,"/","") +B.c.cY(p,0,A.bi(r,"\\","")) +return A.am(null,null,q.d,"file")}}, +cQ(a,b){var s +if(a===b)return!0 +if(a===47)return b===92 +if(a===92)return b===47 +if((a^b)!==32)return!1 +s=a|32 +return s>=97&&s<=122}, +eE(a,b){var s,r +if(a===b)return!0 +s=a.length +if(s!==b.length)return!1 +for(r=0;r")).aq(0,", ")):s}return p.charCodeAt(0)==0?p:p}, +$ia5:1} +A.l9.prototype={ +$1(a){if(t.p.b(a))return"blob ("+a.length+" bytes)" +else return J.b1(a)}, +$S:60} +A.cn.prototype={} +A.h_.prototype={ +gla(){var s,r,q=this.kT("PRAGMA user_version;") +try{s=q.eP(new A.cv(B.aJ)) +r=A.A(J.j2(s).b[0]) +return r}finally{q.n()}}, +h_(a,b,c,d,e){var s,r,q,p,o,n=null,m=this.b,l=B.i.a5(e) +if(l.length>255)A.C(A.ad(e,"functionName","Must not exceed 255 bytes when utf-8 encoded")) +s=new Uint8Array(A.iV(l)) +r=c?526337:2049 +q=m.a +p=q.bZ(s,1) +s=q.d +o=A.oO(s,"dart_sqlite3_create_function_v2",[m.b,p,a.a,r,0,new A.bG(new A.jP(d),n,n)]) +s.dart_sqlite3_free(p) +if(o!==0)A.fF(this,o,n,n,n)}, +a6(a,b,c,d){return this.h_(a,b,!0,c,d)}, +n(){var s,r,q,p,o,n=this +if(n.r)return +n.r=!0 +s=n.b +r=s.b +q=s.a.d +q.dart_sqlite3_updates(r,null) +q.dart_sqlite3_commits(r,null) +q.dart_sqlite3_rollbacks(r,null) +p=s.eS() +o=p!==0?A.oR(n.a,s,p,"closing database",null,null):null +if(o!=null)throw A.b(o)}, +h4(a){var s,r,q,p=this,o=B.q +if(J.at(o)===0){if(p.r)A.C(A.B("This database has already been closed")) +r=p.b +q=r.a +s=q.bZ(B.i.a5(a),1) +q=q.d +r=A.oO(q,"sqlite3_exec",[r.b,s,0,0,0]) +q.dart_sqlite3_free(s) +if(r!==0)A.fF(p,r,"executing",a,o)}else{s=p.d5(a,!0) +try{s.h5(new A.cv(o))}finally{s.n()}}}, +iY(a,b,c,d,a0){var s,r,q,p,o,n,m,l,k,j,i,h,g,f,e=this +if(e.r)A.C(A.B("This database has already been closed")) +s=B.i.a5(a) +r=e.b +q=r.a +p=q.bt(s) +o=q.d +n=o.dart_sqlite3_malloc(4) +o=o.dart_sqlite3_malloc(4) +m=new A.lT(r,p,n,o) +l=A.f([],t.bb) +k=new A.jO(m,l) +for(r=s.length,q=q.b,j=0;j."))}, +dt(a){A:{this.i0(a.a) +break A}}, +eI(){if(!this.f){var s=this.a +s.c.d.sqlite3_reset(s.b) +this.f=!0}}, +n(){var s,r,q=this +if(!q.r){q.r=!0 +q.eI() +s=q.a +r=s.c +r.d.sqlite3_finalize(s.b) +r=r.w +if(r!=null)r.h1(s.d)}}, +eP(a){var s=this +s.fb() +s.eI() +s.dt(a) +return s.je()}, +h5(a){var s=this +s.fb() +s.eI() +s.dt(a) +s.fe()}} +A.he.prototype={ +cg(a,b){return this.d.a4(a)?1:0}, +dc(a,b){this.d.G(0,a)}, +dd(a){return $.fJ().by("/"+a)}, +aX(a,b){var s,r=a.a +if(r==null)r=A.o5(this.b,"/") +s=this.d +if(!s.a4(r))if((b&4)!==0)s.t(0,r,new A.br(new Uint8Array(0),0)) +else throw A.b(A.ca(14)) +return new A.cO(new A.is(this,r,(b&8)!==0),0)}, +df(a){}} +A.is.prototype={ +eG(a,b){var s,r=this.a.d.j(0,this.b) +if(r==null||r.b<=b)return 0 +s=Math.min(a.length,r.b-b) +B.e.M(a,0,s,J.cZ(B.e.gaS(r.a),0,r.b),b) +return s}, +da(){return this.d>=2?1:0}, +ci(){if(this.c)this.a.d.G(0,this.b)}, +ck(){return this.a.d.j(0,this.b).b}, +de(a){this.d=a}, +dg(a){}, +cl(a){var s=this.a.d,r=this.b,q=s.j(0,r) +if(q==null){s.t(0,r,new A.br(new Uint8Array(0),0)) +s.j(0,r).sl(0,a)}else q.sl(0,a)}, +dh(a){this.d=a}, +be(a,b){var s,r=this.a.d,q=this.b,p=r.j(0,q) +if(p==null){p=new A.br(new Uint8Array(0),0) +r.t(0,q,p)}s=b+a.length +if(s>p.b)p.sl(0,s) +p.ad(0,b,s,a)}} +A.jy.prototype={ +i2(){var s,r,q,p,o=A.al(t.N,t.S) +for(s=this.a,r=s.length,q=0;qq.d)throw A.b(A.ca(14)) +s=q.a.b +s===$&&A.x() +s=A.bE(s.buffer,0,null) +r=q.e +B.e.aZ(s,r,p) +s.$flags&2&&A.y(s) +s[r+o]=0}, +$S:0} +A.jG.prototype={ +$0(){var s,r=this,q=r.a.b +q===$&&A.x() +s=A.bE(q.buffer,r.b,r.c) +q=r.d +if(q!=null)A.pe(s,q.b) +else return A.pe(s,null)}, +$S:0} +A.jI.prototype={ +$0(){this.a.df(A.po(this.b,0))}, +$S:0} +A.jB.prototype={ +$0(){return this.a.ci()}, +$S:0} +A.jH.prototype={ +$0(){var s=this,r=s.a.b +r===$&&A.x() +s.b.eO(A.bE(r.buffer,s.c,s.d),A.A(v.G.Number(s.e)))}, +$S:0} +A.jM.prototype={ +$0(){var s=this,r=s.a.b +r===$&&A.x() +s.b.be(A.bE(r.buffer,s.c,s.d),A.A(v.G.Number(s.e)))}, +$S:0} +A.jK.prototype={ +$0(){return this.a.cl(A.A(v.G.Number(this.b)))}, +$S:0} +A.jJ.prototype={ +$0(){return this.a.dg(this.b)}, +$S:0} +A.jD.prototype={ +$0(){var s,r=this.b.ck(),q=this.a.b +q===$&&A.x() +q=A.bD(q.buffer,0,null) +s=B.b.O(this.c,2) +q.$flags&2&&A.y(q) +q[s]=r}, +$S:0} +A.jF.prototype={ +$0(){return this.a.de(this.b)}, +$S:0} +A.jL.prototype={ +$0(){return this.a.dh(this.b)}, +$S:0} +A.jA.prototype={ +$0(){var s,r=this.b.da(),q=this.a.b +q===$&&A.x() +q=A.bD(q.buffer,0,null) +s=B.b.O(this.c,2) +q.$flags&2&&A.y(q) +q[s]=r}, +$S:0} +A.bG.prototype={} +A.e9.prototype={ +R(a,b,c,d){var s,r=null,q={},p=A.a9(A.hm(this.a,v.G.Symbol.asyncIterator,r,r,r,r)),o=A.eP(r,r,!0,this.$ti.c) +q.a=null +s=new A.j5(q,this,p,o) +o.d=s +o.f=new A.j6(q,o,s) +return new A.ar(o,A.r(o).h("ar<1>")).R(a,b,c,d)}, +aV(a,b,c){return this.R(a,null,b,c)}} +A.j5.prototype={ +$0(){var s,r=this,q=r.c.next(),p=r.a +p.a=q +s=r.d +A.T(q,t.m).bE(new A.j7(p,r.b,s,r),s.gfS(),t.P)}, +$S:0} +A.j7.prototype={ +$1(a){var s,r,q=this,p=a.done +if(p==null)p=null +s=a.value +r=q.c +if(p===!0){r.n() +q.a.a=null}else{r.v(0,s==null?q.b.$ti.c.a(s):s) +q.a.a=null +p=r.b +if(!((p&1)!==0?(r.gaQ().e&4)!==0:(p&2)===0))q.d.$0()}}, +$S:9} +A.j6.prototype={ +$0(){var s,r +if(this.a.a==null){s=this.b +r=s.b +s=!((r&1)!==0?(s.gaQ().e&4)!==0:(r&2)===0)}else s=!1 +if(s)this.c.$0()}, +$S:0} +A.cI.prototype={ +K(){var s=0,r=A.l(t.H),q=this,p +var $async$K=A.m(function(a,b){if(a===1)return A.i(b,r) +for(;;)switch(s){case 0:p=q.b +if(p!=null)p.K() +p=q.c +if(p!=null)p.K() +q.c=q.b=null +return A.j(null,r)}}) +return A.k($async$K,r)}, +gm(){var s=this.a +return s==null?A.C(A.B("Await moveNext() first")):s}, +k(){var s,r,q=this,p=q.a +if(p!=null)p.continue() +p=new A.n($.h,t.k) +s=new A.a8(p,t.fa) +r=q.d +q.b=A.aI(r,"success",new A.mq(q,s),!1) +q.c=A.aI(r,"error",new A.mr(q,s),!1) +return p}} +A.mq.prototype={ +$1(a){var s,r=this.a +r.K() +s=r.$ti.h("1?").a(r.d.result) +r.a=s +this.b.P(s!=null)}, +$S:1} +A.mr.prototype={ +$1(a){var s=this.a +s.K() +s=s.d.error +if(s==null)s=a +this.b.aH(s)}, +$S:1} +A.jn.prototype={ +$1(a){this.a.P(this.c.a(this.b.result))}, +$S:1} +A.jo.prototype={ +$1(a){var s=this.b.error +if(s==null)s=a +this.a.aH(s)}, +$S:1} +A.js.prototype={ +$1(a){this.a.P(this.c.a(this.b.result))}, +$S:1} +A.jt.prototype={ +$1(a){var s=this.b.error +if(s==null)s=a +this.a.aH(s)}, +$S:1} +A.ju.prototype={ +$1(a){var s=this.b.error +if(s==null)s=a +this.a.aH(s)}, +$S:1} +A.lN.prototype={ +jV(){var s={} +s.dart=new A.lO(this).$0() +return s}, +d2(a){return this.kG(a)}, +kG(a){var s=0,r=A.l(t.m),q,p=this,o,n +var $async$d2=A.m(function(b,c){if(b===1)return A.i(c,r) +for(;;)switch(s){case 0:s=3 +return A.c(A.T(v.G.WebAssembly.instantiateStreaming(a,p.jV()),t.m),$async$d2) +case 3:o=c +n=o.instance.exports +if("_initialize" in n)t.g.a(n._initialize).call() +q=o.instance +s=1 +break +case 1:return A.j(q,r)}}) +return A.k($async$d2,r)}} +A.lO.prototype={ +$0(){var s=this.a.a,r=A.a9(v.G.Object),q=A.a9(r.create.apply(r,[null])) +q.error_log=A.bu(s.gkJ()) +q.localtime=A.b8(s.gkH()) +q.xOpen=A.oJ(s.gls()) +q.xDelete=A.oI(s.glj()) +q.xAccess=A.dW(s.glb()) +q.xFullPathname=A.dW(s.glo()) +q.xRandomness=A.oI(s.glu()) +q.xSleep=A.b8(s.gly()) +q.xCurrentTimeInt64=A.b8(s.glh()) +q.xClose=A.bu(s.glf()) +q.xRead=A.dW(s.glw()) +q.xWrite=A.dW(s.glG()) +q.xTruncate=A.b8(s.glC()) +q.xSync=A.b8(s.glA()) +q.xFileSize=A.b8(s.glm()) +q.xLock=A.b8(s.glq()) +q.xUnlock=A.b8(s.glE()) +q.xCheckReservedLock=A.b8(s.gld()) +q.xDeviceCharacteristics=A.bu(s.gcj()) +q["dispatch_()v"]=A.bu(s.gkc()) +q["dispatch_()i"]=A.bu(s.gk7()) +q.dispatch_update=A.oJ(s.gka()) +q.dispatch_xFunc=A.dW(s.gki()) +q.dispatch_xStep=A.dW(s.gkm()) +q.dispatch_xInverse=A.dW(s.gkk()) +q.dispatch_xValue=A.b8(s.gko()) +q.dispatch_xFinal=A.b8(s.gkg()) +q.dispatch_compare=A.oJ(s.gke()) +q.dispatch_busy=A.b8(s.gk5()) +q.changeset_apply_filter=A.b8(s.gk_()) +q.changeset_apply_conflict=A.oI(s.gjY()) +return q}, +$S:82} +A.i5.prototype={} +A.du.prototype={ +ja(a,b){var s,r,q=this.e +q.ht(b) +s=this.d.b +r=v.G +r.Atomics.store(s,1,-1) +r.Atomics.store(s,0,a.a) +A.tu(s,0) +r.Atomics.wait(s,1,-1) +s=r.Atomics.load(s,1) +if(s!==0)throw A.b(A.ca(s)) +return a.d.$1(q)}, +a2(a,b){var s=t.cb +return this.ja(a,b,s,s)}, +cg(a,b){return this.a2(B.a5,new A.aV(a,b,0,0)).a}, +dc(a,b){this.a2(B.a6,new A.aV(a,b,0,0))}, +dd(a){var s=this.r.aF(a) +if($.j0().iF("/",s)!==B.K)throw A.b(B.a0) +return s}, +aX(a,b){var s=a.a,r=this.a2(B.ah,new A.aV(s==null?A.o5(this.b,"/"):s,b,0,0)) +return new A.cO(new A.i4(this,r.b),r.a)}, +df(a){this.a2(B.ab,new A.P(B.b.J(a.a,1000),0,0))}, +n(){this.a2(B.a7,B.h)}} +A.i4.prototype={ +gcj(){return 2048}, +eG(a,b){var s,r,q,p,o,n,m,l,k,j,i=a.length +for(s=this.a,r=this.b,q=s.e.a,p=v.G,o=t.Z,n=0;i>0;){m=Math.min(65536,i) +i-=m +l=s.a2(B.af,new A.P(r,b+n,m)).a +k=p.Uint8Array +j=[q] +j.push(0) +j.push(l) +A.hm(a,"set",o.a(A.e1(k,j)),n,null,null) +n+=l +if(l0;){o=Math.min(65536,n) +A.hm(r,"set",o===n&&p===0?a:J.cZ(B.e.gaS(a),a.byteOffset+p,o),0,null,null) +s.a2(B.aa,new A.P(q,b+p,o)) +p+=o +n-=o}}} +A.kP.prototype={} +A.bp.prototype={ +ht(a){var s,r +if(!(a instanceof A.b2))if(a instanceof A.P){s=this.b +s.$flags&2&&A.y(s,8) +s.setInt32(0,a.a,!1) +s.setInt32(4,a.b,!1) +s.setInt32(8,a.c,!1) +if(a instanceof A.aV){r=B.i.a5(a.d) +s.setInt32(12,r.length,!1) +B.e.aZ(this.c,16,r)}}else throw A.b(A.a3("Message "+a.i(0)))}} +A.ac.prototype={ +ae(){return"WorkerOperation."+this.b}} +A.bC.prototype={} +A.b2.prototype={} +A.P.prototype={} +A.aV.prototype={} +A.iE.prototype={} +A.eT.prototype={ +bQ(a,b){return this.j7(a,b)}, +fC(a){return this.bQ(a,!1)}, +j7(a,b){var s=0,r=A.l(t.eg),q,p=this,o,n,m,l,k,j,i,h,g +var $async$bQ=A.m(function(c,d){if(c===1)return A.i(d,r) +for(;;)switch(s){case 0:j=$.fJ() +i=j.eH(a,"/") +h=j.aM(0,i) +g=h.length +j=g>=1 +o=null +if(j){n=g-1 +m=B.c.a0(h,0,n) +o=h[n]}else m=null +if(!j)throw A.b(A.B("Pattern matching error")) +l=p.c +j=m.length,n=t.m,k=0 +case 3:if(!(k") +l=A.aw(new A.bB(j,m),m.h("d.E")) +B.c.hG(l) +s=3 +return A.c(A.o4(new A.E(l,new A.j9(new A.ja(o,a),b),A.N(l).h("E<1,D<~>>")),t.H),$async$b5) +case 3:s=b.c!==n.length?4:5 +break +case 4:k=new A.cI(p.objectStore("files").openCursor(a),t.V) +s=6 +return A.c(k.k(),$async$b5) +case 6:s=7 +return A.c(A.bl(k.gm().update({name:n.name,length:b.c}),t.X),$async$b5) +case 7:case 5:return A.j(null,r)}}) +return A.k($async$b5,r)}, +bd(a,b,c){return this.l8(0,b,c)}, +l8(a,b,c){var s=0,r=A.l(t.H),q=this,p,o,n,m,l,k +var $async$bd=A.m(function(d,e){if(d===1)return A.i(e,r) +for(;;)switch(s){case 0:k=q.a +k.toString +p=k.transaction($.nT(),"readwrite") +o=p.objectStore("files") +n=p.objectStore("blocks") +s=2 +return A.c(q.e_(p,b),$async$bd) +case 2:m=e +s=m.length>c?3:4 +break +case 3:s=5 +return A.c(A.bl(n.delete(q.j_(b,B.b.J(c,4096)*4096+1)),t.X),$async$bd) +case 5:case 4:l=new A.cI(o.openCursor(b),t.V) +s=6 +return A.c(l.k(),$async$bd) +case 6:s=7 +return A.c(A.bl(l.gm().update({name:m.name,length:c}),t.X),$async$bd) +case 7:return A.j(null,r)}}) +return A.k($async$bd,r)}, +cT(a){return this.jX(a)}, +jX(a){var s=0,r=A.l(t.H),q=this,p,o,n +var $async$cT=A.m(function(b,c){if(b===1)return A.i(c,r) +for(;;)switch(s){case 0:n=q.a +n.toString +p=n.transaction(A.f(["files","blocks"],t.s),"readwrite") +o=q.dZ(a,9007199254740992,0) +n=t.X +s=2 +return A.c(A.o4(A.f([A.bl(p.objectStore("blocks").delete(o),n),A.bl(p.objectStore("files").delete(a),n)],t.fG),t.H),$async$cT) +case 2:return A.j(null,r)}}) +return A.k($async$cT,r)}} +A.jb.prototype={ +$1(a){var s=A.a9(this.a.result) +if(J.aj(a.oldVersion,0)){s.createObjectStore("files",{autoIncrement:!0}).createIndex("fileName","name",{unique:!0}) +s.createObjectStore("blocks")}}, +$S:9} +A.j8.prototype={ +$1(a){if(a==null)throw A.b(A.ad(this.a,"fileId","File not found in database")) +else return a}, +$S:84} +A.jc.prototype={ +$0(){var s=0,r=A.l(t.H),q=this,p,o +var $async$$0=A.m(function(a,b){if(a===1)return A.i(b,r) +for(;;)switch(s){case 0:p=q.a +s=A.ku(p.value,"Blob")?2:4 +break +case 2:s=5 +return A.c(A.kO(A.a9(p.value)),$async$$0) +case 5:s=3 +break +case 4:b=t.v.a(p.value) +case 3:o=b +B.e.aZ(q.b,q.c,J.cZ(o,0,q.d)) +return A.j(null,r)}}) +return A.k($async$$0,r)}, +$S:2} +A.ja.prototype={ +hv(a,b){var s=0,r=A.l(t.H),q=this,p,o,n,m,l,k +var $async$$2=A.m(function(c,d){if(c===1)return A.i(d,r) +for(;;)switch(s){case 0:p=q.a +o=q.b +n=t.n +s=2 +return A.c(A.bl(p.openCursor(v.G.IDBKeyRange.only(A.f([o,a],n))),t.A),$async$$2) +case 2:m=d +l=t.v.a(B.e.gaS(b)) +k=t.X +s=m==null?3:5 +break +case 3:s=6 +return A.c(A.bl(p.put(l,A.f([o,a],n)),k),$async$$2) +case 6:s=4 +break +case 5:s=7 +return A.c(A.bl(m.update(l),k),$async$$2) +case 7:case 4:return A.j(null,r)}}) +return A.k($async$$2,r)}, +$2(a,b){return this.hv(a,b)}, +$S:85} +A.j9.prototype={ +$1(a){var s=this.b.b.j(0,a) +s.toString +return this.a.$2(a,s)}, +$S:86} +A.mB.prototype={ +jt(a,b,c){B.e.aZ(this.b.hj(a,new A.mC(this,a)),b,c)}, +jL(a,b){var s,r,q,p,o,n,m,l +for(s=b.length,r=0;rp)B.e.aZ(s,0,J.cZ(B.e.gaS(r),r.byteOffset+p,Math.min(4096,q-p))) +return s}, +$S:120} +A.iA.prototype={} +A.d5.prototype={ +bV(a){var s=this +if(s.e||s.d.a==null)A.C(A.ca(10)) +if(a.eu(s.w)){s.fH() +return a.d.a}else return A.bc(null,t.H)}, +fH(){var s,r,q=this +if(q.f==null&&!q.w.gB(0)){s=q.w +r=q.f=s.gF(0) +s.G(0,r) +r.d.P(A.tS(r.gd8(),t.H).ai(new A.ko(q)))}}, +n(){var s=0,r=A.l(t.H),q,p=this,o,n +var $async$n=A.m(function(a,b){if(a===1)return A.i(b,r) +for(;;)switch(s){case 0:if(!p.e){o=p.bV(new A.dB(p.d.gb6(),new A.a8(new A.n($.h,t.D),t.F))) +p.e=!0 +q=o +s=1 +break}else{n=p.w +if(!n.gB(0)){q=n.gE(0).d.a +s=1 +break}}case 1:return A.j(q,r)}}) +return A.k($async$n,r)}, +bn(a){return this.it(a)}, +it(a){var s=0,r=A.l(t.S),q,p=this,o,n +var $async$bn=A.m(function(b,c){if(b===1)return A.i(c,r) +for(;;)switch(s){case 0:n=p.y +s=n.a4(a)?3:5 +break +case 3:n=n.j(0,a) +n.toString +q=n +s=1 +break +s=4 +break +case 5:s=6 +return A.c(p.d.cV(a),$async$bn) +case 6:o=c +o.toString +n.t(0,a,o) +q=o +s=1 +break +case 4:case 1:return A.j(q,r)}}) +return A.k($async$bn,r)}, +bO(){var s=0,r=A.l(t.H),q=this,p,o,n,m,l,k,j,i,h,g +var $async$bO=A.m(function(a,b){if(a===1)return A.i(b,r) +for(;;)switch(s){case 0:h=q.d +s=2 +return A.c(h.d1(),$async$bO) +case 2:g=b +q.y.aG(0,g) +p=g.gcU(),p=p.gq(p),o=q.r.d +case 3:if(!p.k()){s=4 +break}n=p.gm() +m=n.a +l=n.b +k=new A.br(new Uint8Array(0),0) +s=5 +return A.c(h.bB(l),$async$bO) +case 5:j=b +n=j.length +k.sl(0,n) +i=k.b +if(n>i)A.C(A.S(n,0,i,null,null)) +B.e.M(k.a,0,n,j,0) +o.t(0,m,k) +s=3 +break +case 4:return A.j(null,r)}}) +return A.k($async$bO,r)}, +cg(a,b){return this.r.d.a4(a)?1:0}, +dc(a,b){var s=this +s.r.d.G(0,a) +if(!s.x.G(0,a))s.bV(new A.dz(s,a,new A.a8(new A.n($.h,t.D),t.F)))}, +dd(a){return $.fJ().by("/"+a)}, +aX(a,b){var s,r,q,p=this,o=a.a +if(o==null)o=A.o5(p.b,"/") +s=p.r +r=s.d.a4(o)?1:0 +q=s.aX(new A.eL(o),b) +if(r===0)if((b&8)!==0)p.x.v(0,o) +else p.bV(new A.cH(p,o,new A.a8(new A.n($.h,t.D),t.F))) +return new A.cO(new A.it(p,q.a,o),0)}, +df(a){}} +A.ko.prototype={ +$0(){var s=this.a +s.f=null +s.fH()}, +$S:5} +A.it.prototype={ +eO(a,b){this.b.eO(a,b)}, +gcj(){return 0}, +da(){return this.b.d>=2?1:0}, +ci(){}, +ck(){return this.b.ck()}, +de(a){this.b.d=a +return null}, +dg(a){}, +cl(a){var s=this,r=s.a +if(r.e||r.d.a==null)A.C(A.ca(10)) +s.b.cl(a) +if(!r.x.I(0,s.c))r.bV(new A.dB(new A.mQ(s,a),new A.a8(new A.n($.h,t.D),t.F)))}, +dh(a){this.b.d=a +return null}, +be(a,b){var s,r,q,p,o,n,m=this,l=m.a +if(l.e||l.d.a==null)A.C(A.ca(10)) +s=m.c +if(l.x.I(0,s)){m.b.be(a,b) +return}r=l.r.d.j(0,s) +if(r==null)r=new A.br(new Uint8Array(0),0) +q=J.cZ(B.e.gaS(r.a),0,r.b) +m.b.be(a,b) +p=new Uint8Array(a.length) +B.e.aZ(p,0,a) +o=A.f([],t.gQ) +n=$.h +o.push(new A.iA(b,p)) +l.bV(new A.cR(l,s,q,o,new A.a8(new A.n(n,t.D),t.F)))}, +$iaH:1} +A.mQ.prototype={ +$0(){var s=0,r=A.l(t.H),q,p=this,o,n,m +var $async$$0=A.m(function(a,b){if(a===1)return A.i(b,r) +for(;;)switch(s){case 0:o=p.a +n=o.a +m=n.d +s=3 +return A.c(n.bn(o.c),$async$$0) +case 3:q=m.bd(0,b,p.b) +s=1 +break +case 1:return A.j(q,r)}}) +return A.k($async$$0,r)}, +$S:2} +A.as.prototype={ +eu(a){a.dT(a.c,this,!1) +return!0}} +A.dB.prototype={ +U(){return this.w.$0()}} +A.dz.prototype={ +eu(a){var s,r,q,p +if(!a.gB(0)){s=a.gE(0) +for(r=this.x;s!=null;)if(s instanceof A.dz)if(s.x===r)return!1 +else s=s.gc8() +else if(s instanceof A.cR){q=s.gc8() +if(s.x===r){p=s.a +p.toString +p.e4(A.r(s).h("aL.E").a(s))}s=q}else if(s instanceof A.cH){if(s.x===r){r=s.a +r.toString +r.e4(A.r(s).h("aL.E").a(s)) +return!1}s=s.gc8()}else break}a.dT(a.c,this,!1) +return!0}, +U(){var s=0,r=A.l(t.H),q=this,p,o,n +var $async$U=A.m(function(a,b){if(a===1)return A.i(b,r) +for(;;)switch(s){case 0:p=q.w +o=q.x +s=2 +return A.c(p.bn(o),$async$U) +case 2:n=b +p.y.G(0,o) +s=3 +return A.c(p.d.cT(n),$async$U) +case 3:return A.j(null,r)}}) +return A.k($async$U,r)}} +A.cH.prototype={ +U(){var s=0,r=A.l(t.H),q=this,p,o,n,m +var $async$U=A.m(function(a,b){if(a===1)return A.i(b,r) +for(;;)switch(s){case 0:p=q.w +o=q.x +n=p.y +m=o +s=2 +return A.c(p.d.cR(o),$async$U) +case 2:n.t(0,m,b) +return A.j(null,r)}}) +return A.k($async$U,r)}} +A.cR.prototype={ +eu(a){var s,r=a.b===0?null:a.gE(0) +for(s=this.x;r!=null;)if(r instanceof A.cR)if(r.x===s){B.c.aG(r.z,this.z) +return!1}else r=r.gc8() +else if(r instanceof A.cH){if(r.x===s)break +r=r.gc8()}else break +a.dT(a.c,this,!1) +return!0}, +U(){var s=0,r=A.l(t.H),q=this,p,o,n,m,l,k +var $async$U=A.m(function(a,b){if(a===1)return A.i(b,r) +for(;;)switch(s){case 0:m=q.y +l=new A.mB(m,A.al(t.S,t.p),m.length) +for(m=q.z,p=m.length,o=0;o=2?1:0}, +ci(){var s=this +s.c.flush() +if(s.d)s.a.dU(s.b,!1)}, +ck(){return this.c.getSize()}, +de(a){this.e=a}, +dg(a){this.c.flush()}, +cl(a){this.c.truncate(a)}, +dh(a){this.e=a}, +be(a,b){if(A.o3(this.c,a,{at:b})")),null)}, +i(a){var s=this.a,r=A.N(s) +return new A.E(s,new A.jg(new A.E(s,new A.jh(),r.h("E<1,a>")).em(0,0,B.w)),r.h("E<1,o>")).aq(0,u.q)}, +$iY:1} +A.jd.prototype={ +$1(a){return a.length!==0}, +$S:3} +A.ji.prototype={ +$1(a){return a.gc0()}, +$S:89} +A.jh.prototype={ +$1(a){var s=a.gc0() +return new A.E(s,new A.jf(),A.N(s).h("E<1,a>")).em(0,0,B.w)}, +$S:90} +A.jf.prototype={ +$1(a){return a.gbx().length}, +$S:36} +A.jg.prototype={ +$1(a){var s=a.gc0() +return new A.E(s,new A.je(this.a),A.N(s).h("E<1,o>")).c2(0)}, +$S:92} +A.je.prototype={ +$1(a){return B.a.hg(a.gbx(),this.a)+" "+A.t(a.geA())+"\n"}, +$S:22} +A.L.prototype={ +gey(){var s=this.a +if(s.gZ()==="data")return"data:..." +return $.j0().kU(s)}, +gbx(){var s,r=this,q=r.b +if(q==null)return r.gey() +s=r.c +if(s==null)return r.gey()+" "+A.t(q) +return r.gey()+" "+A.t(q)+":"+A.t(s)}, +i(a){return this.gbx()+" in "+A.t(this.d)}, +geA(){return this.d}} +A.kf.prototype={ +$0(){var s,r,q,p,o,n,m,l=null,k=this.a +if(k==="...")return new A.L(A.am(l,l,l,l),l,l,"...") +s=$.td().a8(k) +if(s==null)return new A.bs(A.am(l,"unparsed",l,l),k) +k=s.b +r=k[1] +r.toString +q=$.rX() +r=A.bi(r,q,"") +p=A.bi(r,"","") +r=k[2] +q=r +q.toString +if(B.a.u(q,"1?A.bh(n[1],l):l +return new A.L(o,m,k>2?A.bh(n[2],l):l,p)}, +$S:12} +A.kd.prototype={ +$0(){var s,r,q,p,o,n="",m=this.a,l=$.tc().a8(m) +if(l!=null){s=l.aK("member") +m=l.aK("uri") +m.toString +r=A.hd(m) +m=l.aK("index") +m.toString +q=l.aK("offset") +q.toString +p=A.bh(q,16) +if(!(s==null))m=s +return new A.L(r,1,p+1,m)}l=$.t8().a8(m) +if(l!=null){m=new A.ke(m) +q=l.b +o=q[2] +if(o!=null){o=o +o.toString +q=q[1] +q.toString +q=A.bi(q,"",n) +q=A.bi(q,"Anonymous function",n) +return m.$2(o,A.bi(q,"(anonymous function)",n))}else{q=q[3] +q.toString +return m.$2(q,n)}}return new A.bs(A.am(null,"unparsed",null,null),m)}, +$S:12} +A.ke.prototype={ +$2(a,b){var s,r,q,p,o,n=null,m=$.t7(),l=m.a8(a) +for(;l!=null;a=s){s=l.b[1] +s.toString +l=m.a8(s)}if(a==="native")return new A.L(A.bt("native"),n,n,b) +r=$.t9().a8(a) +if(r==null)return new A.bs(A.am(n,"unparsed",n,n),this.a) +m=r.b +s=m[1] +s.toString +q=A.hd(s) +s=m[2] +s.toString +p=A.bh(s,n) +o=m[3] +return new A.L(q,p,o!=null?A.bh(o,n):n,b)}, +$S:95} +A.ka.prototype={ +$0(){var s,r,q,p,o=null,n=this.a,m=$.rY().a8(n) +if(m==null)return new A.bs(A.am(o,"unparsed",o,o),n) +n=m.b +s=n[1] +s.toString +r=A.bi(s,"/<","") +s=n[2] +s.toString +q=A.hd(s) +n=n[3] +n.toString +p=A.bh(n,o) +return new A.L(q,p,o,r.length===0||r==="anonymous"?"":r)}, +$S:12} +A.kb.prototype={ +$0(){var s,r,q,p,o,n,m,l,k=null,j=this.a,i=$.t_().a8(j) +if(i!=null){s=i.b +r=s[3] +q=r +q.toString +if(B.a.I(q," line "))return A.tK(j) +j=r +j.toString +p=A.hd(j) +o=s[1] +if(o!=null){j=s[2] +j.toString +o+=B.c.c2(A.b4(B.a.eb("/",j).gl(0),".",!1,t.N)) +if(o==="")o="" +o=B.a.ho(o,$.t4(),"")}else o="" +j=s[4] +if(j==="")n=k +else{j=j +j.toString +n=A.bh(j,k)}j=s[5] +if(j==null||j==="")m=k +else{j=j +j.toString +m=A.bh(j,k)}return new A.L(p,n,m,o)}i=$.t1().a8(j) +if(i!=null){j=i.aK("member") +j.toString +s=i.aK("uri") +s.toString +p=A.hd(s) +s=i.aK("index") +s.toString +r=i.aK("offset") +r.toString +l=A.bh(r,16) +if(!(j.length!==0))j=s +return new A.L(p,1,l+1,j)}i=$.t5().a8(j) +if(i!=null){j=i.aK("member") +j.toString +return new A.L(A.am(k,"wasm code",k,k),k,k,j)}return new A.bs(A.am(k,"unparsed",k,k),j)}, +$S:12} +A.kc.prototype={ +$0(){var s,r,q,p,o=null,n=this.a,m=$.t2().a8(n) +if(m==null)throw A.b(A.af("Couldn't parse package:stack_trace stack trace line '"+n+"'.",o,o)) +n=m.b +s=n[1] +if(s==="data:...")r=A.q9("") +else{s=s +s.toString +r=A.bt(s)}if(r.gZ()===""){s=$.j0() +r=s.hs(s.fR(s.a.d4(A.oM(r)),o,o,o,o,o,o,o,o,o,o,o,o,o,o))}s=n[2] +if(s==null)q=o +else{s=s +s.toString +q=A.bh(s,o)}s=n[3] +if(s==null)p=o +else{s=s +s.toString +p=A.bh(s,o)}return new A.L(r,q,p,n[4])}, +$S:12} +A.hp.prototype={ +gfP(){var s,r=this,q=r.b +if(q===$){s=r.a.$0() +r.b!==$&&A.p4() +r.b=s +q=s}return q}, +gc0(){return this.gfP().gc0()}, +i(a){return this.gfP().i(0)}, +$iY:1, +$iZ:1} +A.Z.prototype={ +i(a){var s=this.a,r=A.N(s) +return new A.E(s,new A.ls(new A.E(s,new A.lt(),r.h("E<1,a>")).em(0,0,B.w)),r.h("E<1,o>")).c2(0)}, +$iY:1, +gc0(){return this.a}} +A.lq.prototype={ +$0(){return A.q5(this.a.i(0))}, +$S:96} +A.lr.prototype={ +$1(a){return a.length!==0}, +$S:3} +A.lp.prototype={ +$1(a){return!B.a.u(a,$.tb())}, +$S:3} +A.lo.prototype={ +$1(a){return a!=="\tat "}, +$S:3} +A.lm.prototype={ +$1(a){return a.length!==0&&a!=="[native code]"}, +$S:3} +A.ln.prototype={ +$1(a){return!B.a.u(a,"=====")}, +$S:3} +A.lt.prototype={ +$1(a){return a.gbx().length}, +$S:36} +A.ls.prototype={ +$1(a){if(a instanceof A.bs)return a.i(0)+"\n" +return B.a.hg(a.gbx(),this.a)+" "+A.t(a.geA())+"\n"}, +$S:22} +A.bs.prototype={ +i(a){return this.w}, +$iL:1, +gbx(){return"unparsed"}, +geA(){return this.w}} +A.ef.prototype={} +A.f1.prototype={ +R(a,b,c,d){var s,r=this.b +if(r.d){a=null +d=null}s=this.a.R(a,b,c,d) +if(!r.d)r.c=s +return s}, +aV(a,b,c){return this.R(a,null,b,c)}, +ez(a,b){return this.R(a,null,b,null)}} +A.f0.prototype={ +n(){var s,r=this.hJ(),q=this.b +q.d=!0 +s=q.c +if(s!=null){s.c6(null) +s.eD(null)}return r}} +A.ep.prototype={ +ghI(){var s=this.b +s===$&&A.x() +return new A.ar(s,A.r(s).h("ar<1>"))}, +ghE(){var s=this.a +s===$&&A.x() +return s}, +hQ(a,b,c,d){var s=this,r=$.h +s.a!==$&&A.j_() +s.a=new A.f9(a,s,new A.a6(new A.n(r,t.D),t.h),!0) +r=A.eP(null,new A.km(c,s),!0,d) +s.b!==$&&A.j_() +s.b=r}, +iU(){var s,r +this.d=!0 +s=this.c +if(s!=null)s.K() +r=this.b +r===$&&A.x() +r.n()}} +A.km.prototype={ +$0(){var s,r,q=this.b +if(q.d)return +s=this.a.a +r=q.b +r===$&&A.x() +q.c=s.aV(r.gjJ(r),new A.kl(q),r.gfS())}, +$S:0} +A.kl.prototype={ +$0(){var s=this.a,r=s.a +r===$&&A.x() +r.iV() +s=s.b +s===$&&A.x() +s.n()}, +$S:0} +A.f9.prototype={ +v(a,b){if(this.e)throw A.b(A.B("Cannot add event after closing.")) +if(this.d)return +this.a.a.v(0,b)}, +a3(a,b){if(this.e)throw A.b(A.B("Cannot add event after closing.")) +if(this.d)return +this.iw(a,b)}, +iw(a,b){this.a.a.a3(a,b) +return}, +n(){var s=this +if(s.e)return s.c.a +s.e=!0 +if(!s.d){s.b.iU() +s.c.P(s.a.a.n())}return s.c.a}, +iV(){this.d=!0 +var s=this.c +if((s.a.a&30)===0)s.aT() +return}, +$iae:1} +A.hO.prototype={} +A.eO.prototype={} +A.dq.prototype={ +gl(a){return this.b}, +j(a,b){if(b>=this.b)throw A.b(A.px(b,this)) +return this.a[b]}, +t(a,b,c){var s +if(b>=this.b)throw A.b(A.px(b,this)) +s=this.a +s.$flags&2&&A.y(s) +s[b]=c}, +sl(a,b){var s,r,q,p,o=this,n=o.b +if(bn){if(n===0)p=new Uint8Array(b) +else p=o.ie(b) +B.e.ad(p,0,o.b,o.a) +o.a=p}}o.b=b}, +ie(a){var s=this.a.length*2 +if(a!=null&&ss)throw A.b(A.S(c,0,s,null,null)) +s=this.a +if(d instanceof A.br)B.e.M(s,b,c,d.a,e) +else B.e.M(s,b,c,d,e)}, +ad(a,b,c,d){return this.M(0,b,c,d,0)}} +A.iu.prototype={} +A.br.prototype={} +A.o2.prototype={} +A.f6.prototype={ +R(a,b,c,d){return A.aI(this.a,this.b,a,!1)}, +aV(a,b,c){return this.R(a,null,b,c)}} +A.im.prototype={ +K(){var s=this,r=A.bc(null,t.H) +if(s.b==null)return r +s.e5() +s.d=s.b=null +return r}, +c6(a){var s,r=this +if(r.b==null)throw A.b(A.B("Subscription has been canceled.")) +r.e5() +if(a==null)s=null +else{s=A.rd(new A.mz(a),t.m) +s=s==null?null:A.bu(s)}r.d=s +r.e3()}, +eD(a){}, +bA(){if(this.b==null)return;++this.a +this.e5()}, +ba(){var s=this +if(s.b==null||s.a<=0)return;--s.a +s.e3()}, +e3(){var s=this,r=s.d +if(r!=null&&s.a<=0)s.b.addEventListener(s.c,r,!1)}, +e5(){var s=this.d +if(s!=null)this.b.removeEventListener(this.c,s,!1)}} +A.my.prototype={ +$1(a){return this.a.$1(a)}, +$S:1} +A.mz.prototype={ +$1(a){return this.a.$1(a)}, +$S:1};(function aliases(){var s=J.bY.prototype +s.hL=s.i +s=A.cF.prototype +s.hN=s.bH +s=A.ag.prototype +s.dm=s.bm +s.bj=s.bk +s.eV=s.ct +s=A.fo.prototype +s.hO=s.ec +s=A.v.prototype +s.eU=s.M +s=A.d.prototype +s.hK=s.hF +s=A.d2.prototype +s.hJ=s.n +s=A.cA.prototype +s.hM=s.n})();(function installTearOffs(){var s=hunkHelpers._static_2,r=hunkHelpers._static_1,q=hunkHelpers._static_0,p=hunkHelpers.installStaticTearOff,o=hunkHelpers._instance_0u,n=hunkHelpers.installInstanceTearOff,m=hunkHelpers._instance_2u,l=hunkHelpers._instance_1i,k=hunkHelpers._instance_1u +s(J,"vJ","tX",97) +r(A,"wl","uD",16) +r(A,"wm","uE",16) +r(A,"wn","uF",16) +q(A,"rg","we",0) +r(A,"wo","vX",14) +s(A,"wp","vZ",6) +q(A,"rf","vY",0) +p(A,"wv",5,null,["$5"],["w7"],98,0) +p(A,"wA",4,null,["$1$4","$4"],["nu",function(a,b,c,d){return A.nu(a,b,c,d,t.z)}],99,0) +p(A,"wC",5,null,["$2$5","$5"],["nw",function(a,b,c,d,e){var i=t.z +return A.nw(a,b,c,d,e,i,i)}],100,0) +p(A,"wB",6,null,["$3$6","$6"],["nv",function(a,b,c,d,e,f){var i=t.z +return A.nv(a,b,c,d,e,f,i,i,i)}],101,0) +p(A,"wy",4,null,["$1$4","$4"],["r6",function(a,b,c,d){return A.r6(a,b,c,d,t.z)}],102,0) +p(A,"wz",4,null,["$2$4","$4"],["r7",function(a,b,c,d){var i=t.z +return A.r7(a,b,c,d,i,i)}],103,0) +p(A,"wx",4,null,["$3$4","$4"],["r5",function(a,b,c,d){var i=t.z +return A.r5(a,b,c,d,i,i,i)}],104,0) +p(A,"wt",5,null,["$5"],["w6"],105,0) +p(A,"wD",4,null,["$4"],["nx"],106,0) +p(A,"ws",5,null,["$5"],["w5"],107,0) +p(A,"wr",5,null,["$5"],["w4"],108,0) +p(A,"ww",4,null,["$4"],["w8"],109,0) +r(A,"wq","w0",110) +p(A,"wu",5,null,["$5"],["r4"],111,0) +var j +o(j=A.cG.prototype,"gbL","ak",0) +o(j,"gbM","al",0) +n(A.dx.prototype,"gjT",0,1,null,["$2","$1"],["bv","aH"],27,0,0) +m(A.n.prototype,"gdB","i6",6) +l(j=A.cP.prototype,"gjJ","v",7) +n(j,"gfS",0,1,null,["$2","$1"],["a3","jK"],27,0,0) +o(j=A.cf.prototype,"gbL","ak",0) +o(j,"gbM","al",0) +o(j=A.ag.prototype,"gbL","ak",0) +o(j,"gbM","al",0) +o(A.f3.prototype,"gfp","iT",0) +k(j=A.dO.prototype,"giN","iO",7) +m(j,"giR","iS",6) +o(j,"giP","iQ",0) +o(j=A.dA.prototype,"gbL","ak",0) +o(j,"gbM","al",0) +k(j,"gdM","dN",7) +m(j,"gdQ","dR",76) +o(j,"gdO","dP",0) +o(j=A.dL.prototype,"gbL","ak",0) +o(j,"gbM","al",0) +k(j,"gdM","dN",7) +m(j,"gdQ","dR",6) +o(j,"gdO","dP",0) +k(A.dM.prototype,"gjP","ec","V<2>(e?)") +r(A,"wH","uz",8) +p(A,"x8",2,null,["$1$2","$2"],["ro",function(a,b){return A.ro(a,b,t.o)}],112,0) +r(A,"xa","xh",4) +r(A,"x9","xg",4) +r(A,"x7","wI",4) +r(A,"xb","xn",4) +r(A,"x4","wj",4) +r(A,"x5","wk",4) +r(A,"x6","wE",4) +k(A.ek.prototype,"giz","iA",7) +k(A.h4.prototype,"gig","dE",15) +k(A.i6.prototype,"gjv","cD",15) +r(A,"yz","qW",20) +r(A,"yx","qU",20) +r(A,"yy","qV",20) +r(A,"rq","w_",25) +r(A,"rr","w2",115) +r(A,"rp","vz",116) +k(j=A.fZ.prototype,"gkJ","kK",10) +m(j,"gkH","kI",63) +n(j,"gls",0,5,null,["$5"],["lt"],64,0,0) +n(j,"glj",0,3,null,["$3"],["lk"],65,0,0) +n(j,"glb",0,4,null,["$4"],["lc"],30,0,0) +n(j,"glo",0,4,null,["$4"],["lp"],30,0,0) +n(j,"glu",0,3,null,["$3"],["lv"],67,0,0) +m(j,"gly","lz",31) +m(j,"glh","li",31) +k(j,"glf","lg",32) +n(j,"glw",0,4,null,["$4"],["lx"],33,0,0) +n(j,"glG",0,4,null,["$4"],["lH"],33,0,0) +m(j,"glC","lD",71) +m(j,"glA","lB",11) +m(j,"glm","ln",11) +m(j,"glq","lr",11) +m(j,"glE","lF",11) +m(j,"gld","le",11) +k(j,"gcj","ll",32) +k(j,"gkc","kd",16) +k(j,"gk7","k8",74) +n(j,"gka",0,5,null,["$5"],["kb"],75,0,0) +n(j,"gki",0,4,null,["$4"],["kj"],19,0,0) +n(j,"gkm",0,4,null,["$4"],["kn"],19,0,0) +n(j,"gkk",0,4,null,["$4"],["kl"],19,0,0) +m(j,"gko","kp",34) +m(j,"gkg","kh",34) +n(j,"gke",0,5,null,["$5"],["kf"],78,0,0) +m(j,"gk5","k6",79) +m(j,"gk_","k0",121) +n(j,"gjY",0,3,null,["$3"],["jZ"],81,0,0) +o(A.du.prototype,"gb6","n",0) +r(A,"bS","u4",117) +r(A,"b9","u5",118) +r(A,"p3","u6",119) +k(A.eT.prototype,"gj1","j2",83) +o(A.fP.prototype,"gb6","n",0) +o(A.d5.prototype,"gb6","n",2) +o(A.dB.prototype,"gd8","U",0) +o(A.dz.prototype,"gd8","U",2) +o(A.cH.prototype,"gd8","U",2) +o(A.cR.prototype,"gd8","U",2) +o(A.dm.prototype,"gb6","n",0) +r(A,"wQ","tR",13) +r(A,"rj","tQ",13) +r(A,"wO","tO",13) +r(A,"wP","tP",13) +r(A,"xr","us",35) +r(A,"xq","ur",35)})();(function inheritance(){var s=hunkHelpers.mixin,r=hunkHelpers.inherit,q=hunkHelpers.inheritMany +r(A.e,null) +q(A.e,[A.o9,J.hi,A.eJ,J.fK,A.d,A.fU,A.O,A.v,A.cp,A.kR,A.b3,A.d9,A.eU,A.ha,A.hR,A.hM,A.hN,A.h7,A.i7,A.er,A.eo,A.hV,A.hQ,A.fi,A.eg,A.iw,A.lv,A.hD,A.em,A.fm,A.Q,A.kz,A.hr,A.cx,A.hq,A.cw,A.dH,A.m7,A.dp,A.n6,A.mn,A.iR,A.be,A.iq,A.nc,A.iO,A.i9,A.iM,A.U,A.V,A.ag,A.cF,A.dx,A.cg,A.n,A.ia,A.hP,A.cP,A.iN,A.ib,A.dP,A.ik,A.mw,A.fh,A.f3,A.dO,A.f5,A.dD,A.ay,A.iT,A.dU,A.iU,A.ir,A.dl,A.mT,A.dG,A.iy,A.aL,A.iz,A.cq,A.cr,A.nk,A.fy,A.a7,A.ip,A.ei,A.bx,A.mx,A.hE,A.eM,A.io,A.aD,A.hh,A.aN,A.R,A.dQ,A.aA,A.fv,A.hY,A.b6,A.hb,A.hC,A.mR,A.d2,A.h1,A.hs,A.hB,A.hW,A.ek,A.iB,A.fX,A.h5,A.h4,A.bZ,A.aO,A.bW,A.c2,A.bn,A.c4,A.bV,A.c5,A.c3,A.bF,A.bI,A.kS,A.fj,A.i6,A.bK,A.bU,A.ed,A.ao,A.ea,A.d0,A.kK,A.lu,A.jQ,A.dg,A.kL,A.eE,A.kJ,A.bo,A.jR,A.lJ,A.h6,A.dj,A.lH,A.l_,A.fY,A.dJ,A.dK,A.lk,A.kH,A.eF,A.c8,A.cn,A.h_,A.l8,A.d1,A.aq,A.fS,A.jy,A.iI,A.mW,A.cv,A.aG,A.eL,A.lR,A.lI,A.lT,A.lS,A.cb,A.bN,A.fZ,A.bG,A.cI,A.lN,A.kP,A.bp,A.bC,A.iE,A.eT,A.dI,A.fP,A.mB,A.iA,A.it,A.lC,A.bk,A.L,A.hp,A.Z,A.bs,A.eO,A.f9,A.hO,A.o2,A.im]) +q(J.hi,[J.hk,J.eu,J.ev,J.aK,J.d7,J.d6,J.bX]) +q(J.ev,[J.bY,J.u,A.db,A.eA]) +q(J.bY,[J.hF,J.cE,J.bz]) +r(J.hj,A.eJ) +r(J.kv,J.u) +q(J.d6,[J.et,J.hl]) +q(A.d,[A.ce,A.q,A.aE,A.aY,A.en,A.cD,A.bJ,A.eK,A.eV,A.by,A.cM,A.i8,A.iL,A.dR,A.ey]) +q(A.ce,[A.co,A.fz]) +r(A.f4,A.co) +r(A.f_,A.fz) +r(A.ak,A.f_) +q(A.O,[A.d8,A.bL,A.hn,A.hU,A.hJ,A.il,A.fN,A.bb,A.eR,A.hT,A.aQ,A.fW]) +q(A.v,[A.dr,A.i1,A.dt,A.dq]) +r(A.fV,A.dr) +q(A.cp,[A.jj,A.kp,A.jk,A.ll,A.nI,A.nK,A.m9,A.m8,A.nm,A.n7,A.n9,A.n8,A.kj,A.mN,A.li,A.lh,A.lf,A.ld,A.n5,A.mv,A.mu,A.n0,A.n_,A.mP,A.kD,A.mk,A.nf,A.nM,A.nQ,A.nR,A.nD,A.jX,A.jY,A.jZ,A.kX,A.kY,A.kZ,A.kV,A.m1,A.lZ,A.m_,A.lX,A.m2,A.m0,A.kM,A.k5,A.ny,A.kx,A.ky,A.kC,A.lU,A.lV,A.jT,A.l5,A.nB,A.nP,A.k_,A.kQ,A.jp,A.jq,A.jr,A.l4,A.l0,A.l3,A.l1,A.l2,A.jw,A.jx,A.nz,A.m6,A.l9,A.j7,A.mq,A.mr,A.jn,A.jo,A.js,A.jt,A.ju,A.jb,A.j8,A.j9,A.l6,A.lD,A.lE,A.lF,A.lG,A.jd,A.ji,A.jh,A.jf,A.jg,A.je,A.lr,A.lp,A.lo,A.lm,A.ln,A.lt,A.ls,A.my,A.mz]) +q(A.jj,[A.nO,A.ma,A.mb,A.nb,A.na,A.ki,A.kg,A.mE,A.mJ,A.mI,A.mG,A.mF,A.mM,A.mL,A.mK,A.lj,A.lg,A.le,A.lc,A.n4,A.n3,A.mm,A.ml,A.mU,A.np,A.nq,A.mt,A.ms,A.mZ,A.mY,A.nt,A.nj,A.ni,A.jW,A.kT,A.kU,A.kW,A.m3,A.m4,A.lY,A.nS,A.mc,A.mh,A.mf,A.mg,A.me,A.md,A.n1,A.n2,A.jV,A.jU,A.mA,A.kA,A.kB,A.lW,A.jS,A.k3,A.k0,A.k1,A.k2,A.jO,A.jC,A.jz,A.jE,A.jG,A.jI,A.jB,A.jH,A.jM,A.jK,A.jJ,A.jD,A.jF,A.jL,A.jA,A.j5,A.j6,A.lO,A.jc,A.mC,A.ko,A.mQ,A.kf,A.kd,A.ka,A.kb,A.kc,A.lq,A.km,A.kl]) +q(A.q,[A.M,A.cu,A.bB,A.ex,A.ew,A.cL,A.fb]) +q(A.M,[A.cC,A.E,A.eI]) +r(A.ct,A.aE) +r(A.el,A.cD) +r(A.d3,A.bJ) +r(A.cs,A.by) +r(A.iC,A.fi) +q(A.iC,[A.ah,A.cO,A.iD]) +r(A.eh,A.eg) +r(A.es,A.kp) +r(A.eC,A.bL) +q(A.ll,[A.lb,A.eb]) +q(A.Q,[A.bA,A.cK]) +q(A.jk,[A.kw,A.nJ,A.nn,A.nA,A.kk,A.mO,A.no,A.kn,A.kE,A.mj,A.lA,A.lM,A.lL,A.lK,A.jP,A.ja,A.ke]) +r(A.da,A.db) +q(A.eA,[A.cy,A.dd]) +q(A.dd,[A.fd,A.ff]) +r(A.fe,A.fd) +r(A.c_,A.fe) +r(A.fg,A.ff) +r(A.aW,A.fg) +q(A.c_,[A.hu,A.hv]) +q(A.aW,[A.hw,A.dc,A.hx,A.hy,A.hz,A.eB,A.c0]) +r(A.fq,A.il) +q(A.V,[A.dN,A.f8,A.eY,A.e9,A.f1,A.f6]) +r(A.ar,A.dN) +r(A.eZ,A.ar) +q(A.ag,[A.cf,A.dA,A.dL]) +r(A.cG,A.cf) +r(A.fp,A.cF) +q(A.dx,[A.a6,A.a8]) +q(A.cP,[A.dw,A.dS]) +q(A.ik,[A.dy,A.f2]) +r(A.fc,A.f8) +r(A.fo,A.hP) +r(A.dM,A.fo) +q(A.iT,[A.ii,A.iH]) +r(A.dE,A.cK) +r(A.fk,A.dl) +r(A.fa,A.fk) +q(A.cq,[A.h8,A.fQ]) +q(A.h8,[A.fL,A.i_]) +q(A.cr,[A.iQ,A.fR,A.i0]) +r(A.fM,A.iQ) +q(A.bb,[A.dh,A.eq]) +r(A.ij,A.fv) +q(A.bZ,[A.ap,A.bf,A.bm,A.bw]) +q(A.mx,[A.de,A.cB,A.c1,A.ds,A.c7,A.cz,A.cc,A.bO,A.kG,A.ac,A.d4]) +r(A.jN,A.kK) +r(A.kF,A.lu) +q(A.jQ,[A.hA,A.k4]) +q(A.ao,[A.ic,A.dF,A.ho]) +q(A.ic,[A.iP,A.h2,A.id,A.f7]) +r(A.fn,A.iP) +r(A.iv,A.dF) +r(A.cA,A.jN) +r(A.fl,A.k4) +q(A.lJ,[A.jl,A.dv,A.dk,A.di,A.eN,A.h3]) +q(A.jl,[A.c6,A.ej]) +r(A.mp,A.kL) +r(A.i3,A.h2) +r(A.iS,A.cA) +r(A.kt,A.lk) +q(A.kt,[A.kI,A.lB,A.m5]) +r(A.dn,A.d1) +r(A.fT,A.aq) +q(A.fT,[A.he,A.du,A.d5,A.dm]) +q(A.fS,[A.is,A.i4,A.iK]) +r(A.iF,A.jy) +r(A.iG,A.iF) +r(A.hI,A.iG) +r(A.iJ,A.iI) +r(A.bq,A.iJ) +r(A.i5,A.l8) +q(A.bC,[A.b2,A.P]) +r(A.aV,A.P) +r(A.as,A.aL) +q(A.as,[A.dB,A.dz,A.cH,A.cR]) +q(A.eO,[A.ef,A.ep]) +r(A.f0,A.d2) +r(A.iu,A.dq) +r(A.br,A.iu) +s(A.dr,A.hV) +s(A.fz,A.v) +s(A.fd,A.v) +s(A.fe,A.eo) +s(A.ff,A.v) +s(A.fg,A.eo) +s(A.dw,A.ib) +s(A.dS,A.iN) +s(A.iF,A.v) +s(A.iG,A.hB) +s(A.iI,A.hW) +s(A.iJ,A.Q)})() +var v={G:typeof self!="undefined"?self:globalThis,typeUniverse:{eC:new Map(),tR:{},eT:{},tPV:{},sEA:[]},mangledGlobalNames:{a:"int",F:"double",b0:"num",o:"String",K:"bool",R:"Null",p:"List",e:"Object",an:"Map",z:"JSObject"},mangledNames:{},types:["~()","~(z)","D<~>()","K(o)","F(b0)","R()","~(e,Y)","~(e?)","o(o)","R(z)","~(a)","a(aH,a)","L()","L(o)","~(@)","e?(e?)","~(~())","D()","~(z?,p?)","~(bG,a,a,a)","o(a)","@()","o(L)","K(~)","D()","b0?(p)","R(@)","~(e[Y?])","a(a)","K()","a(aq,a,a,a)","a(aq,a)","a(aH)","a(aH,a,a,aK)","~(bG,a)","Z(o)","a(L)","D()","D()","@(@,o)","R(@,Y)","a()","D()","an(p)","a(p)","@(o)","R(ao)","D(~)","~(a,@)","D<~>(ap)","a?(a)","K(a)","z(u)","dj()","D()","R(~)","~(ae)","~(K,K,K,p<+(bO,o)>)","R(e,Y)","o(o?)","o(e?)","~(oe,p)","bH?/(ap)","~(aK,a)","aH?(aq,a,a,a,a)","a(aq,a,a)","0&(o,a?)","a(aq?,a,a)","D()","bU<@>?()","ap()","a(aH,aK)","R(K)","R(~())","a(a())","~(~(a,o,a),a,a,a,aK)","~(@,Y)","bf()","a(bG,a,a,a,a)","a(a(a),a)","bn()","a(oh,a,a)","z()","~(dI)","z(z?)","D<~>(a,aX)","D<~>(a)","a(a,a)","D(o)","p(Z)","a(Z)","p(u)","o(Z)","bK(e?)","~(@,@)","L(o,o)","Z()","a(@,@)","~(w?,W?,w,e,Y)","0^(w?,W?,w,0^())","0^(w?,W?,w,0^(1^),1^)","0^(w?,W?,w,0^(1^,2^),1^,2^)","0^()(w,W,w,0^())","0^(1^)(w,W,w,0^(1^))","0^(1^,2^)(w,W,w,0^(1^,2^))","U?(w,W,w,e,Y?)","~(w?,W?,w,~())","eQ(w,W,w,bx,~())","eQ(w,W,w,bx,~(eQ))","~(w,W,w,o)","~(o)","w(w?,W?,w,oq?,an?)","0^(0^,0^)","~(e?,e?)","@(@)","K?(p)","K?(p<@>)","b2(bp)","P(bp)","aV(bp)","aX()","a(oh,a)"],interceptorsByTag:null,leafTags:null,arrayRti:Symbol("$ti"),rttc:{"2;":(a,b)=>c=>c instanceof A.ah&&a.b(c.a)&&b.b(c.b),"2;file,outFlags":(a,b)=>c=>c instanceof A.cO&&a.b(c.a)&&b.b(c.b),"2;result,resultCode":(a,b)=>c=>c instanceof A.iD&&a.b(c.a)&&b.b(c.b)}} +A.v2(v.typeUniverse,JSON.parse('{"hF":"bY","cE":"bY","bz":"bY","xC":"db","u":{"p":["1"],"q":["1"],"z":[],"d":["1"],"av":["1"]},"hk":{"K":[],"I":[]},"eu":{"R":[],"I":[]},"ev":{"z":[]},"bY":{"z":[]},"hj":{"eJ":[]},"kv":{"u":["1"],"p":["1"],"q":["1"],"z":[],"d":["1"],"av":["1"]},"d6":{"F":[],"b0":[]},"et":{"F":[],"a":[],"b0":[],"I":[]},"hl":{"F":[],"b0":[],"I":[]},"bX":{"o":[],"av":["@"],"I":[]},"ce":{"d":["2"]},"co":{"ce":["1","2"],"d":["2"],"d.E":"2"},"f4":{"co":["1","2"],"ce":["1","2"],"q":["2"],"d":["2"],"d.E":"2"},"f_":{"v":["2"],"p":["2"],"ce":["1","2"],"q":["2"],"d":["2"]},"ak":{"f_":["1","2"],"v":["2"],"p":["2"],"ce":["1","2"],"q":["2"],"d":["2"],"v.E":"2","d.E":"2"},"d8":{"O":[]},"fV":{"v":["a"],"p":["a"],"q":["a"],"d":["a"],"v.E":"a"},"q":{"d":["1"]},"M":{"q":["1"],"d":["1"]},"cC":{"M":["1"],"q":["1"],"d":["1"],"d.E":"1","M.E":"1"},"aE":{"d":["2"],"d.E":"2"},"ct":{"aE":["1","2"],"q":["2"],"d":["2"],"d.E":"2"},"E":{"M":["2"],"q":["2"],"d":["2"],"d.E":"2","M.E":"2"},"aY":{"d":["1"],"d.E":"1"},"en":{"d":["2"],"d.E":"2"},"cD":{"d":["1"],"d.E":"1"},"el":{"cD":["1"],"q":["1"],"d":["1"],"d.E":"1"},"bJ":{"d":["1"],"d.E":"1"},"d3":{"bJ":["1"],"q":["1"],"d":["1"],"d.E":"1"},"eK":{"d":["1"],"d.E":"1"},"cu":{"q":["1"],"d":["1"],"d.E":"1"},"eV":{"d":["1"],"d.E":"1"},"by":{"d":["+(a,1)"],"d.E":"+(a,1)"},"cs":{"by":["1"],"q":["+(a,1)"],"d":["+(a,1)"],"d.E":"+(a,1)"},"dr":{"v":["1"],"p":["1"],"q":["1"],"d":["1"]},"eI":{"M":["1"],"q":["1"],"d":["1"],"d.E":"1","M.E":"1"},"eg":{"an":["1","2"]},"eh":{"eg":["1","2"],"an":["1","2"]},"cM":{"d":["1"],"d.E":"1"},"eC":{"bL":[],"O":[]},"hn":{"O":[]},"hU":{"O":[]},"hD":{"a5":[]},"fm":{"Y":[]},"hJ":{"O":[]},"bA":{"Q":["1","2"],"an":["1","2"],"Q.V":"2","Q.K":"1"},"bB":{"q":["1"],"d":["1"],"d.E":"1"},"ex":{"q":["1"],"d":["1"],"d.E":"1"},"ew":{"q":["aN<1,2>"],"d":["aN<1,2>"],"d.E":"aN<1,2>"},"dH":{"hH":[],"ez":[]},"i8":{"d":["hH"],"d.E":"hH"},"dp":{"ez":[]},"iL":{"d":["ez"],"d.E":"ez"},"da":{"z":[],"ec":[],"I":[]},"cy":{"o_":[],"z":[],"I":[]},"dc":{"aW":[],"kr":[],"v":["a"],"p":["a"],"aU":["a"],"q":["a"],"z":[],"av":["a"],"d":["a"],"I":[],"v.E":"a"},"c0":{"aW":[],"aX":[],"v":["a"],"p":["a"],"aU":["a"],"q":["a"],"z":[],"av":["a"],"d":["a"],"I":[],"v.E":"a"},"db":{"z":[],"ec":[],"I":[]},"eA":{"z":[]},"iR":{"ec":[]},"dd":{"aU":["1"],"z":[],"av":["1"]},"c_":{"v":["F"],"p":["F"],"aU":["F"],"q":["F"],"z":[],"av":["F"],"d":["F"]},"aW":{"v":["a"],"p":["a"],"aU":["a"],"q":["a"],"z":[],"av":["a"],"d":["a"]},"hu":{"c_":[],"k8":[],"v":["F"],"p":["F"],"aU":["F"],"q":["F"],"z":[],"av":["F"],"d":["F"],"I":[],"v.E":"F"},"hv":{"c_":[],"k9":[],"v":["F"],"p":["F"],"aU":["F"],"q":["F"],"z":[],"av":["F"],"d":["F"],"I":[],"v.E":"F"},"hw":{"aW":[],"kq":[],"v":["a"],"p":["a"],"aU":["a"],"q":["a"],"z":[],"av":["a"],"d":["a"],"I":[],"v.E":"a"},"hx":{"aW":[],"ks":[],"v":["a"],"p":["a"],"aU":["a"],"q":["a"],"z":[],"av":["a"],"d":["a"],"I":[],"v.E":"a"},"hy":{"aW":[],"lx":[],"v":["a"],"p":["a"],"aU":["a"],"q":["a"],"z":[],"av":["a"],"d":["a"],"I":[],"v.E":"a"},"hz":{"aW":[],"ly":[],"v":["a"],"p":["a"],"aU":["a"],"q":["a"],"z":[],"av":["a"],"d":["a"],"I":[],"v.E":"a"},"eB":{"aW":[],"lz":[],"v":["a"],"p":["a"],"aU":["a"],"q":["a"],"z":[],"av":["a"],"d":["a"],"I":[],"v.E":"a"},"il":{"O":[]},"fq":{"bL":[],"O":[]},"U":{"O":[]},"ag":{"ag.T":"1"},"dD":{"ae":["1"]},"dR":{"d":["1"],"d.E":"1"},"eZ":{"ar":["1"],"dN":["1"],"V":["1"],"V.T":"1"},"cG":{"cf":["1"],"ag":["1"],"ag.T":"1"},"cF":{"ae":["1"]},"fp":{"cF":["1"],"ae":["1"]},"a6":{"dx":["1"]},"a8":{"dx":["1"]},"n":{"D":["1"]},"cP":{"ae":["1"]},"dw":{"cP":["1"],"ae":["1"]},"dS":{"cP":["1"],"ae":["1"]},"ar":{"dN":["1"],"V":["1"],"V.T":"1"},"cf":{"ag":["1"],"ag.T":"1"},"dP":{"ae":["1"]},"dN":{"V":["1"]},"f8":{"V":["2"]},"dA":{"ag":["2"],"ag.T":"2"},"fc":{"f8":["1","2"],"V":["2"],"V.T":"2"},"f5":{"ae":["1"]},"dL":{"ag":["2"],"ag.T":"2"},"eY":{"V":["2"],"V.T":"2"},"dM":{"fo":["1","2"]},"iT":{"w":[]},"ii":{"w":[]},"iH":{"w":[]},"dU":{"W":[]},"iU":{"oq":[]},"cK":{"Q":["1","2"],"an":["1","2"],"Q.V":"2","Q.K":"1"},"dE":{"cK":["1","2"],"Q":["1","2"],"an":["1","2"],"Q.V":"2","Q.K":"1"},"cL":{"q":["1"],"d":["1"],"d.E":"1"},"fa":{"fk":["1"],"dl":["1"],"q":["1"],"d":["1"]},"ey":{"d":["1"],"d.E":"1"},"v":{"p":["1"],"q":["1"],"d":["1"]},"Q":{"an":["1","2"]},"fb":{"q":["2"],"d":["2"],"d.E":"2"},"dl":{"q":["1"],"d":["1"]},"fk":{"dl":["1"],"q":["1"],"d":["1"]},"fL":{"cq":["o","p"]},"iQ":{"cr":["o","p"]},"fM":{"cr":["o","p"]},"fQ":{"cq":["p","o"]},"fR":{"cr":["p","o"]},"h8":{"cq":["o","p"]},"i_":{"cq":["o","p"]},"i0":{"cr":["o","p"]},"F":{"b0":[]},"a":{"b0":[]},"p":{"q":["1"],"d":["1"]},"hH":{"ez":[]},"fN":{"O":[]},"bL":{"O":[]},"bb":{"O":[]},"dh":{"O":[]},"eq":{"O":[]},"eR":{"O":[]},"hT":{"O":[]},"aQ":{"O":[]},"fW":{"O":[]},"hE":{"O":[]},"eM":{"O":[]},"io":{"a5":[]},"aD":{"a5":[]},"hh":{"a5":[],"O":[]},"dQ":{"Y":[]},"fv":{"hX":[]},"b6":{"hX":[]},"ij":{"hX":[]},"hC":{"a5":[]},"d2":{"ae":["1"]},"fX":{"a5":[]},"h5":{"a5":[]},"ap":{"bZ":[]},"bf":{"bZ":[]},"bn":{"ax":[]},"bF":{"ax":[]},"aO":{"bH":[]},"bm":{"bZ":[]},"bw":{"bZ":[]},"de":{"ax":[]},"bW":{"ax":[]},"c2":{"ax":[]},"c4":{"ax":[]},"bV":{"ax":[]},"c5":{"ax":[]},"c3":{"ax":[]},"bI":{"bH":[]},"ed":{"a5":[]},"ic":{"ao":[]},"iP":{"hS":[],"ao":[]},"fn":{"hS":[],"ao":[]},"h2":{"ao":[]},"id":{"ao":[]},"f7":{"ao":[]},"dF":{"ao":[]},"iv":{"hS":[],"ao":[]},"ho":{"ao":[]},"dv":{"a5":[]},"i3":{"ao":[]},"iS":{"cA":["o0"],"cA.0":"o0"},"eF":{"a5":[]},"c8":{"a5":[]},"h_":{"o0":[]},"i1":{"v":["e?"],"p":["e?"],"q":["e?"],"d":["e?"],"v.E":"e?"},"dn":{"d1":[]},"he":{"aq":[]},"is":{"aH":[]},"bq":{"Q":["o","@"],"an":["o","@"],"Q.V":"@","Q.K":"o"},"hI":{"v":["bq"],"p":["bq"],"q":["bq"],"d":["bq"],"v.E":"bq"},"aG":{"a5":[]},"fT":{"aq":[]},"fS":{"aH":[]},"bN":{"of":[]},"cb":{"oe":[]},"dt":{"v":["bN"],"p":["bN"],"q":["bN"],"d":["bN"],"v.E":"bN"},"e9":{"V":["1"],"V.T":"1"},"du":{"aq":[]},"i4":{"aH":[]},"b2":{"bC":[]},"P":{"bC":[]},"aV":{"P":[],"bC":[]},"d5":{"aq":[]},"as":{"aL":["as"]},"it":{"aH":[]},"dB":{"as":[],"aL":["as"],"aL.E":"as"},"dz":{"as":[],"aL":["as"],"aL.E":"as"},"cH":{"as":[],"aL":["as"],"aL.E":"as"},"cR":{"as":[],"aL":["as"],"aL.E":"as"},"dm":{"aq":[]},"iK":{"aH":[]},"bk":{"Y":[]},"hp":{"Z":[],"Y":[]},"Z":{"Y":[]},"bs":{"L":[]},"ef":{"eO":["1"]},"f1":{"V":["1"],"V.T":"1"},"f0":{"ae":["1"]},"ep":{"eO":["1"]},"f9":{"ae":["1"]},"br":{"dq":["a"],"v":["a"],"p":["a"],"q":["a"],"d":["a"],"v.E":"a"},"dq":{"v":["1"],"p":["1"],"q":["1"],"d":["1"]},"iu":{"dq":["a"],"v":["a"],"p":["a"],"q":["a"],"d":["a"]},"f6":{"V":["1"],"V.T":"1"},"ks":{"p":["a"],"q":["a"],"d":["a"]},"aX":{"p":["a"],"q":["a"],"d":["a"]},"lz":{"p":["a"],"q":["a"],"d":["a"]},"kq":{"p":["a"],"q":["a"],"d":["a"]},"lx":{"p":["a"],"q":["a"],"d":["a"]},"kr":{"p":["a"],"q":["a"],"d":["a"]},"ly":{"p":["a"],"q":["a"],"d":["a"]},"k8":{"p":["F"],"q":["F"],"d":["F"]},"k9":{"p":["F"],"q":["F"],"d":["F"]}}')) +A.v1(v.typeUniverse,JSON.parse('{"eU":1,"hM":1,"hN":1,"h7":1,"er":1,"eo":1,"hV":1,"dr":1,"fz":2,"hr":1,"cx":1,"dd":1,"ae":1,"iM":1,"hP":2,"iN":1,"ib":1,"dP":1,"ik":1,"dy":1,"fh":1,"f3":1,"dO":1,"f5":1,"ay":1,"hb":1,"d2":1,"h1":1,"hs":1,"hB":1,"hW":2,"ts":1,"f0":1,"f9":1,"im":1}')) +var u={v:"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\u03f6\x00\u0404\u03f4 \u03f4\u03f6\u01f6\u01f6\u03f6\u03fc\u01f4\u03ff\u03ff\u0584\u03ff\u03ff\u03ff\u03ff\u03ff\u03ff\u03ff\u03ff\u03ff\u03ff\u05d4\u01f4\x00\u01f4\x00\u0504\u05c4\u03ff\u03ff\u03ff\u03ff\u03ff\u03ff\u03ff\u03ff\u03ff\u03ff\u03ff\u03ff\u03ff\u03ff\u03ff\u03ff\u03ff\u03ff\u03ff\u03ff\u03ff\u03ff\u03ff\u03ff\u03ff\u03ff\u0400\x00\u0400\u0200\u03f7\u0200\u03ff\u03ff\u03ff\u03ff\u03ff\u03ff\u03ff\u03ff\u03ff\u03ff\u03ff\u03ff\u03ff\u03ff\u03ff\u03ff\u03ff\u03ff\u03ff\u03ff\u03ff\u03ff\u03ff\u03ff\u03ff\u03ff\u0200\u0200\u0200\u03f7\x00",q:"===== asynchronous gap ===========================\n",l:"Cannot extract a file path from a URI with a fragment component",y:"Cannot extract a file path from a URI with a query component",j:"Cannot extract a non-Windows file path from a file URI with an authority",o:"Cannot fire new event. Controller is already firing an event",c:"Error handler must accept one Object or one Object and a StackTrace as arguments, and return a value of the returned future's type",D:"Tried to operate on a released prepared statement"} +var t=(function rtii(){var s=A.aB +return{b9:s("ts"),cO:s("e9>"),E:s("ec"),fd:s("o_"),g1:s("bU<@>"),eT:s("d1"),ed:s("ej"),gw:s("ek"),Q:s("q<@>"),q:s("b2"),C:s("O"),g8:s("a5"),ez:s("d4"),G:s("P"),h4:s("k8"),gN:s("k9"),B:s("L"),b8:s("xz"),bF:s("D"),cG:s("D"),eY:s("D"),bd:s("d5"),dQ:s("kq"),an:s("kr"),gj:s("ks"),hf:s("d<@>"),b:s("u"),cf:s("u"),e:s("u"),fG:s("u>"),fk:s("u>"),W:s("u"),gP:s("u>"),gz:s("u>"),d:s("u>"),f:s("u"),L:s("u<+(bO,o)>"),bb:s("u"),s:s("u"),be:s("u"),J:s("u"),gQ:s("u"),n:s("u"),gn:s("u<@>"),t:s("u"),c:s("u"),d4:s("u"),r:s("u"),Y:s("u"),bT:s("u<~()>"),aP:s("av<@>"),T:s("eu"),m:s("z"),g:s("bz"),aU:s("aU<@>"),au:s("ey"),e9:s("p>"),cl:s("p"),aS:s("p>"),u:s("p"),j:s("p<@>"),I:s("p"),ee:s("p"),g6:s("an"),eO:s("an<@,@>"),M:s("aE"),fe:s("E"),do:s("E"),fJ:s("bZ"),cb:s("bC"),eN:s("aV"),v:s("da"),gT:s("cy"),ha:s("dc"),aV:s("c_"),eB:s("aW"),Z:s("c0"),bw:s("bF"),P:s("R"),K:s("e"),x:s("ao"),aj:s("dg"),fl:s("xE"),bQ:s("+()"),e1:s("+(z?,z)"),cV:s("+(e?,a)"),cz:s("hH"),al:s("ap"),cc:s("bH"),bJ:s("eI"),fE:s("dj"),fL:s("c6"),gW:s("dm"),f_:s("c8"),l:s("Y"),a7:s("hO"),N:s("o"),aF:s("eQ"),a:s("Z"),w:s("hS"),dm:s("I"),eK:s("bL"),h7:s("lx"),bv:s("ly"),go:s("lz"),p:s("aX"),ak:s("cE"),dD:s("hX"),ei:s("eT"),ab:s("i5"),aT:s("du"),U:s("aY"),eJ:s("eV"),R:s("ac"),dx:s("ac"),b0:s("ac"),bi:s("a6"),co:s("a6"),fu:s("a6"),h:s("a6<~>"),V:s("cI"),fF:s("f6"),et:s("n"),a9:s("n"),k:s("n"),eI:s("n<@>"),gR:s("n"),fX:s("n"),D:s("n<~>"),hg:s("dE"),cT:s("dI"),aR:s("iB"),eg:s("iE"),dn:s("fp<~>"),eC:s("a8"),fa:s("a8"),F:s("a8<~>"),y:s("K"),i:s("F"),z:s("@"),bI:s("@(e)"),_:s("@(e,Y)"),S:s("a"),eH:s("D?"),A:s("z?"),dE:s("c0?"),X:s("e?"),ah:s("ax?"),O:s("bH?"),dk:s("o?"),fN:s("br?"),aD:s("aX?"),fQ:s("K?"),cD:s("F?"),h6:s("a?"),cg:s("b0?"),o:s("b0"),H:s("~"),d5:s("~(e)"),da:s("~(e,Y)")}})();(function constants(){var s=hunkHelpers.makeConstList +B.aB=J.hi.prototype +B.c=J.u.prototype +B.b=J.et.prototype +B.aC=J.d6.prototype +B.a=J.bX.prototype +B.aD=J.bz.prototype +B.aE=J.ev.prototype +B.aN=A.cy.prototype +B.e=A.c0.prototype +B.Z=J.hF.prototype +B.D=J.cE.prototype +B.ai=new A.cn(0) +B.l=new A.cn(1) +B.p=new A.cn(2) +B.L=new A.cn(3) +B.bD=new A.cn(-1) +B.aj=new A.fM(127) +B.w=new A.es(A.x8(),A.aB("es")) +B.ak=new A.fL() +B.bE=new A.fR() +B.al=new A.fQ() +B.M=new A.ed() +B.am=new A.fX() +B.bF=new A.h1() +B.N=new A.h4() +B.O=new A.h7() +B.h=new A.b2() +B.an=new A.hh() +B.P=function getTagFallback(o) { + var s = Object.prototype.toString.call(o); + return s.substring(8, s.length - 1); +} +B.ao=function() { + var toStringFunction = Object.prototype.toString; + function getTag(o) { + var s = toStringFunction.call(o); + return s.substring(8, s.length - 1); + } + function getUnknownTag(object, tag) { + if (/^HTML[A-Z].*Element$/.test(tag)) { + var name = toStringFunction.call(object); + if (name == "[object Object]") return null; + return "HTMLElement"; + } + } + function getUnknownTagGenericBrowser(object, tag) { + if (object instanceof HTMLElement) return "HTMLElement"; + return getUnknownTag(object, tag); + } + function prototypeForTag(tag) { + if (typeof window == "undefined") return null; + if (typeof window[tag] == "undefined") return null; + var constructor = window[tag]; + if (typeof constructor != "function") return null; + return constructor.prototype; + } + function discriminator(tag) { return null; } + var isBrowser = typeof HTMLElement == "function"; + return { + getTag: getTag, + getUnknownTag: isBrowser ? getUnknownTagGenericBrowser : getUnknownTag, + prototypeForTag: prototypeForTag, + discriminator: discriminator }; +} +B.at=function(getTagFallback) { + return function(hooks) { + if (typeof navigator != "object") return hooks; + var userAgent = navigator.userAgent; + if (typeof userAgent != "string") return hooks; + if (userAgent.indexOf("DumpRenderTree") >= 0) return hooks; + if (userAgent.indexOf("Chrome") >= 0) { + function confirm(p) { + return typeof window == "object" && window[p] && window[p].name == p; + } + if (confirm("Window") && confirm("HTMLElement")) return hooks; + } + hooks.getTag = getTagFallback; + }; +} +B.ap=function(hooks) { + if (typeof dartExperimentalFixupGetTag != "function") return hooks; + hooks.getTag = dartExperimentalFixupGetTag(hooks.getTag); +} +B.as=function(hooks) { + if (typeof navigator != "object") return hooks; + var userAgent = navigator.userAgent; + if (typeof userAgent != "string") return hooks; + if (userAgent.indexOf("Firefox") == -1) return hooks; + var getTag = hooks.getTag; + var quickMap = { + "BeforeUnloadEvent": "Event", + "DataTransfer": "Clipboard", + "GeoGeolocation": "Geolocation", + "Location": "!Location", + "WorkerMessageEvent": "MessageEvent", + "XMLDocument": "!Document"}; + function getTagFirefox(o) { + var tag = getTag(o); + return quickMap[tag] || tag; + } + hooks.getTag = getTagFirefox; +} +B.ar=function(hooks) { + if (typeof navigator != "object") return hooks; + var userAgent = navigator.userAgent; + if (typeof userAgent != "string") return hooks; + if (userAgent.indexOf("Trident/") == -1) return hooks; + var getTag = hooks.getTag; + var quickMap = { + "BeforeUnloadEvent": "Event", + "DataTransfer": "Clipboard", + "HTMLDDElement": "HTMLElement", + "HTMLDTElement": "HTMLElement", + "HTMLPhraseElement": "HTMLElement", + "Position": "Geoposition" + }; + function getTagIE(o) { + var tag = getTag(o); + var newTag = quickMap[tag]; + if (newTag) return newTag; + if (tag == "Object") { + if (window.DataView && (o instanceof window.DataView)) return "DataView"; + } + return tag; + } + function prototypeForTagIE(tag) { + var constructor = window[tag]; + if (constructor == null) return null; + return constructor.prototype; + } + hooks.getTag = getTagIE; + hooks.prototypeForTag = prototypeForTagIE; +} +B.aq=function(hooks) { + var getTag = hooks.getTag; + var prototypeForTag = hooks.prototypeForTag; + function getTagFixed(o) { + var tag = getTag(o); + if (tag == "Document") { + if (!!o.xmlVersion) return "!Document"; + return "!HTMLDocument"; + } + return tag; + } + function prototypeForTagFixed(tag) { + if (tag == "Document") return null; + return prototypeForTag(tag); + } + hooks.getTag = getTagFixed; + hooks.prototypeForTag = prototypeForTagFixed; +} +B.Q=function(hooks) { return hooks; } + +B.o=new A.hs() +B.au=new A.kF() +B.av=new A.hA() +B.aw=new A.hE() +B.f=new A.kR() +B.j=new A.i_() +B.i=new A.i0() +B.x=new A.mw() +B.d=new A.iH() +B.y=new A.bx(0) +B.az=new A.aD("Unknown tag",null,null) +B.aA=new A.aD("Cannot read message",null,null) +B.aF=s([11],t.t) +B.F=new A.bO(0,"opfs") +B.a2=new A.cc(0,"opfsShared") +B.a3=new A.cc(1,"opfsLocks") +B.a4=new A.bO(1,"indexedDb") +B.u=new A.cc(2,"sharedIndexedDb") +B.E=new A.cc(3,"unsafeIndexedDb") +B.bn=new A.cc(4,"inMemory") +B.aG=s([B.a2,B.a3,B.u,B.E,B.bn],A.aB("u")) +B.be=new A.ds(0,"insert") +B.bf=new A.ds(1,"update") +B.bg=new A.ds(2,"delete") +B.R=s([B.be,B.bf,B.bg],A.aB("u")) +B.aH=s([B.F,B.a4],A.aB("u")) +B.z=s([],t.W) +B.aI=s([],t.gz) +B.aJ=s([],t.f) +B.A=s([],t.s) +B.q=s([],t.c) +B.B=s([],t.L) +B.ax=new A.d4("/database",0,"database") +B.ay=new A.d4("/database-journal",1,"journal") +B.S=s([B.ax,B.ay],A.aB("u")) +B.a5=new A.ac(A.p3(),A.b9(),0,"xAccess",t.b0) +B.a6=new A.ac(A.p3(),A.bS(),1,"xDelete",A.aB("ac")) +B.ah=new A.ac(A.p3(),A.b9(),2,"xOpen",t.b0) +B.af=new A.ac(A.b9(),A.b9(),3,"xRead",t.dx) +B.aa=new A.ac(A.b9(),A.bS(),4,"xWrite",t.R) +B.ab=new A.ac(A.b9(),A.bS(),5,"xSleep",t.R) +B.ac=new A.ac(A.b9(),A.bS(),6,"xClose",t.R) +B.ag=new A.ac(A.b9(),A.b9(),7,"xFileSize",t.dx) +B.ad=new A.ac(A.b9(),A.bS(),8,"xSync",t.R) +B.ae=new A.ac(A.b9(),A.bS(),9,"xTruncate",t.R) +B.a8=new A.ac(A.b9(),A.bS(),10,"xLock",t.R) +B.a9=new A.ac(A.b9(),A.bS(),11,"xUnlock",t.R) +B.a7=new A.ac(A.bS(),A.bS(),12,"stopServer",A.aB("ac")) +B.aL=s([B.a5,B.a6,B.ah,B.af,B.aa,B.ab,B.ac,B.ag,B.ad,B.ae,B.a8,B.a9,B.a7],A.aB("u>")) +B.m=new A.c7(0,"sqlite") +B.aV=new A.c7(1,"mysql") +B.aW=new A.c7(2,"postgres") +B.aX=new A.c7(3,"duckdb") +B.aY=new A.c7(4,"mariadb") +B.T=s([B.m,B.aV,B.aW,B.aX,B.aY],A.aB("u")) +B.aZ=new A.cB(0,"custom") +B.b_=new A.cB(1,"deleteOrUpdate") +B.b0=new A.cB(2,"insert") +B.b1=new A.cB(3,"select") +B.U=s([B.aZ,B.b_,B.b0,B.b1],A.aB("u")) +B.W=new A.c1(0,"beginTransaction") +B.aO=new A.c1(1,"commit") +B.aP=new A.c1(2,"rollback") +B.X=new A.c1(3,"startExclusive") +B.Y=new A.c1(4,"endExclusive") +B.V=s([B.W,B.aO,B.aP,B.X,B.Y],A.aB("u")) +B.aQ={} +B.aM=new A.eh(B.aQ,[],A.aB("eh")) +B.C=new A.de(0,"terminateAll") +B.bG=new A.kG(2,"readWriteCreate") +B.r=new A.cz(0,0,"legacy") +B.aR=new A.cz(1,1,"v1") +B.aS=new A.cz(2,2,"v2") +B.aT=new A.cz(3,3,"v3") +B.t=new A.cz(4,4,"v4") +B.aK=s([],t.d) +B.aU=new A.bI(B.aK) +B.a_=new A.hQ("drift.runtime.cancellation") +B.b2=A.bj("ec") +B.b3=A.bj("o_") +B.b4=A.bj("k8") +B.b5=A.bj("k9") +B.b6=A.bj("kq") +B.b7=A.bj("kr") +B.b8=A.bj("ks") +B.b9=A.bj("e") +B.ba=A.bj("lx") +B.bb=A.bj("ly") +B.bc=A.bj("lz") +B.bd=A.bj("aX") +B.bh=new A.aG(10) +B.bi=new A.aG(12) +B.a0=new A.aG(14) +B.bj=new A.aG(2570) +B.bk=new A.aG(3850) +B.bl=new A.aG(522) +B.a1=new A.aG(778) +B.bm=new A.aG(8) +B.bo=new A.dJ("reaches root") +B.G=new A.dJ("below root") +B.H=new A.dJ("at root") +B.I=new A.dJ("above root") +B.k=new A.dK("different") +B.J=new A.dK("equal") +B.n=new A.dK("inconclusive") +B.K=new A.dK("within") +B.v=new A.dQ("") +B.bp=new A.ay(B.d,A.wv()) +B.bq=new A.ay(B.d,A.wr()) +B.br=new A.ay(B.d,A.wz()) +B.bs=new A.ay(B.d,A.ws()) +B.bt=new A.ay(B.d,A.wt()) +B.bu=new A.ay(B.d,A.wu()) +B.bv=new A.ay(B.d,A.ww()) +B.bw=new A.ay(B.d,A.wy()) +B.bx=new A.ay(B.d,A.wA()) +B.by=new A.ay(B.d,A.wB()) +B.bz=new A.ay(B.d,A.wC()) +B.bA=new A.ay(B.d,A.wD()) +B.bB=new A.ay(B.d,A.wx()) +B.bC=new A.iU(null,null,null,null,null,null,null,null,null,null,null,null,null)})();(function staticFields(){$.mS=null +$.cT=A.f([],t.f) +$.rt=null +$.pI=null +$.pj=null +$.pi=null +$.rl=null +$.re=null +$.ru=null +$.nF=null +$.nL=null +$.oV=null +$.mV=A.f([],A.aB("u?>")) +$.dX=null +$.fB=null +$.fC=null +$.oL=!1 +$.h=B.d +$.mX=null +$.qh=null +$.qi=null +$.qj=null +$.qk=null +$.or=A.mo("_lastQuoRemDigits") +$.os=A.mo("_lastQuoRemUsed") +$.eX=A.mo("_lastRemUsed") +$.ot=A.mo("_lastRem_nsh") +$.qa="" +$.qb=null +$.qT=null +$.nr=null})();(function lazyInitializers(){var s=hunkHelpers.lazyFinal,r=hunkHelpers.lazy +s($,"xv","e6",()=>A.wS("_$dart_dartClosure")) +s($,"yA","tg",()=>B.d.bb(new A.nO(),A.aB("D<~>"))) +s($,"yl","t6",()=>A.f([new J.hj()],A.aB("u"))) +s($,"xK","rC",()=>A.bM(A.lw({ +toString:function(){return"$receiver$"}}))) +s($,"xL","rD",()=>A.bM(A.lw({$method$:null, +toString:function(){return"$receiver$"}}))) +s($,"xM","rE",()=>A.bM(A.lw(null))) +s($,"xN","rF",()=>A.bM(function(){var $argumentsExpr$="$arguments$" +try{null.$method$($argumentsExpr$)}catch(q){return q.message}}())) +s($,"xQ","rI",()=>A.bM(A.lw(void 0))) +s($,"xR","rJ",()=>A.bM(function(){var $argumentsExpr$="$arguments$" +try{(void 0).$method$($argumentsExpr$)}catch(q){return q.message}}())) +s($,"xP","rH",()=>A.bM(A.q6(null))) +s($,"xO","rG",()=>A.bM(function(){try{null.$method$}catch(q){return q.message}}())) +s($,"xT","rL",()=>A.bM(A.q6(void 0))) +s($,"xS","rK",()=>A.bM(function(){try{(void 0).$method$}catch(q){return q.message}}())) +s($,"xW","p7",()=>A.uC()) +s($,"xB","cm",()=>$.tg()) +s($,"xA","rz",()=>A.uN(!1,B.d,t.y)) +s($,"y5","rS",()=>{var q=t.z +return A.pw(q,q)}) +s($,"y9","rW",()=>A.pF(4096)) +s($,"y7","rU",()=>new A.nj().$0()) +s($,"y8","rV",()=>new A.ni().$0()) +s($,"xX","rN",()=>A.u7(A.iV(A.f([-2,-2,-2,-2,-2,-2,-2,-2,-2,-2,-2,-2,-2,-2,-2,-2,-2,-2,-2,-2,-2,-2,-2,-2,-2,-2,-2,-2,-2,-2,-2,-2,-2,-2,-2,-2,-2,-1,-2,-2,-2,-2,-2,62,-2,62,-2,63,52,53,54,55,56,57,58,59,60,61,-2,-2,-2,-1,-2,-2,-2,0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,-2,-2,-2,-2,63,-2,26,27,28,29,30,31,32,33,34,35,36,37,38,39,40,41,42,43,44,45,46,47,48,49,50,51,-2,-2,-2,-2,-2],t.t)))) +s($,"y3","ba",()=>A.eW(0)) +s($,"y1","fI",()=>A.eW(1)) +s($,"y2","rQ",()=>A.eW(2)) +s($,"y_","p9",()=>$.fI().aA(0)) +s($,"xY","p8",()=>A.eW(1e4)) +r($,"y0","rP",()=>A.H("^\\s*([+-]?)((0x[a-f0-9]+)|(\\d+)|([a-z0-9]+))\\s*$",!1,!1,!1,!1)) +s($,"xZ","rO",()=>A.pF(8)) +s($,"y4","rR",()=>typeof FinalizationRegistry=="function"?FinalizationRegistry:null) +s($,"y6","rT",()=>A.H("^[\\-\\.0-9A-Z_a-z~]*$",!0,!1,!1,!1)) +s($,"yi","nV",()=>A.oY(B.b9)) +s($,"xD","rA",()=>{var q=new A.mR(new DataView(new ArrayBuffer(A.vy(8)))) +q.hU() +return q}) +s($,"xV","p6",()=>A.tH(B.aH,A.aB("bO"))) +s($,"yD","th",()=>A.jv(null,$.fH())) +s($,"yB","fJ",()=>A.jv(null,$.cY())) +s($,"yv","j0",()=>new A.fY($.p5(),null)) +s($,"xH","rB",()=>new A.kI(A.H("/",!0,!1,!1,!1),A.H("[^/]$",!0,!1,!1,!1),A.H("^/",!0,!1,!1,!1))) +s($,"xJ","fH",()=>new A.m5(A.H("[/\\\\]",!0,!1,!1,!1),A.H("[^/\\\\]$",!0,!1,!1,!1),A.H("^(\\\\\\\\[^\\\\]+\\\\[^\\\\/]+|[a-zA-Z]:[/\\\\])",!0,!1,!1,!1),A.H("^[/\\\\](?![/\\\\])",!0,!1,!1,!1))) +s($,"xI","cY",()=>new A.lB(A.H("/",!0,!1,!1,!1),A.H("(^[a-zA-Z][-+.a-zA-Z\\d]*://|[^/])$",!0,!1,!1,!1),A.H("[a-zA-Z][-+.a-zA-Z\\d]*://[^/]*",!0,!1,!1,!1),A.H("^/",!0,!1,!1,!1))) +s($,"xG","p5",()=>A.un()) +s($,"yu","tf",()=>A.pg("-9223372036854775808")) +s($,"yt","te",()=>A.pg("9223372036854775807")) +s($,"xu","fG",()=>$.rA()) +s($,"xU","rM",()=>new A.hb(new WeakMap())) +s($,"xt","nT",()=>A.u2(A.f(["files","blocks"],t.s))) +s($,"xw","nU",()=>{var q,p,o=A.al(t.N,t.ez) +for(q=0;q<2;++q){p=B.S[q] +o.t(0,p.c,p)}return o}) +s($,"ys","td",()=>A.H("^#\\d+\\s+(\\S.*) \\((.+?)((?::\\d+){0,2})\\)$",!0,!1,!1,!1)) +s($,"yn","t8",()=>A.H("^\\s*at (?:(\\S.*?)(?: \\[as [^\\]]+\\])? \\((.*)\\)|(.*))$",!0,!1,!1,!1)) +s($,"yo","t9",()=>A.H("^(.*?):(\\d+)(?::(\\d+))?$|native$",!0,!1,!1,!1)) +s($,"yr","tc",()=>A.H("^\\s*at (?:(?.+) )?(?:\\(?(?:(?\\S+):wasm-function\\[(?\\d+)\\]\\:0x(?[0-9a-fA-F]+))\\)?)$",!0,!1,!1,!1)) +s($,"ym","t7",()=>A.H("^eval at (?:\\S.*?) \\((.*)\\)(?:, .*?:\\d+:\\d+)?$",!0,!1,!1,!1)) +s($,"yb","rY",()=>A.H("(\\S+)@(\\S+) line (\\d+) >.* (Function|eval):\\d+:\\d+",!0,!1,!1,!1)) +s($,"yd","t_",()=>A.H("^(?:([^@(/]*)(?:\\(.*\\))?((?:/[^/]*)*)(?:\\(.*\\))?@)?(.*?):(\\d*)(?::(\\d*))?$",!0,!1,!1,!1)) +s($,"yf","t1",()=>A.H("^(?.*?)@(?:(?\\S+).*?:wasm-function\\[(?\\d+)\\]:0x(?[0-9a-fA-F]+))$",!0,!1,!1,!1)) +s($,"yk","t5",()=>A.H("^.*?wasm-function\\[(?.*)\\]@\\[wasm code\\]$",!0,!1,!1,!1)) +s($,"yg","t2",()=>A.H("^(\\S+)(?: (\\d+)(?::(\\d+))?)?\\s+([^\\d].*)$",!0,!1,!1,!1)) +s($,"ya","rX",()=>A.H("<(|[^>]+)_async_body>",!0,!1,!1,!1)) +s($,"yj","t4",()=>A.H("^\\.",!0,!1,!1,!1)) +s($,"xx","rx",()=>A.H("^[a-zA-Z][-+.a-zA-Z\\d]*://",!0,!1,!1,!1)) +s($,"xy","ry",()=>A.H("^([a-zA-Z]:[\\\\/]|\\\\\\\\)",!0,!1,!1,!1)) +s($,"yp","ta",()=>A.H("\\n ?at ",!0,!1,!1,!1)) +s($,"yq","tb",()=>A.H(" ?at ",!0,!1,!1,!1)) +s($,"yc","rZ",()=>A.H("@\\S+ line \\d+ >.* (Function|eval):\\d+:\\d+",!0,!1,!1,!1)) +s($,"ye","t0",()=>A.H("^(([.0-9A-Za-z_$/<]|\\(.*\\))*@)?[^\\s]*:\\d*$",!0,!1,!0,!1)) +s($,"yh","t3",()=>A.H("^[^\\s<][^\\s]*( \\d+(:\\d+)?)?[ \\t]+[^\\s]+$",!0,!1,!0,!1)) +s($,"yC","pa",()=>A.H("^\\n?$",!0,!1,!0,!1))})();(function nativeSupport(){!function(){var s=function(a){var m={} +m[a]=1 +return Object.keys(hunkHelpers.convertToFastObject(m))[0]} +v.getIsolateTag=function(a){return s("___dart_"+a+v.isolateTag)} +var r="___dart_isolate_tags_" +var q=Object[r]||(Object[r]=Object.create(null)) +var p="_ZxYxX" +for(var o=0;;o++){var n=s(p+"_"+o+"_") +if(!(n in q)){q[n]=1 +v.isolateTag=n +break}}v.dispatchPropertyName=v.getIsolateTag("dispatch_record")}() +hunkHelpers.setOrUpdateInterceptorsByTag({SharedArrayBuffer:A.db,ArrayBuffer:A.da,ArrayBufferView:A.eA,DataView:A.cy,Float32Array:A.hu,Float64Array:A.hv,Int16Array:A.hw,Int32Array:A.dc,Int8Array:A.hx,Uint16Array:A.hy,Uint32Array:A.hz,Uint8ClampedArray:A.eB,CanvasPixelArray:A.eB,Uint8Array:A.c0}) +hunkHelpers.setOrUpdateLeafTags({SharedArrayBuffer:true,ArrayBuffer:true,ArrayBufferView:false,DataView:true,Float32Array:true,Float64Array:true,Int16Array:true,Int32Array:true,Int8Array:true,Uint16Array:true,Uint32Array:true,Uint8ClampedArray:true,CanvasPixelArray:true,Uint8Array:false}) +A.dd.$nativeSuperclassTag="ArrayBufferView" +A.fd.$nativeSuperclassTag="ArrayBufferView" +A.fe.$nativeSuperclassTag="ArrayBufferView" +A.c_.$nativeSuperclassTag="ArrayBufferView" +A.ff.$nativeSuperclassTag="ArrayBufferView" +A.fg.$nativeSuperclassTag="ArrayBufferView" +A.aW.$nativeSuperclassTag="ArrayBufferView"})() +Function.prototype.$0=function(){return this()} +Function.prototype.$1=function(a){return this(a)} +Function.prototype.$2=function(a,b){return this(a,b)} +Function.prototype.$1$1=function(a){return this(a)} +Function.prototype.$3=function(a,b,c){return this(a,b,c)} +Function.prototype.$4=function(a,b,c,d){return this(a,b,c,d)} +Function.prototype.$3$1=function(a){return this(a)} +Function.prototype.$2$1=function(a){return this(a)} +Function.prototype.$3$3=function(a,b,c){return this(a,b,c)} +Function.prototype.$2$2=function(a,b){return this(a,b)} +Function.prototype.$2$3=function(a,b,c){return this(a,b,c)} +Function.prototype.$1$2=function(a,b){return this(a,b)} +Function.prototype.$5=function(a,b,c,d,e){return this(a,b,c,d,e)} +Function.prototype.$6=function(a,b,c,d,e,f){return this(a,b,c,d,e,f)} +Function.prototype.$1$0=function(){return this()} +convertAllToFastObject(w) +convertToFastObject($);(function(a){if(typeof document==="undefined"){a(null) +return}if(typeof document.currentScript!="undefined"){a(document.currentScript) +return}var s=document.scripts +function onLoad(b){for(var q=0;q - + diff --git a/web/sqlite3.wasm b/web/sqlite3.wasm new file mode 100644 index 0000000..c4c1a97 Binary files /dev/null and b/web/sqlite3.wasm differ diff --git a/windows/flutter/generated_plugins.cmake b/windows/flutter/generated_plugins.cmake index 5e1e076..2602ff1 100644 --- a/windows/flutter/generated_plugins.cmake +++ b/windows/flutter/generated_plugins.cmake @@ -13,6 +13,7 @@ list(APPEND FLUTTER_PLUGIN_LIST ) list(APPEND FLUTTER_FFI_PLUGIN_LIST + jni ) set(PLUGIN_BUNDLED_LIBRARIES)