From 0c37f46b53f5a9f728173abfe7cb9e9ce3cfaa62 Mon Sep 17 00:00:00 2001 From: "Stanislav N. aka pztrn" Date: Sat, 13 Sep 2025 09:23:47 +0500 Subject: [PATCH] Basic GUI client, login dialog, various comments fixes after copypaste. --- client/cmd/client/main.go | 5 + client/internal/helpers/platform.go | 13 +++ client/internal/services/core/mainwindow.go | 12 ++- .../services/core/mainwindow/dto/tab.go | 20 ++++ .../services/core/mainwindow/mainwindow.go | 39 ++++---- .../core/mainwindow/mainwindow_desktop.go | 23 +++++ .../mainwindow_desktop_statusbar.go | 42 +++++++++ .../core/mainwindow/mainwindow_mobile.go | 10 ++ .../mainwindow/mainwindow_switcher_desktop.go | 26 ++++++ .../mainwindow/mainwindow_switcher_mobiles.go | 5 + .../services/core/mainwindow/models/tab.go | 18 ++++ .../internal/services/core/mainwindow/tabs.go | 10 +- client/internal/services/core/options.go | 16 ++-- .../internal/services/core/options/options.go | 13 ++- .../internal/services/core/options/widgets.go | 3 +- client/internal/services/core/translations.go | 12 +-- .../core/translations/translations.go | 13 ++- client/internal/services/features/accounts.go | 4 + .../services/features/accounts/accounts.go | 93 +++++++++++++++++++ .../features/accounts/login_dialog.go | 53 +++++++++++ client/internal/services/features/tasks.go | 4 + .../internal/services/features/tasks/tasks.go | 85 +++++++++++++++++ client/internal/widgets/toolbar_label.go | 21 +++++ .../internal/widgets/toolbar_progressbar.go | 23 +++++ 24 files changed, 507 insertions(+), 56 deletions(-) create mode 100644 client/internal/helpers/platform.go create mode 100644 client/internal/services/core/mainwindow/dto/tab.go create mode 100644 client/internal/services/core/mainwindow/mainwindow_desktop.go create mode 100644 client/internal/services/core/mainwindow/mainwindow_desktop_statusbar.go create mode 100644 client/internal/services/core/mainwindow/mainwindow_mobile.go create mode 100644 client/internal/services/core/mainwindow/mainwindow_switcher_desktop.go create mode 100644 client/internal/services/core/mainwindow/mainwindow_switcher_mobiles.go create mode 100644 client/internal/services/core/mainwindow/models/tab.go create mode 100644 client/internal/services/features/accounts.go create mode 100644 client/internal/services/features/accounts/accounts.go create mode 100644 client/internal/services/features/accounts/login_dialog.go create mode 100644 client/internal/services/features/tasks.go create mode 100644 client/internal/services/features/tasks/tasks.go create mode 100644 client/internal/widgets/toolbar_label.go create mode 100644 client/internal/widgets/toolbar_progressbar.go diff --git a/client/cmd/client/main.go b/client/cmd/client/main.go index 262824f..8d210ae 100644 --- a/client/cmd/client/main.go +++ b/client/cmd/client/main.go @@ -8,6 +8,8 @@ import ( "bunker/client/internal/services/core/mainwindow" "bunker/client/internal/services/core/options" "bunker/client/internal/services/core/translations" + "bunker/client/internal/services/features/accounts" + "bunker/client/internal/services/features/tasks" "bunker/commons" "fyne.io/fyne/v2" @@ -33,6 +35,9 @@ func main() { checkError(options.Initialize(app)) checkError(mainwindow.Initialize(app)) + checkError(accounts.Initialize(app)) + checkError(tasks.Initialize(app)) + checkError(app.Start()) } diff --git a/client/internal/helpers/platform.go b/client/internal/helpers/platform.go new file mode 100644 index 0000000..d85f768 --- /dev/null +++ b/client/internal/helpers/platform.go @@ -0,0 +1,13 @@ +package helpers + +import "runtime" + +// IsMobile returns true if current platform related to mobile devices (phones, tablets). +func IsMobile() bool { + switch runtime.GOOS { + case "android", "ios": + return true + default: + return false + } +} diff --git a/client/internal/services/core/mainwindow.go b/client/internal/services/core/mainwindow.go index 9ca8122..913bb6d 100644 --- a/client/internal/services/core/mainwindow.go +++ b/client/internal/services/core/mainwindow.go @@ -3,8 +3,9 @@ package core import ( "errors" + "bunker/client/internal/services/core/mainwindow/dto" + "fyne.io/fyne/v2" - "fyne.io/fyne/v2/container" ) // ServiceNameMainWindow is a name for main window service. @@ -20,11 +21,18 @@ var ( // MainWindow is an interface for main window service. type MainWindow interface { // AddTab adds tab in main window. - AddTab(tab *container.TabItem) + AddTab(tab *dto.Tab) // MainWindow returns main window instance (e.g. for using as parent with dialogs). MainWindow() fyne.Window // RegisterAboutWindowSysInfoHandler registers handler for System Info tab in About dialog. RegisterAboutWindowSysInfoHandler(name string, hndl SysInfoHandler) error + // SetStatusProgressBarCurrentValue sets current value for progressbar in status bar. + SetStatusProgressBarCurrentValue(current float64) + // SetStatusProgressBarMaxValue sets maximum value for progressbar in status bar. + SetStatusProgressBarMaxValue(current float64) + // SetStatus sets text in status bar. If non-empty text is passed - then progress bar is also shown, and hidden + // if passed text is empty. + SetStatus(status string) } // SysInfoHandler is a function signature for registering with additional system information handler for About dialog. diff --git a/client/internal/services/core/mainwindow/dto/tab.go b/client/internal/services/core/mainwindow/dto/tab.go new file mode 100644 index 0000000..8116f63 --- /dev/null +++ b/client/internal/services/core/mainwindow/dto/tab.go @@ -0,0 +1,20 @@ +package dto + +import ( + "fyne.io/fyne/v2" + "fyne.io/fyne/v2/canvas" +) + +// Tab is a DTO of main window's tab that is responsible for showing content. +type Tab struct { + // Name is a name for tab. Won't render by default on desktop, only on mouse hover, but will be rendered on mobiles. + Name string + // Sidebar is a sidebar widget. On desktop in will be shown on left side of window, on mobiles as separate window. + Sidebar fyne.CanvasObject + // Widget is a widget shown in window. + Widget fyne.CanvasObject + // Icon is an icon to show on tab. + Icon canvas.Image + // BadgeCount is a number to show on tab, like unread messages, incompleted tasks, etc. + BadgeCount uint16 +} diff --git a/client/internal/services/core/mainwindow/mainwindow.go b/client/internal/services/core/mainwindow/mainwindow.go index 3b966b5..eb37bff 100644 --- a/client/internal/services/core/mainwindow/mainwindow.go +++ b/client/internal/services/core/mainwindow/mainwindow.go @@ -6,12 +6,12 @@ import ( "os" "bunker/client/internal/application" + "bunker/client/internal/helpers" "bunker/client/internal/services/core" "bunker/client/internal/services/core/mainwindow/models" "bunker/commons" "fyne.io/fyne/v2" - "fyne.io/fyne/v2/container" "fyne.io/fyne/v2/lang" "fyne.io/fyne/v2/widget" ) @@ -19,15 +19,18 @@ import ( var _ = core.MainWindow(&mainWindow{}) type mainWindow struct { - app *application.Application - logger *slog.Logger - window fyne.Window - options core.Options - tabs *container.AppTabs - sysInfoHandlers map[string]*models.SysInfoHandler + app *application.Application + logger *slog.Logger + window fyne.Window + options core.Options + tabsWidget *fyne.Container + statusBarProgress *widget.ProgressBar + statusBarStatus *widget.Label + sysInfoHandlers map[string]*models.SysInfoHandler + tabs []*models.Tab } -// Initialize инициализирует сервис. +// Initialize initializes service. func Initialize(app *application.Application) error { mainW := &mainWindow{ app: app, @@ -70,20 +73,20 @@ func (m *mainWindow) Initialize() error { m.window = m.app.Fyne().NewWindow(lang.L("window.title")) // ToDo: сохранение и восстановление размеров окна. //nolint:mnd - m.window.Resize(fyne.NewSize(800, 650)) + m.window.Resize(fyne.NewSize(1100, 800)) - m.initializeMenu() + var mainWindowCanvas fyne.CanvasObject + + if helpers.IsMobile() { + mainWindowCanvas = m.initializeMainWindowMobile() + } else { + mainWindowCanvas = m.initializeMainWindowDesktop() + } + + m.window.SetContent(mainWindowCanvas) m.window.SetCloseIntercept(m.stopApp) - welcomeLabel := widget.NewLabel(lang.L("window.lorem_ipsum.text")) - welcomeLabel.Wrapping = fyne.TextWrapWord - - m.tabs = container.NewAppTabs( - container.NewTabItem(lang.L("window.lorem_ipsum.tab_name"), welcomeLabel), - ) - m.window.SetContent(m.tabs) - return nil } diff --git a/client/internal/services/core/mainwindow/mainwindow_desktop.go b/client/internal/services/core/mainwindow/mainwindow_desktop.go new file mode 100644 index 0000000..3bcd2ba --- /dev/null +++ b/client/internal/services/core/mainwindow/mainwindow_desktop.go @@ -0,0 +1,23 @@ +package mainwindow + +import ( + "fyne.io/fyne/v2" + "fyne.io/fyne/v2/container" + "fyne.io/fyne/v2/widget" +) + +func (m *mainWindow) initializeMainWindowDesktop() fyne.CanvasObject { + switcherButton := m.initializeSwitcherDesktop() + appNameLabel := widget.NewLabel("Bunker " + m.app.Fyne().Metadata().Custom["Version"]) + sidebarHeader := container.NewVBox(container.NewHBox(switcherButton, appNameLabel), widget.NewSeparator()) + sideBar := container.NewBorder(sidebarHeader, nil, nil, nil) + + splitter := container.NewHSplit(sideBar, container.NewVBox(widget.NewLabel("widget data"), widget.NewSeparator())) + splitter.SetOffset(0.2) + + statusBar := m.initializeDesktopStatusBar() + + mainWidget := container.NewBorder(nil, statusBar, nil, nil, splitter) + + return mainWidget +} diff --git a/client/internal/services/core/mainwindow/mainwindow_desktop_statusbar.go b/client/internal/services/core/mainwindow/mainwindow_desktop_statusbar.go new file mode 100644 index 0000000..48dab60 --- /dev/null +++ b/client/internal/services/core/mainwindow/mainwindow_desktop_statusbar.go @@ -0,0 +1,42 @@ +package mainwindow + +import ( + "bunker/client/internal/widgets" + + "fyne.io/fyne/v2" + "fyne.io/fyne/v2/widget" +) + +func (m *mainWindow) initializeDesktopStatusBar() fyne.CanvasObject { + m.statusBarStatus = widget.NewLabel("Ready.") + m.statusBarProgress = widget.NewProgressBar() + m.statusBarProgress.Hide() + + statusBarForToolbar := widgets.NewToolbarProgressBarWithBar(m.statusBarProgress) + + statusBar := widget.NewToolbar() + statusBar.Append(widgets.NewToolbarLabelWithLabel(m.statusBarStatus)) + statusBar.Append(statusBarForToolbar) + statusBar.Append(widget.NewToolbarSpacer()) + + return statusBar +} + +func (m *mainWindow) SetStatusProgressBarCurrentValue(current float64) { + m.statusBarProgress.SetValue(current) +} + +func (m *mainWindow) SetStatusProgressBarMaxValue(maxValue float64) { + m.statusBarProgress.Max = maxValue +} + +func (m *mainWindow) SetStatus(status string) { + m.statusBarStatus.SetText(status) + + if status == "" { + m.statusBarStatus.SetText("Ready.") + m.statusBarProgress.Hide() + } else { + m.statusBarProgress.Show() + } +} diff --git a/client/internal/services/core/mainwindow/mainwindow_mobile.go b/client/internal/services/core/mainwindow/mainwindow_mobile.go new file mode 100644 index 0000000..1d07ffc --- /dev/null +++ b/client/internal/services/core/mainwindow/mainwindow_mobile.go @@ -0,0 +1,10 @@ +package mainwindow + +import ( + "fyne.io/fyne/v2" + "fyne.io/fyne/v2/widget" +) + +func (m *mainWindow) initializeMainWindowMobile() fyne.CanvasObject { + return widget.NewLabel("Mobile interface not yet implemented.") +} diff --git a/client/internal/services/core/mainwindow/mainwindow_switcher_desktop.go b/client/internal/services/core/mainwindow/mainwindow_switcher_desktop.go new file mode 100644 index 0000000..c8c4f64 --- /dev/null +++ b/client/internal/services/core/mainwindow/mainwindow_switcher_desktop.go @@ -0,0 +1,26 @@ +package mainwindow + +import ( + "fyne.io/fyne/v2" + "fyne.io/fyne/v2/theme" + "fyne.io/fyne/v2/widget" +) + +func (m *mainWindow) initializeSwitcherDesktop() fyne.CanvasObject { + m.logger.Debug("Initializing desktop switcher...") + + btn := widget.NewButtonWithIcon( + "", + m.app.Fyne().Settings().Theme().Icon(theme.IconNameMenu), + m.desktopSwitcherButtonTapped, + ) + + return btn +} + +func (m *mainWindow) desktopSwitcherButtonTapped() { + m.logger.Debug("Showing desktop switcher...") + + popup := widget.NewPopUp(widget.NewLabel("All hail switcher!"), m.window.Canvas()) + popup.ShowAtRelativePosition(fyne.NewPos(0, 0), m.window.Content()) +} diff --git a/client/internal/services/core/mainwindow/mainwindow_switcher_mobiles.go b/client/internal/services/core/mainwindow/mainwindow_switcher_mobiles.go new file mode 100644 index 0000000..f74427f --- /dev/null +++ b/client/internal/services/core/mainwindow/mainwindow_switcher_mobiles.go @@ -0,0 +1,5 @@ +package mainwindow + +func (m *mainWindow) initializeSwitcherMobile() { + m.logger.Debug("Initializing mobile switcher...") +} diff --git a/client/internal/services/core/mainwindow/models/tab.go b/client/internal/services/core/mainwindow/models/tab.go new file mode 100644 index 0000000..7fd5bae --- /dev/null +++ b/client/internal/services/core/mainwindow/models/tab.go @@ -0,0 +1,18 @@ +package models + +import ( + "fyne.io/fyne/v2" + "fyne.io/fyne/v2/canvas" +) + +// Tab is an internal representation of main window's tab that is responsible for showing content. +type Tab struct { + // Name is a name for tab. Won't render by default on desktop, only on mouse hover, but will be rendered on mobiles. + Name string + // Widget is a widget shown in window. + Widget fyne.CanvasObject + // Icon is an icon to show on tab. + Icon canvas.Image + // BadgeCount is a number to show on tab, like unread messages, incompleted tasks, etc. + BadgeCount uint16 +} diff --git a/client/internal/services/core/mainwindow/tabs.go b/client/internal/services/core/mainwindow/tabs.go index 9dd2b4b..8831b7e 100644 --- a/client/internal/services/core/mainwindow/tabs.go +++ b/client/internal/services/core/mainwindow/tabs.go @@ -1,14 +1,8 @@ package mainwindow import ( - "fyne.io/fyne/v2/container" - "fyne.io/fyne/v2/lang" + "bunker/client/internal/services/core/mainwindow/dto" ) -func (m *mainWindow) AddTab(tab *container.TabItem) { - if len(m.tabs.Items) == 1 && m.tabs.Items[0].Text == lang.L("window.lorem_ipsum.tab_name") { - m.tabs.Remove(m.tabs.Items[0]) - } - - m.tabs.Append(tab) +func (m *mainWindow) AddTab(tab *dto.Tab) { } diff --git a/client/internal/services/core/options.go b/client/internal/services/core/options.go index 4ed30df..24e1afe 100644 --- a/client/internal/services/core/options.go +++ b/client/internal/services/core/options.go @@ -6,20 +6,16 @@ import ( "bunker/client/internal/services/core/options/dto" ) -// ServiceNameOptions это название для сервиса работы с настройками. +// ServiceNameOptions is a name for options service which controls options dialog and options storage. const ServiceNameOptions = "core/options" -var ( - // ErrOptions говорит о возникновении ошибки в сервисе работы с настройками. - ErrOptions = errors.New("options core service") - // ErrOptionsIsInvalid говорит о неверной имплементации сервиса работы с настройками. - ErrOptionsIsInvalid = errors.New("options service implementation is invalid") -) +// ErrOptionsIsInvalid appears when options service implementation is invalid. +var ErrOptionsIsInvalid = errors.New("options service implementation is invalid") -// Options это интерфейс для сервиса работы с настройками. +// Options is an interface for options service. type Options interface { - // RegisterOptionsWidget регистрирует виджет настроек, а также необходимые дополнительные параметры. + // RegisterOptionsWidget registers options widget for options dialog. RegisterOptionsWidget(widgetData *dto.OptionPane) error - // ShowOptionsDialog показывает диалог с настройками. Используется только главным окном! + // ShowOptionsDialog shows options dialog. ShowOptionsDialog() } diff --git a/client/internal/services/core/options/options.go b/client/internal/services/core/options/options.go index 1543d51..20276fb 100644 --- a/client/internal/services/core/options/options.go +++ b/client/internal/services/core/options/options.go @@ -1,6 +1,7 @@ package options import ( + "errors" "fmt" "log/slog" @@ -9,7 +10,11 @@ import ( "bunker/client/internal/services/core/options/models" ) -var _ = core.Options(&options{}) +var ( + _ = core.Options(&options{}) + + errOptions = errors.New("options core service") +) type options struct { app *application.Application @@ -18,17 +23,17 @@ type options struct { mainWindow core.MainWindow widgets map[string]*models.OptionPane - widgetsItems []string // для рисования списка Fyne. + widgetsItems []string // for Fyne's list widget. } -// Initialize инициализирует сервис. +// Initialize initializes service. func Initialize(app *application.Application) error { opts := &options{ app: app, } if err := app.RegisterService(opts); err != nil { - return fmt.Errorf("%w: %w", core.ErrOptions, err) + return fmt.Errorf("%w: %w", &errOptions, err) } return nil diff --git a/client/internal/services/core/options/widgets.go b/client/internal/services/core/options/widgets.go index 5fb5689..923f203 100644 --- a/client/internal/services/core/options/widgets.go +++ b/client/internal/services/core/options/widgets.go @@ -4,7 +4,6 @@ import ( "errors" "fmt" - "bunker/client/internal/services/core" "bunker/client/internal/services/core/options/dto" "bunker/client/internal/services/core/options/models" ) @@ -15,7 +14,7 @@ func (o *options) RegisterOptionsWidget(widgetData *dto.OptionPane) error { if _, found := o.widgets[widgetData.Name]; found { return fmt.Errorf( "%w: RegisterOptionsWidget: '%s': %w", - core.ErrOptions, + errOptions, widgetData.Name, errWidgetPaneAlreadyRegistered, ) diff --git a/client/internal/services/core/translations.go b/client/internal/services/core/translations.go index 7708799..264626c 100644 --- a/client/internal/services/core/translations.go +++ b/client/internal/services/core/translations.go @@ -4,15 +4,11 @@ import ( "errors" ) -// ServiceNameTranslations это название для сервиса работы с переводами. +// ServiceNameTranslations is a name for translations service. const ServiceNameTranslations = "core/translations" -var ( - // ErrTranslations говорит о возникновении ошибки в сервисе работы с настройками. - ErrTranslations = errors.New("translations core service") - // ErrTranslationsIsInvalid говорит о неверной имплементации сервиса работы с переводами. - ErrTranslationsIsInvalid = errors.New("translations service implementation is invalid") -) +// ErrTranslationsIsInvalid appears when translations service implementation is invalid +var ErrTranslationsIsInvalid = errors.New("translations service implementation is invalid") -// Translations это интерфейс для сервиса работы с переводами. +// Translations is an interface for translations service. type Translations interface{} diff --git a/client/internal/services/core/translations/translations.go b/client/internal/services/core/translations/translations.go index c34e276..2c5b7e6 100644 --- a/client/internal/services/core/translations/translations.go +++ b/client/internal/services/core/translations/translations.go @@ -1,6 +1,7 @@ package translations import ( + "errors" "fmt" "log/slog" "os" @@ -12,7 +13,11 @@ import ( "fyne.io/fyne/v2/lang" ) -var _ = core.Translations(&translations{}) +var ( + _ = core.Translations(&translations{}) + + errTranslations = errors.New("translations core service") +) type translations struct { app *application.Application @@ -20,14 +25,14 @@ type translations struct { mainWindow core.MainWindow } -// Initialize инициализирует сервис. +// Initialize initializes service. func Initialize(app *application.Application) error { transl := &translations{ app: app, } if err := app.RegisterService(transl); err != nil { - return fmt.Errorf("%w: %w", core.ErrOptions, err) + return fmt.Errorf("%w: %w", errTranslations, err) } return nil @@ -67,7 +72,7 @@ func (t *translations) Initialize() error { t.logger.Info("Current system locale.", "locale", lang.SystemLocale().String(), "LANG", langFromEnv) if err := lang.AddTranslationsFS(langfiles.LangFiles, "files"); err != nil { - return fmt.Errorf("%w: load translations: %w", core.ErrTranslations, err) + return fmt.Errorf("%w: load translations: %w", errTranslations, err) } return nil diff --git a/client/internal/services/features/accounts.go b/client/internal/services/features/accounts.go new file mode 100644 index 0000000..83b0eab --- /dev/null +++ b/client/internal/services/features/accounts.go @@ -0,0 +1,4 @@ +package features + +// ServiceNameAccounts is a name for accounts service. +const ServiceNameAccounts = "features/accounts" diff --git a/client/internal/services/features/accounts/accounts.go b/client/internal/services/features/accounts/accounts.go new file mode 100644 index 0000000..d455a8f --- /dev/null +++ b/client/internal/services/features/accounts/accounts.go @@ -0,0 +1,93 @@ +package accounts + +import ( + "errors" + "fmt" + "log/slog" + + "bunker/client/internal/application" + "bunker/client/internal/services/core" + "bunker/client/internal/services/features" + + "fyne.io/fyne/v2/widget" +) + +var errAccounts = errors.New("accounts feature service") + +type accounts struct { + app *application.Application + logger *slog.Logger + db core.Database + mainWindow core.MainWindow + + loginDialogInstanceAddressEntry *widget.Entry + loginDialogUsernameEntry *widget.Entry + loginDialogPasswordEntry *widget.Entry +} + +// Initialize initializes service. +func Initialize(app *application.Application) error { + accts := &accounts{ + app: app, + } + + if err := app.RegisterService(accts); err != nil { + return fmt.Errorf("%w: %w", errAccounts, err) + } + + return nil +} + +func (a *accounts) Configure() error { + return nil +} + +func (a *accounts) ConnectDependencies() error { + databaseRaw := a.app.Service(core.ServiceNameDatabase) + if databaseRaw == nil { + return fmt.Errorf("connect dependencies: get database service: %w", application.ErrServiceNotFound) + } + + database, valid := databaseRaw.(core.Database) + if !valid { + return fmt.Errorf("connect dependencies: type assert database service: %w", core.ErrDatabaseIsInvalid) + } + + a.db = database + + mainWindowRaw := a.app.Service(core.ServiceNameMainWindow) + if mainWindowRaw == nil { + return fmt.Errorf("connect dependencies: get main window: %w", application.ErrServiceNotFound) + } + + mainWindow, valid := mainWindowRaw.(core.MainWindow) + if !valid { + return fmt.Errorf("connect dependencies: type assert main window: %w", core.ErrMainWindowIsInvalid) + } + + a.mainWindow = mainWindow + + return nil +} + +func (a *accounts) Initialize() error { + a.logger = a.app.NewLogger("service", features.ServiceNameTasks) + + a.logger.Info("Initializing...") + + return nil +} + +func (a *accounts) Name() string { + return features.ServiceNameTasks +} + +func (a *accounts) LaunchStartupTasks() error { + a.loginDialogShow() + + return nil +} + +func (a *accounts) Shutdown() error { + return nil +} diff --git a/client/internal/services/features/accounts/login_dialog.go b/client/internal/services/features/accounts/login_dialog.go new file mode 100644 index 0000000..fad4c01 --- /dev/null +++ b/client/internal/services/features/accounts/login_dialog.go @@ -0,0 +1,53 @@ +package accounts + +import ( + "fyne.io/fyne/v2/container" + "fyne.io/fyne/v2/dialog" + "fyne.io/fyne/v2/widget" +) + +func (a *accounts) loginDialogLogin() { + a.logger.Info( + "Trying to log in...", + "instance", a.loginDialogInstanceAddressEntry.Text, + "username", a.loginDialogUsernameEntry.Text, + "password", a.loginDialogPasswordEntry.Text, + ) +} + +func (a *accounts) loginDialogShow() { + if a.loginDialogInstanceAddressEntry == nil { + a.loginDialogInstanceAddressEntry = widget.NewEntry() + a.loginDialogInstanceAddressEntry.SetText("http://localhost:53400") + } + + if a.loginDialogUsernameEntry == nil { + a.loginDialogUsernameEntry = widget.NewEntry() + a.loginDialogUsernameEntry.SetPlaceHolder("username") + } + + if a.loginDialogPasswordEntry == nil { + a.loginDialogPasswordEntry = widget.NewEntry() + } else { + a.loginDialogPasswordEntry.SetText("") + } + + loginForm := widget.NewForm( + widget.NewFormItem("Instance address:", a.loginDialogInstanceAddressEntry), + widget.NewFormItem("Login:", a.loginDialogUsernameEntry), + widget.NewFormItem("Password:", a.loginDialogPasswordEntry), + ) + + loginButton := widget.NewButton("Log in", a.loginDialogLogin) + + loginDialogContent := container.NewBorder(nil, loginButton, nil, nil, loginForm) + + dialog := dialog.NewCustomWithoutButtons( + "Login to Bunker instance", + loginDialogContent, + a.mainWindow.MainWindow(), + ) + dialog.Resize(dialog.MinSize().AddWidthHeight(200, 0)) + + dialog.Show() +} diff --git a/client/internal/services/features/tasks.go b/client/internal/services/features/tasks.go new file mode 100644 index 0000000..f00a589 --- /dev/null +++ b/client/internal/services/features/tasks.go @@ -0,0 +1,4 @@ +package features + +// ServiceNameTasks is a name for tasks service. +const ServiceNameTasks = "features/tasks" diff --git a/client/internal/services/features/tasks/tasks.go b/client/internal/services/features/tasks/tasks.go new file mode 100644 index 0000000..25054dd --- /dev/null +++ b/client/internal/services/features/tasks/tasks.go @@ -0,0 +1,85 @@ +package tasks + +import ( + "errors" + "fmt" + "log/slog" + + "bunker/client/internal/application" + "bunker/client/internal/services/core" + "bunker/client/internal/services/features" +) + +var errTasks = errors.New("tasks feature service") + +type tasks struct { + app *application.Application + logger *slog.Logger + db core.Database + mainWindow core.MainWindow +} + +// Initialize initializes service. +func Initialize(app *application.Application) error { + tsks := &tasks{ + app: app, + } + + if err := app.RegisterService(tsks); err != nil { + return fmt.Errorf("%w: %w", errTasks, err) + } + + return nil +} + +func (t *tasks) Configure() error { + return nil +} + +func (t *tasks) ConnectDependencies() error { + databaseRaw := t.app.Service(core.ServiceNameDatabase) + if databaseRaw == nil { + return fmt.Errorf("connect dependencies: get database service: %w", application.ErrServiceNotFound) + } + + database, valid := databaseRaw.(core.Database) + if !valid { + return fmt.Errorf("connect dependencies: type assert database service: %w", core.ErrDatabaseIsInvalid) + } + + t.db = database + + mainWindowRaw := t.app.Service(core.ServiceNameMainWindow) + if mainWindowRaw == nil { + return fmt.Errorf("connect dependencies: get main window: %w", application.ErrServiceNotFound) + } + + mainWindow, valid := mainWindowRaw.(core.MainWindow) + if !valid { + return fmt.Errorf("connect dependencies: type assert main window: %w", core.ErrMainWindowIsInvalid) + } + + t.mainWindow = mainWindow + + return nil +} + +func (t *tasks) Initialize() error { + t.logger = t.app.NewLogger("service", features.ServiceNameTasks) + + t.logger.Info("Initializing...") + + return nil +} + +func (t *tasks) Name() string { + return features.ServiceNameAccounts +} + +func (t *tasks) LaunchStartupTasks() error { + return nil +} + +func (t *tasks) Shutdown() error { + return nil +} diff --git a/client/internal/widgets/toolbar_label.go b/client/internal/widgets/toolbar_label.go new file mode 100644 index 0000000..57d8746 --- /dev/null +++ b/client/internal/widgets/toolbar_label.go @@ -0,0 +1,21 @@ +package widgets + +import ( + "fyne.io/fyne/v2" + "fyne.io/fyne/v2/widget" +) + +// ToolbarLabel is a label widget for toolbar. +type ToolbarLabel struct { + *widget.Label +} + +// NewToolbarLabelWithLabel creates new toolbar label with passed label as base widget. +func NewToolbarLabelWithLabel(label *widget.Label) widget.ToolbarItem { + return &ToolbarLabel{label} +} + +// ToolbarObject returns toolbar item. +func (tl *ToolbarLabel) ToolbarObject() fyne.CanvasObject { + return tl.Label +} diff --git a/client/internal/widgets/toolbar_progressbar.go b/client/internal/widgets/toolbar_progressbar.go new file mode 100644 index 0000000..c2621ee --- /dev/null +++ b/client/internal/widgets/toolbar_progressbar.go @@ -0,0 +1,23 @@ +package widgets + +import ( + "fyne.io/fyne/v2" + "fyne.io/fyne/v2/widget" +) + +// ToolbarProgressBar is a progressbar widget for toolbar. +type ToolbarProgressBar struct { + *widget.ProgressBar +} + +// NewToolbarProgressBarWithBar creates new progressbar for toolbar with provided progressbar. +func NewToolbarProgressBarWithBar(bar *widget.ProgressBar) *ToolbarProgressBar { + return &ToolbarProgressBar{ + bar, + } +} + +// ToolbarObject returns toolbar item. +func (tl *ToolbarProgressBar) ToolbarObject() fyne.CanvasObject { + return tl +}