~whereswaldon/sprig

075871a621bb8d95b98348ecbd85a56e151c8b92 — Chris Waldon 8 days ago 9e1b1e1
chore: refactor theme management into a service
M connect-form-view.go => connect-form-view.go +5 -7
@@ 15,15 15,13 @@ type ConnectFormView struct {
	Form    sprigWidget.TextForm

	core.App
	*sprigTheme.Theme
}

var _ View = &ConnectFormView{}

func NewConnectFormView(app core.App, theme *sprigTheme.Theme) View {
func NewConnectFormView(app core.App) View {
	c := &ConnectFormView{
		App:   app,
		Theme: theme,
		App: app,
	}
	return c
}


@@ 56,17 54,17 @@ func (c *ConnectFormView) Update(gtx layout.Context) {
}

func (c *ConnectFormView) Layout(gtx layout.Context) layout.Dimensions {
	theme := c.Theme.Theme
	theme := c.Theme().Current()
	inset := layout.UniformInset(unit.Dp(8))
	return inset.Layout(gtx, func(gtx C) D {
		return layout.Flex{Axis: layout.Vertical}.Layout(gtx,
			layout.Rigid(func(gtx C) D {
				return inset.Layout(gtx,
					material.H6(theme, "Arbor Relay Address:").Layout,
					material.H6(theme.Theme, "Arbor Relay Address:").Layout,
				)
			}),
			layout.Rigid(func(gtx C) D {
				return inset.Layout(gtx, sprigTheme.TextForm(c.Theme, &c.Form, "Connect", "HOST:PORT").Layout)
				return inset.Layout(gtx, sprigTheme.TextForm(theme, &c.Form, "Connect", "HOST:PORT").Layout)
			}),
		)
	})

M consent-view.go => consent-view.go +7 -10
@@ 8,7 8,6 @@ import (

	"git.sr.ht/~whereswaldon/materials"
	"git.sr.ht/~whereswaldon/sprig/core"
	sprigTheme "git.sr.ht/~whereswaldon/sprig/widget/theme"
)

type ConsentView struct {


@@ 16,15 15,13 @@ type ConsentView struct {
	AgreeButton widget.Clickable

	core.App
	*sprigTheme.Theme
}

var _ View = &ConsentView{}

func NewConsentView(app core.App, theme *sprigTheme.Theme) View {
func NewConsentView(app core.App) View {
	c := &ConsentView{
		App:   app,
		Theme: theme,
		App: app,
	}

	return c


@@ 63,20 60,20 @@ const (
)

func (c *ConsentView) Layout(gtx layout.Context) layout.Dimensions {
	theme := c.Theme.Theme
	theme := c.Theme().Current()
	return layout.Center.Layout(gtx, func(gtx layout.Context) layout.Dimensions {
		return layout.Flex{Axis: layout.Vertical}.Layout(gtx,
			layout.Rigid(func(gtx layout.Context) layout.Dimensions {
				return layout.Center.Layout(gtx, func(gtx layout.Context) layout.Dimensions {
					return layout.UniformInset(unit.Dp(4)).Layout(gtx,
						material.H2(theme, "Notice").Layout,
						material.H2(theme.Theme, "Notice").Layout,
					)
				})
			}),
			layout.Rigid(func(gtx layout.Context) layout.Dimensions {
				return layout.Center.Layout(gtx, func(gtx layout.Context) layout.Dimensions {
					return layout.UniformInset(unit.Dp(4)).Layout(gtx,
						material.Body1(theme, Notice).Layout,
						material.Body1(theme.Theme, Notice).Layout,
					)
				})
			}),


@@ 84,7 81,7 @@ func (c *ConsentView) Layout(gtx layout.Context) layout.Dimensions {
				if c.Settings().AcknowledgedNoticeVersion() != 0 {
					return layout.Center.Layout(gtx, func(gtx layout.Context) layout.Dimensions {
						return layout.UniformInset(unit.Dp(4)).Layout(gtx,
							material.Body2(theme, UpdateText).Layout,
							material.Body2(theme.Theme, UpdateText).Layout,
						)
					})
				}


@@ 93,7 90,7 @@ func (c *ConsentView) Layout(gtx layout.Context) layout.Dimensions {
			layout.Rigid(func(gtx layout.Context) layout.Dimensions {
				return layout.Center.Layout(gtx, func(gtx layout.Context) layout.Dimensions {
					return layout.UniformInset(unit.Dp(4)).Layout(gtx,
						material.Button(theme, &(c.AgreeButton), "I Understand And Agree").Layout,
						material.Button(theme.Theme, &(c.AgreeButton), "I Understand And Agree").Layout,
					)
				})
			}),

M core/app.go => core/app.go +10 -0
@@ 11,6 11,7 @@ type App interface {
	Arbor() ArborService
	Settings() SettingsService
	Sprout() SproutService
	Theme() ThemeService
}

// app bundles services together.


@@ 19,6 20,7 @@ type app struct {
	SettingsService
	ArborService
	SproutService
	ThemeService
}

var _ App = &app{}


@@ 53,6 55,9 @@ func NewApp(stateDir string) (application App, err error) {
	if a.SproutService, err = newSproutService(a.ArborService); err != nil {
		return nil, err
	}
	if a.ThemeService, err = newThemeService(); err != nil {
		return nil, err
	}

	// Connect services together
	if addr := a.Settings().Address(); addr != "" {


@@ 82,3 87,8 @@ func (a *app) Notifications() NotificationService {
func (a *app) Sprout() SproutService {
	return a.SproutService
}

// Theme returns the app's theme service implmentation.
func (a *app) Theme() ThemeService {
	return a.ThemeService
}

A core/theme-service.go => core/theme-service.go +29 -0
@@ 0,0 1,29 @@
package core

import (
	sprigTheme "git.sr.ht/~whereswaldon/sprig/widget/theme"
)

// ThemeService provides methods to fetch and manipulate the current
// application theme.
type ThemeService interface {
	Current() *sprigTheme.Theme
}

// themeService implements ThemeService.
type themeService struct {
	*sprigTheme.Theme
}

var _ ThemeService = &themeService{}

func newThemeService() (ThemeService, error) {
	return &themeService{
		Theme: sprigTheme.New(),
	}, nil
}

// Current returns the current theme.
func (t *themeService) Current() *sprigTheme.Theme {
	return t.Theme
}

M go.sum => go.sum +0 -30
@@ 2,8 2,6 @@ dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7
gioui.org v0.0.0-20200726090130-3b95e2918359/go.mod h1:jiUwifN9cRl/zmco43aAqh0aV+s9GbhG13KcD+gEpkU=
gioui.org v0.0.0-20200726090339-83673ecb203f h1:09YCGWJqqcRI/X3HEODsA/val03uLETNjTmQ/dUBSAY=
gioui.org v0.0.0-20200726090339-83673ecb203f/go.mod h1:jiUwifN9cRl/zmco43aAqh0aV+s9GbhG13KcD+gEpkU=
gioui.org v0.0.0-20200829162755-829ee4559c5a h1:mciXRGzQwU0TbgCILZzl6L1nYIWU31lDGIb+8RYige8=
gioui.org v0.0.0-20200829162755-829ee4559c5a/go.mod h1:Y+uS7hHMvku1Q+ooaoq6fYD5B2LGoT8JtFgvmYmRzTw=
gioui.org v0.0.0-20200912153211-2f67feafc0a2 h1:77DuKJMYSpehg7NummLgCh+FHvpOh/p2SP4oKXdE1nE=
gioui.org v0.0.0-20200912153211-2f67feafc0a2/go.mod h1:Y+uS7hHMvku1Q+ooaoq6fYD5B2LGoT8JtFgvmYmRzTw=
gioui.org/cmd v0.0.0-20200828113410-1584f3a64a09 h1:lb9vX6UiO8U3U73eqHGUp9AZvca8GQ9x+AxcJrK94tM=


@@ 14,19 12,10 @@ git.sr.ht/~whereswaldon/colorpicker v0.0.0-20200801012301-b0b7a5822cd7 h1:mifEjr
git.sr.ht/~whereswaldon/colorpicker v0.0.0-20200801012301-b0b7a5822cd7/go.mod h1:r9pvrmlC21HDzv1lW5Udi/+01w4jIWf4NHPi2yHhjk8=
git.sr.ht/~whereswaldon/forest-go v0.0.0-20200908023146-ee23841ca265 h1:fk98DKjCg7OIAHQCqr2yLCyXSyGhFidEHHe8IgfqjNI=
git.sr.ht/~whereswaldon/forest-go v0.0.0-20200908023146-ee23841ca265/go.mod h1:aGmm4R7ifFBvJWOHINDvZcKVOu+ODkD75NmNm/O0zME=
git.sr.ht/~whereswaldon/materials v0.0.0-20200907201657-4a20a9ed4130 h1:dU8zitHq59nhajeOEQpeNjDGoVb8KF8fW7hJNk9oGzQ=
git.sr.ht/~whereswaldon/materials v0.0.0-20200907201657-4a20a9ed4130/go.mod h1:/7Ok61qMnX5ggEUzMi+V2g8B/Q2fuKIXos+rBPTXDz0=
<<<<<<< Updated upstream
git.sr.ht/~whereswaldon/materials v0.0.0-20200909213740-20cedf43594a h1:yn0utYsl1C4U9ZN2n16kXHME4llYfEuYg4I0tsLN/98=
git.sr.ht/~whereswaldon/materials v0.0.0-20200909213740-20cedf43594a/go.mod h1:/7Ok61qMnX5ggEUzMi+V2g8B/Q2fuKIXos+rBPTXDz0=
=======
git.sr.ht/~whereswaldon/materials v0.0.0-20200913214226-fffc71ca41aa h1:joGop3C10V08GL4cW3fKcE7YDPKQsrshJgFcVnwAVLI=
git.sr.ht/~whereswaldon/materials v0.0.0-20200913214226-fffc71ca41aa/go.mod h1:83h9SWCZR03wxWMs7qUlaexugfSlEorznGkxgCwsrC8=
>>>>>>> Stashed changes
git.sr.ht/~whereswaldon/niotify v0.0.4-0.20200831115722-2354c7f8372f h1:2FGooeaKxC8XksxGniJEIIJN1bcKWJmKkfDp+VJ/2j4=
git.sr.ht/~whereswaldon/niotify v0.0.4-0.20200831115722-2354c7f8372f/go.mod h1:c237AiA2cwcg+n865LzGlKY4z6H3UbCJHWOumUj7fGk=
git.sr.ht/~whereswaldon/sprout-go v0.0.0-20200908023616-4e6573e18230 h1:/CWosV8MKFO3AxoBnt8+fyImxLUmVS0+cUFOD7lDLbc=
git.sr.ht/~whereswaldon/sprout-go v0.0.0-20200908023616-4e6573e18230/go.mod h1:PR+m176x7YsC0xLcsVOwBoLKOB7AlAFQJZghn+renDo=
git.sr.ht/~whereswaldon/sprout-go v0.0.0-20200908122259-60e4aacb6740 h1:Uu3AIFJNHGLMEDIT6/MfSxbj8M9R8bK2Ur6PiTiOCMQ=
git.sr.ht/~whereswaldon/sprout-go v0.0.0-20200908122259-60e4aacb6740/go.mod h1:qP+nfEb283lduTciw9fX2MdAaDtmqmCw9MLgxxqNdRU=
git.wow.st/gmp/jni v0.0.0-20200626194017-b74a17279b1f/go.mod h1:+axXBRUTIDlCeE73IKeD/os7LoEnTKdkp8/gQOFjqyo=


@@ 59,33 48,22 @@ github.com/mailru/easyjson v0.7.0/go.mod h1:KAzv3t3aY1NaHWoQz1+4F1ccyAH66Jk7yos7
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20191002040644-a1355ae1e2c3/go.mod h1:NOZ3BPKG0ec/BKJQgnvsSFpcKLM5xXVWnvZS97DWHgE=
golang.org/x/exp v0.0.0-20200901203048-c4f52b2c50aa h1:i1+omYRtqpxiCaQJB4MQhUToKvMPFqUUJKvRiRp0gtE=
golang.org/x/exp v0.0.0-20200901203048-c4f52b2c50aa/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU=
golang.org/x/exp v0.0.0-20200908183739-ae8ad444f925 h1:5XVKs2rlCg8EFyRcvO8/XFwYxh1oKJO1Q3X5vttIf9c=
golang.org/x/exp v0.0.0-20200908183739-ae8ad444f925/go.mod h1:1phAWC201xIgDyaFpmDeZkgf70Q4Pd/CNqfRtVPtxNw=
golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js=
golang.org/x/image v0.0.0-20190802002840-cff245a6509b h1:+qEpEAPhDZ1o0x3tHzZTQDArnOixOzGD9HUJfcg0mb4=
golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
golang.org/x/image v0.0.0-20200618115811-c13761719519 h1:1e2ufUJNM3lCHEY5jIgac/7UTjd6cgJNdatjPdFWf34=
golang.org/x/image v0.0.0-20200618115811-c13761719519/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
golang.org/x/image v0.0.0-20200801110659-972c09e46d76 h1:U7GPaoQyQmX+CBRWXKrvRzWTbd+slqeSh8uARsIyhAw=
golang.org/x/image v0.0.0-20200801110659-972c09e46d76/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o=
golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY=
golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
golang.org/x/mod v0.1.1-0.20191107180719-034126e5016b h1:GgiSbuUyC0BlbUmHQBgFqu32eiRR/CEYdjOjOd4zE6Y=
golang.org/x/mod v0.1.1-0.20191107180719-034126e5016b/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
golang.org/x/mod v0.3.0 h1:RM4zey1++hCTbCVQfnWeKs9/IEsaBLA8vTkd0WVtmH4=
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
<<<<<<< Updated upstream
golang.org/x/mod v0.3.1-0.20200828183125-ce943fd02449 h1:xUIPaMhvROX9dhPvRCenIJtU78+lbEenGbgqB5hfHCQ=
=======
>>>>>>> Stashed changes
golang.org/x/mod v0.3.1-0.20200828183125-ce943fd02449/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
golang.org/x/sync v0.0.0-20190423024810-112230192c58 h1:8gQV6CLnAEikrhgkHFbMAEhagSSnXWGV915qUMm9mrU=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208 h1:qwRHBd0NqMbJxfbotnDhm2ByMI1Shq4Y6oRJo21SGJA=
golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=


@@ 95,28 73,20 @@ golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7w
golang.org/x/sys v0.0.0-20190626150813-e07cf5db2756/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191001151750-bb3f8db39f24/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191113165036-4c7a9d0fe056/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200124204421-9fbb57f87de9 h1:1/DFK4b7JH8DmkqhUk48onnSfrPzImPoVxuomtbT2nk=
golang.org/x/sys v0.0.0-20200124204421-9fbb57f87de9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200905004654-be1d3432aa8f h1:Fqb3ao1hUmOR3GkUOg/Y+BadLwykBIzs5q8Ez2SbHyc=
golang.org/x/sys v0.0.0-20200905004654-be1d3432aa8f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200909081042-eff7692f9009 h1:W0lCpv29Hv0UaM1LXb9QlBHLNP8UFfcKjblhVCWftOM=
golang.org/x/sys v0.0.0-20200909081042-eff7692f9009/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/text v0.3.0 h1:g61tztE5qeGQ89tm6NTjjM9VPIm088od1l6aSorWRWg=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.3 h1:cokOdA+Jmi5PJGXLlLllQSgYigAEfHXJAERHVMaCc2k=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20190927191325-030b2cf1153e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20200207183749-b753a1ba74fa h1:5E4dL8+NgFOgjwbTKz+OOEGGhP+ectTmF842l6KjupQ=
golang.org/x/tools v0.0.0-20200207183749-b753a1ba74fa/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20200904185747-39188db58858 h1:xLt+iB5ksWcZVxqc+g9K41ZHy+6MKWfXCDsjSThnsPA=
golang.org/x/tools v0.0.0-20200904185747-39188db58858/go.mod h1:Cj7w3i3Rnn0Xh82ur9kSqwfTHTeVxaDqrfMjpcNT6bE=
golang.org/x/tools v0.0.0-20200909210914-44a2922940c2 h1:daAzF/Ytp6YSqJDu1hZJthJIhOrsAa7UbIkziU1t0K4=
golang.org/x/tools v0.0.0-20200909210914-44a2922940c2/go.mod h1:Cj7w3i3Rnn0Xh82ur9kSqwfTHTeVxaDqrfMjpcNT6bE=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898 h1:/atklqdjdhuosWIl6AIbOeHJjicWYPqR9bpxqxYG2pA=
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE=
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=

M identity-form.go => identity-form.go +3 -6
@@ 7,7 7,6 @@ import (
	"gioui.org/widget/material"
	"git.sr.ht/~whereswaldon/materials"
	"git.sr.ht/~whereswaldon/sprig/core"
	sprigTheme "git.sr.ht/~whereswaldon/sprig/widget/theme"
)

type IdentityFormView struct {


@@ 16,15 15,13 @@ type IdentityFormView struct {
	CreateButton widget.Clickable

	core.App
	*sprigTheme.Theme
}

var _ View = &IdentityFormView{}

func NewIdentityFormView(app core.App, theme *sprigTheme.Theme) View {
func NewIdentityFormView(app core.App) View {
	c := &IdentityFormView{
		App:   app,
		Theme: theme,
		App: app,
	}

	return c


@@ 52,7 49,7 @@ func (c *IdentityFormView) Update(gtx layout.Context) {
}

func (c *IdentityFormView) Layout(gtx layout.Context) layout.Dimensions {
	theme := c.Theme.Theme
	theme := c.Theme().Current().Theme
	return layout.Center.Layout(gtx, func(gtx layout.Context) layout.Dimensions {
		return layout.Flex{Axis: layout.Vertical}.Layout(gtx,
			layout.Rigid(func(gtx layout.Context) layout.Dimensions {

M main.go => main.go +9 -10
@@ 47,15 47,13 @@ func eventLoop(w *app.Window) error {
		log.Fatalf("Failed initializing application: %v", err)
	}

	theme := sprigTheme.New()

	viewManager := NewViewManager(w, theme, *profile)
	viewManager := NewViewManager(w, app, *profile)
	viewManager.ApplySettings(app.Settings())
	viewManager.RegisterView(ReplyViewID, NewReplyListView(app, theme))
	viewManager.RegisterView(ConnectFormID, NewConnectFormView(app, theme))
	viewManager.RegisterView(SettingsID, NewCommunityMenuView(app, theme))
	viewManager.RegisterView(IdentityFormID, NewIdentityFormView(app, theme))
	viewManager.RegisterView(ConsentViewID, NewConsentView(app, theme))
	viewManager.RegisterView(ReplyViewID, NewReplyListView(app))
	viewManager.RegisterView(ConnectFormID, NewConnectFormView(app))
	viewManager.RegisterView(SettingsID, NewCommunityMenuView(app))
	viewManager.RegisterView(IdentityFormID, NewIdentityFormView(app))
	viewManager.RegisterView(ConsentViewID, NewConsentView(app))
	if app.Settings().AcknowledgedNoticeVersion() < NoticeVersion {
		viewManager.RequestViewSwitch(ConsentViewID)
	} else if app.Settings().Address() == "" {


@@ 82,9 80,10 @@ func eventLoop(w *app.Window) error {
			}
		case system.FrameEvent:
			gtx := layout.NewContext(&ops, event)
			th := app.Theme().Current()
			layout.Stack{}.Layout(gtx,
				layout.Expanded(func(gtx C) D {
					return sprigTheme.DrawRect(gtx, theme.Background.Dark, f32.Pt(float32(gtx.Constraints.Max.X), float32(gtx.Constraints.Max.Y)), 0)
					return sprigTheme.DrawRect(gtx, th.Background.Dark, f32.Pt(float32(gtx.Constraints.Max.X), float32(gtx.Constraints.Max.Y)), 0)
				}),
				layout.Stacked(func(gtx C) D {
					return layout.Inset{


@@ 95,7 94,7 @@ func eventLoop(w *app.Window) error {
					}.Layout(gtx, func(gtx C) D {
						return layout.Stack{}.Layout(gtx,
							layout.Expanded(func(gtx C) D {
								return sprigTheme.DrawRect(gtx, theme.Background.Default, f32.Pt(float32(gtx.Constraints.Max.X), float32(gtx.Constraints.Max.Y)), 0)
								return sprigTheme.DrawRect(gtx, th.Background.Default, f32.Pt(float32(gtx.Constraints.Max.X), float32(gtx.Constraints.Max.Y)), 0)
							}),
							layout.Stacked(viewManager.Layout),
						)

M reply-view.go => reply-view.go +24 -24
@@ 30,7 30,6 @@ type ReplyListView struct {
	manager ViewManager

	core.App
	*sprigTheme.Theme

	CopyReplyButton widget.Clickable



@@ 74,10 73,9 @@ type ReplyListView struct {

var _ View = &ReplyListView{}

func NewReplyListView(app core.App, th *sprigTheme.Theme) View {
func NewReplyListView(app core.App) View {
	c := &ReplyListView{
		App:   app,
		Theme: th,
		App: app,
		ReplyAnim: anim.Normal{
			Duration: time.Millisecond * 100,
		},


@@ 104,7 102,7 @@ func (c *ReplyListView) NavItem() *materials.NavItem {
}

func (c *ReplyListView) AppBarData() (bool, string, []materials.AppBarAction, []materials.OverflowAction) {
	th := c.Theme.Theme
	th := c.Theme().Current().Theme
	return true, "Messages", []materials.AppBarAction{
			materials.SimpleIconAction(
				th,


@@ 147,7 145,7 @@ func (c *ReplyListView) HandleClipboard(contents string) {
}

func (c *ReplyListView) getContextualActions() ([]materials.AppBarAction, []materials.OverflowAction) {
	th := c.Theme.Theme
	th := c.Theme().Current().Theme
	return []materials.AppBarAction{
		materials.SimpleIconAction(
			th,


@@ 512,12 510,13 @@ func (c *ReplyListView) statusOf(reply *forest.Reply) sprigTheme.ReplyStatus {
}

func (c *ReplyListView) Layout(gtx layout.Context) layout.Dimensions {
	theme := c.Theme().Current()
	key.InputOp{Tag: c, Focus: c.ShouldRequestKeyboardFocus}.Add(gtx.Ops)
	c.ShouldRequestKeyboardFocus = false
	return layout.Stack{}.Layout(gtx,
		layout.Expanded(func(gtx C) D {
			sprigTheme.Rect{
				Color: c.Theme.Background.Default,
				Color: theme.Background.Default,
				Size:  layout.FPt(gtx.Constraints.Max),
			}.Layout(gtx)
			return layout.Dimensions{}


@@ 581,6 580,7 @@ func (c *ReplyListView) layoutReplyList(gtx layout.Context) layout.Dimensions {
	var (
		stateIndex = 0
		dims       layout.Dimensions
		th         = c.Theme().Current()
	)
	gtx.Constraints.Min = gtx.Constraints.Max
	c.Arbor().Replies().WithReplies(func(replies []ds.ReplyData) {


@@ 645,7 645,7 @@ func (c *ReplyListView) layoutReplyList(gtx layout.Context) layout.Dimensions {
								Left:   interpolateInset(anim, c.ReplyAnim.Progress(gtx)),
							}.Layout(gtx, func(gtx C) D {
								gtx.Constraints.Max.X = messageWidth
								replyWidget := sprigTheme.Reply(c.Theme, anim, reply)
								replyWidget := sprigTheme.Reply(th, anim, reply)
								replyWidget.CollapseMetadata = collapseMetadata
								return replyWidget.Layout(gtx)
							})


@@ 673,8 673,8 @@ func (c *ReplyListView) layoutReplyList(gtx layout.Context) layout.Dimensions {
							Right: unit.Dp(scrollSlotWidthDp),
						}.Layout(gtx, func(gtx C) D {
							return material.IconButtonStyle{
								Background: c.Theme.Secondary.Light,
								Color:      c.Theme.Background.Dark,
								Background: th.Secondary.Light,
								Color:      th.Background.Dark,
								Button:     &c.CreateReplyButton,
								Icon:       icons.ReplyIcon,
								Size:       unit.Dp(sprigTheme.DefaultIconButtonWidthDp),


@@ 689,19 689,19 @@ func (c *ReplyListView) layoutReplyList(gtx layout.Context) layout.Dimensions {
	sprigTheme.ScrollBar{
		Scrollable: &c.ScrollBar,
		Progress:   float32(c.ReplyList.Position.First) / float32(c.replyCount),
		Color:      sprigTheme.WithAlpha(c.Theme.Background.Dark, 200),
		Color:      sprigTheme.WithAlpha(th.Background.Dark, 200),
	}.Layout(gtx)
	return dims
}

func (c *ReplyListView) layoutEditor(gtx layout.Context) layout.Dimensions {
	var (
		th = c.Theme.Theme
		th = c.Theme().Current()
	)
	return layout.Stack{}.Layout(gtx,
		layout.Expanded(func(gtx C) D {
			sprigTheme.Rect{
				Color: c.Theme.Primary.Light,
				Color: th.Primary.Light,
				Size: f32.Point{
					X: float32(gtx.Constraints.Max.X),
					Y: float32(gtx.Constraints.Max.Y),


@@ 718,9 718,9 @@ func (c *ReplyListView) layoutEditor(gtx layout.Context) layout.Dimensions {
								gtx.Constraints.Max.X = gtx.Px(unit.Dp(30))
								gtx.Constraints.Min.X = gtx.Constraints.Max.X
								if c.CreatingConversation {
									return material.Body1(th, "In:").Layout(gtx)
									return material.Body1(th.Theme, "In:").Layout(gtx)
								}
								return material.Body1(th, "Re:").Layout(gtx)
								return material.Body1(th.Theme, "Re:").Layout(gtx)

							})
						}),


@@ 734,17 734,17 @@ func (c *ReplyListView) layoutEditor(gtx layout.Context) layout.Dimensions {
											if c.CommunityChoice.Value == "" && index == 0 {
												c.CommunityChoice.Value = community.ID().String()
											}
											radio := material.RadioButton(th, &c.CommunityChoice, community.ID().String(), string(community.Name.Blob))
											radio.IconColor = c.Theme.Secondary.Default
											radio := material.RadioButton(th.Theme, &c.CommunityChoice, community.ID().String(), string(community.Name.Blob))
											radio.IconColor = th.Secondary.Default
											return radio.Layout(gtx)
										})
									})
									return dims
								}
								reply := sprigTheme.Reply(c.Theme, &theme.ReplyAnimationState{
								reply := sprigTheme.Reply(th, &theme.ReplyAnimationState{
									Normal: &c.ReplyAnim,
								}, c.ReplyingTo)
								reply.Highlight = c.Theme.Primary.Default
								reply.Highlight = th.Primary.Default
								reply.MaxLines = 5
								return reply.Layout(gtx)
							})


@@ 752,7 752,7 @@ func (c *ReplyListView) layoutEditor(gtx layout.Context) layout.Dimensions {
						layout.Rigid(func(gtx C) D {
							return layout.UniformInset(unit.Dp(6)).Layout(gtx, func(gtx C) D {
								return sprigTheme.IconButton{
									Theme:  c.Theme,
									Theme:  th,
									Button: &c.CancelReplyButton,
									Icon:   icons.CancelReplyIcon,
								}.Layout(gtx)


@@ 765,7 765,7 @@ func (c *ReplyListView) layoutEditor(gtx layout.Context) layout.Dimensions {
						layout.Rigid(func(gtx C) D {
							return layout.UniformInset(unit.Dp(6)).Layout(gtx, func(gtx C) D {
								return sprigTheme.IconButton{
									Theme:  c.Theme,
									Theme:  th,
									Button: &c.PasteIntoReplyButton,
									Icon:   icons.PasteIcon,
								}.Layout(gtx)


@@ 776,7 776,7 @@ func (c *ReplyListView) layoutEditor(gtx layout.Context) layout.Dimensions {
								return layout.Stack{}.Layout(gtx,
									layout.Expanded(func(gtx C) D {
										return sprigTheme.Rect{
											Color: c.Theme.Background.Light,
											Color: th.Background.Light,
											Size: f32.Point{
												X: float32(gtx.Constraints.Max.X),
												Y: float32(gtx.Constraints.Min.Y),


@@ 787,7 787,7 @@ func (c *ReplyListView) layoutEditor(gtx layout.Context) layout.Dimensions {
									}),
									layout.Stacked(func(gtx C) D {
										return layout.UniformInset(unit.Dp(6)).Layout(gtx, func(gtx C) D {
											editor := material.Editor(th, &c.ReplyEditor, "type your reply here")
											editor := material.Editor(th.Theme, &c.ReplyEditor, "type your reply here")
											editor.Editor.Submit = true
											return editor.Layout(gtx)
										})


@@ 798,7 798,7 @@ func (c *ReplyListView) layoutEditor(gtx layout.Context) layout.Dimensions {
						layout.Rigid(func(gtx C) D {
							return layout.UniformInset(unit.Dp(6)).Layout(gtx, func(gtx C) D {
								return sprigTheme.IconButton{
									Theme:  c.Theme,
									Theme:  th,
									Button: &c.SendReplyButton,
									Icon:   icons.SendReplyIcon,
								}.Layout(gtx)

M settings-view.go => settings-view.go +5 -6
@@ 18,7 18,6 @@ type SettingsView struct {
	manager ViewManager

	core.App
	*sprigTheme.Theme

	layout.List
	ConnectionForm          sprigWidget.TextForm


@@ 36,10 35,9 @@ type SettingsView struct {

var _ View = &SettingsView{}

func NewCommunityMenuView(app core.App, theme *sprigTheme.Theme) View {
func NewCommunityMenuView(app core.App) View {
	c := &SettingsView{
		App:   app,
		Theme: theme,
		App: app,
	}
	c.List.Axis = layout.Vertical
	c.ConnectionForm.SetText(c.Settings().Address())


@@ 119,7 117,8 @@ func (c *SettingsView) BecomeVisible() {
}

func (c *SettingsView) Layout(gtx layout.Context) layout.Dimensions {
	theme := c.Theme.Theme
	sTheme := c.Theme().Current()
	theme := sTheme.Theme
	itemInset := layout.UniformInset(unit.Dp(8))
	areas := []func(C) D{
		func(gtx C) D {


@@ 143,7 142,7 @@ func (c *SettingsView) Layout(gtx layout.Context) layout.Dimensions {
				}),
				layout.Rigid(func(gtx C) D {
					return itemInset.Layout(gtx, func(gtx C) D {
						form := sprigTheme.TextForm(c.Theme, &c.ConnectionForm, "Connect", "HOST:PORT")
						form := sprigTheme.TextForm(sTheme, &c.ConnectionForm, "Connect", "HOST:PORT")
						return form.Layout(gtx)
					})
				}),

M view-manager.go => view-manager.go +9 -8
@@ 53,7 53,8 @@ type viewManager struct {
	views   map[ViewID]View
	current ViewID
	window  *app.Window
	Theme   *sprigTheme.Theme

	core.App

	*materials.ModalLayer
	materials.NavDrawer


@@ 80,22 81,22 @@ type viewManager struct {
	themeView View
}

func NewViewManager(window *app.Window, theme *sprigTheme.Theme, profile bool) ViewManager {
func NewViewManager(window *app.Window, app core.App, profile bool) ViewManager {
	modal := materials.NewModal()
	drawer := materials.NewNav(theme.Theme, "Sprig", "Arbor chat client")
	drawer := materials.NewNav(app.Theme().Current().Theme, "Sprig", "Arbor chat client")
	vm := &viewManager{
		App:        app,
		views:      make(map[ViewID]View),
		window:     window,
		profiling:  profile,
		Theme:      theme,
		themeView:  NewThemeEditorView(theme),
		themeView:  NewThemeEditorView(app.Theme().Current()),
		ModalLayer: modal,
		NavDrawer:  drawer,
		navAnim: materials.VisibilityAnimation{
			Duration: time.Millisecond * 250,
			State:    materials.Invisible,
		},
		AppBar: materials.NewAppBar(theme.Theme, modal),
		AppBar: materials.NewAppBar(app.Theme().Current().Theme, modal),
	}
	vm.ModalNavDrawer = materials.ModalNavFrom(&vm.NavDrawer, vm.ModalLayer)
	vm.AppBar.NavigationIcon = icons.MenuIcon


@@ 287,7 288,7 @@ func (vm *viewManager) layoutProfileTimings(gtx layout.Context) layout.Dimension
	return layout.Stack{}.Layout(gtx,
		layout.Expanded(func(gtx C) D {
			return sprigTheme.DrawRect(gtx,
				vm.Theme.Background.Light,
				vm.App.Theme().Current().Background.Light,
				f32.Point{
					X: float32(gtx.Constraints.Min.X),
					Y: float32(gtx.Constraints.Min.Y),


@@ 296,7 297,7 @@ func (vm *viewManager) layoutProfileTimings(gtx layout.Context) layout.Dimension
		}),
		layout.Stacked(func(gtx C) D {
			return layout.Inset{Top: unit.Dp(4), Left: unit.Dp(4)}.Layout(gtx, func(gtx C) D {
				label := material.Body1(vm.Theme.Theme, text)
				label := material.Body1(vm.App.Theme().Current().Theme, text)
				label.Font.Variant = "Mono"
				return label.Layout(gtx)
			})