diff --git a/data/io.github.diegopvlk.Dosage.data.gresource.xml b/data/io.github.diegopvlk.Dosage.data.gresource.xml index 06a4fc4..3107187 100644 --- a/data/io.github.diegopvlk.Dosage.data.gresource.xml +++ b/data/io.github.diegopvlk.Dosage.data.gresource.xml @@ -7,9 +7,10 @@ styles/style-dark.css styles/card-colors.css sounds/ding.ogg - ui/window.ui ui/med-dialog.ui ui/preferences.ui + ui/refill-dialog.ui + ui/window.ui icons/ui/today-icn-symbolic.svg diff --git a/data/meson.build b/data/meson.build index cda8489..7a80797 100644 --- a/data/meson.build +++ b/data/meson.build @@ -7,6 +7,7 @@ blueprints = custom_target( 'gtk/help-overlay.blp', 'ui/med-dialog.blp', 'ui/preferences.blp', + 'ui/refill-dialog.blp', 'ui/window.blp', ), output: '.', @@ -73,4 +74,4 @@ if compile_schemas.found() ) endif -subdir('icons') \ No newline at end of file +subdir('icons') diff --git a/data/styles/style.css b/data/styles/style.css index 872f09d..fec40ff 100644 --- a/data/styles/style.css +++ b/data/styles/style.css @@ -48,6 +48,10 @@ statuspage .title { text-transform: capitalize; } +.refill-amount-btn { + padding: 7px 10px; +} + dialog.about .boxed-list { box-shadow: var(--list-border), var(--list-shadow); border-radius: var(--list-border-radius); @@ -350,10 +354,9 @@ button.floating:not(.suggested-action):active { calc(alpha * 1) ); } - -#medication-dialog - button:not(.circular):not(.image-button):not(.arrow-button):not(.toggle):not(.am-pm) { - min-width: 60px; +#refill-dialog headerbar button, +#medication-dialog headerbar button { + min-width: 50px; } #medication-dialog .linked-circular { background: rgb(from currentColor r g b / calc(alpha * 0.1)); diff --git a/data/ui/refill-dialog.blp b/data/ui/refill-dialog.blp new file mode 100644 index 0000000..7029432 --- /dev/null +++ b/data/ui/refill-dialog.blp @@ -0,0 +1,131 @@ +using Gtk 4.0; +using Adw 1; + +Adw.Dialog refillDialog { + name: 'refill-dialog'; + content-width: 424; + title: _("Refill"); + + Adw.ToolbarView { + [top] + Adw.HeaderBar headerBar { + show-start-title-buttons: false; + show-end-title-buttons: false; + decoration-layout: ''; + + [start] + Button cancelButton { + label: _("Cancel"); + name: 'cancel'; + } + + [end] + Button saveButton { + styles [ + 'suggested-action' + ] + + label: _("Save"); + name: 'save'; + } + } + + content: ScrolledWindow { + Adw.Clamp refillDialogClamp { + maximum-size: 400; + + Box { + orientation: vertical; + margin-start: 10; + margin-end: 10; + margin-top: 15; + margin-bottom: 20; + + ListBox { + styles [ + "boxed-list-separate" + ] + + selection-mode: none; + + Adw.SpinRow refillRow { + styles [ + "property" + ] + + title: _("Inventory"); + climb-rate: 0.2; + digits: 2; + + adjustment: Adjustment refillInventory { + lower: 0; + upper: 99999; + step-increment: 1; + }; + } + } + + Box { + margin-top: 18; + margin-start: 2; + margin-end: 2; + spacing: 8; + halign: fill; + orientation: horizontal; + homogeneous: true; + + Button refill5Button { + styles [ + "refill-amount-btn", + "pill" + ] + + label: "+5"; + valign: center; + } + + Button refill10Button { + styles [ + "refill-amount-btn", + "pill" + ] + + label: "+10"; + valign: center; + } + + Button refill30Button { + styles [ + "refill-amount-btn", + "pill" + ] + + label: "+30"; + valign: center; + } + + Button refill60Button { + styles [ + "refill-amount-btn", + "pill" + ] + + label: "+60"; + valign: center; + } + + Button refill100Button { + styles [ + "refill-amount-btn", + "pill" + ] + + label: "+100"; + valign: center; + } + } + } + } + }; + } +} diff --git a/src/io.github.diegopvlk.Dosage.src.gresource.xml b/src/io.github.diegopvlk.Dosage.src.gresource.xml index ec5aeac..bf00002 100644 --- a/src/io.github.diegopvlk.Dosage.src.gresource.xml +++ b/src/io.github.diegopvlk.Dosage.src.gresource.xml @@ -6,6 +6,7 @@ window.js medDialog.js prefsDialog.js + refillDialog.js medication.js todayFactory.js historyFactory.js diff --git a/src/medDialog.js b/src/medDialog.js index 62aa96d..4e85990 100644 --- a/src/medDialog.js +++ b/src/medDialog.js @@ -551,9 +551,12 @@ export function openMedicationDialog(DosageWindow, list, position, mode) { i.obj.inventory.current += diff - adjusts; } if (tempInv === i.obj.inventory.current) break; - DosageWindow._treatmentsList.model = new Gtk.NoSelection({ - model: treatmentsLS, - }); + + // reload-ish of treatments list + // necessary for updating low stock and cycle date labels + DosageWindow._treatmentsList.visible = false; + DosageWindow._treatmentsList.visible = true; + DosageWindow._updateJsonFile('treatments', treatmentsLS); DosageWindow._checkInventory(); } diff --git a/src/refillDialog.js b/src/refillDialog.js new file mode 100644 index 0000000..8bd7b2e --- /dev/null +++ b/src/refillDialog.js @@ -0,0 +1,66 @@ +/* + * Copyright 2023 Diego Povliuk + * SPDX-License-Identifier: GPL-3.0-only + */ +'use strict'; + +import Gdk from 'gi://Gdk'; +import Gtk from 'gi://Gtk'; + +import { DosageApplication } from './main.js'; + +export function openRefillDialog(listItem, position) { + const item = listItem.get_item().obj; + const DosageWindow = DosageApplication.get_default().activeWindow; + const builder = Gtk.Builder.new_from_resource('/io/github/diegopvlk/Dosage/ui/refill-dialog.ui'); + + const refillDialog = builder.get_object('refillDialog'); + const refillRow = builder.get_object('refillRow'); + const refillInv = builder.get_object('refillInventory'); + const refill5Btn = builder.get_object('refill5Button'); + const refill10Btn = builder.get_object('refill10Button'); + const refill30Btn = builder.get_object('refill30Button'); + const refill60Btn = builder.get_object('refill60Button'); + const refill100Btn = builder.get_object('refill100Button'); + const cancelButton = builder.get_object('cancelButton'); + const saveButton = builder.get_object('saveButton'); + + const keyController = new Gtk.EventControllerKey(); + keyController.connect('key-pressed', (_, keyval, keycode, state) => { + const shiftPressed = (state & Gdk.ModifierType.SHIFT_MASK) !== 0; + const controlPressed = (state & Gdk.ModifierType.CONTROL_MASK) !== 0; + const enterPressed = keyval === Gdk.KEY_Return; + + if ((controlPressed || shiftPressed) && enterPressed) { + saveButton.activate(); + } + }); + refillDialog.add_controller(keyController); + + refillRow.subtitle = item.name; + + refillInv.value = item.inventory.current; + + refill5Btn.connect('clicked', () => (refillInv.value += 5)); + refill10Btn.connect('clicked', () => (refillInv.value += 10)); + refill30Btn.connect('clicked', () => (refillInv.value += 30)); + refill60Btn.connect('clicked', () => (refillInv.value += 60)); + refill100Btn.connect('clicked', () => (refillInv.value += 100)); + + cancelButton.connect('clicked', () => refillDialog.force_close()); + + saveButton.connect('clicked', () => { + item.inventory.current = refillInv.value; + + DosageWindow._updateEverything(['skipHistUp', position]); + DosageWindow._scheduleNotifications('saving'); + + refillDialog.force_close(); + }); + + const refillDialogClamp = builder.get_object('refillDialogClamp'); + const [refillDialogClampHeight] = refillDialogClamp.measure(Gtk.Orientation.VERTICAL, -1); + refillDialog.content_height = refillDialogClampHeight + 48; + + refillDialog.present(DosageWindow); +} diff --git a/src/treatmentsFactory.js b/src/treatmentsFactory.js index 03bff3b..22de0e3 100644 --- a/src/treatmentsFactory.js +++ b/src/treatmentsFactory.js @@ -11,6 +11,7 @@ import Pango from 'gi://Pango'; import { getSpecificDaysLabel } from './utils.js'; import { DosageApplication } from './main.js'; import { confirmDeleteDialog } from './medDialog.js'; +import { openRefillDialog } from './refillDialog.js'; export const treatmentsFactory = new Gtk.SignalListItemFactory(); @@ -88,10 +89,17 @@ treatmentsFactory.connect('bind', (factory, listItem) => { const today = new Date().setHours(0, 0, 0, 0); const start = new Date(item.duration.start).setHours(0, 0, 0, 0); const end = new Date(item.duration.end).setHours(0, 0, 0, 0); + const inv = item.inventory; const DosageWindow = DosageApplication.get_default().activeWindow; const app = DosageWindow.get_application(); + if (inv.enabled) { + const refillAct = new Gio.SimpleAction({ name: `refillMed${pos}` }); + refillAct.connect('activate', () => openRefillDialog(listItem, pos)); + app.add_action(refillAct); + } + const duplicateAct = new Gio.SimpleAction({ name: `duplicateMed${pos}` }); duplicateAct.connect('activate', () => { const list = DosageWindow._treatmentsList; @@ -103,10 +111,11 @@ treatmentsFactory.connect('bind', (factory, listItem) => { deleteAct.connect('activate', () => confirmDeleteDialog(item, pos, DosageWindow)); app.add_action(deleteAct); + const refillMed = Gio.MenuItem.new(_('Refill'), `app.refillMed${pos}`); const duplicateMed = Gio.MenuItem.new(_('Duplicate'), `app.duplicateMed${pos}`); const deleteMed = Gio.MenuItem.new(_('Delete'), `app.deleteMed${pos}`); - optionsMenu.remove_all(); + optionsMenu.append_item(refillMed); optionsMenu.append_item(duplicateMed); optionsMenu.append_item(deleteMed); @@ -122,8 +131,6 @@ treatmentsFactory.connect('bind', (factory, listItem) => { nameLabel.label = item.name; - const inv = item.inventory; - if (inv.enabled) { let currInv = inv.current < 0 ? 0 : inv.current; @@ -202,20 +209,34 @@ treatmentsFactory.connect('bind', (factory, listItem) => { box.add_css_class(item.color); icon.icon_name = item.icon; +}); - function formatDate(date, weekday) { - if (weekday) { - return new Date(date).toLocaleDateString(undefined, { - weekday: 'short', - month: 'short', - day: 'numeric', - }); - } +treatmentsFactory.connect('unbind', (factory, listItem) => { + const DosageWindow = DosageApplication.get_default().activeWindow; + const app = DosageWindow.get_application(); + const box = listItem.get_child(); + const pos = listItem.get_position(); + const optionsMenu = box.get_last_child().get_menu_model(); + + app.remove_action(`refillMed${pos}`); + app.remove_action(`duplicateMed${pos}`); + app.remove_action(`deleteMed${pos}`); + optionsMenu.remove_all(); +}); + +function formatDate(date, weekday) { + if (weekday) { return new Date(date).toLocaleDateString(undefined, { + weekday: 'short', month: 'short', day: 'numeric', - year: 'numeric', }); } -}); + + return new Date(date).toLocaleDateString(undefined, { + month: 'short', + day: 'numeric', + year: 'numeric', + }); +}