Light/dark theme selection
Flutter on IOS/Android/MacOS (at least) seems to be have the ability to automatically select between the light/dark themes of a MaterialApp based on system settings.
Running the same app with hover doesn't behave the same way and MediaQuery.platformBrightnessOf(context) always reports Brightness.light which I assume is the default.
Is this something that could be implemented on either go-flutter or as a plugin?
It seems that the embedder controls the Brightness. I tried to test this in go-flutter but did not manage to get it working. I based the implementation of code found in the android embedder here.
Is this something that could be implemented on either go-flutter or as a plugin?
Yes, this is implemented by the flutter engine as a native plugin, just like text input.
I'll take a further look at this this week-end.
Here is the go/cmd/options.go file content:
package main
import (
"encoding/json"
"fmt"
"github.com/go-flutter-desktop/go-flutter"
"github.com/go-flutter-desktop/go-flutter/plugin"
)
var options = []flutter.Option{
flutter.WindowInitialDimensions(800, 1280),
flutter.AddPlugin(&SettingsPlugin{}),
}
// SettingsPlugin is a plugin to interact with the internal flutter setting
// system. See https://github.com/flutter/engine/blob/master/shell/platform/android/io/flutter/embedding/engine/systemchannels/SettingsChannel.java
type SettingsPlugin struct{}
var _ flutter.Plugin = &SettingsPlugin{} // compile-time type check
type settingsJSONMessageCodec struct{} // JSON MessageCodec impl has provided by the plugin writer
// EncodeMessage encodes a settingsJSONMessage to a slice of bytes.
func (j settingsJSONMessageCodec) EncodeMessage(message interface{}) (binaryMessage []byte, err error) {
return json.Marshal(message)
}
// send-only channel
func (j settingsJSONMessageCodec) DecodeMessage(binaryMessage []byte) (message interface{}, err error) {
return message, err
}
type settingsJSONMessage struct {
PlatformBrightness string `json:"platformBrightness"`
AlwaysUse24HourFormat bool `json:"alwaysUse24HourFormat"`
TextScaleFactor float32 `json:"textScaleFactor"`
}
// InitPlugin creates a BasicMessageChannel for "flutter/settings"
func (p *SettingsPlugin) InitPlugin(messenger plugin.BinaryMessenger) error {
channel := plugin.NewBasicMessageChannel(messenger, "flutter/settings", settingsJSONMessageCodec{})
message := settingsJSONMessage{
PlatformBrightness: "dark",
AlwaysUse24HourFormat: true,
TextScaleFactor: 1.0,
}
err := channel.Send(message)
if err != nil {
fmt.Printf("Error sending settings on 'flutter/settings': %v", err)
}
return nil
}
I have updated the above snippet with a working example, sadly, I don't know how to query for dark/light theme on all platform/display environment. As the above settings are quite hard to query on all platform, maybe it's up the the flutter developer to provide the right values.
@GeertJohan @geoah do you thinks those settings (PlatformBrightness, AlwaysUse24HourFormat, TextScaleFactor) should be available through go-flutter options
@pchampio thanks so much for the update example.
I do agree with the idea of delegating the detection to the developers. The plugin is minimal enough that an example is enough I think
If you'd rather have an option for that maybe the option could accept a channel on which the developer could push updates?
For example macOS can automatically change to dark mode after sundown so applications are expected to dynamically change their light/dark mode. :P I think they are doing this just to annoy devs.
ps. An example of detection on macOS is here: https://gist.github.com/jerblack/869a303d1a604171bf8f00bbbefa59c2#file-2-dark-monitor-go and it also deals with watching for updates.
This is literally a 2min mash of @pchampio's example and the gist linked above. Flutter takes a couple more seconds than native flutter but works as expected.
package plugin
import (
"encoding/json"
"fmt"
"log"
"os"
"os/exec"
"path/filepath"
"github.com/go-flutter-desktop/go-flutter"
"github.com/go-flutter-desktop/go-flutter/plugin"
"gopkg.in/fsnotify.v1"
)
// SettingsPlugin is a plugin to interact with the internal flutter setting
// system. See https://github.com/flutter/engine/blob/master/shell/platform/android/io/flutter/embedding/engine/systemchannels/SettingsChannel.java
type SettingsPlugin struct{}
var _ flutter.Plugin = &SettingsPlugin{} // compile-time type check
type settingsJSONMessageCodec struct{} // JSON MessageCodec impl has provided by the plugin writer
// EncodeMessage encodes a settingsJSONMessage to a slice of bytes.
func (j settingsJSONMessageCodec) EncodeMessage(message interface{}) (binaryMessage []byte, err error) {
return json.Marshal(message)
}
// send-only channel
func (j settingsJSONMessageCodec) DecodeMessage(binaryMessage []byte) (message interface{}, err error) {
return message, err
}
type settingsJSONMessage struct {
PlatformBrightness string `json:"platformBrightness"`
AlwaysUse24HourFormat bool `json:"alwaysUse24HourFormat"`
TextScaleFactor float32 `json:"textScaleFactor"`
}
// InitPlugin creates a BasicMessageChannel for "flutter/settings"
func (p *SettingsPlugin) InitPlugin(messenger plugin.BinaryMessenger) error {
channel := plugin.NewBasicMessageChannel(messenger, "flutter/settings", settingsJSONMessageCodec{})
sendPlatformBrightness := func(isDark bool) {
platformBrightness := "light"
if isDark {
platformBrightness = "dark"
}
message := settingsJSONMessage{
PlatformBrightness: platformBrightness,
AlwaysUse24HourFormat: true,
TextScaleFactor: 1.0,
}
err := channel.Send(message)
if err != nil {
fmt.Printf("Error sending settings on 'flutter/settings': %v", err)
}
}
sendPlatformBrightness(checkDarkMode())
go startWatcher(sendPlatformBrightness)
return nil
}
func checkDarkMode() bool {
cmd := exec.Command("defaults", "read", "-g", "AppleInterfaceStyle")
if err := cmd.Run(); err != nil {
if _, ok := err.(*exec.ExitError); ok {
return false
}
}
return true
}
const plistPath = `/Library/Preferences/.GlobalPreferences.plist`
var plist = filepath.Join(os.Getenv("HOME"), plistPath)
var wasDark bool
func startWatcher(fn func(bool)) {
watcher, err := fsnotify.NewWatcher()
if err != nil {
log.Fatal(err)
}
defer watcher.Close()
done := make(chan bool)
go func() {
for {
select {
case event, ok := <-watcher.Events:
if !ok {
return
}
if event.Op&fsnotify.Create == fsnotify.Create {
isDark := checkDarkMode()
if isDark && !wasDark {
fn(isDark)
wasDark = isDark
}
if !isDark && wasDark {
fn(isDark)
wasDark = isDark
}
}
case err, ok := <-watcher.Errors:
if !ok {
return
}
log.Println("error:", err)
}
}
}()
err = watcher.Add(plist)
if err != nil {
log.Fatal(err)
}
<-done
}
A snippet for windows: https://gist.github.com/jerblack/1d05bbcebb50ad55c312e4d7cf1bc909
I guess detection on linux is going to be a lot harder