Skip to content
60 changes: 60 additions & 0 deletions AppShell.qml
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import QtQuick
import Quickshell
import Quickshell.Io
import Clavis.Weather 1.0
import Clavis.WeatherMap 1.0
import qs.Modules.Bar
import qs.Modules.Keystone
Expand All @@ -14,12 +15,47 @@ import qs.Services
Item {
id: root

function sendReloadNotification(success, errorString) {
const command = [
"notify-send",
"--app-name=Clavis",
"--icon=org.quickshell",
"--urgency=" + (success ? "low" : "critical"),
"--expire-time=" + (success ? "4000" : "10000")
];

if (success)
command.push("--transient");

command.push(
success ? qsTr("Configuration reloaded")
: qsTr("Configuration reload failed"),
success ? qsTr("Clavis is running with the latest configuration.")
: (errorString || qsTr("Unknown reload error"))
);
Quickshell.execDetached(command);
}

Component.onCompleted: {
I18nService.initialize();
WallpaperService.primaryInstance = true;
AwwwWallpaperService.primaryInstance = true;
}

Connections {
target: Quickshell

function onReloadCompleted() {
Quickshell.inhibitReloadPopup();
root.sendReloadNotification(true, "");
}

function onReloadFailed(errorString) {
Quickshell.inhibitReloadPopup();
root.sendReloadNotification(false, errorString);
}
}

WallpaperBackground {}

Bar {}
Expand Down Expand Up @@ -134,6 +170,30 @@ Item {
}
}

IpcHandler {
target: "weather"

function setLocation(latitude: string, longitude: string,
name: string): string {
const parsedLatitude = Number(latitude);
const parsedLongitude = Number(longitude);
if (!isFinite(parsedLatitude) || !isFinite(parsedLongitude)
|| parsedLatitude < -90 || parsedLatitude > 90
|| parsedLongitude < -180 || parsedLongitude > 180) {
return "INVALID_LOCATION";
}

WeatherPlugin.setManualLocation(
parsedLatitude, parsedLongitude, name || "");
return "OK";
}

function clearLocation(): string {
WeatherPlugin.clearManualLocation();
return "OK";
}
}

