Add KeePassGO branding assets

This commit is contained in:
Joe Julian
2026-03-31 22:34:36 -07:00
parent 0a6b7fe5c0
commit 3d53ff8e95
15 changed files with 262 additions and 11 deletions
+5 -1
View File
@@ -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.
```
+1
View File
@@ -28,4 +28,5 @@ apk:
-version $(APK_VERSION) \
-minsdk $(ANDROID_MIN_SDK) \
-targetsdk $(ANDROID_TARGET_SDK) \
-icon assets/keepassgo-icon.png \
.
+2 -2
View File
@@ -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.
+23
View File
@@ -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
}
Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

+18
View File
@@ -0,0 +1,18 @@
<svg xmlns="http://www.w3.org/2000/svg" width="256" height="256" viewBox="0 0 256 256" fill="none">
<title>KeePassGO icon</title>
<desc>Vault-shaped mark with lock, layered record lines, and navigation accent.</desc>
<polygon points="128,18 214,38 214,176 128,218 42,176 42,38" fill="#3F4E63"/>
<polygon points="128,34 198,50 198,166 128,200 58,166 58,50" fill="#55657A" opacity="0.16"/>
<polygon points="128,178 128,218 42,176 42,160" fill="#6D89A8"/>
<rect x="44" y="78" width="62" height="10" rx="2" fill="#FFFFFF" opacity="0.92"/>
<rect x="44" y="98" width="52" height="10" rx="2" fill="#FFFFFF" opacity="0.92"/>
<path d="M100 86C100 70.536 112.536 58 128 58C143.464 58 156 70.536 156 86V106H142V86C142 78.268 135.732 72 128 72C120.268 72 114 78.268 114 86V106H100V86Z" fill="#FFFFFF"/>
<rect x="76" y="102" width="104" height="64" rx="10" fill="#FFFFFF"/>
<rect x="80" y="106" width="96" height="56" rx="8" fill="#E9EDF0"/>
<path d="M128 118C139.046 118 148 126.954 148 138C148 145.669 143.68 152.329 137.338 155.7V173H118.662V155.7C112.32 152.329 108 145.669 108 138C108 126.954 116.954 118 128 118Z" fill="#3F4E63"/>
<circle cx="128" cy="138" r="8" fill="#FFFFFF" opacity="0.2"/>
<path d="M178 130H214V150H166V144L178 130Z" fill="#E79A17"/>
<path d="M178 130H214V140H174L166 144V144L178 130Z" fill="#F0B13D" opacity="0.35"/>
</svg>

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 40 KiB

+22
View File
@@ -0,0 +1,22 @@
<svg xmlns="http://www.w3.org/2000/svg" width="920" height="260" viewBox="0 0 920 260" fill="none">
<title>KeePassGO horizontal logo</title>
<desc>KeePassGO symbol with wordmark for light-theme desktop UI.</desc>
<defs>
<symbol id="kpg-icon" viewBox="0 0 256 256">
<polygon points="128,18 214,38 214,176 128,218 42,176 42,38" fill="#3F4E63"/>
<polygon points="128,34 198,50 198,166 128,200 58,166 58,50" fill="#55657A" opacity="0.16"/>
<polygon points="128,178 128,218 42,176 42,160" fill="#6D89A8"/>
<rect x="44" y="78" width="62" height="10" rx="2" fill="#FFFFFF" opacity="0.92"/>
<rect x="44" y="98" width="52" height="10" rx="2" fill="#FFFFFF" opacity="0.92"/>
<path d="M100 86C100 70.536 112.536 58 128 58C143.464 58 156 70.536 156 86V106H142V86C142 78.268 135.732 72 128 72C120.268 72 114 78.268 114 86V106H100V86Z" fill="#FFFFFF"/>
<rect x="76" y="102" width="104" height="64" rx="10" fill="#FFFFFF"/>
<rect x="80" y="106" width="96" height="56" rx="8" fill="#E9EDF0"/>
<path d="M128 118C139.046 118 148 126.954 148 138C148 145.669 143.68 152.329 137.338 155.7V173H118.662V155.7C112.32 152.329 108 145.669 108 138C108 126.954 116.954 118 128 118Z" fill="#3F4E63"/>
<path d="M178 130H214V150H166V144L178 130Z" fill="#E79A17"/>
<path d="M178 130H214V140H174L166 144V144L178 130Z" fill="#F0B13D" opacity="0.35"/>
</symbol>
</defs>
<rect width="920" height="260" fill="transparent"/>
<use href="#kpg-icon" x="24" y="20" width="176" height="176"/>
<text x="220" y="132" font-family="Inter, Noto Sans, Segoe UI, Arial, sans-serif" font-size="84" font-weight="650" fill="#3F4E63">KeePassGO</text>
</svg>

After

Width:  |  Height:  |  Size: 1.7 KiB

+75
View File
@@ -0,0 +1,75 @@
<svg xmlns="http://www.w3.org/2000/svg" width="1280" height="800" viewBox="0 0 1280 800" fill="none">
<title>KeePassGO splash screen</title>
<desc>Light-theme desktop splash screen with structured panels and the KeePassGO logo.</desc>
<defs>
<symbol id="kpg-icon" viewBox="0 0 256 256">
<polygon points="128,18 214,38 214,176 128,218 42,176 42,38" fill="#3F4E63"/>
<polygon points="128,34 198,50 198,166 128,200 58,166 58,50" fill="#55657A" opacity="0.16"/>
<polygon points="128,178 128,218 42,176 42,160" fill="#6D89A8"/>
<rect x="44" y="78" width="62" height="10" rx="2" fill="#FFFFFF" opacity="0.92"/>
<rect x="44" y="98" width="52" height="10" rx="2" fill="#FFFFFF" opacity="0.92"/>
<path d="M100 86C100 70.536 112.536 58 128 58C143.464 58 156 70.536 156 86V106H142V86C142 78.268 135.732 72 128 72C120.268 72 114 78.268 114 86V106H100V86Z" fill="#FFFFFF"/>
<rect x="76" y="102" width="104" height="64" rx="10" fill="#FFFFFF"/>
<rect x="80" y="106" width="96" height="56" rx="8" fill="#E9EDF0"/>
<path d="M128 118C139.046 118 148 126.954 148 138C148 145.669 143.68 152.329 137.338 155.7V173H118.662V155.7C112.32 152.329 108 145.669 108 138C108 126.954 116.954 118 128 118Z" fill="#3F4E63"/>
<path d="M178 130H214V150H166V144L178 130Z" fill="#E79A17"/>
<path d="M178 130H214V140H174L166 144V144L178 130Z" fill="#F0B13D" opacity="0.35"/>
</symbol>
<linearGradient id="fade" x1="0" y1="0" x2="0" y2="1">
<stop offset="0" stop-color="#D9E0E6" stop-opacity="0.9"/>
<stop offset="1" stop-color="#D9E0E6" stop-opacity="0.2"/>
</linearGradient>
</defs>
<rect width="1280" height="800" fill="#F7F7F5"/>
<g opacity="0.9">
<rect x="48" y="48" width="238" height="188" fill="#EEF2F4"/>
<rect x="286" y="48" width="152" height="104" fill="#F7F7F5" stroke="#C7D0D8"/>
<rect x="438" y="48" width="214" height="104" fill="#E7EDF1"/>
<rect x="652" y="48" width="146" height="188" fill="#F7F7F5" stroke="#C7D0D8"/>
<rect x="798" y="48" width="224" height="118" fill="#EEF2F4"/>
<rect x="1022" y="48" width="210" height="188" fill="#F7F7F5" stroke="#C7D0D8"/>
<rect x="48" y="236" width="128" height="164" fill="#F7F7F5" stroke="#C7D0D8"/>
<rect x="176" y="236" width="262" height="164" fill="#E7EDF1"/>
<rect x="438" y="236" width="160" height="84" fill="#F7F7F5" stroke="#C7D0D8"/>
<rect x="598" y="236" width="200" height="164" fill="#F7F7F5" stroke="#C7D0D8"/>
<rect x="798" y="236" width="434" height="164" fill="#EEF2F4"/>
<rect x="48" y="400" width="224" height="128" fill="#EEF2F4"/>
<rect x="272" y="400" width="166" height="128" fill="#F7F7F5" stroke="#C7D0D8"/>
<rect x="438" y="400" width="360" height="128" fill="#F7F7F5" stroke="#C7D0D8"/>
<rect x="798" y="400" width="152" height="128" fill="#E7EDF1"/>
<rect x="950" y="400" width="282" height="128" fill="#F7F7F5" stroke="#C7D0D8"/>
<rect x="48" y="528" width="114" height="224" fill="#F7F7F5" stroke="#C7D0D8"/>
<rect x="162" y="528" width="276" height="224" fill="#E7EDF1"/>
<rect x="438" y="528" width="212" height="224" fill="#F7F7F5" stroke="#C7D0D8"/>
<rect x="650" y="528" width="318" height="224" fill="#EEF2F4"/>
<rect x="968" y="528" width="264" height="224" fill="#F7F7F5" stroke="#C7D0D8"/>
</g>
<g opacity="0.45">
<path d="M48 152H1232" stroke="#C7D0D8"/>
<path d="M48 320H1232" stroke="#C7D0D8"/>
<path d="M48 528H1232" stroke="#C7D0D8"/>
<path d="M176 48V752" stroke="#C7D0D8"/>
<path d="M438 48V752" stroke="#C7D0D8"/>
<path d="M798 48V752" stroke="#C7D0D8"/>
<path d="M1022 48V752" stroke="#C7D0D8"/>
</g>
<rect x="290" y="190" width="700" height="420" rx="18" fill="#F7F7F5" opacity="0.92"/>
<rect x="290" y="190" width="700" height="420" rx="18" stroke="#C7D0D8"/>
<rect x="290" y="190" width="700" height="120" rx="18" fill="url(#fade)"/>
<use href="#kpg-icon" x="530" y="232" width="220" height="220"/>
<text x="640" y="530" text-anchor="middle" font-family="Inter, Noto Sans, Segoe UI, Arial, sans-serif" font-size="88" font-weight="650" fill="#3F4E63">KeePassGO</text>
<text x="640" y="582" text-anchor="middle" font-family="Inter, Noto Sans, Segoe UI, Arial, sans-serif" font-size="28" font-weight="500" fill="#55657A">KeePass-compatible password manager</text>
<g opacity="0.9">
<rect x="516" y="634" width="248" height="10" rx="5" fill="#D9E0E6"/>
<rect x="516" y="634" width="132" height="10" rx="5" fill="#6D89A8"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 4.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 47 KiB

+41
View File
@@ -0,0 +1,41 @@
<svg xmlns="http://www.w3.org/2000/svg" width="1024" height="1024" viewBox="0 0 1024 1024" fill="none">
<title>KeePassGO square splash preview</title>
<desc>Square crop of the KeePassGO splash system.</desc>
<defs>
<symbol id="kpg-icon" viewBox="0 0 256 256">
<polygon points="128,18 214,38 214,176 128,218 42,176 42,38" fill="#3F4E63"/>
<polygon points="128,34 198,50 198,166 128,200 58,166 58,50" fill="#55657A" opacity="0.16"/>
<polygon points="128,178 128,218 42,176 42,160" fill="#6D89A8"/>
<rect x="44" y="78" width="62" height="10" rx="2" fill="#FFFFFF" opacity="0.92"/>
<rect x="44" y="98" width="52" height="10" rx="2" fill="#FFFFFF" opacity="0.92"/>
<path d="M100 86C100 70.536 112.536 58 128 58C143.464 58 156 70.536 156 86V106H142V86C142 78.268 135.732 72 128 72C120.268 72 114 78.268 114 86V106H100V86Z" fill="#FFFFFF"/>
<rect x="76" y="102" width="104" height="64" rx="10" fill="#FFFFFF"/>
<rect x="80" y="106" width="96" height="56" rx="8" fill="#E9EDF0"/>
<path d="M128 118C139.046 118 148 126.954 148 138C148 145.669 143.68 152.329 137.338 155.7V173H118.662V155.7C112.32 152.329 108 145.669 108 138C108 126.954 116.954 118 128 118Z" fill="#3F4E63"/>
<path d="M178 130H214V150H166V144L178 130Z" fill="#E79A17"/>
<path d="M178 130H214V140H174L166 144V144L178 130Z" fill="#F0B13D" opacity="0.35"/>
</symbol>
</defs>
<rect width="1024" height="1024" fill="#F7F7F5"/>
<g opacity="0.9">
<rect x="32" y="32" width="180" height="184" fill="#EEF2F4"/>
<rect x="212" y="32" width="264" height="140" fill="#F7F7F5" stroke="#C7D0D8"/>
<rect x="476" y="32" width="180" height="184" fill="#E7EDF1"/>
<rect x="656" y="32" width="336" height="140" fill="#F7F7F5" stroke="#C7D0D8"/>
<rect x="656" y="172" width="336" height="180" fill="#EEF2F4"/>
<rect x="32" y="216" width="236" height="210" fill="#F7F7F5" stroke="#C7D0D8"/>
<rect x="268" y="216" width="388" height="210" fill="#EEF2F4"/>
<rect x="32" y="426" width="180" height="268" fill="#E7EDF1"/>
<rect x="212" y="426" width="312" height="268" fill="#F7F7F5" stroke="#C7D0D8"/>
<rect x="524" y="426" width="468" height="268" fill="#F7F7F5" stroke="#C7D0D8"/>
<rect x="32" y="694" width="280" height="298" fill="#F7F7F5" stroke="#C7D0D8"/>
<rect x="312" y="694" width="268" height="298" fill="#EEF2F4"/>
<rect x="580" y="694" width="412" height="298" fill="#F7F7F5" stroke="#C7D0D8"/>
</g>
<rect x="162" y="190" width="700" height="644" rx="24" fill="#F7F7F5" opacity="0.95"/>
<rect x="162" y="190" width="700" height="644" rx="24" stroke="#C7D0D8"/>
<use href="#kpg-icon" x="332" y="252" width="360" height="360"/>
<text x="512" y="680" text-anchor="middle" font-family="Inter, Noto Sans, Segoe UI, Arial, sans-serif" font-size="88" font-weight="650" fill="#3F4E63">KeePassGO</text>
<text x="512" y="740" text-anchor="middle" font-family="Inter, Noto Sans, Segoe UI, Arial, sans-serif" font-size="28" font-weight="500" fill="#55657A">KeePass-compatible password manager</text>
</svg>

After

Width:  |  Height:  |  Size: 3.0 KiB

+12
View File
@@ -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()
}
+6
View File
@@ -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 {
+11 -8
View File
@@ -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),
)
+46
View File
@@ -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)
}