mirror of
https://github.com/Theaninova/TheaninovOS.git
synced 2026-02-16 05:22:43 +00:00
refactor: make the whole thing more generic
This commit is contained in:
@@ -0,0 +1,61 @@
|
||||
import * as Utils from "resource:///com/github/Aylur/ags/utils.js";
|
||||
import Widget from "resource:///com/github/Aylur/ags/widget.js";
|
||||
import icons from "../../icons.js";
|
||||
import Asusctl from "../../services/asusctl.js";
|
||||
import { ArrowToggleButton, Menu } from "../ToggleButton.js";
|
||||
|
||||
export const ProfileToggle = () =>
|
||||
ArrowToggleButton({
|
||||
name: "asusctl-profile",
|
||||
icon: Widget.Icon({
|
||||
icon: Asusctl.bind("profile").transform((p) => icons.asusctl.profile[p]),
|
||||
}),
|
||||
label: Widget.Label({
|
||||
label: Asusctl.bind("profile"),
|
||||
}),
|
||||
connection: [Asusctl, () => Asusctl.profile !== "Balanced"],
|
||||
activate: () => Asusctl.setProfile("Quiet"),
|
||||
deactivate: () => Asusctl.setProfile("Balanced"),
|
||||
activateOnArrow: false,
|
||||
});
|
||||
|
||||
export const ProfileSelector = () =>
|
||||
Menu({
|
||||
name: "asusctl-profile",
|
||||
icon: Widget.Icon({
|
||||
icon: Asusctl.bind("profile").transform((p) => icons.asusctl.profile[p]),
|
||||
}),
|
||||
title: Widget.Label("Profile Selector"),
|
||||
content: [
|
||||
Widget.Box({
|
||||
vertical: true,
|
||||
hexpand: true,
|
||||
children: [
|
||||
Widget.Box({
|
||||
vertical: true,
|
||||
children: Asusctl.profiles.map((prof) =>
|
||||
Widget.Button({
|
||||
on_clicked: () => Asusctl.setProfile(prof),
|
||||
child: Widget.Box({
|
||||
children: [
|
||||
Widget.Icon(icons.asusctl.profile[prof]),
|
||||
Widget.Label(prof),
|
||||
],
|
||||
}),
|
||||
}),
|
||||
),
|
||||
}),
|
||||
],
|
||||
}),
|
||||
Widget.Separator(),
|
||||
Widget.Button({
|
||||
on_clicked: () => Utils.execAsync("rog-control-center"),
|
||||
child: Widget.Box({
|
||||
children: [
|
||||
Widget.Icon(icons.ui.settings),
|
||||
Widget.Label("Rog Control Center"),
|
||||
],
|
||||
}),
|
||||
}),
|
||||
],
|
||||
});
|
||||
@@ -0,0 +1,74 @@
|
||||
import Bluetooth from "resource:///com/github/Aylur/ags/service/bluetooth.js";
|
||||
import Widget from "resource:///com/github/Aylur/ags/widget.js";
|
||||
import icons from "../../icons.js";
|
||||
import { Menu, ArrowToggleButton } from "../ToggleButton.js";
|
||||
|
||||
export const BluetoothToggle = () =>
|
||||
ArrowToggleButton({
|
||||
name: "bluetooth",
|
||||
icon: Widget.Icon({
|
||||
icon: Bluetooth.bind("enabled").transform(
|
||||
(p) => icons.bluetooth[p ? "enabled" : "disabled"],
|
||||
),
|
||||
}),
|
||||
label: Widget.Label({
|
||||
truncate: "end",
|
||||
setup: (self) =>
|
||||
self.hook(Bluetooth, () => {
|
||||
if (!Bluetooth.enabled) return (self.label = "Disabled");
|
||||
|
||||
if (Bluetooth.connected_devices.length === 0)
|
||||
return (self.label = "Not Connected");
|
||||
|
||||
if (Bluetooth.connected_devices.length === 1)
|
||||
return (self.label = Bluetooth.connected_devices[0].alias);
|
||||
|
||||
self.label = `${Bluetooth.connected_devices.length} Connected`;
|
||||
}),
|
||||
}),
|
||||
connection: [Bluetooth, () => Bluetooth.enabled],
|
||||
deactivate: () => (Bluetooth.enabled = false),
|
||||
activate: () => (Bluetooth.enabled = true),
|
||||
});
|
||||
|
||||
/** @param {import('types/service/bluetooth').BluetoothDevice} device */
|
||||
const DeviceItem = (device) =>
|
||||
Widget.Box({
|
||||
children: [
|
||||
Widget.Icon(device.icon_name + "-symbolic"),
|
||||
Widget.Label(device.name),
|
||||
Widget.Label({
|
||||
label: `${device.battery_percentage}%`,
|
||||
visible: device.bind("battery_percentage").transform((p) => p > 0),
|
||||
}),
|
||||
Widget.Box({ hexpand: true }),
|
||||
Widget.Spinner({
|
||||
active: device.bind("connecting"),
|
||||
visible: device.bind("connecting"),
|
||||
}),
|
||||
Widget.Switch({
|
||||
active: device.connected,
|
||||
visible: device.bind("connecting").transform((p) => !p),
|
||||
setup: (self) =>
|
||||
self.on("notify::active", () => {
|
||||
device.setConnection(self.active);
|
||||
}),
|
||||
}),
|
||||
],
|
||||
});
|
||||
|
||||
export const BluetoothDevices = () =>
|
||||
Menu({
|
||||
name: "bluetooth",
|
||||
icon: Widget.Icon(icons.bluetooth.disabled),
|
||||
title: Widget.Label("Bluetooth"),
|
||||
content: [
|
||||
Widget.Box({
|
||||
hexpand: true,
|
||||
vertical: true,
|
||||
children: Bluetooth.bind("devices").transform((ds) =>
|
||||
ds.filter((d) => d.name).map(DeviceItem),
|
||||
),
|
||||
}),
|
||||
],
|
||||
});
|
||||
@@ -0,0 +1,24 @@
|
||||
import Widget from "resource:///com/github/Aylur/ags/widget.js";
|
||||
import icons from "../../icons.js";
|
||||
import Brightness from "../../services/brightness.js";
|
||||
|
||||
const BrightnessSlider = () =>
|
||||
Widget.Slider({
|
||||
draw_value: false,
|
||||
hexpand: true,
|
||||
value: Brightness.bind("screen"),
|
||||
on_change: ({ value }) => (Brightness.screen = value),
|
||||
});
|
||||
|
||||
export default () =>
|
||||
Widget.Box({
|
||||
children: [
|
||||
Widget.Button({
|
||||
child: Widget.Icon(icons.brightness.indicator),
|
||||
tooltip_text: Brightness.bind("screen").transform(
|
||||
(v) => `Screen Brightness: ${Math.floor(v * 100)}%`,
|
||||
),
|
||||
}),
|
||||
BrightnessSlider(),
|
||||
],
|
||||
});
|
||||
@@ -0,0 +1,15 @@
|
||||
import Widget from "resource:///com/github/Aylur/ags/widget.js";
|
||||
import Notifications from "resource:///com/github/Aylur/ags/service/notifications.js";
|
||||
import icons from "../../icons.js";
|
||||
import { SimpleToggleButton } from "../ToggleButton.js";
|
||||
|
||||
export default () =>
|
||||
SimpleToggleButton({
|
||||
icon: Widget.Icon({
|
||||
icon: Notifications.bind("dnd").transform(
|
||||
(dnd) => icons.notifications[dnd ? "silent" : "noisy"],
|
||||
),
|
||||
}),
|
||||
toggle: () => (Notifications.dnd = !Notifications.dnd),
|
||||
connection: [Notifications, () => Notifications.dnd],
|
||||
});
|
||||
@@ -0,0 +1,50 @@
|
||||
import Widget from "resource:///com/github/Aylur/ags/widget.js";
|
||||
import Battery from "resource:///com/github/Aylur/ags/service/battery.js";
|
||||
import PowerMenu from "../../services/powermenu.js";
|
||||
import Lockscreen from "../../services/lockscreen.js";
|
||||
import Avatar from "../../misc/Avatar.js";
|
||||
import icons from "../../icons.js";
|
||||
import { openSettings } from "../../settings/theme.js";
|
||||
import { uptime } from "../../variables.js";
|
||||
import DND from "./DND.js";
|
||||
|
||||
export default () =>
|
||||
Widget.Box({
|
||||
class_name: "header horizontal",
|
||||
children: [
|
||||
Avatar(),
|
||||
Widget.Box({
|
||||
hpack: "end",
|
||||
vpack: "center",
|
||||
hexpand: true,
|
||||
children: [
|
||||
/*Widget.Box({
|
||||
class_name: "battery horizontal",
|
||||
children: [
|
||||
Widget.Icon({ icon: Battery.bind("icon_name") }),
|
||||
Widget.Label({
|
||||
label: Battery.bind("percent").transform((p) => `${p}%`),
|
||||
}),
|
||||
],
|
||||
}),*/
|
||||
DND(),
|
||||
Widget.Label({
|
||||
class_name: "uptime",
|
||||
label: uptime.bind().transform((v) => `up: ${v}`),
|
||||
}),
|
||||
Widget.Button({
|
||||
on_clicked: openSettings,
|
||||
child: Widget.Icon(icons.ui.settings),
|
||||
}),
|
||||
Widget.Button({
|
||||
on_clicked: () => Lockscreen.lockscreen(),
|
||||
child: Widget.Icon(icons.lock),
|
||||
}),
|
||||
Widget.Button({
|
||||
on_clicked: () => PowerMenu.action("shutdown"),
|
||||
child: Widget.Icon(icons.powermenu.shutdown),
|
||||
}),
|
||||
],
|
||||
}),
|
||||
],
|
||||
});
|
||||
@@ -0,0 +1,91 @@
|
||||
import Mpris from "resource:///com/github/Aylur/ags/service/mpris.js";
|
||||
import Widget from "resource:///com/github/Aylur/ags/widget.js";
|
||||
import * as mpris from "../../misc/mpris.js";
|
||||
import options from "../../options.js";
|
||||
|
||||
/** @param {import('types/service/mpris').MprisPlayer} player */
|
||||
const Footer = (player) =>
|
||||
Widget.CenterBox({
|
||||
class_name: "footer-box",
|
||||
start_widget: Widget.Box({
|
||||
class_name: "position",
|
||||
children: [
|
||||
mpris.PositionLabel(player),
|
||||
mpris.Slash(player),
|
||||
mpris.LengthLabel(player),
|
||||
],
|
||||
}),
|
||||
center_widget: Widget.Box({
|
||||
class_name: "controls",
|
||||
children: [
|
||||
mpris.ShuffleButton(player),
|
||||
mpris.PreviousButton(player),
|
||||
mpris.PlayPauseButton(player),
|
||||
mpris.NextButton(player),
|
||||
mpris.LoopButton(player),
|
||||
],
|
||||
}),
|
||||
end_widget: mpris.PlayerIcon(player, {
|
||||
symbolic: false,
|
||||
hexpand: true,
|
||||
hpack: "end",
|
||||
}),
|
||||
});
|
||||
|
||||
/** @param {import('types/service/mpris').MprisPlayer} player */
|
||||
const TextBox = (player) =>
|
||||
Widget.Box({
|
||||
children: [
|
||||
mpris.CoverArt(player, {
|
||||
hpack: "end",
|
||||
hexpand: false,
|
||||
}),
|
||||
Widget.Box({
|
||||
hexpand: true,
|
||||
vertical: true,
|
||||
class_name: "labels",
|
||||
children: [
|
||||
mpris.TitleLabel(player, {
|
||||
xalign: 0,
|
||||
justification: "left",
|
||||
wrap: true,
|
||||
}),
|
||||
mpris.ArtistLabel(player, {
|
||||
xalign: 0,
|
||||
justification: "left",
|
||||
wrap: true,
|
||||
}),
|
||||
],
|
||||
}),
|
||||
],
|
||||
});
|
||||
|
||||
/** @param {import('types/service/mpris').MprisPlayer} player */
|
||||
const PlayerBox = (player) =>
|
||||
Widget.Box({
|
||||
class_name: `player ${player.name}`,
|
||||
child: mpris.BlurredCoverArt(player, {
|
||||
hexpand: true,
|
||||
child: Widget.Box({
|
||||
hexpand: true,
|
||||
vertical: true,
|
||||
children: [
|
||||
TextBox(player),
|
||||
mpris.PositionSlider(player),
|
||||
Footer(player),
|
||||
],
|
||||
}),
|
||||
}),
|
||||
});
|
||||
|
||||
export default () =>
|
||||
Widget.Box({
|
||||
vertical: true,
|
||||
class_name: "media vertical",
|
||||
visible: Mpris.bind("players").transform((p) => p.length > 0),
|
||||
children: Mpris.bind("players").transform((ps) =>
|
||||
ps
|
||||
.filter((p) => !options.mpris.black_list.value.includes(p.identity))
|
||||
.map(PlayerBox),
|
||||
),
|
||||
});
|
||||
@@ -0,0 +1,19 @@
|
||||
import Audio from "resource:///com/github/Aylur/ags/service/audio.js";
|
||||
import Widget from "resource:///com/github/Aylur/ags/widget.js";
|
||||
import icons from "../../icons.js";
|
||||
import { SimpleToggleButton } from "../ToggleButton.js";
|
||||
|
||||
export default () =>
|
||||
SimpleToggleButton({
|
||||
icon: Widget.Icon().hook(
|
||||
Audio,
|
||||
(self) => {
|
||||
self.icon = Audio.microphone?.is_muted
|
||||
? icons.audio.mic.muted
|
||||
: icons.audio.mic.high;
|
||||
},
|
||||
"microphone-changed",
|
||||
),
|
||||
toggle: () => (Audio.microphone.is_muted = !Audio.microphone.is_muted),
|
||||
connection: [Audio, () => Audio.microphone?.is_muted || false],
|
||||
});
|
||||
@@ -0,0 +1,71 @@
|
||||
import Widget from "resource:///com/github/Aylur/ags/widget.js";
|
||||
import Network from "resource:///com/github/Aylur/ags/service/network.js";
|
||||
import * as Utils from "resource:///com/github/Aylur/ags/utils.js";
|
||||
import icons from "../../icons.js";
|
||||
import { Menu, ArrowToggleButton } from "../ToggleButton.js";
|
||||
import Applications from "resource:///com/github/Aylur/ags/service/applications.js";
|
||||
|
||||
export const NetworkToggle = () =>
|
||||
ArrowToggleButton({
|
||||
name: "network",
|
||||
icon: Widget.Icon({
|
||||
icon: Network.wifi.bind("icon_name"),
|
||||
}),
|
||||
label: Widget.Label({
|
||||
truncate: "end",
|
||||
label: Network.wifi
|
||||
.bind("ssid")
|
||||
.transform((ssid) => ssid || "Not Connected"),
|
||||
}),
|
||||
connection: [Network, () => Network.wifi.enabled],
|
||||
deactivate: () => (Network.wifi.enabled = false),
|
||||
activate: () => {
|
||||
Network.wifi.enabled = true;
|
||||
Network.wifi.scan();
|
||||
},
|
||||
});
|
||||
|
||||
export const WifiSelection = () =>
|
||||
Menu({
|
||||
name: "network",
|
||||
icon: Widget.Icon({
|
||||
icon: Network.wifi.bind("icon_name"),
|
||||
}),
|
||||
title: Widget.Label("Wifi Selection"),
|
||||
content: [
|
||||
Widget.Box({
|
||||
vertical: true,
|
||||
setup: (self) =>
|
||||
self.hook(
|
||||
Network,
|
||||
() =>
|
||||
(self.children = Network.wifi?.access_points.map((ap) =>
|
||||
Widget.Button({
|
||||
on_clicked: () =>
|
||||
Utils.execAsync(`nmcli device wifi connect ${ap.bssid}`),
|
||||
child: Widget.Box({
|
||||
children: [
|
||||
Widget.Icon(ap.iconName),
|
||||
Widget.Label(ap.ssid || ""),
|
||||
ap.active &&
|
||||
Widget.Icon({
|
||||
icon: icons.ui.tick,
|
||||
hexpand: true,
|
||||
hpack: "end",
|
||||
}),
|
||||
],
|
||||
}),
|
||||
}),
|
||||
)),
|
||||
),
|
||||
}),
|
||||
Widget.Separator(),
|
||||
Widget.Button({
|
||||
on_clicked: () =>
|
||||
Applications.query("gnome-control-center")?.[0].launch(),
|
||||
child: Widget.Box({
|
||||
children: [Widget.Icon(icons.ui.settings), Widget.Label("Network")],
|
||||
}),
|
||||
}),
|
||||
],
|
||||
});
|
||||
@@ -0,0 +1,55 @@
|
||||
import Widget from "resource:///com/github/Aylur/ags/widget.js";
|
||||
import { ArrowToggleButton, Menu, opened } from "../ToggleButton.js";
|
||||
import themes from "../../themes.js";
|
||||
import icons from "../../icons.js";
|
||||
import options from "../../options.js";
|
||||
import { setTheme, openSettings } from "../../settings/theme.js";
|
||||
|
||||
export const ThemeToggle = () =>
|
||||
ArrowToggleButton({
|
||||
name: "theme",
|
||||
icon: Widget.Label().bind("label", options.theme.icon),
|
||||
label: Widget.Label().bind("label", options.theme.name),
|
||||
connection: [opened, () => opened.value === "theme"],
|
||||
activate: () => opened.setValue("theme"),
|
||||
activateOnArrow: false,
|
||||
deactivate: () => {},
|
||||
});
|
||||
|
||||
export const ThemeSelector = () =>
|
||||
Menu({
|
||||
name: "theme",
|
||||
icon: Widget.Label().bind("label", options.theme.icon),
|
||||
title: Widget.Label("Theme Selector"),
|
||||
content: [
|
||||
...themes.map(({ name, icon }) =>
|
||||
Widget.Button({
|
||||
on_clicked: () => setTheme(name),
|
||||
child: Widget.Box({
|
||||
children: [
|
||||
Widget.Label(icon),
|
||||
Widget.Label(name),
|
||||
Widget.Icon({
|
||||
icon: icons.ui.tick,
|
||||
hexpand: true,
|
||||
hpack: "end",
|
||||
visible: options.theme.name
|
||||
.bind("value")
|
||||
.transform((v) => v === name),
|
||||
}),
|
||||
],
|
||||
}),
|
||||
}),
|
||||
),
|
||||
Widget.Separator(),
|
||||
Widget.Button({
|
||||
on_clicked: openSettings,
|
||||
child: Widget.Box({
|
||||
children: [
|
||||
Widget.Icon(icons.ui.settings),
|
||||
Widget.Label("Theme Settings"),
|
||||
],
|
||||
}),
|
||||
}),
|
||||
],
|
||||
});
|
||||
@@ -0,0 +1,158 @@
|
||||
import Audio from "resource:///com/github/Aylur/ags/service/audio.js";
|
||||
import Widget from "resource:///com/github/Aylur/ags/widget.js";
|
||||
import * as Utils from "resource:///com/github/Aylur/ags/utils.js";
|
||||
import icons from "../../icons.js";
|
||||
import FontIcon from "../../misc/FontIcon.js";
|
||||
import { getAudioTypeIcon } from "../../utils.js";
|
||||
import { Arrow } from "../ToggleButton.js";
|
||||
import { Menu } from "../ToggleButton.js";
|
||||
|
||||
/** @param {'speaker' | 'microphone'=} type */
|
||||
const VolumeIndicator = (type = "speaker") =>
|
||||
Widget.Button({
|
||||
on_clicked: () => (Audio[type].is_muted = !Audio[type].is_muted),
|
||||
child: Widget.Icon().hook(Audio[type], (icon) => {
|
||||
icon.icon =
|
||||
type === "speaker"
|
||||
? getAudioTypeIcon(Audio[type].icon_name || "")
|
||||
: icons.audio.mic.high;
|
||||
|
||||
icon.tooltip_text = `Volume ${Math.floor(Audio[type].volume * 100)}%`;
|
||||
}),
|
||||
});
|
||||
|
||||
/** @param {'speaker' | 'microphone'=} type */
|
||||
const VolumeSlider = (type = "speaker") =>
|
||||
Widget.Slider({
|
||||
hexpand: true,
|
||||
draw_value: false,
|
||||
on_change: ({ value }) => (Audio[type].volume = value),
|
||||
setup: (self) =>
|
||||
self.hook(Audio[type], () => {
|
||||
self.value = Audio[type].volume || 0;
|
||||
}),
|
||||
});
|
||||
|
||||
export const Volume = () =>
|
||||
Widget.Box({
|
||||
children: [
|
||||
VolumeIndicator("speaker"),
|
||||
VolumeSlider("speaker"),
|
||||
Widget.Box({
|
||||
vpack: "center",
|
||||
child: Arrow("sink-selector"),
|
||||
}),
|
||||
Widget.Box({
|
||||
vpack: "center",
|
||||
child: Arrow("app-mixer"),
|
||||
visible: Audio.bind("apps").transform((a) => a.length > 0),
|
||||
}),
|
||||
],
|
||||
});
|
||||
|
||||
export const Microhone = () =>
|
||||
Widget.Box({
|
||||
class_name: "slider horizontal",
|
||||
visible: Audio.bind("recorders").transform((a) => a.length > 0),
|
||||
children: [VolumeIndicator("microphone"), VolumeSlider("microphone")],
|
||||
});
|
||||
|
||||
/** @param {import('types/service/audio').Stream} stream */
|
||||
const MixerItem = (stream) =>
|
||||
Widget.Box({
|
||||
hexpand: true,
|
||||
class_name: "mixer-item horizontal",
|
||||
children: [
|
||||
Widget.Icon({
|
||||
tooltip_text: stream.bind("name").transform((n) => n || ""),
|
||||
icon: stream.bind("name").transform((n) => {
|
||||
return Utils.lookUpIcon(n || "") ? n || "" : icons.mpris.fallback;
|
||||
}),
|
||||
}),
|
||||
Widget.Box({
|
||||
vertical: true,
|
||||
children: [
|
||||
Widget.Label({
|
||||
xalign: 0,
|
||||
truncate: "end",
|
||||
label: stream.bind("description").transform((d) => d || ""),
|
||||
}),
|
||||
Widget.Slider({
|
||||
hexpand: true,
|
||||
draw_value: false,
|
||||
value: stream.bind("volume"),
|
||||
on_change: ({ value }) => (stream.volume = value),
|
||||
}),
|
||||
],
|
||||
}),
|
||||
Widget.Label({
|
||||
xalign: 1,
|
||||
label: stream.bind("volume").transform((v) => `${Math.floor(v * 100)}`),
|
||||
}),
|
||||
],
|
||||
});
|
||||
|
||||
/** @param {import('types/service/audio').Stream} stream */
|
||||
const SinkItem = (stream) =>
|
||||
Widget.Button({
|
||||
hexpand: true,
|
||||
on_clicked: () => (Audio.speaker = stream),
|
||||
child: Widget.Box({
|
||||
children: [
|
||||
Widget.Icon({
|
||||
icon: getAudioTypeIcon(stream.icon_name || ""),
|
||||
tooltip_text: stream.icon_name,
|
||||
}),
|
||||
Widget.Label(
|
||||
(stream.description || "").split(" ").slice(0, 4).join(" "),
|
||||
),
|
||||
Widget.Icon({
|
||||
icon: icons.ui.tick,
|
||||
hexpand: true,
|
||||
hpack: "end",
|
||||
visible: Audio.speaker
|
||||
.bind("stream")
|
||||
.transform((s) => s === stream.stream),
|
||||
}),
|
||||
],
|
||||
}),
|
||||
});
|
||||
|
||||
const SettingsButton = () =>
|
||||
Widget.Button({
|
||||
on_clicked: () => Utils.execAsync("pavucontrol"),
|
||||
hexpand: true,
|
||||
child: Widget.Box({
|
||||
children: [Widget.Icon(icons.ui.settings), Widget.Label("Settings")],
|
||||
}),
|
||||
});
|
||||
|
||||
export const AppMixer = () =>
|
||||
Menu({
|
||||
name: "app-mixer",
|
||||
icon: FontIcon(icons.audio.mixer),
|
||||
title: Widget.Label("App Mixer"),
|
||||
content: [
|
||||
Widget.Box({
|
||||
vertical: true,
|
||||
children: Audio.bind("apps").transform((a) => a.map(MixerItem)),
|
||||
}),
|
||||
Widget.Separator(),
|
||||
SettingsButton(),
|
||||
],
|
||||
});
|
||||
|
||||
export const SinkSelector = () =>
|
||||
Menu({
|
||||
name: "sink-selector",
|
||||
icon: Widget.Icon(icons.audio.type.headset),
|
||||
title: Widget.Label("Sink Selector"),
|
||||
content: [
|
||||
Widget.Box({
|
||||
vertical: true,
|
||||
children: Audio.bind("speakers").transform((a) => a.map(SinkItem)),
|
||||
}),
|
||||
Widget.Separator(),
|
||||
SettingsButton(),
|
||||
],
|
||||
});
|
||||
Reference in New Issue
Block a user