diff --git a/daily-quote/DesktopWidget.qml b/daily-quote/DesktopWidget.qml new file mode 100644 index 000000000..182cd67f2 --- /dev/null +++ b/daily-quote/DesktopWidget.qml @@ -0,0 +1,405 @@ +import QtQuick +import QtQuick.Layouts +import Quickshell +import Quickshell.Io +import qs.Commons +import qs.Modules.DesktopWidgets +import qs.Services.System +import qs.Services.UI +import qs.Widgets + +DraggableDesktopWidget { + id: root + property var pluginApi: null + + // --- Read from widgetData (injected by DesktopWidgets.qml) --- + readonly property string storedQuote: widgetData?.currentQuote ?? "" + readonly property string storedAuthor: widgetData?.currentAuthor ?? "" + readonly property string storedDate: widgetData?.lastDate ?? "" + readonly property string scrambleSpeed: widgetData?.scrambleSpeed ?? "medium" + readonly property string scrambleChars: widgetData?.scrambleChars ?? "mix" + readonly property bool showAuthor: widgetData?.showAuthor ?? true + readonly property bool autoChangeDaily: widgetData?.autoChangeDaily ?? true + readonly property string quoteFont: widgetData?.quoteFont ?? "" + readonly property string authorFont: widgetData?.authorFont ?? "" + readonly property string quoteColor: widgetData?.quoteColor ?? "primary" + readonly property string textAlign: widgetData?.textAlign ?? "left" + readonly property bool showGradientOverlay: widgetData?.showGradientOverlay ?? true + readonly property string gradientDirection: widgetData?.gradientDirection ?? "vertical" + + // --- Adaptive gradient color: dark mode = black, light mode = white --- + readonly property real _gradR: Settings.data.colorSchemes.darkMode ? 0 : 1 + readonly property real _gradG: Settings.data.colorSchemes.darkMode ? 0 : 1 + readonly property real _gradB: Settings.data.colorSchemes.darkMode ? 0 : 1 + + // --- Resolved alignment --- + readonly property int _effectiveAlign: textAlign === "center" ? Qt.AlignHCenter : textAlign === "right" ? Qt.AlignRight : Qt.AlignLeft + + // --- Resolved colors from color key --- + readonly property color resolvedTextColor: Color.resolveColorKey(quoteColor) + readonly property color resolvedOnTextColor: Color.resolveOnColorKey(quoteColor) + + // --- Internal display state --- + property string displayedQuote: "" + property string displayedAuthor: "" + + // --- Scramble engine --- + property var _charStates: [] + property bool _isAnimating: false + property string _targetText: "" + property int _revealIndex: 0 // next char to start scrambling + property int _activeCount: 0 // chars with iteration >= 1 (being scrambled) + property bool _pendingPersist: false + property bool _selfUpdate: false + + readonly property var _charSets: ({ + "ascii": "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789", + "symbols": "!@#$%^&*()_+-=[]{}|;:<>?/~\u2591\u2592\u2593\u2588\u2584\u2580\u25A0\u25A1\u2557\u2551\u255A\u255D\u2310\u00AC\u2569\u2566\u2560\u2550\u256C", + "mix": "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789!@#$%^&*\u2591\u2592\u2593\u2588\u2584\u2580\u25A0\u25A1\u2557\u255A\u255D\u2551\u2569\u2566\u2560\u2550\u256C" + }) + readonly property string _charSet: _charSets[scrambleChars] || _charSets["mix"] + + readonly property var speedPresets: ({ + "fast": { "tick": 30, "iterations": 3, "revealPerTick": 3 }, + "medium": { "tick": 35, "iterations": 5, "revealPerTick": 2 }, + "slow": { "tick": 50, "iterations": 7, "revealPerTick": 1 } + }) + readonly property var speedConfig: speedPresets[scrambleSpeed] || speedPresets["medium"] + + // --- Sizing --- + implicitWidth: Math.round(320 * widgetScale) + implicitHeight: Math.round(contentLayout.implicitHeight + Style.marginL * 2 * widgetScale) + width: implicitWidth + height: implicitHeight + + // --- Built-in quote pool (loaded from quotes.json) --- + property var builtinQuotes: [] + readonly property string _quotesPath: pluginApi ? pluginApi.pluginDir + "/quotes.json" : "" + + FileView { + id: quotesFileView + path: root._quotesPath || undefined + watchChanges: true + onLoaded: { + try { + var data = JSON.parse(quotesFileView.text()); + if (Array.isArray(data) && data.length > 0) { + root.builtinQuotes = data; + // Trigger init if widget data was already injected but quotes weren't loaded yet + if (root.widgetData && !root.initialized) { + root._tryInit(); + } + } + } catch (e) { + Logger.w("DailyQuote", "Failed to parse quotes.json:", e.message); + } + } + onFileChanged: reload() + } + + // --- Deferred init: wait for both widgetData and quotes --- + function _tryInit() { + if (initialized || !widgetData || builtinQuotes.length === 0) return; + initialized = true; + var today = Qt.formatDate(new Date(), "yyyy-MM-dd"); + if (storedQuote && storedDate === today) { + displayedQuote = storedQuote; + displayedAuthor = storedAuthor; + _startScramble(storedQuote, false); + } else { + refreshQuote(); + } + } + + // --- Scramble engine --- + function _startScramble(text, animate) { + _tickTimer.stop(); + _targetText = text; + _revealIndex = 0; + _activeCount = 0; + + if (!animate) { + _isAnimating = false; + var states = []; + for (var i = 0; i < text.length; i++) { + states.push({ "settled": true, "iteration": speedConfig.iterations + 1, "current": text.charAt(i) }); + } + _charStates = states; + _updateDisplay(); + return; + } + + _isAnimating = true; + var states = []; + for (var i = 0; i < text.length; i++) { + if (text.charAt(i) === " ") { + states.push({ "settled": true, "iteration": 0, "current": " " }); + } else { + states.push({ + "settled": false, + "iteration": 0, + "current": _charSet.charAt(Math.floor(Math.random() * _charSet.length)) + }); + } + } + _charStates = states; + _updateDisplay(); + _tickTimer.start(); + } + + // --- Persist current quote to widgetData --- + function _persistQuote() { + _selfUpdate = true; + if (widgetIndex >= 0 && screen) { + DesktopWidgetRegistry.updateWidgetData(screen.name, widgetIndex, { + "currentQuote": displayedQuote, + "currentAuthor": displayedAuthor, + "lastDate": Qt.formatDate(new Date(), "yyyy-MM-dd") + }); + } + Qt.callLater(function() { _selfUpdate = false; }); + } + + // --- Refresh: new quote + scramble animation --- + function refreshQuote() { + var pool = builtinQuotes.concat(widgetData?.userQuotes || []); + if (pool.length === 0) { + displayedQuote = "Agrega tu primera frase en la configuracion"; + displayedAuthor = ""; + _startScramble(displayedQuote, false); + _persistQuote(); + return; + } + var attempts = 0; + var selected; + do { + selected = pool[Math.floor(Math.random() * pool.length)]; + attempts++; + } while (pool.length > 1 && selected.text === displayedQuote && attempts < 10); + + displayedQuote = selected.text; + displayedAuthor = selected.author || ""; + _pendingPersist = true; + _startScramble(displayedQuote, true); + } + + function _updateDisplay() { + var html = ""; + var settledColor = root.resolvedTextColor.toString(); + var accentR = root.resolvedTextColor.r; + var accentG = root.resolvedTextColor.g; + var accentB = root.resolvedTextColor.b; + + for (var i = 0; i < _charStates.length; i++) { + var s = _charStates[i]; + var ch = s.current; + if (ch === "<") ch = "<"; + else if (ch === ">") ch = ">"; + else if (ch === "&") ch = "&"; + else if (ch === "\"") ch = """; + + if (s.settled || _targetText.charAt(i) === " ") { + html += "" + ch + ""; + } else if (s.iteration === 0) { + var dimColor = Qt.rgba(accentR, accentG, accentB, 0.3); + html += "" + ch + ""; + } else { + var progress = s.iteration / (speedConfig.iterations + 1); + var alpha = 0.5 + (progress * 0.5); + var scrambleColor = Qt.rgba(accentR, accentG, accentB, alpha); + html += "" + ch + ""; + } + } + scrambleDisplay.text = html; + } + + // --- Single animation timer --- + Timer { + id: _tickTimer + interval: root.speedConfig.tick + repeat: true + running: false + + onTriggered: { + var states = root._charStates.slice(); + var changed = false; + + // 1) Reveal: activate next characters + for (var r = 0; r < root.speedConfig.revealPerTick; r++) { + if (root._revealIndex >= states.length) break; + if (root._targetText.charAt(root._revealIndex) !== " ") { + states[root._revealIndex] = { + "settled": false, + "iteration": 1, + "current": root._charSet.charAt(Math.floor(Math.random() * root._charSet.length)) + }; + changed = true; + } + root._revealIndex++; + } + + // 2) Scramble: advance all active chars + var allSettled = true; + for (var i = 0; i < states.length; i++) { + if (states[i].settled) continue; + if (states[i].iteration === 0) { allSettled = false; continue; } + + allSettled = false; + var iter = states[i].iteration + 1; + if (iter > root.speedConfig.iterations) { + states[i] = { "settled": true, "iteration": iter, "current": root._targetText.charAt(i) }; + } else { + states[i] = { "settled": false, "iteration": iter, "current": root._charSet.charAt(Math.floor(Math.random() * root._charSet.length)) }; + } + changed = true; + } + + if (changed) { + root._charStates = states; + root._updateDisplay(); + } + + if (allSettled) { + _tickTimer.stop(); + root._isAnimating = false; + if (root._pendingPersist) { + root._pendingPersist = false; + root._persistQuote(); + } + } + } + } + + // Safety net: force-end animation if stuck (max 8s) + Timer { + id: _animationWatchdog + interval: 8000 + repeat: false + running: root._isAnimating + onTriggered: { + if (root._isAnimating) { + _tickTimer.stop(); + root._isAnimating = false; + // Force-settle all remaining chars + var states = root._charStates.slice(); + for (var i = 0; i < states.length; i++) { + if (!states[i].settled) { + states[i] = { "settled": true, "iteration": root.speedConfig.iterations + 1, "current": root._targetText.charAt(i) }; + } + } + root._charStates = states; + root._updateDisplay(); + if (root._pendingPersist) { + root._pendingPersist = false; + root._persistQuote(); + } + } + } + } + + // --- Init --- + property bool initialized: false + onWidgetDataChanged: { + if (!widgetData) return; + if (_isAnimating) return; + if (_selfUpdate) return; + _tryInit(); + } + + // --- Midnight auto-change --- + Timer { + interval: 60000 + repeat: true + running: root.initialized && root.autoChangeDaily + onTriggered: { + var today = Qt.formatDate(new Date(), "yyyy-MM-dd"); + if (root.storedDate !== today) root.refreshQuote(); + } + } + + // --- Click anywhere to refresh --- + MouseArea { + id: clickArea + anchors.fill: parent + z: 2 + acceptedButtons: Qt.LeftButton + hoverEnabled: true + cursorShape: root._isAnimating ? Qt.BusyCursor : Qt.PointingHandCursor + onClicked: { + if (!root._isAnimating) root.refreshQuote(); + } + } + + // --- Gradient overlay (when background is off) --- + Rectangle { + id: gradientOverlay + anchors.fill: parent + z: 0 + visible: !root.showBackground && root.showGradientOverlay + radius: root.roundedCorners ? Math.min(Math.round(Style.radiusL * widgetScale), Style.radiusL, width / 2, height / 2) : 0 + gradient: Gradient { + orientation: root.gradientDirection === "horizontal" ? Gradient.Horizontal : Gradient.Vertical + GradientStop { + position: 0.0 + color: Qt.rgba(root._gradR, root._gradG, root._gradB, 0.35) + } + GradientStop { + position: 0.6 + color: Qt.rgba(root._gradR, root._gradG, root._gradB, 0.15) + } + GradientStop { + position: 1.0 + color: Qt.rgba(root._gradR, root._gradG, root._gradB, 0.05) + } + } + } + + // --- Visual content --- + ColumnLayout { + id: contentLayout + anchors.fill: parent + z: 1 + anchors.leftMargin: Math.round(Style.marginL * widgetScale) + anchors.rightMargin: Math.round(Style.marginL * widgetScale) + anchors.topMargin: Math.round(Style.marginM * widgetScale) + anchors.bottomMargin: Math.round(Style.marginM * widgetScale) + spacing: Math.round(Style.marginS * widgetScale) + + Text { + id: scrambleDisplay + Layout.fillWidth: true + Layout.preferredHeight: implicitHeight + textFormat: Text.RichText + wrapMode: Text.WordWrap + horizontalAlignment: root._effectiveAlign + color: Color.mOnSurface + font.pointSize: Math.round(Style.fontSizeM * widgetScale) + font.family: root.quoteFont || "monospace" + } + + NText { + id: authorText + Layout.fillWidth: true + text: root.displayedAuthor ? "\u2014 " + root.displayedAuthor : "" + color: root.resolvedTextColor + pointSize: Math.round(Style.fontSizeS * widgetScale) + family: root.authorFont || Settings.data.ui.fontDefault + font.italic: true + horizontalAlignment: root._effectiveAlign + visible: root.showAuthor && root.displayedAuthor !== "" + opacity: visible ? 1.0 : 0.0 + Behavior on opacity { NumberAnimation { duration: 300 } } + } + + // Subtle hint + NText { + Layout.fillWidth: true + Layout.alignment: Qt.AlignHCenter + text: root._isAnimating ? "" : "click anywhere to refresh" + color: root.resolvedTextColor + pointSize: Math.round(Style.fontSizeXS * widgetScale) + opacity: clickArea.containsMouse && !root._isAnimating ? 0.6 : 0.0 + Behavior on opacity { NumberAnimation { duration: 200 } } + } + } +} diff --git a/daily-quote/DesktopWidgetSettings.qml b/daily-quote/DesktopWidgetSettings.qml new file mode 100644 index 000000000..f4ea03fbe --- /dev/null +++ b/daily-quote/DesktopWidgetSettings.qml @@ -0,0 +1,330 @@ +import QtQuick +import QtQuick.Layouts +import qs.Commons +import qs.Services.System +import qs.Widgets + +ColumnLayout { + id: root + spacing: Style.marginM + + property var pluginApi: null + property var widgetSettings: null + + // --- Read from widgetSettings.data --- + property string scrambleSpeed: widgetSettings?.data?.scrambleSpeed ?? "medium" + property string scrambleChars: widgetSettings?.data?.scrambleChars ?? "mix" + property bool showAuthor: widgetSettings?.data?.showAuthor ?? true + property bool autoChangeDaily: widgetSettings?.data?.autoChangeDaily ?? true + property var userQuotes: widgetSettings?.data?.userQuotes ?? [] + property string quoteFont: widgetSettings?.data?.quoteFont ?? "" + property string authorFont: widgetSettings?.data?.authorFont ?? "" + property bool showBackground: widgetSettings?.data?.showBackground ?? true + property bool showGradientOverlay: widgetSettings?.data?.showGradientOverlay ?? true + property string gradientDirection: widgetSettings?.data?.gradientDirection ?? "vertical" + property string quoteColor: widgetSettings?.data?.quoteColor ?? "primary" + property string textAlign: widgetSettings?.data?.textAlign ?? "left" + + function saveSettings() { + if (!widgetSettings || !widgetSettings.data) return; + widgetSettings.data.scrambleSpeed = scrambleSpeed; + widgetSettings.data.scrambleChars = scrambleChars; + widgetSettings.data.showAuthor = showAuthor; + widgetSettings.data.autoChangeDaily = autoChangeDaily; + widgetSettings.data.userQuotes = userQuotes; + widgetSettings.data.quoteFont = quoteFont; + widgetSettings.data.authorFont = authorFont; + widgetSettings.data.showBackground = showBackground; + widgetSettings.data.showGradientOverlay = showGradientOverlay; + widgetSettings.data.gradientDirection = gradientDirection; + widgetSettings.data.quoteColor = quoteColor; + widgetSettings.data.textAlign = textAlign; + widgetSettings.save(); + } + + // --- Header --- + NLabel { + label: "Daily Quote" + description: "Widget de frases diarias con efecto scramble decode" + Layout.fillWidth: true + } + + NDivider { Layout.fillWidth: true } + + // --- User Quotes List --- + NLabel { + label: "Tus frases" + description: userQuotes.length + " frases personalizadas" + Layout.fillWidth: true + } + +ListView { + id: quotesList + Layout.fillWidth: true + Layout.preferredHeight: Math.min(quotesList.contentHeight, 220) + Layout.minimumHeight: root.userQuotes.length > 0 ? 60 : 0 + Layout.maximumHeight: 220 + clip: true + spacing: Style.marginS + model: root.userQuotes + visible: root.userQuotes.length > 0 + + delegate: Rectangle { + id: quoteCard + width: quotesList.width + height: quoteContent.height + Style.marginM * 2 + color: Color.mSurfaceVariant + radius: Style.radiusM + + RowLayout { + id: quoteRow + anchors.left: parent.left + anchors.right: parent.right + anchors.top: parent.top + anchors.margins: Style.marginM + spacing: Style.marginS + + ColumnLayout { + id: quoteContent + Layout.fillWidth: true + spacing: 2 + + NText { + text: modelData.text + color: Color.mOnSurface + pointSize: Style.fontSizeS + Layout.fillWidth: true + wrapMode: Text.WordWrap + } + NText { + text: modelData.author ? "\u2014 " + modelData.author : "" + color: Color.mOnSurfaceVariant + pointSize: Style.fontSizeXS + visible: modelData.author && modelData.author !== "" + Layout.fillWidth: true + } + } + + NIconButton { + icon: "close" + color: hovering ? Color.mError : Color.mOnSurfaceVariant + Layout.alignment: Qt.AlignTop + onClicked: { + var quotes = root.userQuotes.slice(); + quotes.splice(index, 1); + root.userQuotes = quotes; + root.saveSettings(); + } + } + } + } +} + + NDivider { Layout.fillWidth: true } + + // --- Add new quote --- + NLabel { + label: "Agregar frase" + Layout.fillWidth: true + } + + ColumnLayout { + Layout.fillWidth: true + spacing: Style.marginS + + NTextInput { + id: newQuoteInput + Layout.fillWidth: true + placeholderText: "Escribe tu frase aqui..." + } + + NTextInput { + id: newAuthorInput + Layout.fillWidth: true + placeholderText: "Autor (opcional)..." + } + + NButton { + text: "+ Agregar" + Layout.fillWidth: true + onClicked: { + if (newQuoteInput.text.trim() !== "") { + var quotes = root.userQuotes.slice(); + quotes.push({ + "text": newQuoteInput.text.trim(), + "author": newAuthorInput.text.trim() + }); + root.userQuotes = quotes; + root.saveSettings(); + newQuoteInput.text = ""; + newAuthorInput.text = ""; + } + } + } + } + + NDivider { Layout.fillWidth: true } + + // --- Typography --- + NLabel { + label: "Tipografia" + Layout.fillWidth: true + } + + NSearchableComboBox { + Layout.fillWidth: true + label: "Fuente de la frase" + model: FontService.availableFonts + currentKey: root.quoteFont + placeholder: "Predeterminada del sistema" + searchPlaceholder: "Buscar fuente..." + popupHeight: 320 + minimumWidth: 260 + onSelected: function(key) { + root.quoteFont = key; + root.saveSettings(); + } + defaultValue: "" + } + + NSearchableComboBox { + Layout.fillWidth: true + label: "Fuente del autor" + model: FontService.availableFonts + currentKey: root.authorFont + placeholder: "Predeterminada del sistema" + searchPlaceholder: "Buscar fuente..." + popupHeight: 320 + minimumWidth: 260 + onSelected: function(key) { + root.authorFont = key; + root.saveSettings(); + } + defaultValue: "" + } + + NColorChoice { + Layout.fillWidth: true + label: "Color del texto" + currentKey: root.quoteColor + onSelected: function(key) { + root.quoteColor = key; + root.saveSettings(); + } + defaultValue: "primary" + } + + NComboBox { + Layout.fillWidth: true + label: "Alineacion" + model: [ + { "key": "left", "name": "Izquierda" }, + { "key": "center", "name": "Centro" }, + { "key": "right", "name": "Derecha" } + ] + currentKey: root.textAlign + onSelected: function(key) { + root.textAlign = key; + root.saveSettings(); + } + } + + NDivider { Layout.fillWidth: true } + + // --- Options --- + NLabel { + label: "Opciones" + Layout.fillWidth: true + } + + NComboBox { + Layout.fillWidth: true + label: "Velocidad decodificacion" + model: [ + { "key": "fast", "name": "Rapido" }, + { "key": "medium", "name": "Medio" }, + { "key": "slow", "name": "Lento" } + ] + currentKey: root.scrambleSpeed + onSelected: function(key) { + root.scrambleSpeed = key; + root.saveSettings(); + } + } + + NComboBox { + Layout.fillWidth: true + label: "Estilo caracteres" + model: [ + { "key": "mix", "name": "Mixto" }, + { "key": "ascii", "name": "ASCII" }, + { "key": "symbols", "name": "Simbolos" } + ] + currentKey: root.scrambleChars + onSelected: function(key) { + root.scrambleChars = key; + root.saveSettings(); + } + } + + NToggle { + Layout.fillWidth: true + label: "Mostrar fondo" + checked: root.showBackground + defaultValue: true + onToggled: function(val) { + root.showBackground = val; + root.saveSettings(); + } + } + + NToggle { + Layout.fillWidth: true + label: "Gradiente sobre wallpaper" + description: "Suave velo para mejorar legibilidad (se adapta al modo claro/oscuro)" + visible: !root.showBackground + checked: root.showGradientOverlay + defaultValue: true + onToggled: function(val) { + root.showGradientOverlay = val; + root.saveSettings(); + } + } + + NComboBox { + Layout.fillWidth: true + visible: !root.showBackground && root.showGradientOverlay + label: "Direccion del gradiente" + model: [ + { "key": "vertical", "name": "Vertical" }, + { "key": "horizontal", "name": "Horizontal" } + ] + currentKey: root.gradientDirection + onSelected: function(key) { + root.gradientDirection = key; + root.saveSettings(); + } + } + + NToggle { + Layout.fillWidth: true + label: "Mostrar autor" + checked: root.showAuthor + defaultValue: true + onToggled: function(val) { + root.showAuthor = val; + root.saveSettings(); + } + } + + NToggle { + Layout.fillWidth: true + label: "Cambio automatico diario" + checked: root.autoChangeDaily + defaultValue: true + onToggled: function(val) { + root.autoChangeDaily = val; + root.saveSettings(); + } + } +} diff --git a/daily-quote/README.md b/daily-quote/README.md new file mode 100644 index 000000000..304a8bf91 --- /dev/null +++ b/daily-quote/README.md @@ -0,0 +1,54 @@ +# Daily Quote + +A desktop widget that displays a daily quote with a hacker-style scramble decode animation. Click anywhere on the widget to refresh with a new quote. + +![Daily Quote preview](preview.png) + +## Features + +- **Scramble decode animation** — characters randomize then settle left-to-right with color transitions, like a terminal decrypting text +- **3 speed presets** — Fast, Medium, Slow (controls tick rate, iteration count, and reveal speed) +- **3 character sets** — Mix (alphanumeric + box-drawing), ASCII, Symbols (Unicode box-drawing characters) +- **Custom quotes** — add your own quotes with author, view and remove them from settings +- **Custom fonts** — separate font selectors for the quote text and author line +- **Theme-aware text colors** — choose from Primary, Secondary, Tertiary, Error, or None; colors automatically adapt to your color scheme and wallpaper +- **Text alignment** — Left, Center, or Right +- **Background options** — solid panel (default), gradient overlay, or fully transparent +- **Adaptive gradient overlay** — when background is off, a subtle gradient veil improves readability; auto-switches between black (dark mode) and white (light mode) +- **Daily auto-change** — automatically shows a new quote at midnight +- **Click to refresh** — click the widget at any time for a new quote with animation + +## Settings + +| Setting | Description | Default | +|---|---|---| +| **Your Quotes** | Add, view, and remove custom quotes | — | +| **Quote Font** | Font for the quote text (scramble + settled) | System monospace | +| **Author Font** | Font for the author line | System default | +| **Text Color** | Color key that adapts to your color scheme | Primary | +| **Text Alignment** | Horizontal alignment for quote and author | Left | +| **Show Background** | Solid panel with border and shadow | On | +| **Gradient Overlay** | Subtle veil behind text when background is off | On | +| **Gradient Direction** | Vertical or Horizontal | Vertical | +| **Decoding Speed** | Fast, Medium, or Slow | Medium | +| **Character Style** | Mix, ASCII, or Symbols | Mix | +| **Show Author** | Toggle the author line | On | +| **Daily Auto-Change** | New quote at midnight | On | + +## Custom Quotes + +You can add your own quotes through the settings panel. Quotes are stored in your widget data and persist across sessions. + +Additionally, the built-in quote pool is loaded from `quotes.json` in the plugin directory. You can edit this file directly to change the default quotes — the widget will hot-reload when the file changes. + +## Requirements + +- Noctalia Shell 4.7.0 or later + +## License + +MIT + +## Author + +Vikthor diff --git a/daily-quote/manifest.json b/daily-quote/manifest.json new file mode 100644 index 000000000..cb7416659 --- /dev/null +++ b/daily-quote/manifest.json @@ -0,0 +1,29 @@ +{ + "id": "daily-quote", + "name": "Daily Quote", + "version": "1.0.0", + "minNoctaliaVersion": "4.7.0", + "author": "Vikthor", + "license": "MIT", + "repository": "https://github.com/noctalia-dev/noctalia-plugins", + "description": "Desktop widget that displays a daily quote with a scramble decode animation", + "tags": ["Desktop", "Fun"], + "entryPoints": { + "desktopWidget": "DesktopWidget.qml", + "desktopWidgetSettings": "DesktopWidgetSettings.qml" + }, + "dependencies": { + "plugins": [] + }, + "metadata": { + "defaultSettings": { + "quoteFont": "", + "authorFont": "", + "showBackground": true, + "showGradientOverlay": true, + "gradientDirection": "vertical", + "quoteColor": "primary", + "textAlign": "left" + } + } +} \ No newline at end of file diff --git a/daily-quote/preview.png b/daily-quote/preview.png new file mode 100644 index 000000000..5c504ed46 Binary files /dev/null and b/daily-quote/preview.png differ diff --git a/daily-quote/quotes.json b/daily-quote/quotes.json new file mode 100644 index 000000000..57406dc83 --- /dev/null +++ b/daily-quote/quotes.json @@ -0,0 +1,63 @@ +[ + { "text": "El unico modo de hacer un gran trabajo es amar lo que haces", "author": "Steve Jobs" }, + { "text": "Talk is cheap, show me the code", "author": "Linus Torvalds" }, + { "text": "La simplicidad es la maxima sofisticacion", "author": "Leonardo da Vinci" }, + { "text": "Any fool can write code that a computer can understand. Good programmers write code that humans can understand", "author": "Martin Fowler" }, + { "text": "La vida es lo que pasa mientras estas ocupado haciendo otros planes", "author": "John Lennon" }, + { "text": "First, solve the problem. Then, write the code", "author": "John Johnson" }, + { "text": "La creatividad es la inteligencia divirtiendose", "author": "Albert Einstein" }, + { "text": "The best way to predict the future is to implement it", "author": "DHH" }, + { "text": "Code is like humor. When you have to explain it, it's bad", "author": "Cory House" }, + { "text": "Simplicity is prerequisite for reliability", "author": "Edsger Dijkstra" }, + { "text": "There are only two hard things in CS: cache invalidation and naming things", "author": "Phil Karlton" }, + { "text": "Premature optimization is the root of all evil", "author": "Donald Knuth" }, + { "text": "Any sufficiently advanced technology is indistinguishable from magic", "author": "Arthur C. Clarke" }, + { "text": "The only way to go fast is to go well", "author": "Robert C. Martin" }, + { "text": "No llores porque se termino, sonrie porque sucedio", "author": "Dr. Seuss" }, + + { "text": "七転び八起き (Nanakorobi yaoki): Cae siete veces, levantate ocho", "author": "Proverbio Japones" }, + { "text": "一期一会 (Ichigo ichie): Cada encuentro es unico", "author": "Proverbio Japones" }, + { "text": "改善 (Kaizen): Mejora continua", "author": "Filosofia Japonesa" }, + { "text": "武士道 (Bushido): El camino del guerrero", "author": "Codigo Samurai" }, + { "text": "夢を諦めない (Yume wo akiramenai): Nunca abandones tus sueños", "author": "Frase Japonesa" }, + + { "text": "Memento mori: Recuerda que moriras", "author": "Tradicion Latina" }, + { "text": "Per aspera ad astra: Hacia las estrellas a traves de las dificultades", "author": "Seneca" }, + { "text": "Carpe diem: Aprovecha el dia", "author": "Horacio" }, + { "text": "Acta non verba: Hechos, no palabras", "author": "Lema Latino" }, + { "text": "Fortes fortuna adiuvat: La fortuna favorece a los valientes", "author": "Virgilio" }, + { "text": "Ora et labora: Ora y trabaja", "author": "San Benito" }, + { "text": "Fiat lux: Hagase la luz", "author": "Genesis 1:3" }, + { "text": "Ad victoriam: Hacia la victoria", "author": "Lema Latino" }, + + { "text": "Chi va piano va sano e va lontano", "author": "Proverbio Italiano" }, + { "text": "La notte e piu oscura prima dell'alba", "author": "Proverbio Italiano" }, + { "text": "Coraggio sopra ogni cosa", "author": "Proverbio Italiano" }, + { "text": "Dio e la mia luce", "author": "Lema Italiano" }, + + { "text": "La disciplina tarde o temprano vencera al talento", "author": "Anonimo" }, + { "text": "Great things never came from comfort zones", "author": "Anonimo" }, + { "text": "Discipline equals freedom", "author": "Jocko Willink" }, + { "text": "Dreams demand sacrifice", "author": "Anonimo" }, + { "text": "The obstacle is the way", "author": "Marco Aurelio" }, + { "text": "Victory belongs to the most persevering", "author": "Napoleon Bonaparte" }, + { "text": "Stay hungry, stay foolish", "author": "Steve Jobs" }, + { "text": "He who has a why to live can bear almost any how", "author": "Friedrich Nietzsche" }, + + { "text": "No temas, porque yo estoy contigo", "author": "Isaias 41:10" }, + { "text": "La verdad os hara libres", "author": "Juan 8:32" }, + { "text": "Todo tiene su tiempo debajo del cielo", "author": "Eclesiastes 3:1" }, + { "text": "Aunque ande en valle de sombra de muerte, no temere mal alguno", "author": "Salmos 23:4" }, + { "text": "El hierro se afila con hierro", "author": "Proverbios 27:17" }, + + { "text": "The quieter you become, the more you are able to hear", "author": "Rumi" }, + { "text": "Your future is created by what you do today, not tomorrow", "author": "Robert Kiyosaki" }, + { "text": "Fall seven times, stand up eight", "author": "Proverbio Japones" }, + { "text": "Never trust a computer you cant throw out a window", "author": "Steve Wozniak" }, + { "text": "Programs must be written for people to read", "author": "Harold Abelson" }, + { "text": "Reality is wrong, dreams are for real", "author": "Tupac Shakur" }, + { "text": "The soul becomes dyed with the color of its thoughts", "author": "Marco Aurelio" }, + { "text": "In the middle of difficulty lies opportunity", "author": "Albert Einstein" }, + { "text": "Nothing worth having comes easy", "author": "Theodore Roosevelt" }, + { "text": "Stars cant shine without darkness", "author": "Anonimo" } +] \ No newline at end of file