diff options
Diffstat (limited to 'main.go')
| -rw-r--r-- | main.go | 235 |
1 files changed, 126 insertions, 109 deletions
@@ -1,144 +1,161 @@ package main import ( - "fmt" - "os" - "time" - - "fyne.io/fyne/v2" - "fyne.io/fyne/v2/app" - "fyne.io/fyne/v2/container" - "fyne.io/fyne/v2/dialog" - "fyne.io/fyne/v2/widget" + "errors" + "fmt" + "log" + "os" + "path/filepath" + "time" + + "fyne.io/fyne/v2" + "fyne.io/fyne/v2/app" + "fyne.io/fyne/v2/container" + "fyne.io/fyne/v2/dialog" + "fyne.io/fyne/v2/widget" ) const ( - appId = "org.buetow.quicklogger" + appID = "org.buetow.quicklogger" placeholderText = "Enter text here..." maxTextLength = 5000 // Limit text length to prevent performance issues ) -var ( - defaultDirectory = "." -) +const defaultDirectory = "." var windowSize = fyne.NewSize(400, 100) -func createPreferenceWindow(a fyne.App) fyne.Window { - window := a.NewWindow("Preferences") - directoryPreference := widget.NewEntry() - directoryPreference.SetText(a.Preferences().StringWithFallback("Directory", defaultDirectory)) - - window.SetContent(container.NewVBox( - container.NewVBox( - widget.NewLabel("Directory:"), - directoryPreference, - ), - container.NewHBox( - widget.NewButton("Save", func() { - a.Preferences().SetString("Directory", directoryPreference.Text) - window.Hide() - }), - ))) - window.Resize(windowSize) - - return window +// logEntry writes text to a timestamped markdown file in dir. +// Separates persistence logic from the UI so it can be tested independently. +func logEntry(dir, text string) error { + filename := filepath.Join(dir, "ql-"+time.Now().Format("060102-150405")+".md") + return os.WriteFile(filename, []byte(text), 0o644) } -func createMainWindow(a fyne.App) fyne.Window { - // Create main window - window := a.NewWindow("Quick logger") - +// newInputWidget creates the multi-line text entry with platform-appropriate +// wrapping and row count settings. +func newInputWidget() *widget.Entry { input := widget.NewMultiLineEntry() - // Optimization 1: Disable word wrapping on Android to improve performance - // Word wrapping causes expensive recalculations on every text change + input.SetPlaceHolder(placeholderText) + + // On mobile, disable word wrapping and reduce visible rows to limit + // expensive recalculations and rendering area. if fyne.CurrentDevice().IsMobile() { input.Wrapping = fyne.TextWrapOff + input.SetMinRowsVisible(10) } else { input.Wrapping = fyne.TextWrapWord + input.SetMinRowsVisible(30) } - input.SetPlaceHolder(placeholderText) - // Optimization 2: Reduce visible rows on mobile to limit rendering area - if fyne.CurrentDevice().IsMobile() { - input.SetMinRowsVisible(10) - } else { - input.SetMinRowsVisible(30) - } - - // Optimization 3: Add text length indicator - charCount := widget.NewLabel("0 chars") - // Optimization 4: Throttle text changes with validation - input.OnChanged = func(text string) { - // Update character count - charCount.SetText(fmt.Sprintf("%d chars", len(text))) + return input +} + +func createPreferenceWindow(a fyne.App) fyne.Window { + window := a.NewWindow("Preferences") + directoryPreference := widget.NewEntry() + directoryPreference.SetText(a.Preferences().StringWithFallback("Directory", defaultDirectory)) + + window.SetContent(container.NewVBox( + container.NewVBox( + widget.NewLabel("Directory:"), + directoryPreference, + ), + container.NewHBox( + widget.NewButton("Save", func() { + a.Preferences().SetString("Directory", directoryPreference.Text) + window.Hide() + }), + ))) + window.Resize(windowSize) + + return window +} + +func createMainWindow(a fyne.App) fyne.Window { + window := a.NewWindow("Quick logger") + input := newInputWidget() + charCount := widget.NewLabel("0 chars") - // Warn if text is getting too long - if len(text) > maxTextLength { + // Track whether the length warning has been shown so we don't fire a + // modal dialog on every keystroke above the limit. + warnShown := false + input.OnChanged = func(text string) { + charCount.SetText(fmt.Sprintf("%d chars", len(text))) + if len(text) > maxTextLength && !warnShown { + warnShown = true dialog.ShowInformation("Text Limit", fmt.Sprintf("Text is getting long (%d chars). Consider logging to avoid performance issues.", len(text)), window) + } else if len(text) <= maxTextLength { + warnShown = false + } + } + + // resetInput clears the text entry and character count. + resetInput := func() { + input.SetText("") + charCount.SetText("0 chars") + } + + logTextButton := widget.NewButton("Log text", func() { + dir := a.Preferences().StringWithFallback("Directory", defaultDirectory) + if err := logEntry(dir, input.Text); err != nil { + dialog.ShowError(err, window) + return + } + resetInput() + }) + + clearButton := widget.NewButton("Clear", func() { + resetInput() + window.Canvas().Focus(input) + }) + + // loadSharedText reads Android-shared text from cache and populates the input. + // Used both at startup and when the app returns to the foreground. + // A missing cache file is expected (no share pending); real errors are logged. + loadSharedText := func() { + txt, err := readSharedFromCache() + if err != nil { + if !errors.Is(err, os.ErrNotExist) { + log.Printf("readSharedFromCache: %v", err) + } + return + } + if txt != "" { + input.SetText(txt) + charCount.SetText(fmt.Sprintf("%d chars", len(txt))) + window.Canvas().Focus(input) } } - logTextButton := widget.NewButton("Log text", func() { - filename := fmt.Sprintf("%s/ql-%s.md", - a.Preferences().StringWithFallback("Directory", defaultDirectory), - time.Now().Format("060102-150405"), - ) - if err := os.WriteFile(filename, []byte(input.Text), 0644); err != nil { - dialog.ShowError(err, window) - return - } - input.SetText("") - input.SetPlaceHolder(placeholderText) - // Reset character count - charCount.SetText("0 chars") - }) - - // Optimization 5: Add clear button for quick text clearing - clearButton := widget.NewButton("Clear", func() { - input.SetText("") - charCount.SetText("0 chars") - window.Canvas().Focus(input) - }) - - // If running on Android and shared text file exists, load it into the input - if fyne.CurrentDevice().IsMobile() { - if txt, err := readSharedFromCache(); err == nil && txt != "" { - input.SetText(txt) - charCount.SetText(fmt.Sprintf("%d chars", len(txt))) - } - } - - window.SetContent(container.NewVBox( - input, - container.NewHBox( - logTextButton, - clearButton, - widget.NewButton("Preferences", func() { - createPreferenceWindow(a).Show() - }), - charCount, // Show character count - ), - )) - window.Resize(windowSize) - window.Canvas().Focus(input) - - // On Android, also check for new shared text whenever app returns to foreground. - if lc := a.Lifecycle(); lc != nil { - lc.SetOnEnteredForeground(func() { - if txt, err := readSharedFromCache(); err == nil && txt != "" { - input.SetText(txt) - charCount.SetText(fmt.Sprintf("%d chars", len(txt))) - window.Canvas().Focus(input) - } - }) - } + if fyne.CurrentDevice().IsMobile() { + loadSharedText() + } + + window.SetContent(container.NewVBox( + input, + container.NewHBox( + logTextButton, + clearButton, + widget.NewButton("Preferences", func() { + createPreferenceWindow(a).Show() + }), + charCount, + ), + )) + window.Resize(windowSize) + window.Canvas().Focus(input) + + // On Android, also check for new shared text whenever app returns to foreground. + if lc := a.Lifecycle(); lc != nil { + lc.SetOnEnteredForeground(loadSharedText) + } return window } func main() { - createMainWindow(app.NewWithID(appId)).ShowAndRun() + createMainWindow(app.NewWithID(appID)).ShowAndRun() } |
