Merge pull request 'Persist theme' (#2) from persist-theme into master

Reviewed-on: #2
This commit is contained in:
marco 2024-04-04 18:46:25 +00:00
commit f342b52efa
8 changed files with 409 additions and 157 deletions

View File

@ -8,17 +8,23 @@ import 'package:sqlite3_flutter_libs/sqlite3_flutter_libs.dart';
part 'database.g.dart';
class NoteTable extends Table {
var database = AppDatabase(); //global, since we should only use one instance
class PersistentNote extends Table {
TextColumn get id => text()();
TextColumn get content => text()();
}
@DriftDatabase(tables: [NoteTable])
class PersistentTheme extends Table {
TextColumn get brightness => text().withDefault(const Constant("dark"))();
}
@DriftDatabase(tables: [PersistentNote, PersistentTheme])
class AppDatabase extends _$AppDatabase {
AppDatabase() : super(_openConnection());
@override
int get schemaVersion => 1;
int get schemaVersion => 2;
@override
MigrationStrategy get migration {
@ -27,30 +33,26 @@ class AppDatabase extends _$AppDatabase {
await m.createAll();
},
onUpgrade: (Migrator m, int from, int to) async {
if (from < 2) {}
if (from < 2) {
await m.renameTable(persistentNote, "note_table");
await m.createTable(persistentTheme);
}
},
);
}
}
LazyDatabase _openConnection() {
// the LazyDatabase util lets us find the right location for the file async.
return LazyDatabase(() async {
// put the database file, called db.sqlite here, into the documents folder
// for your app.
final dbFolder = await getApplicationDocumentsDirectory();
final file = File(p.join(dbFolder.path, 'db.sqlite'));
// Also work around limitations on old Android versions
if (Platform.isAndroid) {
await applyWorkaroundToOpenSqlite3OnOldAndroidVersions();
}
// Make sqlite3 pick a more suitable location for temporary files - the
// one from the system may be inaccessible due to sandboxing.
final cachebase = (await getTemporaryDirectory()).path;
// We can't access /tmp on Android, which sqlite3 would try by default.
// Explicitly tell it about the correct temporary directory.
sqlite3.tempDirectory = cachebase;
return NativeDatabase.createInBackground(file);

View File

@ -3,12 +3,12 @@
part of 'database.dart';
// ignore_for_file: type=lint
class $NoteTableTable extends NoteTable
with TableInfo<$NoteTableTable, NoteTableData> {
class $PersistentNoteTable extends PersistentNote
with TableInfo<$PersistentNoteTable, PersistentNoteData> {
@override
final GeneratedDatabase attachedDatabase;
final String? _alias;
$NoteTableTable(this.attachedDatabase, [this._alias]);
$PersistentNoteTable(this.attachedDatabase, [this._alias]);
static const VerificationMeta _idMeta = const VerificationMeta('id');
@override
late final GeneratedColumn<String> id = GeneratedColumn<String>(
@ -26,9 +26,9 @@ class $NoteTableTable extends NoteTable
String get aliasedName => _alias ?? actualTableName;
@override
String get actualTableName => $name;
static const String $name = 'note_table';
static const String $name = 'persistent_note';
@override
VerificationContext validateIntegrity(Insertable<NoteTableData> instance,
VerificationContext validateIntegrity(Insertable<PersistentNoteData> instance,
{bool isInserting = false}) {
final context = VerificationContext();
final data = instance.toColumns(true);
@ -49,9 +49,9 @@ class $NoteTableTable extends NoteTable
@override
Set<GeneratedColumn> get $primaryKey => const {};
@override
NoteTableData map(Map<String, dynamic> data, {String? tablePrefix}) {
PersistentNoteData map(Map<String, dynamic> data, {String? tablePrefix}) {
final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : '';
return NoteTableData(
return PersistentNoteData(
id: attachedDatabase.typeMapping
.read(DriftSqlType.string, data['${effectivePrefix}id'])!,
content: attachedDatabase.typeMapping
@ -60,15 +60,16 @@ class $NoteTableTable extends NoteTable
}
@override
$NoteTableTable createAlias(String alias) {
return $NoteTableTable(attachedDatabase, alias);
$PersistentNoteTable createAlias(String alias) {
return $PersistentNoteTable(attachedDatabase, alias);
}
}
class NoteTableData extends DataClass implements Insertable<NoteTableData> {
class PersistentNoteData extends DataClass
implements Insertable<PersistentNoteData> {
final String id;
final String content;
const NoteTableData({required this.id, required this.content});
const PersistentNoteData({required this.id, required this.content});
@override
Map<String, Expression> toColumns(bool nullToAbsent) {
final map = <String, Expression>{};
@ -77,17 +78,17 @@ class NoteTableData extends DataClass implements Insertable<NoteTableData> {
return map;
}
NoteTableCompanion toCompanion(bool nullToAbsent) {
return NoteTableCompanion(
PersistentNoteCompanion toCompanion(bool nullToAbsent) {
return PersistentNoteCompanion(
id: Value(id),
content: Value(content),
);
}
factory NoteTableData.fromJson(Map<String, dynamic> json,
factory PersistentNoteData.fromJson(Map<String, dynamic> json,
{ValueSerializer? serializer}) {
serializer ??= driftRuntimeOptions.defaultSerializer;
return NoteTableData(
return PersistentNoteData(
id: serializer.fromJson<String>(json['id']),
content: serializer.fromJson<String>(json['content']),
);
@ -101,13 +102,14 @@ class NoteTableData extends DataClass implements Insertable<NoteTableData> {
};
}
NoteTableData copyWith({String? id, String? content}) => NoteTableData(
PersistentNoteData copyWith({String? id, String? content}) =>
PersistentNoteData(
id: id ?? this.id,
content: content ?? this.content,
);
@override
String toString() {
return (StringBuffer('NoteTableData(')
return (StringBuffer('PersistentNoteData(')
..write('id: $id, ')
..write('content: $content')
..write(')'))
@ -119,27 +121,27 @@ class NoteTableData extends DataClass implements Insertable<NoteTableData> {
@override
bool operator ==(Object other) =>
identical(this, other) ||
(other is NoteTableData &&
(other is PersistentNoteData &&
other.id == this.id &&
other.content == this.content);
}
class NoteTableCompanion extends UpdateCompanion<NoteTableData> {
class PersistentNoteCompanion extends UpdateCompanion<PersistentNoteData> {
final Value<String> id;
final Value<String> content;
final Value<int> rowid;
const NoteTableCompanion({
const PersistentNoteCompanion({
this.id = const Value.absent(),
this.content = const Value.absent(),
this.rowid = const Value.absent(),
});
NoteTableCompanion.insert({
PersistentNoteCompanion.insert({
required String id,
required String content,
this.rowid = const Value.absent(),
}) : id = Value(id),
content = Value(content);
static Insertable<NoteTableData> custom({
static Insertable<PersistentNoteData> custom({
Expression<String>? id,
Expression<String>? content,
Expression<int>? rowid,
@ -151,9 +153,9 @@ class NoteTableCompanion extends UpdateCompanion<NoteTableData> {
});
}
NoteTableCompanion copyWith(
PersistentNoteCompanion copyWith(
{Value<String>? id, Value<String>? content, Value<int>? rowid}) {
return NoteTableCompanion(
return PersistentNoteCompanion(
id: id ?? this.id,
content: content ?? this.content,
rowid: rowid ?? this.rowid,
@ -177,7 +179,7 @@ class NoteTableCompanion extends UpdateCompanion<NoteTableData> {
@override
String toString() {
return (StringBuffer('NoteTableCompanion(')
return (StringBuffer('PersistentNoteCompanion(')
..write('id: $id, ')
..write('content: $content, ')
..write('rowid: $rowid')
@ -186,12 +188,170 @@ class NoteTableCompanion extends UpdateCompanion<NoteTableData> {
}
}
class $PersistentThemeTable extends PersistentTheme
with TableInfo<$PersistentThemeTable, PersistentThemeData> {
@override
final GeneratedDatabase attachedDatabase;
final String? _alias;
$PersistentThemeTable(this.attachedDatabase, [this._alias]);
static const VerificationMeta _brightnessMeta =
const VerificationMeta('brightness');
@override
late final GeneratedColumn<String> brightness = GeneratedColumn<String>(
'brightness', aliasedName, false,
type: DriftSqlType.string,
requiredDuringInsert: false,
defaultValue: const Constant("dark"));
@override
List<GeneratedColumn> get $columns => [brightness];
@override
String get aliasedName => _alias ?? actualTableName;
@override
String get actualTableName => $name;
static const String $name = 'persistent_theme';
@override
VerificationContext validateIntegrity(
Insertable<PersistentThemeData> instance,
{bool isInserting = false}) {
final context = VerificationContext();
final data = instance.toColumns(true);
if (data.containsKey('brightness')) {
context.handle(
_brightnessMeta,
brightness.isAcceptableOrUnknown(
data['brightness']!, _brightnessMeta));
}
return context;
}
@override
Set<GeneratedColumn> get $primaryKey => const {};
@override
PersistentThemeData map(Map<String, dynamic> data, {String? tablePrefix}) {
final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : '';
return PersistentThemeData(
brightness: attachedDatabase.typeMapping
.read(DriftSqlType.string, data['${effectivePrefix}brightness'])!,
);
}
@override
$PersistentThemeTable createAlias(String alias) {
return $PersistentThemeTable(attachedDatabase, alias);
}
}
class PersistentThemeData extends DataClass
implements Insertable<PersistentThemeData> {
final String brightness;
const PersistentThemeData({required this.brightness});
@override
Map<String, Expression> toColumns(bool nullToAbsent) {
final map = <String, Expression>{};
map['brightness'] = Variable<String>(brightness);
return map;
}
PersistentThemeCompanion toCompanion(bool nullToAbsent) {
return PersistentThemeCompanion(
brightness: Value(brightness),
);
}
factory PersistentThemeData.fromJson(Map<String, dynamic> json,
{ValueSerializer? serializer}) {
serializer ??= driftRuntimeOptions.defaultSerializer;
return PersistentThemeData(
brightness: serializer.fromJson<String>(json['brightness']),
);
}
@override
Map<String, dynamic> toJson({ValueSerializer? serializer}) {
serializer ??= driftRuntimeOptions.defaultSerializer;
return <String, dynamic>{
'brightness': serializer.toJson<String>(brightness),
};
}
PersistentThemeData copyWith({String? brightness}) => PersistentThemeData(
brightness: brightness ?? this.brightness,
);
@override
String toString() {
return (StringBuffer('PersistentThemeData(')
..write('brightness: $brightness')
..write(')'))
.toString();
}
@override
int get hashCode => brightness.hashCode;
@override
bool operator ==(Object other) =>
identical(this, other) ||
(other is PersistentThemeData && other.brightness == this.brightness);
}
class PersistentThemeCompanion extends UpdateCompanion<PersistentThemeData> {
final Value<String> brightness;
final Value<int> rowid;
const PersistentThemeCompanion({
this.brightness = const Value.absent(),
this.rowid = const Value.absent(),
});
PersistentThemeCompanion.insert({
this.brightness = const Value.absent(),
this.rowid = const Value.absent(),
});
static Insertable<PersistentThemeData> custom({
Expression<String>? brightness,
Expression<int>? rowid,
}) {
return RawValuesInsertable({
if (brightness != null) 'brightness': brightness,
if (rowid != null) 'rowid': rowid,
});
}
PersistentThemeCompanion copyWith(
{Value<String>? brightness, Value<int>? rowid}) {
return PersistentThemeCompanion(
brightness: brightness ?? this.brightness,
rowid: rowid ?? this.rowid,
);
}
@override
Map<String, Expression> toColumns(bool nullToAbsent) {
final map = <String, Expression>{};
if (brightness.present) {
map['brightness'] = Variable<String>(brightness.value);
}
if (rowid.present) {
map['rowid'] = Variable<int>(rowid.value);
}
return map;
}
@override
String toString() {
return (StringBuffer('PersistentThemeCompanion(')
..write('brightness: $brightness, ')
..write('rowid: $rowid')
..write(')'))
.toString();
}
}
abstract class _$AppDatabase extends GeneratedDatabase {
_$AppDatabase(QueryExecutor e) : super(e);
late final $NoteTableTable noteTable = $NoteTableTable(this);
late final $PersistentNoteTable persistentNote = $PersistentNoteTable(this);
late final $PersistentThemeTable persistentTheme =
$PersistentThemeTable(this);
@override
Iterable<TableInfo<Table, Object?>> get allTables =>
allSchemaEntities.whereType<TableInfo<Table, Object?>>();
@override
List<DatabaseSchemaEntity> get allSchemaEntities => [noteTable];
List<DatabaseSchemaEntity> get allSchemaEntities =>
[persistentNote, persistentTheme];
}

View File

@ -1,25 +1,31 @@
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:fnotes/notes_app.dart';
import 'package:fnotes/persistence_bloc.dart';
import 'package:fnotes/persisted_brightness.dart';
import 'package:fnotes/persistent_notes_bloc.dart';
import 'package:fnotes/theme_bloc.dart';
void main() {
void main() async {
WidgetsFlutterBinding.ensureInitialized(); //for drift
runApp(const MainApp());
PersistedBrightness persistedBrightness =
await PersistedThemeBloc.getPersistedBrightness();
runApp(MainApp(brightness: persistedBrightness.toFlutterBrightness()));
}
class MainApp extends StatelessWidget {
const MainApp({super.key});
final Brightness brightness;
const MainApp({super.key, required this.brightness});
@override
Widget build(BuildContext context) {
return MultiBlocProvider(
providers: [
BlocProvider(create: (context) => ThemeBloc()),
BlocProvider(create: (context) => PersistenceBloc())
BlocProvider(create: (context) => PersistedThemeBloc(brightness)),
BlocProvider(create: (context) => PersistentNotesBloc()),
],
child: BlocBuilder<ThemeBloc, ThemeState>(
child: BlocBuilder<PersistedThemeBloc, ThemeState>(
builder: (context, state) {
return MaterialApp(
theme: state.theme,

View File

@ -1,7 +1,7 @@
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:fnotes/note.dart';
import 'package:fnotes/persistence_bloc.dart';
import 'package:fnotes/persistent_notes_bloc.dart';
import 'package:fnotes/theme_bloc.dart';
class NotesApp extends StatefulWidget {
@ -18,7 +18,7 @@ class _NotesAppState extends State<NotesApp> {
@override
void initState() {
context.read<PersistenceBloc>().add(LoadNotesEvent());
context.read<PersistentNotesBloc>().add(LoadNotesEvent());
super.initState();
}
@ -31,29 +31,29 @@ class _NotesAppState extends State<NotesApp> {
IconButton(
icon: const Icon(Icons.light_mode),
onPressed: () {
context.read<ThemeBloc>().add(ThemeEvent());
context.read<PersistedThemeBloc>().add(ThemeChangedEvent());
},
)
],
),
body: BlocBuilder<ThemeBloc, ThemeState>(
builder: (context, state) {
return BlocBuilder<PersistenceBloc, PersistenceState>(
builder: (context, blocState) {
body: BlocBuilder<PersistedThemeBloc, ThemeState>(
builder: (context, themeState) {
return BlocBuilder<PersistentNotesBloc, PersistentNotesState>(
builder: (context, notesState) {
return ListView.builder(
itemCount: blocState.notes.length,
itemCount: notesState.notes.length,
itemBuilder: (context, index) {
return Dismissible(
onDismissed: (direction) {
context.read<PersistenceBloc>().add(NoteDismissed(
context.read<PersistentNotesBloc>().add(NoteDismissed(
Note.withId(
id: blocState.notes[index].id,
content: blocState.notes[index].content)));
id: notesState.notes[index].id,
content: notesState.notes[index].content)));
},
key: ValueKey(blocState.notes[index]),
key: ValueKey(notesState.notes[index]),
child: Card(
elevation: 5,
color: state.theme.colorScheme.primaryContainer,
color: themeState.theme.colorScheme.primaryContainer,
child: SizedBox(
height: 50,
child: Padding(
@ -61,10 +61,10 @@ class _NotesAppState extends State<NotesApp> {
child: Align(
alignment: Alignment.centerLeft,
child: Text(
blocState.notes[index].content,
notesState.notes[index].content,
style: TextStyle(
color:
state.theme.colorScheme.onPrimaryContainer),
color: themeState
.theme.colorScheme.onPrimaryContainer),
),
),
),
@ -83,7 +83,7 @@ class _NotesAppState extends State<NotesApp> {
(value) {
if (value != null && value.isNotEmpty) {
context
.read<PersistenceBloc>()
.read<PersistentNotesBloc>()
.add(NoteEntered(Note(content: value)));
}
},

View File

@ -0,0 +1,40 @@
import 'package:flutter/material.dart';
class PersistedBrightness {
final String brightness;
PersistedBrightness(this.brightness);
factory PersistedBrightness.fromFlutterBrightness(Brightness brightness) {
var persistedBrightness = "light";
if (brightness == Brightness.dark) {
persistedBrightness = "dark";
}
return PersistedBrightness(persistedBrightness);
}
Brightness toFlutterBrightness() {
Brightness flutterBrightness = Brightness.light;
if (brightness == "dark") {
flutterBrightness = Brightness.dark;
}
return flutterBrightness;
}
@override
String toString() => brightness;
PersistedBrightness toOpposite() {
var newBrightness = "light";
if (brightness == "light") {
newBrightness = "dark";
}
return PersistedBrightness(newBrightness);
}
}

View File

@ -1,79 +0,0 @@
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:fnotes/database.dart';
import 'package:fnotes/note.dart';
class PersistenceBloc extends Bloc<PersistenceEvent, PersistenceState> {
static final database = AppDatabase();
PersistenceBloc() : super(PersistenceState.init()) {
on<StoreNotesEvent>(storeAllNotes);
on<LoadNotesEvent>(loadAllNotes);
on<NoteEntered>(storeNote);
on<NoteDismissed>(deleteNote);
}
void storeAllNotes(PersistenceEvent event, Emitter<PersistenceState> emit) {}
void loadAllNotes(
PersistenceEvent event, Emitter<PersistenceState> emit) async {
List<Note> list = [];
await database.select(database.noteTable).get().then(
(value) {
list = value.map((row) {
return Note.withId(id: row.id, content: row.content);
}).toList();
},
);
emit(PersistenceState(notes: list));
}
void storeNote(NoteEntered event, Emitter<PersistenceState> emit) async {
await database.into(database.noteTable).insert(NoteTableCompanion.insert(
id: event.note.id,
content: event.note.content,
));
var newNotes = state.notes;
newNotes.add(Note.withId(id: event.note.id, content: event.note.content));
emit(PersistenceState(notes: newNotes));
}
void deleteNote(NoteDismissed event, Emitter<PersistenceState> emit) {
(database.delete(database.noteTable)
..where((tbl) => tbl.id.equals(event.note.id)))
.go();
var newNotes =
state.notes.where((note) => note.id != event.note.id).toList();
emit(PersistenceState(notes: newNotes));
}
}
class PersistenceEvent {}
class LoadNotesEvent extends PersistenceEvent {}
class StoreNotesEvent extends PersistenceEvent {}
class NoteEntered extends PersistenceEvent {
final Note note;
NoteEntered(this.note);
}
class NoteDismissed extends PersistenceEvent {
final Note note;
NoteDismissed(this.note);
}
class PersistenceState {
List<Note> notes = List.empty(growable: true);
PersistenceState({required this.notes});
factory PersistenceState.init() {
return PersistenceState(notes: []);
}
}

View File

@ -0,0 +1,78 @@
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:fnotes/database.dart';
import 'package:fnotes/note.dart';
class PersistentNotesBloc
extends Bloc<PersistentNotesEvent, PersistentNotesState> {
PersistentNotesBloc() : super(PersistentNotesState.init()) {
on<LoadNotesEvent>(loadAllNotes);
on<NoteEntered>(storeNote);
on<NoteDismissed>(deleteNote);
}
void storeAllNotes(
PersistentNotesEvent event, Emitter<PersistentNotesState> emit) {}
void loadAllNotes(
PersistentNotesEvent event, Emitter<PersistentNotesState> emit) async {
List<Note> list = [];
await database.select(database.persistentNote).get().then(
(value) {
list = value.map((row) {
return Note.withId(id: row.id, content: row.content);
}).toList();
},
);
emit(PersistentNotesState(notes: list));
}
void storeNote(NoteEntered event, Emitter<PersistentNotesState> emit) async {
await database
.into(database.persistentNote)
.insert(PersistentNoteCompanion.insert(
id: event.note.id,
content: event.note.content,
));
var newNotes = state.notes;
newNotes.add(Note.withId(id: event.note.id, content: event.note.content));
emit(PersistentNotesState(notes: newNotes));
}
void deleteNote(NoteDismissed event, Emitter<PersistentNotesState> emit) {
(database.delete(database.persistentNote)
..where((tbl) => tbl.id.equals(event.note.id)))
.go();
var newNotes =
state.notes.where((note) => note.id != event.note.id).toList();
emit(PersistentNotesState(notes: newNotes));
}
}
class PersistentNotesEvent {}
class LoadNotesEvent extends PersistentNotesEvent {}
class NoteEntered extends PersistentNotesEvent {
final Note note;
NoteEntered(this.note);
}
class NoteDismissed extends PersistentNotesEvent {
final Note note;
NoteDismissed(this.note);
}
class PersistentNotesState {
List<Note> notes = List.empty(growable: true);
PersistentNotesState({required this.notes});
factory PersistentNotesState.init() {
return PersistentNotesState(notes: []);
}
}

View File

@ -1,33 +1,78 @@
import 'package:drift/drift.dart';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:fnotes/database.dart';
import 'package:fnotes/persisted_brightness.dart';
class ThemeBloc extends Bloc<ThemeEvent, ThemeState> {
ThemeBloc() : super(ThemeState.init()) {
on<ThemeEvent>(switchTheme);
class PersistedThemeBloc extends Bloc<ThemeEvent, ThemeState> {
PersistedThemeBloc(Brightness brightness)
: super(ThemeState.withBrightness(brightness)) {
on<ThemeChangedEvent>(switchTheme);
on<LoadThemeEvent>(loadTheme);
}
void switchTheme(ThemeEvent event, Emitter<ThemeState> emit) {
if (state.theme.brightness == Brightness.light) {
emit(ThemeState.withBrightness(Brightness.dark));
} else {
emit(ThemeState.withBrightness(Brightness.light));
}
void switchTheme(ThemeChangedEvent event, Emitter<ThemeState> emit) async {
await database.delete(database.persistentTheme).go();
var newBrightness =
PersistedBrightness.fromFlutterBrightness(state.theme.brightness)
.toOpposite();
await database
.into(database.persistentTheme)
.insert(PersistentThemeCompanion.insert(
brightness: Value(newBrightness.toString()),
));
emit(ThemeState.withBrightness(newBrightness.toFlutterBrightness()));
}
void loadTheme(LoadThemeEvent event, Emitter<ThemeState> emit) async {
PersistedBrightness persistedBrightness = await getPersistedBrightness();
emit(ThemeState.withBrightness(persistedBrightness.toFlutterBrightness()));
}
static Future<PersistedBrightness> getPersistedBrightness() async {
PersistedBrightness persistedBrightness = PersistedBrightness("light");
await database.select(database.persistentTheme).get().then(
(value) {
var brightnessIter = value.map((row) {
return row.brightness;
});
if (brightnessIter.isNotEmpty) {
persistedBrightness = PersistedBrightness(brightnessIter.first);
}
},
);
return persistedBrightness;
}
}
class ThemeEvent {}
class LoadThemeEvent extends ThemeEvent {}
class ThemeChangedEvent extends ThemeEvent {}
class ThemeState {
final ThemeData theme;
static final ThemeData initTheme = ThemeData(
colorScheme: ColorScheme.fromSeed(
seedColor: Colors.lightBlue, brightness: Brightness.light),
);
ThemeState({required this.theme});
factory ThemeState.init() {
return ThemeState(theme: initTheme);
static Future<ThemeState> fetchAndConstructInitState() async {
PersistedBrightness persistedBrightness = PersistedBrightness('light');
await database.select(database.persistentTheme).get().then(
(value) {
var brightnessIter = value.map((row) {
return row.brightness;
});
persistedBrightness = PersistedBrightness(brightnessIter.first);
},
);
return ThemeState.withBrightness(persistedBrightness.toFlutterBrightness());
}
factory ThemeState.withBrightness(Brightness brightness) {