Basic client and server apps. #41
@ -8,6 +8,8 @@ import (
|
|||||||
"bunker/client/internal/services/core/mainwindow"
|
"bunker/client/internal/services/core/mainwindow"
|
||||||
"bunker/client/internal/services/core/options"
|
"bunker/client/internal/services/core/options"
|
||||||
"bunker/client/internal/services/core/translations"
|
"bunker/client/internal/services/core/translations"
|
||||||
|
"bunker/client/internal/services/features/accounts"
|
||||||
|
"bunker/client/internal/services/features/tasks"
|
||||||
"bunker/commons"
|
"bunker/commons"
|
||||||
|
|
||||||
"fyne.io/fyne/v2"
|
"fyne.io/fyne/v2"
|
||||||
@ -33,6 +35,9 @@ func main() {
|
|||||||
checkError(options.Initialize(app))
|
checkError(options.Initialize(app))
|
||||||
checkError(mainwindow.Initialize(app))
|
checkError(mainwindow.Initialize(app))
|
||||||
|
|
||||||
|
checkError(accounts.Initialize(app))
|
||||||
|
checkError(tasks.Initialize(app))
|
||||||
|
|
||||||
checkError(app.Start())
|
checkError(app.Start())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
13
client/internal/helpers/platform.go
Normal file
13
client/internal/helpers/platform.go
Normal file
@ -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
|
||||||
|
}
|
||||||
|
}
|
@ -3,8 +3,9 @@ package core
|
|||||||
import (
|
import (
|
||||||
"errors"
|
"errors"
|
||||||
|
|
||||||
|
"bunker/client/internal/services/core/mainwindow/dto"
|
||||||
|
|
||||||
"fyne.io/fyne/v2"
|
"fyne.io/fyne/v2"
|
||||||
"fyne.io/fyne/v2/container"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// ServiceNameMainWindow is a name for main window service.
|
// ServiceNameMainWindow is a name for main window service.
|
||||||
@ -20,11 +21,18 @@ var (
|
|||||||
// MainWindow is an interface for main window service.
|
// MainWindow is an interface for main window service.
|
||||||
type MainWindow interface {
|
type MainWindow interface {
|
||||||
// AddTab adds tab in main window.
|
// 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 returns main window instance (e.g. for using as parent with dialogs).
|
||||||
MainWindow() fyne.Window
|
MainWindow() fyne.Window
|
||||||
// RegisterAboutWindowSysInfoHandler registers handler for System Info tab in About dialog.
|
// RegisterAboutWindowSysInfoHandler registers handler for System Info tab in About dialog.
|
||||||
RegisterAboutWindowSysInfoHandler(name string, hndl SysInfoHandler) error
|
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.
|
// SysInfoHandler is a function signature for registering with additional system information handler for About dialog.
|
||||||
|
20
client/internal/services/core/mainwindow/dto/tab.go
Normal file
20
client/internal/services/core/mainwindow/dto/tab.go
Normal file
@ -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
|
||||||
|
}
|
@ -6,12 +6,12 @@ import (
|
|||||||
"os"
|
"os"
|
||||||
|
|
||||||
"bunker/client/internal/application"
|
"bunker/client/internal/application"
|
||||||
|
"bunker/client/internal/helpers"
|
||||||
"bunker/client/internal/services/core"
|
"bunker/client/internal/services/core"
|
||||||
"bunker/client/internal/services/core/mainwindow/models"
|
"bunker/client/internal/services/core/mainwindow/models"
|
||||||
"bunker/commons"
|
"bunker/commons"
|
||||||
|
|
||||||
"fyne.io/fyne/v2"
|
"fyne.io/fyne/v2"
|
||||||
"fyne.io/fyne/v2/container"
|
|
||||||
"fyne.io/fyne/v2/lang"
|
"fyne.io/fyne/v2/lang"
|
||||||
"fyne.io/fyne/v2/widget"
|
"fyne.io/fyne/v2/widget"
|
||||||
)
|
)
|
||||||
@ -19,15 +19,18 @@ import (
|
|||||||
var _ = core.MainWindow(&mainWindow{})
|
var _ = core.MainWindow(&mainWindow{})
|
||||||
|
|
||||||
type mainWindow struct {
|
type mainWindow struct {
|
||||||
app *application.Application
|
app *application.Application
|
||||||
logger *slog.Logger
|
logger *slog.Logger
|
||||||
window fyne.Window
|
window fyne.Window
|
||||||
options core.Options
|
options core.Options
|
||||||
tabs *container.AppTabs
|
tabsWidget *fyne.Container
|
||||||
sysInfoHandlers map[string]*models.SysInfoHandler
|
statusBarProgress *widget.ProgressBar
|
||||||
|
statusBarStatus *widget.Label
|
||||||
|
sysInfoHandlers map[string]*models.SysInfoHandler
|
||||||
|
tabs []*models.Tab
|
||||||
}
|
}
|
||||||
|
|
||||||
// Initialize инициализирует сервис.
|
// Initialize initializes service.
|
||||||
func Initialize(app *application.Application) error {
|
func Initialize(app *application.Application) error {
|
||||||
mainW := &mainWindow{
|
mainW := &mainWindow{
|
||||||
app: app,
|
app: app,
|
||||||
@ -70,20 +73,20 @@ func (m *mainWindow) Initialize() error {
|
|||||||
m.window = m.app.Fyne().NewWindow(lang.L("window.title"))
|
m.window = m.app.Fyne().NewWindow(lang.L("window.title"))
|
||||||
// ToDo: сохранение и восстановление размеров окна.
|
// ToDo: сохранение и восстановление размеров окна.
|
||||||
//nolint:mnd
|
//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)
|
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
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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
|
||||||
|
}
|
@ -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()
|
||||||
|
}
|
||||||
|
}
|
@ -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.")
|
||||||
|
}
|
@ -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())
|
||||||
|
}
|
@ -0,0 +1,5 @@
|
|||||||
|
package mainwindow
|
||||||
|
|
||||||
|
func (m *mainWindow) initializeSwitcherMobile() {
|
||||||
|
m.logger.Debug("Initializing mobile switcher...")
|
||||||
|
}
|
18
client/internal/services/core/mainwindow/models/tab.go
Normal file
18
client/internal/services/core/mainwindow/models/tab.go
Normal file
@ -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
|
||||||
|
}
|
@ -1,14 +1,8 @@
|
|||||||
package mainwindow
|
package mainwindow
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fyne.io/fyne/v2/container"
|
"bunker/client/internal/services/core/mainwindow/dto"
|
||||||
"fyne.io/fyne/v2/lang"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func (m *mainWindow) AddTab(tab *container.TabItem) {
|
func (m *mainWindow) AddTab(tab *dto.Tab) {
|
||||||
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)
|
|
||||||
}
|
}
|
||||||
|
@ -6,20 +6,16 @@ import (
|
|||||||
"bunker/client/internal/services/core/options/dto"
|
"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"
|
const ServiceNameOptions = "core/options"
|
||||||
|
|
||||||
var (
|
// ErrOptionsIsInvalid appears when options service implementation is invalid.
|
||||||
// ErrOptions говорит о возникновении ошибки в сервисе работы с настройками.
|
var ErrOptionsIsInvalid = errors.New("options service implementation is invalid")
|
||||||
ErrOptions = errors.New("options core service")
|
|
||||||
// ErrOptionsIsInvalid говорит о неверной имплементации сервиса работы с настройками.
|
|
||||||
ErrOptionsIsInvalid = errors.New("options service implementation is invalid")
|
|
||||||
)
|
|
||||||
|
|
||||||
// Options это интерфейс для сервиса работы с настройками.
|
// Options is an interface for options service.
|
||||||
type Options interface {
|
type Options interface {
|
||||||
// RegisterOptionsWidget регистрирует виджет настроек, а также необходимые дополнительные параметры.
|
// RegisterOptionsWidget registers options widget for options dialog.
|
||||||
RegisterOptionsWidget(widgetData *dto.OptionPane) error
|
RegisterOptionsWidget(widgetData *dto.OptionPane) error
|
||||||
// ShowOptionsDialog показывает диалог с настройками. Используется только главным окном!
|
// ShowOptionsDialog shows options dialog.
|
||||||
ShowOptionsDialog()
|
ShowOptionsDialog()
|
||||||
}
|
}
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
package options
|
package options
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"log/slog"
|
"log/slog"
|
||||||
|
|
||||||
@ -9,7 +10,11 @@ import (
|
|||||||
"bunker/client/internal/services/core/options/models"
|
"bunker/client/internal/services/core/options/models"
|
||||||
)
|
)
|
||||||
|
|
||||||
var _ = core.Options(&options{})
|
var (
|
||||||
|
_ = core.Options(&options{})
|
||||||
|
|
||||||
|
errOptions = errors.New("options core service")
|
||||||
|
)
|
||||||
|
|
||||||
type options struct {
|
type options struct {
|
||||||
app *application.Application
|
app *application.Application
|
||||||
@ -18,17 +23,17 @@ type options struct {
|
|||||||
mainWindow core.MainWindow
|
mainWindow core.MainWindow
|
||||||
|
|
||||||
widgets map[string]*models.OptionPane
|
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 {
|
func Initialize(app *application.Application) error {
|
||||||
opts := &options{
|
opts := &options{
|
||||||
app: app,
|
app: app,
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := app.RegisterService(opts); err != nil {
|
if err := app.RegisterService(opts); err != nil {
|
||||||
return fmt.Errorf("%w: %w", core.ErrOptions, err)
|
return fmt.Errorf("%w: %w", &errOptions, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
|
@ -4,7 +4,6 @@ import (
|
|||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
|
||||||
"bunker/client/internal/services/core"
|
|
||||||
"bunker/client/internal/services/core/options/dto"
|
"bunker/client/internal/services/core/options/dto"
|
||||||
"bunker/client/internal/services/core/options/models"
|
"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 {
|
if _, found := o.widgets[widgetData.Name]; found {
|
||||||
return fmt.Errorf(
|
return fmt.Errorf(
|
||||||
"%w: RegisterOptionsWidget: '%s': %w",
|
"%w: RegisterOptionsWidget: '%s': %w",
|
||||||
core.ErrOptions,
|
errOptions,
|
||||||
widgetData.Name,
|
widgetData.Name,
|
||||||
errWidgetPaneAlreadyRegistered,
|
errWidgetPaneAlreadyRegistered,
|
||||||
)
|
)
|
||||||
|
@ -4,15 +4,11 @@ import (
|
|||||||
"errors"
|
"errors"
|
||||||
)
|
)
|
||||||
|
|
||||||
// ServiceNameTranslations это название для сервиса работы с переводами.
|
// ServiceNameTranslations is a name for translations service.
|
||||||
const ServiceNameTranslations = "core/translations"
|
const ServiceNameTranslations = "core/translations"
|
||||||
|
|
||||||
var (
|
// ErrTranslationsIsInvalid appears when translations service implementation is invalid
|
||||||
// ErrTranslations говорит о возникновении ошибки в сервисе работы с настройками.
|
var ErrTranslationsIsInvalid = errors.New("translations service implementation is invalid")
|
||||||
ErrTranslations = errors.New("translations core service")
|
|
||||||
// ErrTranslationsIsInvalid говорит о неверной имплементации сервиса работы с переводами.
|
|
||||||
ErrTranslationsIsInvalid = errors.New("translations service implementation is invalid")
|
|
||||||
)
|
|
||||||
|
|
||||||
// Translations это интерфейс для сервиса работы с переводами.
|
// Translations is an interface for translations service.
|
||||||
type Translations interface{}
|
type Translations interface{}
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
package translations
|
package translations
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"log/slog"
|
"log/slog"
|
||||||
"os"
|
"os"
|
||||||
@ -12,7 +13,11 @@ import (
|
|||||||
"fyne.io/fyne/v2/lang"
|
"fyne.io/fyne/v2/lang"
|
||||||
)
|
)
|
||||||
|
|
||||||
var _ = core.Translations(&translations{})
|
var (
|
||||||
|
_ = core.Translations(&translations{})
|
||||||
|
|
||||||
|
errTranslations = errors.New("translations core service")
|
||||||
|
)
|
||||||
|
|
||||||
type translations struct {
|
type translations struct {
|
||||||
app *application.Application
|
app *application.Application
|
||||||
@ -20,14 +25,14 @@ type translations struct {
|
|||||||
mainWindow core.MainWindow
|
mainWindow core.MainWindow
|
||||||
}
|
}
|
||||||
|
|
||||||
// Initialize инициализирует сервис.
|
// Initialize initializes service.
|
||||||
func Initialize(app *application.Application) error {
|
func Initialize(app *application.Application) error {
|
||||||
transl := &translations{
|
transl := &translations{
|
||||||
app: app,
|
app: app,
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := app.RegisterService(transl); err != nil {
|
if err := app.RegisterService(transl); err != nil {
|
||||||
return fmt.Errorf("%w: %w", core.ErrOptions, err)
|
return fmt.Errorf("%w: %w", errTranslations, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
@ -67,7 +72,7 @@ func (t *translations) Initialize() error {
|
|||||||
t.logger.Info("Current system locale.", "locale", lang.SystemLocale().String(), "LANG", langFromEnv)
|
t.logger.Info("Current system locale.", "locale", lang.SystemLocale().String(), "LANG", langFromEnv)
|
||||||
|
|
||||||
if err := lang.AddTranslationsFS(langfiles.LangFiles, "files"); err != nil {
|
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
|
return nil
|
||||||
|
4
client/internal/services/features/accounts.go
Normal file
4
client/internal/services/features/accounts.go
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
package features
|
||||||
|
|
||||||
|
// ServiceNameAccounts is a name for accounts service.
|
||||||
|
const ServiceNameAccounts = "features/accounts"
|
93
client/internal/services/features/accounts/accounts.go
Normal file
93
client/internal/services/features/accounts/accounts.go
Normal file
@ -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
|
||||||
|
}
|
53
client/internal/services/features/accounts/login_dialog.go
Normal file
53
client/internal/services/features/accounts/login_dialog.go
Normal file
@ -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()
|
||||||
|
}
|
4
client/internal/services/features/tasks.go
Normal file
4
client/internal/services/features/tasks.go
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
package features
|
||||||
|
|
||||||
|
// ServiceNameTasks is a name for tasks service.
|
||||||
|
const ServiceNameTasks = "features/tasks"
|
85
client/internal/services/features/tasks/tasks.go
Normal file
85
client/internal/services/features/tasks/tasks.go
Normal file
@ -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
|
||||||
|
}
|
21
client/internal/widgets/toolbar_label.go
Normal file
21
client/internal/widgets/toolbar_label.go
Normal file
@ -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
|
||||||
|
}
|
23
client/internal/widgets/toolbar_progressbar.go
Normal file
23
client/internal/widgets/toolbar_progressbar.go
Normal file
@ -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
|
||||||
|
}
|
Loading…
x
Reference in New Issue
Block a user