IpcHandler {
target: "weather-map"

Expand Down
32 changes: 32 additions & 0 deletions Common/functions/WeatherFormat.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
.pragma library

function textForCode(code, fallback) {
const normalized = Number(code)

if (normalized === 0) return "晴"
if (normalized === 1) return "晴间多云"
if (normalized === 2) return "多云"
if (normalized === 3) return "阴"
if (normalized === 45) return "雾"
if (normalized === 48) return "雾凇"
if (normalized >= 51 && normalized <= 55) return "毛毛雨"
if (normalized === 56 || normalized === 57) return "冻毛毛雨"
if (normalized === 61) return "小雨"
if (normalized === 63) return "中雨"
if (normalized === 65) return "大雨"
if (normalized === 66 || normalized === 67) return "冻雨"
if (normalized === 71) return "小雪"
if (normalized === 73) return "中雪"
if (normalized === 75) return "大雪"
if (normalized === 77) return "米雪"
if (normalized === 80) return "阵雨"
if (normalized === 81) return "较强阵雨"
if (normalized === 82) return "强阵雨"
if (normalized === 85) return "阵雪"
if (normalized === 86) return "强阵雪"
if (normalized === 95) return "雷暴"
if (normalized === 96 || normalized === 99) return "雷暴伴冰雹"

const fallbackText = String(fallback || "").trim()
return fallbackText.length > 0 && fallbackText !== "Unknown" ? fallbackText : "未知"
}
8 changes: 7 additions & 1 deletion Modules/ControlCenter/ControlCenterWindow.qml
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,6 @@ FloatingWindow {
Component.onCompleted: I18nService.initialize()

property real contentPadding: 8
property int currentPage: 0
property bool navExpanded: width > 900
readonly property var pages: [
({ "id": "account", "title": qsTr("账户"), "icon": "account_circle", "source": "AccountPage.qml" }),
Expand All @@ -36,6 +35,13 @@ FloatingWindow {
({ "id": "weather", "title": qsTr("天气"), "icon": "partly_cloudy_day", "source": "WeatherPage.qml" }),
({ "id": "advanced", "title": qsTr("高级"), "icon": "tune", "source": "AdvancedPage.qml" })
]
property int currentPage: {
const requestedPage = Quickshell.env(
"CLAVIS_CONTROL_CENTER_PAGE");
const requestedIndex = pages.findIndex(
page => page.id === requestedPage);
return Math.max(0, requestedIndex);
}

function pageSource(index) {
if (index < 0 || index >= pages.length)
Expand Down
210 changes: 210 additions & 0 deletions Modules/ControlCenter/WeatherApiSettings.qml
Original file line number Diff line number Diff line change
Expand Up @@ -24,12 +24,76 @@ StyledFlickable {
property string feedbackText: ""
property bool feedbackError: false
property string selectedMapMode: "temp"
property bool locationFieldsInitialized: false
property string locationFeedbackText: ""
property bool locationFeedbackError: false

Component.onCompleted: {
root.initializeLocationFields()
if (!WeatherPlugin.hasValidData && !WeatherPlugin.loading)
WeatherPlugin.refresh()
}

function initializeLocationFields() {
if (!WeatherPlugin.hasValidData)
return

locationNameField.text = WeatherPlugin.locationName || ""
latitudeField.text = Number(WeatherPlugin.latitude).toFixed(6)
longitudeField.text = Number(WeatherPlugin.longitude).toFixed(6)
locationFieldsInitialized = true
}

function notifyMainWeather(method, argumentsList) {
const command = [
"qs",
"--path",
Paths.shellDir + "/shell.qml",
"ipc",
"call",
"weather",
method
]
for (const argument of argumentsList || [])
command.push(String(argument))
Quickshell.execDetached(command)
}

function saveLocation() {
const latitude = Number(latitudeField.text.trim())
const longitude = Number(longitudeField.text.trim())
if (!isFinite(latitude) || latitude < -90 || latitude > 90) {
locationFeedbackError = true
locationFeedbackText = qsTr("纬度必须介于 -90 和 90 之间")
latitudeField.forceActiveFocus()
return
}
if (!isFinite(longitude) || longitude < -180 || longitude > 180) {
locationFeedbackError = true
locationFeedbackText = qsTr("经度必须介于 -180 和 180 之间")
longitudeField.forceActiveFocus()
return
}

const locationName = locationNameField.text.trim()
|| qsTr("手动位置")
WeatherPlugin.setManualLocation(latitude, longitude, locationName)
root.notifyMainWeather("setLocation", [
latitude, longitude, locationName
])
locationFeedbackError = false
locationFeedbackText = qsTr("位置已保存,正在刷新天气")
locationFieldsInitialized = true
}

function clearLocation() {
WeatherPlugin.clearManualLocation()
root.notifyMainWeather("clearLocation", [])
locationFeedbackError = false
locationFeedbackText = qsTr("已恢复自动定位,正在刷新天气")
locationFieldsInitialized = false
}

function applyApiKey() {
const value = apiKeyField.text.trim()
if (value.length < 16) {
Expand Down Expand Up @@ -62,6 +126,15 @@ StyledFlickable {
])
}

Connections {
target: WeatherPlugin

function onDataChanged() {
if (!root.locationFieldsInitialized)
root.initializeLocationFields()
}
}

Connections {
target: WeatherMapPlugin

Expand Down Expand Up @@ -91,6 +164,143 @@ StyledFlickable {
y: 28
spacing: 24

Rectangle {
Layout.fillWidth: true
Layout.preferredHeight: locationContent.implicitHeight + 48
radius: Appearance.rounding.large
color: Appearance.colors.colSurfaceContainer

ColumnLayout {
id: locationContent

anchors.fill: parent
anchors.margins: 24
spacing: 16

RowLayout {
Layout.fillWidth: true
spacing: 12

Rectangle {
Layout.preferredWidth: 44
Layout.preferredHeight: 44
radius: Appearance.rounding.full
color: Appearance.colors.colPrimaryContainer

MaterialSymbol {
anchors.centerIn: parent
text: "edit_location_alt"
iconSize: 22
fill: 1
color: Appearance.colors.colOnPrimaryContainer
}
}

ColumnLayout {
Layout.fillWidth: true
spacing: 2

Text {
Layout.fillWidth: true
text: qsTr("天气位置")
color: Appearance.colors.colOnSurface
font.family: Sizes.fontFamily
font.pixelSize: 16
font.weight: Font.Medium
textFormat: Text.PlainText
}

Text {
Layout.fillWidth: true
text: WeatherPlugin.hasManualLocation
? qsTr("使用手动位置")
: qsTr("使用网络自动定位")
color: Appearance.colors.colOnSurfaceVariant
font.family: Sizes.fontFamily
font.pixelSize: 12
textFormat: Text.PlainText
}
}
}

MaterialTextField {
id: locationNameField

Layout.fillWidth: true
placeholderText: qsTr("位置名称")
maximumLength: 96
Material.containerStyle: Material.Outlined
onAccepted: root.saveLocation()
}

RowLayout {
Layout.fillWidth: true
spacing: 12

MaterialTextField {
id: latitudeField

Layout.fillWidth: true
placeholderText: qsTr("纬度")
inputMethodHints: Qt.ImhFormattedNumbersOnly
validator: DoubleValidator {
bottom: -90
top: 90
decimals: 8
}
Material.containerStyle: Material.Outlined
onAccepted: root.saveLocation()
}

MaterialTextField {
id: longitudeField

Layout.fillWidth: true
placeholderText: qsTr("经度")
inputMethodHints: Qt.ImhFormattedNumbersOnly
validator: DoubleValidator {
bottom: -180
top: 180
decimals: 8
}
Material.containerStyle: Material.Outlined
onAccepted: root.saveLocation()
}
}

InlineStatusBanner {
Layout.fillWidth: true
visible: root.locationFeedbackText !== ""
tone: root.locationFeedbackError ? "error" : "success"
message: root.locationFeedbackText
}

RowLayout {
Layout.fillWidth: true
spacing: 8

Item { Layout.fillWidth: true }

Button {
text: qsTr("恢复自动定位")
flat: true
enabled: WeatherPlugin.hasManualLocation
onClicked: root.clearLocation()
}

Button {
text: qsTr("保存位置")
highlighted: true
enabled: latitudeField.acceptableInput
&& longitudeField.acceptableInput
Material.background: Appearance.colors.colPrimary
Material.foreground: Appearance.colors.colOnPrimary
onClicked: root.saveLocation()
}
}
}
}

WeatherMapCard {
id: weatherMap

Expand Down
2 changes: 2 additions & 0 deletions Modules/FilePicker/FilePickerWindow.qml
Original file line number Diff line number Diff line change
Expand Up @@ -197,6 +197,8 @@ FloatingWindow {
_completionHandled = false;
visible = true;
Qt.callLater(() => {
root.raise();
root.requestActivate();
dialogFocus.forceActiveFocus();
fileGrid.forceActiveFocus();
});
Expand Down
Loading