Basic GUI client, login dialog, various comments fixes after copypaste.
	
		
			
	
		
	
	
		
	
		
			Some checks failed
		
		
	
	
		
			
				
	
				Linting and tests / Linting (push) Failing after 6s
				
			
		
		
	
	
				
					
				
			
		
			Some checks failed
		
		
	
	Linting and tests / Linting (push) Failing after 6s
				
			This commit is contained in:
		| @@ -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()) | ||||
| } | ||||
|  | ||||
|   | ||||
							
								
								
									
										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 ( | ||||
| 	"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. | ||||
|   | ||||
							
								
								
									
										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" | ||||
|  | ||||
| 	"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 | ||||
| } | ||||
|  | ||||
|   | ||||
| @@ -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 | ||||
|  | ||||
| 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) { | ||||
| } | ||||
|   | ||||
| @@ -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() | ||||
| } | ||||
|   | ||||
| @@ -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 | ||||
|   | ||||
| @@ -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, | ||||
| 		) | ||||
|   | ||||
| @@ -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{} | ||||
|   | ||||
| @@ -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 | ||||
|   | ||||
							
								
								
									
										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 | ||||
| } | ||||
		Reference in New Issue
	
	Block a user