Add internal/api package to fetch server list from /api/servers.json endpoint Replace hardcoded server list with dynamic API call Implement loading states, error handling, and refresh capability
226 lines
5.8 KiB
Go
226 lines
5.8 KiB
Go
// package screens implements application screens (login, main menu, settings).
|
|
package screens
|
|
|
|
import (
|
|
"fmt"
|
|
|
|
"fyne.io/fyne/v2"
|
|
"fyne.io/fyne/v2/container"
|
|
"fyne.io/fyne/v2/dialog"
|
|
"fyne.io/fyne/v2/layout"
|
|
"fyne.io/fyne/v2/widget"
|
|
|
|
"gitea.mrixs.me/Mrixs/MrixsCraft-launcher/internal/auth"
|
|
"gitea.mrixs.me/Mrixs/MrixsCraft-launcher/internal/ui/components"
|
|
)
|
|
|
|
// Modpack represents a single server/modpack entry from the backend.
|
|
type Modpack struct {
|
|
Slug string
|
|
Name string
|
|
}
|
|
|
|
// MainScreen builds the primary launcher window content.
|
|
func MainScreen(
|
|
w fyne.Window,
|
|
client *auth.Client,
|
|
session *auth.Session,
|
|
serverList []Modpack,
|
|
onPlay func(),
|
|
onSettings func(),
|
|
onServerListRefresh func(),
|
|
isServerListLoading bool,
|
|
serverListError error,
|
|
) fyne.CanvasObject {
|
|
|
|
// ── Sidebar (Left) ──────────────────────────────────────
|
|
var sidebar fyne.CanvasObject
|
|
if isServerListLoading {
|
|
// Show loading state
|
|
loadingLabel := widget.NewLabelWithStyle("Loading server list...", fyne.TextAlignCenter, fyne.TextStyle{})
|
|
sidebar = container.NewVScroll(container.NewVBox(loadingLabel))
|
|
} else if len(serverList) == 0 && serverListError == nil {
|
|
// Show empty state (no error, just empty list)
|
|
emptyLabel := widget.NewLabelWithStyle("No servers available", fyne.TextAlignCenter, fyne.TextStyle{})
|
|
sidebar = container.NewVScroll(container.NewVBox(emptyLabel))
|
|
} else if serverListError != nil {
|
|
// Show error state
|
|
errorLabel := widget.NewLabelWithStyle("Failed to load server list", fyne.TextAlignCenter, fyne.TextStyle{})
|
|
detailLabel := widget.NewLabel(serverListError.Error())
|
|
detailLabel.Wrapping = fyne.TextWrapWord
|
|
retryBtn := components.SettingsButton(onServerListRefresh)
|
|
retryBtn.Text = "Retry"
|
|
sidebar = container.NewVScroll(container.NewVBox(
|
|
errorLabel,
|
|
detailLabel,
|
|
retryBtn,
|
|
))
|
|
} else {
|
|
// Show normal server list
|
|
serverLabels := make([]*widget.Label, len(serverList))
|
|
serverContainer := container.NewVBox()
|
|
|
|
for i, sp := range serverList {
|
|
label := widget.NewLabel(sp.Name)
|
|
serverLabels[i] = label
|
|
index := i
|
|
card := components.ServerCard(sp.Name, index == 0, func() {
|
|
for j, l := range serverLabels {
|
|
l.TextStyle.Bold = (j == index)
|
|
l.Refresh()
|
|
}
|
|
})
|
|
serverContainer.Add(card)
|
|
}
|
|
|
|
sidebar = container.NewVScroll(serverContainer)
|
|
}
|
|
|
|
// ── Center (background + info) ──────────────────────────
|
|
bg := widget.NewRichTextFromMarkdown(
|
|
"# Welcome to MrixsCraft\n\n" +
|
|
"Select a server from the left panel and press **PLAY**.\n\n" +
|
|
"---\n\n" +
|
|
"*Version: dev*",
|
|
)
|
|
center := container.NewMax(widget.NewCard("", "", bg))
|
|
|
|
// ── Bottom bar ──────────────────────────────────────────
|
|
avatar := components.AvatarImage()
|
|
|
|
displayName := "Not logged in"
|
|
if session != nil && session.Username != "" {
|
|
displayName = session.Username
|
|
}
|
|
usernameLabel := widget.NewLabel(displayName)
|
|
|
|
logoutBtn := components.LogoutButton(func() {
|
|
dialog.ShowConfirm(
|
|
"Logout",
|
|
"Are you sure you want to log out?",
|
|
func(confirmed bool) {
|
|
if confirmed {
|
|
_ = auth.Delete()
|
|
w.Close()
|
|
}
|
|
},
|
|
w,
|
|
)
|
|
})
|
|
|
|
bottomLeft := container.NewHBox(
|
|
avatar,
|
|
usernameLabel,
|
|
layout.NewSpacer(),
|
|
logoutBtn,
|
|
)
|
|
|
|
progress := components.NewProgressBar()
|
|
|
|
playBtn := components.PlayButton(onPlay)
|
|
settingsBtn := components.SettingsButton(onSettings)
|
|
|
|
// Refresh button for server list
|
|
refreshBtn := components.SettingsButton(onServerListRefresh)
|
|
|
|
bottomRight := container.NewHBox(
|
|
settingsBtn,
|
|
widget.NewSeparator(),
|
|
refreshBtn,
|
|
widget.NewSeparator(),
|
|
playBtn,
|
|
)
|
|
|
|
bottomCenter := container.NewMax(progress)
|
|
|
|
bottom := container.NewBorder(
|
|
nil, nil,
|
|
bottomLeft,
|
|
bottomRight,
|
|
bottomCenter,
|
|
)
|
|
|
|
// ── Assemble ────────────────────────────────────────────
|
|
return container.NewBorder(
|
|
nil,
|
|
bottom,
|
|
sidebar,
|
|
nil,
|
|
center,
|
|
)
|
|
}
|
|
|
|
// LoginScreen builds a modal login form.
|
|
func LoginScreen(w fyne.Window, client *auth.Client, onLogin func(*auth.Session)) {
|
|
username := widget.NewEntry()
|
|
username.SetPlaceHolder("Username or email")
|
|
|
|
password := widget.NewPasswordEntry()
|
|
password.SetPlaceHolder("Password")
|
|
|
|
form := widget.NewForm(
|
|
widget.NewFormItem("Username", username),
|
|
widget.NewFormItem("Password", password),
|
|
)
|
|
|
|
dialog.ShowCustomConfirm(
|
|
"Login — MrixsCraft",
|
|
"Login",
|
|
"Cancel",
|
|
form,
|
|
func(confirmed bool) {
|
|
if !confirmed {
|
|
return
|
|
}
|
|
sess, err := client.Authenticate(username.Text, password.Text)
|
|
if err != nil {
|
|
dialog.ShowError(err, w)
|
|
return
|
|
}
|
|
if err := sess.Save(); err != nil {
|
|
dialog.ShowError(err, w)
|
|
return
|
|
}
|
|
onLogin(sess)
|
|
},
|
|
w,
|
|
)
|
|
}
|
|
|
|
// SettingsScreen builds the settings modal.
|
|
func SettingsScreen(w fyne.Window, onSave func(memoryMB int, extraArgs string)) {
|
|
memorySlider := widget.NewSlider(1024, 16384)
|
|
memorySlider.Step = 512
|
|
memorySlider.Value = 4096
|
|
|
|
memLabel := widget.NewLabel("4096 MB")
|
|
memorySlider.OnChanged = func(v float64) {
|
|
memLabel.SetText(fmt.Sprintf("%.0f MB", v))
|
|
}
|
|
|
|
extraArgs := widget.NewMultiLineEntry()
|
|
extraArgs.SetPlaceHolder("e.g. -XX:+UseG1GC -XX:MaxGCPauseMillis=50")
|
|
|
|
content := container.NewVBox(
|
|
widget.NewLabelWithStyle("Memory (RAM)", fyne.TextAlignLeading, fyne.TextStyle{Bold: true}),
|
|
memLabel,
|
|
memorySlider,
|
|
widget.NewSeparator(),
|
|
widget.NewLabelWithStyle("Extra JVM arguments", fyne.TextAlignLeading, fyne.TextStyle{}),
|
|
extraArgs,
|
|
)
|
|
|
|
dialog.ShowCustomConfirm(
|
|
"Settings",
|
|
"Save",
|
|
"Cancel",
|
|
content,
|
|
func(confirmed bool) {
|
|
if confirmed {
|
|
onSave(int(memorySlider.Value), extraArgs.Text)
|
|
}
|
|
},
|
|
w,
|
|
)
|
|
}
|