diff --git a/APK.md b/APK.md
index d9c7d0a..5fbb7d6 100644
--- a/APK.md
+++ b/APK.md
@@ -29,6 +29,11 @@ The repo tracks `gogio` as a Go tool, so the build runs through:
```sh
go tool gogio -target android ...
+```
+
+The Android build uses the branded icon asset at:
+
+- `assets/keepassgo-icon.png`
Note:
@@ -41,4 +46,3 @@ Note:
rendered correctly with `gioui.org v0.8.0` on the same emulator and SDK/JDK
pipeline. KeePassGO is pinned to the working Gio line until that regression is
understood upstream.
-```
diff --git a/Makefile b/Makefile
index 0aaa63c..0e133f1 100644
--- a/Makefile
+++ b/Makefile
@@ -28,4 +28,5 @@ apk:
-version $(APK_VERSION) \
-minsdk $(ANDROID_MIN_SDK) \
-targetsdk $(ANDROID_TARGET_SDK) \
+ -icon assets/keepassgo-icon.png \
.
diff --git a/README.md b/README.md
index e7a3a16..ebfce1f 100644
--- a/README.md
+++ b/README.md
@@ -58,13 +58,13 @@ go test ./...
Install:
```bash
-go install gioui.org/cmd/gogio@latest
+go get -tool gioui.org/cmd/gogio@latest
```
Package:
```bash
-gogio -target android .
+go tool gogio -target android -icon assets/keepassgo-icon.png .
```
You will need the Android SDK and NDK installed and configured for real device or release packaging.
diff --git a/assets/assets.go b/assets/assets.go
new file mode 100644
index 0000000..30fcb3d
--- /dev/null
+++ b/assets/assets.go
@@ -0,0 +1,23 @@
+package assets
+
+import (
+ "bytes"
+ "embed"
+ "image"
+ "image/png"
+)
+
+//go:embed *.png *.svg
+var files embed.FS
+
+func MustPNG(name string) image.Image {
+ data, err := files.ReadFile(name)
+ if err != nil {
+ panic(err)
+ }
+ img, err := png.Decode(bytes.NewReader(data))
+ if err != nil {
+ panic(err)
+ }
+ return img
+}
diff --git a/assets/keepassgo-icon.png b/assets/keepassgo-icon.png
new file mode 100644
index 0000000..c8b3e98
Binary files /dev/null and b/assets/keepassgo-icon.png differ
diff --git a/assets/keepassgo-icon.svg b/assets/keepassgo-icon.svg
new file mode 100644
index 0000000..302102f
--- /dev/null
+++ b/assets/keepassgo-icon.svg
@@ -0,0 +1,18 @@
+
diff --git a/assets/keepassgo-logo-horizontal.png b/assets/keepassgo-logo-horizontal.png
new file mode 100644
index 0000000..ff22b6e
Binary files /dev/null and b/assets/keepassgo-logo-horizontal.png differ
diff --git a/assets/keepassgo-logo-horizontal.svg b/assets/keepassgo-logo-horizontal.svg
new file mode 100644
index 0000000..25ec1be
--- /dev/null
+++ b/assets/keepassgo-logo-horizontal.svg
@@ -0,0 +1,22 @@
+
diff --git a/assets/keepassgo-splash-light.svg b/assets/keepassgo-splash-light.svg
new file mode 100644
index 0000000..c56b8d2
--- /dev/null
+++ b/assets/keepassgo-splash-light.svg
@@ -0,0 +1,75 @@
+
diff --git a/assets/keepassgo-splash-square.png b/assets/keepassgo-splash-square.png
new file mode 100644
index 0000000..4000819
Binary files /dev/null and b/assets/keepassgo-splash-square.png differ
diff --git a/assets/keepassgo-splash-square.svg b/assets/keepassgo-splash-square.svg
new file mode 100644
index 0000000..e855192
--- /dev/null
+++ b/assets/keepassgo-splash-square.svg
@@ -0,0 +1,41 @@
+
diff --git a/buildapk/config.go b/buildapk/config.go
index 100d750..84cd13e 100644
--- a/buildapk/config.go
+++ b/buildapk/config.go
@@ -15,6 +15,7 @@ const (
DefaultVersion = "0.1.0.1"
DefaultMinSDK = "28"
DefaultTargetSDK = "35"
+ DefaultIconPath = "assets/keepassgo-icon.png"
)
type Config struct {
@@ -26,6 +27,7 @@ type Config struct {
Version string
MinSDK string
TargetSDK string
+ IconPath string
}
func DefaultConfig() Config {
@@ -38,6 +40,7 @@ func DefaultConfig() Config {
Version: DefaultVersion,
MinSDK: DefaultMinSDK,
TargetSDK: DefaultTargetSDK,
+ IconPath: DefaultIconPath,
}
}
@@ -50,6 +53,7 @@ func (c Config) GogioArgs() []string {
"-version", c.Version,
"-minsdk", c.MinSDK,
"-targetsdk", c.TargetSDK,
+ "-icon", c.IconPath,
".",
}
}
@@ -73,6 +77,9 @@ func (c Config) Validate() error {
if !isDir(filepath.Join(c.SDKRoot, "build-tools")) {
return fmt.Errorf("Android build-tools are missing")
}
+ if !isFile(c.IconPath) {
+ return fmt.Errorf("Android icon asset is missing: %s", c.IconPath)
+ }
return nil
}
@@ -88,3 +95,8 @@ func isExecutable(path string) bool {
}
return info.Mode()&0o111 != 0
}
+
+func isFile(path string) bool {
+ info, err := os.Stat(path)
+ return err == nil && !info.IsDir()
+}
diff --git a/buildapk/config_test.go b/buildapk/config_test.go
index c6be37f..91c2fd4 100644
--- a/buildapk/config_test.go
+++ b/buildapk/config_test.go
@@ -19,6 +19,7 @@ func TestDefaultConfigGogioArgs(t *testing.T) {
"-version", DefaultVersion,
"-minsdk", DefaultMinSDK,
"-targetsdk", DefaultTargetSDK,
+ "-icon", DefaultIconPath,
".",
}
@@ -53,6 +54,11 @@ func TestValidateAcceptsCompleteAndroidToolchainLayout(t *testing.T) {
Version: DefaultVersion,
MinSDK: DefaultMinSDK,
TargetSDK: DefaultTargetSDK,
+ IconPath: filepath.Join(root, "icon.png"),
+ }
+
+ if err := os.WriteFile(cfg.IconPath, []byte("png"), 0o644); err != nil {
+ t.Fatalf("WriteFile(%q) error = %v", cfg.IconPath, err)
}
if err := cfg.Validate(); err != nil {
diff --git a/main.go b/main.go
index 9741d21..466c47b 100644
--- a/main.go
+++ b/main.go
@@ -29,6 +29,7 @@ import (
"git.julianfamily.org/keepassgo/apiapproval"
"git.julianfamily.org/keepassgo/apiaudit"
"git.julianfamily.org/keepassgo/apitokens"
+ keepassassets "git.julianfamily.org/keepassgo/assets"
"git.julianfamily.org/keepassgo/appstate"
"git.julianfamily.org/keepassgo/clipboard"
"git.julianfamily.org/keepassgo/passwords"
@@ -130,6 +131,8 @@ const (
type ui struct {
mode string
theme *material.Theme
+ logoHorizontal paint.ImageOp
+ splashSquare paint.ImageOp
search widget.Editor
vaultPath widget.Editor
saveAsPath widget.Editor
@@ -342,8 +345,10 @@ func newUIWithState(mode string, sess appstate.CurrentSession, paths statePaths)
th.Palette.ContrastFg = color.NRGBA{R: 255, G: 252, B: 247, A: 255}
u := &ui{
- mode: mode,
- theme: th,
+ mode: mode,
+ theme: th,
+ logoHorizontal: paint.NewImageOp(keepassassets.MustPNG("keepassgo-logo-horizontal.png")),
+ splashSquare: paint.NewImageOp(keepassassets.MustPNG("keepassgo-splash-square.png")),
search: widget.Editor{
SingleLine: true,
Submit: false,
@@ -2040,6 +2045,8 @@ func (u *ui) lifecycleScreen(gtx layout.Context) layout.Dimensions {
}
return panel(gtx, func(gtx layout.Context) layout.Dimensions {
rows := []layout.Widget{
+ u.lifecycleBranding,
+ layout.Spacer{Height: unit.Dp(8)}.Layout,
u.lifecycleControls,
}
return material.List(u.theme, &u.lifecycleList).Layout(gtx, len(rows), func(gtx layout.Context, i int) layout.Dimensions {
@@ -2367,9 +2374,7 @@ func (u *ui) header(gtx layout.Context) layout.Dimensions {
return compactCard(gtx, func(gtx layout.Context) layout.Dimensions {
return layout.Flex{Alignment: layout.Middle}.Layout(gtx,
layout.Flexed(1, func(gtx layout.Context) layout.Dimensions {
- lbl := material.Label(u.theme, unit.Sp(20), productName)
- lbl.Color = accentColor
- return lbl.Layout(gtx)
+ return u.brandMark(gtx, 132, 42)
}),
layout.Rigid(u.headerActions),
)
@@ -2381,9 +2386,7 @@ func (u *ui) header(gtx layout.Context) layout.Dimensions {
return card(gtx, func(gtx layout.Context) layout.Dimensions {
return layout.Flex{Alignment: layout.Middle}.Layout(gtx,
layout.Flexed(1, func(gtx layout.Context) layout.Dimensions {
- lbl := material.Label(u.theme, unit.Sp(24), productName)
- lbl.Color = accentColor
- return lbl.Layout(gtx)
+ return u.brandMark(gtx, 196, 56)
}),
layout.Rigid(u.headerActions),
)
diff --git a/ui_branding.go b/ui_branding.go
new file mode 100644
index 0000000..90c9c13
--- /dev/null
+++ b/ui_branding.go
@@ -0,0 +1,46 @@
+package main
+
+import (
+ "image"
+
+ "gioui.org/layout"
+ "gioui.org/op/paint"
+ "gioui.org/unit"
+ "gioui.org/widget"
+)
+
+func (u *ui) lifecycleBranding(gtx layout.Context) layout.Dimensions {
+ if u.mode != "phone" {
+ return layout.Dimensions{}
+ }
+ return layout.Center.Layout(gtx, func(gtx layout.Context) layout.Dimensions {
+ return u.brandImage(gtx, u.splashSquare, 132, 132)
+ })
+}
+
+func (u *ui) brandMark(gtx layout.Context, widthDP, heightDP float32) layout.Dimensions {
+ if u.mode == "phone" {
+ return u.brandImage(gtx, u.splashSquare, widthDP, heightDP)
+ }
+ return u.brandImage(gtx, u.logoHorizontal, widthDP, heightDP)
+}
+
+func (u *ui) brandImage(gtx layout.Context, src paint.ImageOp, widthDP, heightDP float32) layout.Dimensions {
+ width := gtx.Dp(unit.Dp(widthDP))
+ height := gtx.Dp(unit.Dp(heightDP))
+ if width > gtx.Constraints.Max.X {
+ width = gtx.Constraints.Max.X
+ }
+ if height > gtx.Constraints.Max.Y && gtx.Constraints.Max.Y > 0 {
+ height = gtx.Constraints.Max.Y
+ }
+ img := widget.Image{
+ Src: src,
+ Fit: widget.Contain,
+ Position: layout.W,
+ Scale: 1.0 / gtx.Metric.PxPerDp,
+ }
+ gtx.Constraints.Min = image.Point{}
+ gtx.Constraints.Max = image.Pt(width, height)
+ return img.Layout(gtx)
+}